user1540823
user1540823

Reputation: 11

XSLT 1.0: counting nodes in XSLT

I need to output outbuilding nodes (only and only the ones that have a 'vacant'-child) with correct sequence number (see XML)

There are no attributes in XML below, just elements and values.

<stuff>
    <locations>
        <location>
            <properties>
                <property>
                    <belongings>
                        <house>
                            <houseWithAC/>
                        </house>
                    </belongings>
                </property>
                <property>
                    <belongings>
                        <outbuilding/>
                    </belongings>
                </property>
                <property>
                    <belongings>
                        <outbuilding>
                            <vacant/>
                        </outbuilding>
                    </belongings>
                </property>
                <property>              
                    <belongings>
                        <outbuilding>
                            <vacant/>
                        </outbuilding>
                    </belongings>
                </property>
                <property>
                    <belongings>
                        <vehicle/>
                    </belongings>
                </property>
            </properties>
        </location>
        <location>
            <properties>
                <property>
                    <belongings>
                        <vehicle/>
                    </belongings>
                </property>
                <property>
                   <belongings>
                       <outbuilding/>
                   </belongings>
                </property>
                <property>
                    <belongings>
                        <house/>
                    </belongings>
                </property>
                <property>
                     <belongings>
                        <outbuilding>
                            <vacant/>
                        </outbuilding>
                     </belongings>
                 </property>
                 <property>
                     <belongings>
                         <barn/>
                     </belongings>
                 </property>
             </properties>
          </location>
      </locations>
</stuff>
  1. sequencing: I need to count every belonging child node per location (for ex. house, outbuilding, vehicle) Note, house with houseWithAC child counts as two nodes !! the sequence format is Loc ### / Item ###

  2. I need to output every outbuilding node that has vacant child, with the correct sequence numbers (see above)

Note also: "collection" nodes like locations, properties have many children, while node belongings - only one.

I tried to create a recursive loop, but id does not work: if I have outbuilding node without "vacant"-child I still step into the if statement (looks like that condition is allways true)

Something like this: . . . . . . . . . . .

<xsl:for-each select="Locations/Location">
    <xsl:variable name="LOCID">
        <xsl:number level="single" count="*" format="001"/>
    </xsl:variable>
   <xsl:call-template name="WriteItems">
       <xsl:with-param name="propertiesNodes" select="properties/property"/>
       <xsl:with-param name="NumberOfProperties" select="count(properties/property)"/>
       <xsl:with-param name="LocCount" select="$LOCID"/>
   </xsl:call-template>
</xsl:for-each>     
. . . . . . . . . . . . .


<xsl:template name="WriteItems">
    <xsl:param name="propertiesNodes"/>
    <xsl:param name="NumberOfProperties"/>
    <xsl:param name="LocCount"/>
    <xsl:param name="Index">1</xsl:param>
    <xsl:param name="ItemCount">1</xsl:param>     
    <xsl:choose>
        <xsl:when test="$Index > $NumberOfProperties"/>
         <xsl:otherwise>
           <xsl:choose>
               <xsl:when test="$propertiesNodes[$Index]/belongings/house/houseWithAC">
                  <xsl:call-template name="WriteItems">
                      <xsl:with-param name="propertiesNodes" select="$propertiesNodes"/>
                      <xsl:with-param name="NumberOfProperties" select="$NumberOfProperties"/>
                      <xsl:with-param name="LocCount" select="$LocCount"/>
                      <xsl:with-param name="Index" select="$Index + 1"/>
                      <xsl:with-param name="ItemCount" select="$ItemCount + 2"/>
                  </xsl:call-template>
               </xsl:when>
               <xsl:when test="$propertiesNodes[$Index]/belongings/house">
                   <xsl:call-template name="WriteItems">
                       <xsl:with-param name="propertiesNodes" select="$propertiesNodes"/>
                       <xsl:with-param name="NumberOfProperties" select="$NumberOfProperties"/>
                       <xsl:with-param name="LocCount" select="$LocCount"/>
                       <xsl:with-param name="Index" select="$Index + 1"/>
                       <xsl:with-param name="ItemCount" select="$ItemCount + 1"/>
                   </xsl:call-template>
               </xsl:when>
               <xsl:when test="$propertiesNodes[$Index]/belongings/vehicle">
                   <xsl:call-template name="WriteItems">
                       <xsl:with-param name="propertiesNodes" select="$propertiesNodes"/>
                       <xsl:with-param name="NumberOfProperties" select="$NumberOfProperties"/>
                       <xsl:with-param name="LocCount" select="$LocCount"/>
                       <xsl:with-param name="Index" select="$Index + 1"/>
                       <xsl:with-param name="ItemCount" select="$ItemCount + 1"/>
                   </xsl:call-template>
               </xsl:when>
               <xsl:when test="$propertiesNodes[$Index]/belongings/barn">
                   <xsl:call-template name="WriteItems">
                       <xsl:with-param name="propertiesNodes" select="$propertiesNodes"/>
                       <xsl:with-param name="NumberOfProperties" select="$NumberOfProperties"/>
                       <xsl:with-param name="LocCount" select="$LocCount"/>
                       <xsl:with-param name="Index" select="$Index + 1"/>
                       <xsl:with-param name="ItemCount" select="$ItemCount + 1"/>
                   </xsl:call-template>
               </xsl:when>
               <xsl:when test="$propertiesNodes[$Index]/belongings/outbuilding">
                  <xsl:if test="$propertiesNodes[$Index]/belongings/outbuilding/vacant">
                        <xsl:variable name="ITEMID">
                             <xsl:value-of select="format-number($ItemCount,'000')"/>
                        </xsl:variable>  

                         .... output as  concat('-  ', $LocCount, '/', $ITEMID) ...... some description ....


                   </xsl:if>
                   <xsl:call-template name="WriteItems">
                       <xsl:with-param name="propertiesNodes" select="$propertiesNodes"/>
                       <xsl:with-param name="NumberOfProperties" select="$NumberOfProperties"/>
                       <xsl:with-param name="LocCount" select="$LocCount"/>
                       <xsl:with-param name="Index" select="$Index + 1"/>
                       <xsl:with-param name="ItemCount" select="$ItemCount + 1"/>
                   </xsl:call-template>
               </xsl:when>
           </xsl:choose>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

Upvotes: 0

Views: 2902

Answers (1)

Jukka Matilainen
Jukka Matilainen

Reputation: 10188

I am not sure whether I understood your question correctly, but if this is what you want:

For each vacant "item", output:

  1. the position of the containing location within all locations, and
  2. the position of the "item" within all "items" in the containing location

Then you could achieve this by looping over the locations and "items" and using the position() function to tell the position when a match is found. Like this:

<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text"/>

  <xsl:template match="/">
    <xsl:for-each select="//location">
      <xsl:variable name="locposition" select="position()"/>
      <xsl:for-each select=".//belongings//*[not(self::vacant)]">
        <xsl:if test="vacant">
          <xsl:value-of select="format-number($locposition, '000')"/>
          <xsl:text> / </xsl:text>
          <xsl:value-of select="format-number(position(), '000')"/>
          <xsl:text>&#xa;</xsl:text>
        </xsl:if>
      </xsl:for-each>
    </xsl:for-each>
  </xsl:template>

</xsl:stylesheet>

With your example input, this will output the following:

001 / 004
001 / 005
002 / 004

Edited to add: Here is an alternative solution using xsl:number which avoids explicit looping:

<xsl:stylesheet version="1.0"
                    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text"/>

  <xsl:template match="*[vacant]">
    <xsl:number count="location" format="001"/> 
    <xsl:text> / </xsl:text>
    <xsl:number count="*[not(self::vacant)][ancestor::belongings]"
                level="any" from="location" format="001"/>
    <xsl:text>&#xa;</xsl:text>
  </xsl:template>

  <xsl:template match="text()"/>

</xsl:stylesheet>

Upvotes: 2

Related Questions