简体   繁体   中英

XPath/XSL: produce a clean table through a single for-each loop

Probably a really simple question, but I want to produce a clean table through using a single for-each loop in my XSL file using Xpath. I've come a long way, which lead to the resulting file and image you can see below. 像这样

XSL file

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
 <body>
    
  <h2>Provincies</h2>
  <table border="1">
    <tr>
      <th bgcolor="#9acd32" style="text-align:left">Provincie</th>
      <th bgcolor="#9acd32" style="text-align:left">Gemeente</th>
      <th bgcolor="#9acd32" style="text-align:left">Stad</th>
      <th bgcolor="#9acd32" style="text-align:left">Aantal bedrijven</th>
    </tr>
                
    <xsl:for-each select="bedrijven/provincie/gemeente/stad">
    <xsl:variable name="provincie" select="../../@naam"/>
    <xsl:variable name="gemeente" select="../@naam"/>
    <xsl:variable name="stad" select="@naam"/>
       <tr>
          <xsl:choose>
             <xsl:when test="preceding::*/text() != $provincie"><td></td></xsl:when>
             <xsl:otherwise><td><xsl:value-of select="$provincie"/></td></xsl:otherwise>
          </xsl:choose>
        
          <xsl:choose>
             <xsl:when test="preceding-sibling::*/text() != $gemeente"><td></td></xsl:when>
             <xsl:otherwise><td><xsl:value-of select="$gemeente"/></td></xsl:otherwise>
          </xsl:choose>
          <td><xsl:value-of select="$stad"/></td>
          <td><xsl:value-of select="count(child::bedrijf)"/></td>
       </tr>

   </xsl:for-each>
  </table>
 </body>
</html>
</xsl:template>
</xsl:stylesheet>

However, the issue is that it only prints the first Province and not the others. I only want every province to be printed once, no repetition. I've been trying to do this all day, and googling all over the place, but I can't seem to find an answer. So I hope someone here can help me. Below is my XML file.

Example of XML structure

<bedrijven
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="file:///C:\Users\Boro\Documents\XML opdrachten\XML Opdrachten\Opdracht 1\db\test.xsd">
   <provincie naam="Drenthe">
       <gemeente naam="aa en Hunze">
           <stad naam="anloo">
              <bedrijf>
                  <naam>v.o.f. Anloo</naam>
                  <postcode>9468CG</postcode>
              </bedrijf>
           </stad>
           <stad naam="gasselternijveen">
              <bedrijf>
                  <naam>podiumdelen.nl</naam>
                  <postcode>9514BV</postcode>
              </bedrijf>
           </stad>
        </gemeente>
    </provincie>
    <provincie naam="Flevoland">
        <gemeente naam="almere">
           <stad naam="almere">
               <bedrijf>
                   <naam>mood media</naam>
                   <postcode>1322CE</postcode>
               </bedrijf>
               <bedrijf>
                   <naam>dutch Meat Service</naam>
                   <postcode>1311XC</postcode>
               </bedrijf>
               <bedrijf>
                   <naam>jossafety</naam>
                   <postcode>1338XX</postcode>
               </bedrijf>
           </stad>
        </gemeente>
    </provincie>
</bedrijven>

I get a correct output with this piece of code:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:template match="bedrijven">
    <html>
      <body>
        <h2>Provincies</h2>
        <table border="1">
          <tr>
            <th bgcolor="#9acd32" style="text-align:left">Provincie</th>
            <th bgcolor="#9acd32" style="text-align:left">Gemeente</th>
            <th bgcolor="#9acd32" style="text-align:left">Stad</th>
            <th bgcolor="#9acd32" style="text-align:left">Aantal bedrijven</th>
          </tr>
          <xsl:for-each select="provincie">
            <xsl:variable name="provincie" select="@naam"/>
            <xsl:for-each select="gemeente">
              <xsl:variable name="gemeente" select="@naam"/>
              <xsl:for-each select="stad">
                <xsl:variable name="stad" select="@naam"/>
                <tr>
                  <td>
                    <xsl:if 
                      test="count(preceding::stad[
                        ancestor::provincie/@naam = $provincie
                      ]) = 0">
                      <xsl:value-of select="$provincie"/>
                    </xsl:if>
                  </td>
                  <td>
                    <xsl:if 
                      test="count(preceding::stad[
                        ancestor::gemeente/@naam = $gemeente
                      ]) = 0">                       
                      <xsl:value-of select="$gemeente"/>
                    </xsl:if>
                  </td>
                  <td>
                    <xsl:value-of select="$stad"/>
                  </td>
                  <td>
                    <xsl:value-of select="count(child::bedrijf)"/>
                  </td>
                </tr>
              </xsl:for-each>
            </xsl:for-each>
          </xsl:for-each>
        </table>
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>

I use three levels of <xsl:for-each> to avoid recomputing $provincie and $gemeente in each iteration.

I use a simple test to see if the field provincie or gemeente corresponds to the first stad inside of a provincie or a gemeente . If it is the first occurence (which means the count of the preceding ones is zero), I print its value.

Martin Ronnen suggests in its comment above to take advantage from the HTML attribute rowspan in outputting the results. If you like that idea, you could modify the code in this way:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:template match="bedrijven">
    <html>
      <body>
        <h2>Provincies</h2>
        <table border="1">
          <tr>
            <th bgcolor="#9acd32" style="text-align:left">Provincie</th>
            <th bgcolor="#9acd32" style="text-align:left">Gemeente</th>
            <th bgcolor="#9acd32" style="text-align:left">Stad</th>
            <th bgcolor="#9acd32" style="text-align:left">Aantal bedrijven</th>
          </tr>
          <xsl:for-each select="provincie">
            <xsl:variable name="provincie" select="@naam"/>
            <xsl:variable name="steden-per-provincie" 
              select="count(gemeente/stad)"/>
            <xsl:for-each select="gemeente">
              <xsl:variable name="gemeente" select="@naam"/>
              <xsl:variable name="steden-per-gemeente" select="count(stad)"/>
              <xsl:for-each select="stad">
                <xsl:variable name="stad" select="@naam"/>
                <tr>
                  <xsl:if 
                    test="count(preceding::stad[
                      ancestor::provincie/@naam = $provincie
                    ]) = 0">
                    <td rowspan="{$steden-per-provincie}">
                      <xsl:value-of select="$provincie"/>
                    </td>
                  </xsl:if>
                  <xsl:if 
                    test="count(preceding::stad[
                      ancestor::gemeente/@naam = $gemeente
                    ]) = 0">                       
                  <td rowspan="{$steden-per-gemeente}">
                      <xsl:value-of select="$gemeente"/>
                    </td>
                  </xsl:if>
                  <td>
                    <xsl:value-of select="$stad"/>
                  </td>
                  <td style="text-align:right">
                    <xsl:value-of select="count(child::bedrijf)"/>
                  </td>
                </tr>
              </xsl:for-each>
            </xsl:for-each>
          </xsl:for-each>
        </table>
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>

Here is a screenshot of the result using the rowspan attribute: XSLT 输出

If you really need a single for-each loop, try this, but it will be less optimal:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:template match="bedrijven">
    <html>
      <body>
        <h2>Provincies</h2>
        <table border="1">
          <tr>
            <th bgcolor="#9acd32" style="text-align:left">Provincie</th>
            <th bgcolor="#9acd32" style="text-align:left">Gemeente</th>
            <th bgcolor="#9acd32" style="text-align:left">Stad</th>
            <th bgcolor="#9acd32" style="text-align:left">Aantal bedrijven</th>
          </tr>
          <xsl:for-each select="//stad">
            <xsl:variable name="provincie" select="ancestor::provincie/@naam"/>
            <xsl:variable name="gemeente" select="ancestor::gemeente/@naam"/>
            <xsl:variable name="stad" select="@naam"/>
            <tr>
              <xsl:if 
                test="count(preceding::stad[
                  ancestor::provincie/@naam = $provincie
                ]) = 0">
              <td rowspan="{count(following::stad[
                  ancestor::provincie/@naam = $provincie
                ]) + 1}">
                  <xsl:value-of select="$provincie"/>
                </td>
              </xsl:if>
              <xsl:if 
                test="count(preceding::stad[
                  ancestor::gemeente/@naam = $gemeente
                ]) = 0">                       
              <td rowspan="{count(following::stad[
                  ancestor::gemeente/@naam = $gemeente
                ]) + 1}">
                  <xsl:value-of select="$gemeente"/>
                </td>
              </xsl:if>
              <td>
                <xsl:value-of select="$stad"/>
              </td>
              <td style="text-align:right">
                <xsl:value-of select="count(child::bedrijf)"/>
              </td>
            </tr>
          </xsl:for-each>
        </table>
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>

And, finally, if you want to use the xsl:number feature, here you have a version of the code which is perhaps easier to read:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:template match="bedrijven">
    <html>
      <body>
        <h2>Provincies</h2>
        <table border="1">
          <tr>
            <th bgcolor="#9acd32" style="text-align:left">Provincie</th>
            <th bgcolor="#9acd32" style="text-align:left">Gemeente</th>
            <th bgcolor="#9acd32" style="text-align:left">Stad</th>
            <th bgcolor="#9acd32" style="text-align:left">Aantal bedrijven</th>
          </tr>
          <xsl:for-each select="//stad">
            <xsl:variable name="provincie" select="ancestor::provincie/@naam"/>
            <xsl:variable name="gemeente" select="ancestor::gemeente/@naam"/>
            <xsl:variable name="stad" select="@naam"/>
            <xsl:variable name="gemeente-nr">
              <xsl:number count="gemeente" from="provincie"/>
            </xsl:variable>
            <xsl:variable name="stad-nr">
              <xsl:number count="stad" from="gemeente"/>
            </xsl:variable>
            <tr>
              <xsl:if test="($gemeente-nr = 1) and ($stad-nr = 1)">
                <td rowspan="{count(ancestor::provincie//stad)}">
                  <xsl:value-of select="$provincie"/>
                </td>
              </xsl:if>
              <xsl:if test="$stad-nr = 1">                       
                <td rowspan="{count(ancestor::gemeente//stad)}">
                  <xsl:value-of select="$gemeente"/>
                </td>
              </xsl:if>
              <td><xsl:value-of select="$stad"/></td>
              <td style="text-align:right">
                <xsl:value-of select="count(child::bedrijf)"/>
              </td>
            </tr>
          </xsl:for-each>
        </table>
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>

You can use xsl:number :

<xsl:variable name="gemeente-pos">
    <xsl:number level="any" from="provincie"/>
</xsl:variable>

<xsl:variable name="stad-pos">
    <xsl:number/>
</xsl:variable>

   <tr>
      <xsl:choose>
         <xsl:when test="$gemeente-pos != 1"><td></td></xsl:when>
         <xsl:otherwise><td><xsl:value-of select="$provincie"/></td></xsl:otherwise>
      </xsl:choose>
    
      <xsl:choose>
         <xsl:when test="$stad-pos != 1"><td></td></xsl:when>
         <xsl:otherwise><td><xsl:value-of select="$gemeente"/></td></xsl:otherwise>
      </xsl:choose>
      <td><xsl:value-of select="$stad"/></td>
      <td><xsl:value-of select="count(child::bedrijf)"/></td>
   </tr>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM