How can I break a table row in xsl after a specified count?

后端 未结 4 382
南笙
南笙 2021-01-20 12:23

I have the following xsl that sorts my xml alphabetically:


  
 



        
相关标签:
4条回答
  • 2021-01-20 12:31

    Here is a second solution, which doesn't require any extension functions. Do note that it is not recursive and may be more efficient than a recursive one.

    This transformation:

    <xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     xmlns:my="my:namespace"
     exclude-result-prefixes="my"
     >
     <xsl:output method="html"/>
    
      <my:alpha>
        <l>A</l><l>B</l><l>C</l><l>D</l><l>E</l>
        <l>F</l><l>G</l><l>H</l><l>I</l><l>J</l>
        <l>K</l><l>L</l><l>M</l><l>N</l><l>O</l>
        <l>P</l><l>Q</l><l>R</l><l>S</l><l>T</l>
        <l>U</l><l>V</l><l>W</l><l>X</l><l>Y</l>
        <l>Z</l>
      </my:alpha>
        <xsl:variable name="vDoc" select="/"/>
        <xsl:variable name="vNumCols" select="4"/>
        <xsl:variable name="vLower"  select="'abcdefghijklmnopqrstuvwxyz'"  />
        <xsl:variable name="vUpper"  select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"  />
    
        <xsl:key name="rows-by-FirstLetter" match="Row"
          use="translate(substring(@Title,1,1),
                         'abcdefghijklmnopqrstuvwxyz',
                         'ABCDEFGHIJKLMNOPQRSTUVWXYZ')" />
    
      <xsl:variable name="vStartingLetters">
        <xsl:for-each select=
            "/*/*/Row
               [generate-id()
               =
                generate-id(key('rows-by-FirstLetter',
                                 translate(substring(@Title,1,1),
                                                     $vLower,
                                                     $vUpper)
                                 )[1]
                             )
                ]">
          <xsl:value-of select=
             "translate(substring(@Title,1,1),
                        $vLower,
                        $vUpper)"/>
        </xsl:for-each>
      </xsl:variable>
    
      <xsl:variable name="vMyLetters" select=
       "document('')/*/my:alpha/l[contains($vStartingLetters,.)]"
       />
    
        <xsl:template match="Rows">
            <table>
              <xsl:for-each select=
                "$vMyLetters[position() mod $vNumCols = 1]">
                <xsl:variable name="vPos"
                   select="(position()-1)*$vNumCols+1"/>
    
                  <tr>
                     <xsl:apply-templates select=
                      "$vMyLetters[position() >= $vPos
                                  and
                                   not(position() > $vPos+$vNumCols -1)
                                  ]">
                <xsl:with-param name="pDoc" select="$vDoc"/>
                     </xsl:apply-templates>
                  </tr>
                 </xsl:for-each>
            </table>
        </xsl:template>
    
        <xsl:template match="l">
            <xsl:param name="pDoc"/>
    
            <xsl:variable name="pThis" select="."/>
    
            <td>
                <xsl:value-of select="."/>
                <br />
                <table>
                    <xsl:for-each select="$pDoc">
                        <xsl:for-each select=
                                "key('rows-by-FirstLetter', $pThis)">
                    <tr>
                      <td>
    
                        <xsl:value-of select="@Title"/>
                      </td>
                    </tr>
                        </xsl:for-each>
                    </xsl:for-each>
                </table>
            </td>
        </xsl:template>
    </xsl:stylesheet>
    

    when applied on the following XML document:

    <dsQueryResponse>
        <Rows>
            <Row Title="Agenda" />
            <Row Title="Policy" />
            <Row Title="Policy" />
            <Row Title="Report" />
            <Row Title="Report" />
            <Row Title="Test2" />
            <Row Title="Test1" />
            <Row Title="Boo" />
            <Row Title="Foo" />
        </Rows>
    </dsQueryResponse>
    

    produces the wanted result:

    
    <table>
       <tr>
          <td>A<br><table>
                <tr>
                   <td>Agenda</td>
                </tr>
             </table>
          </td>
          <td>B<br><table>
                <tr>
                   <td>Boo</td>
                </tr>
             </table>
          </td>
          <td>F<br><table>
                <tr>
                   <td>Foo</td>
                </tr>
             </table>
          </td>
          <td>P<br><table>
                <tr>
                   <td>Policy</td>
                </tr>
                <tr>
                   <td>Policy</td>
                </tr>
             </table>
          </td>
       </tr>
       <tr>
          <td>R<br><table>
                <tr>
                   <td>Report</td>
                </tr>
                <tr>
                   <td>Report</td>
                </tr>
             </table>
          </td>
          <td>T<br><table>
                <tr>
                   <td>Test2</td>
                </tr>
                <tr>
                   <td>Test1</td>
                </tr>
             </table>
          </td>
       </tr>
    </table>
    

    Do note that most of the explanations in my first answer apply also to this solution with the only exception that here we do not use modes.

    0 讨论(0)
  • 2021-01-20 12:43

    Here is my solution.

    You can decide via parameters "per-row" and "show-empty" if you want empty cells to show up or if you want to hide them. I'm sure a much more elegant version exists, but I could not come up with one. ;-) Comments welcome.

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:output method="html" version="4.0" encoding="iso-8859-1" indent="yes"/>
    
      <xsl:key name="rows-by-title" match="Row" use="translate(substring(@Title, 1, 1), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')" />
      <xsl:variable name="alphabet" select="string('ABCDEFGHIJKLMNOPQRSTUVWXYZ')" />
      <xsl:variable name="per-row" select="number(4)" /> 
      <xsl:variable name="show-empty" select="false()" />
    
      <xsl:template match="/">
        <xsl:apply-templates select="dsQueryResponse/Rows" />
      </xsl:template> 
    
      <xsl:template match="Rows">
        <table>
          <xsl:call-template name="create-rows" />
        </table>
      </xsl:template>
    
      <xsl:template name="create-rows">
        <xsl:param name="index" select="1" />
    
        <xsl:variable name="letters">
          <xsl:call-template name="next-letters">
            <xsl:with-param name="index" select="$index" />
          </xsl:call-template>
        </xsl:variable>
    
        <xsl:if test="$letters != ''">
          <tr title="{$letters}">
            <xsl:call-template name="create-cells">
              <xsl:with-param name="letters" select="$letters" />
            </xsl:call-template>
          </tr>
        </xsl:if>
    
        <xsl:if test="string-length($letters) = $per-row">
          <xsl:call-template name="create-rows">
            <xsl:with-param name="index" select="string-length(substring-before($alphabet, substring($letters, string-length($letters), 1))) + 2" />
          </xsl:call-template>
        </xsl:if>
      </xsl:template>
    
      <xsl:template name="next-letters">
        <xsl:param name="index" />
    
        <xsl:variable name="letter" select="substring($alphabet, $index, 1)" />
    
        <xsl:variable name="letters">
          <xsl:if test="$index &lt;= string-length($alphabet)">
            <xsl:if test="$show-empty or key('rows-by-title', $letter)">
              <xsl:value-of select="$letter" />
            </xsl:if>
    
            <xsl:call-template name="next-letters">
              <xsl:with-param name="index" select="$index + 1" />
            </xsl:call-template>
          </xsl:if>
        </xsl:variable>
    
        <xsl:value-of select="substring($letters, 1, $per-row)" />
      </xsl:template>
    
      <xsl:template name="create-cells">
        <xsl:param name="letters" />
    
        <xsl:variable name="letter" select="substring($letters, 1, 1)" />
    
        <xsl:if test="$letter != ''">
          <td title="{$letter}">
            <strong>
              <xsl:value-of select="$letter" />
            </strong>
            <xsl:apply-templates select="key('rows-by-title', $letter)">
              <xsl:sort select="@Title" />
            </xsl:apply-templates>
          </td>
          <xsl:call-template name="create-cells">
            <xsl:with-param name="letters" select="substring($letters, 2, string-length($letters) - 1)" />
          </xsl:call-template>
        </xsl:if>
      </xsl:template>
    
      <xsl:template match="Row">
        <br />
        <xsl:value-of select="@Title" />
      </xsl:template>
    
    </xsl:stylesheet>
    

    With this input:

    <dsQueryResponse>
      <Rows>
        <Row Title="Agenda" />
        <Row Title="Policy" />
        <Row Title="Policy" />
        <Row Title="Report" />
        <Row Title="Report" />
        <Row Title="Test2" />
        <Row Title="Test1" />
        <Row Title="Boo" />
        <Row Title="Foo" />
      </Rows>
    </dsQueryResponse>
    

    This output is produced (the title attributes were just for debugging. I left them in, remove them anytime):

    <table>
      <tr title="ABFP">
        <td title="A">
          <strong>A</strong>
          <br>Agenda
        </td>
        <td title="B">
          <strong>B</strong>
          <br>Boo
        </td>
        <td title="F">
          <strong>F</strong>
          <br>Foo
        </td>
        <td title="P">
          <strong>P</strong>
          <br>Policy
          <br>Policy
        </td>
      </tr>
      <tr title="RT">
        <td title="R">
          <strong>R</strong>
          <br>Report
          <br>Report
        </td>
        <td title="T">
          <strong>T</strong>
          <br>Test1
          <br>Test2
        </td>
      </tr>
    </table>
    
    0 讨论(0)
  • 2021-01-20 12:45

    This question must be edited in order for anyone to understand what the problem really is. The comment by Tomalak reveals that the OP "wants lists of items in an alphabetically ordered grid. One list for each letter. Four letters horizontally, as much as it takes vertically"

    The following transformation:

    <xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     xmlns:ext="http://exslt.org/common"
     extension-element-prefixes="ext"
     >
    
     <xsl:variable name="vDoc" select="/"/>
     <xsl:variable name="vNumCols" select="4"/>
    
     <xsl:variable name="vLower"
      select="'abcdefghijklmnopqrstuvwxyz'"
      />
    
     <xsl:variable name="vUpper"
      select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"
      />
    
      <xsl:key name="rows-by-FirstLetter" match="Row"
      use="translate(substring(@Title,1,1),
                     'abcdefghijklmnopqrstuvwxyz',
                     'ABCDEFGHIJKLMNOPQRSTUVWXYZ')" />
    
      <xsl:variable name="vrtfStartLetters">
        <xsl:for-each select=
        "/*/*/Row
               [count(.
                   |
                    key('rows-by-FirstLetter',
                       translate(substring(@Title,1,1),
                                 $vLower,
                                 $vUpper)
                      )[1]
                      )
                  = 1
               ]">
    
             <startLetter>
               <xsl:value-of select=
                 "translate(substring(@Title,1,1),
                          $vLower,
                          $vUpper)"/>
             </startLetter>
          </xsl:for-each>
        </xsl:variable>
    
        <xsl:variable name="vStartLetters" select=
          "ext:node-set($vrtfStartLetters)"/>
    
      <xsl:template match="Rows">
        <table>
          <xsl:apply-templates select=
           "$vStartLetters/*[position() mod $vNumCols = 1]">
            <xsl:with-param name="pDoc" select="$vDoc"/>
            <xsl:with-param name="pNumCols" select="$vNumCols"/>
          </xsl:apply-templates>
        </table>
      </xsl:template>
    
      <xsl:template match="startLetter">
        <xsl:param name="pDoc"/>
        <xsl:param name="pNumCols" select="10"/>
        <tr>
          <xsl:apply-templates mode="copy" select=
          ". | following-sibling::*
                   [not(position() >= $pNumCols)]">
             <xsl:with-param name="pDoc" select="$pDoc"/>
             <xsl:sort/>
    
          </xsl:apply-templates>
        </tr>
      </xsl:template>
    
      <xsl:template match="startLetter" mode="copy">
        <xsl:param name="pDoc"/>
    
        <xsl:variable name="pThis" select="."/>
    
        <td>
          <xsl:value-of select="."/>
          <br />
          <table>
         <xsl:for-each select="$pDoc">
          <xsl:for-each select="key('rows-by-FirstLetter', $pThis)">
            <tr><td><xsl:value-of select="@Title"/></td></tr>
          </xsl:for-each>
         </xsl:for-each>
        </table>
        </td>
      </xsl:template>
    </xsl:stylesheet>
    

    when applied on this XML document:

    <dsQueryResponse>
      <Rows>
        <Row Title="Agenda" />
        <Row Title="Accrual" />
        <Row Title="Ads" />
        <Row Title="Averages" />
        <Row Title="Bindings" />
        <Row Title="Budget" />
        <Row Title="Cars" />
        <Row Title="Categories" />
        <Row Title="Costs" />
        <Row Title="Policy" />
        <Row Title="Politics" />
        <Row Title="Reevaluations" />
        <Row Title="Report" />
      </Rows>
    </dsQueryResponse>
    

    produces the wanted result:

    <table>
      <tr>
        <td>A
          <br/>
          <table>
            <tr>
              <td>Agenda</td>
            </tr>
            <tr>
              <td>Accrual</td>
            </tr>
            <tr>
              <td>Ads</td>
            </tr>
            <tr>
              <td>Averages</td>
            </tr>
          </table>
        </td>
        <td>B
          <br/>
          <table>
            <tr>
              <td>Bindings</td>
            </tr>
            <tr>
              <td>Budget</td>
            </tr>
          </table>
        </td>
        <td>C
          <br/>
          <table>
            <tr>
              <td>Cars</td>
            </tr>
            <tr>
              <td>Categories</td>
            </tr>
            <tr>
              <td>Costs</td>
            </tr>
          </table>
        </td>
        <td>P
          <br/>
          <table>
            <tr>
              <td>Policy</td>
            </tr>
            <tr>
              <td>Politics</td>
            </tr>
          </table>
        </td>
      </tr>
      <tr>
        <td>R
          <br/>
          <table>
            <tr>
              <td>Reevaluations</td>
            </tr>
            <tr>
              <td>Report</td>
            </tr>
          </table>
        </td>
      </tr>
    </table>
    

    Do note three things:

    • We are using the (exslt) ext:node-set() extension function to convert an intermediate result from RTF (Result-Tree Fragment) to a temporary tree.

    • The <xsl:for-each select="$pDoc"> necessary to make the original XML document the current XML document again, so that the key() function will use the index made for this document and not for the temporary tree.

    • Each start letter that must start a new row of (4) starting letters is processed in a special template, in which the <tr> is produced. Then this and the remaining (3) starting letters in the row are processed within the body of the <tr> in "copy" mode, just creating a <td> each.

    Here we have covered and demonstrated a few advanced XSLT techniques:

    • Using the mod operator for grouping by quantity.
    • Using the key() function for a different document than the current one.
    • Modes
    • Converting an RTF into a temporary tree

    Enjoy :)

    0 讨论(0)
  • 2021-01-20 12:45

    I'm sort of confused by the question but I think what you're looking for is an xsl:if test with a combination of position() and mod

    0 讨论(0)
提交回复
热议问题