One of the most grave handicaps of XSLT is, that there are no real variables like in other languages. For some tasks, like in the following example, it would be desirable to have them. There is an indirect way to create them by recursive templates.
A Stylesheet should sort datasets by an ID. We would like to list all single entries and as well all double entries in separated containers.
Source Example
<?xml version="1.0" encoding="UTF-8"?>
<source>
<row>
<id>001</id>
<value>1000</value>
</row>
<row>
<id>002</id>
<value>1020</value>
</row>
<row>
<id>003</id>
<value>980</value>
</row>
<row>
<id>002</id>
<value>1020</value>
</row>
<row>
<id>002</id>
<value>1040</value>
</row>
</source>
Target
<?xml version="1.0" encoding="UTF-8"?>
<target>
<singleRows>
<row>
<id>001</id>
<value>1000</value>
</row>
<row>
<id>003</id>
<value>980</value>
</row>
</singleRows>
<doubleRows>
<row>
<id>002</id>
<value>1020</value>
</row>
</doubleRows>
</target>
Lets define the rules that
A traditional program would now
A Stylesheet is not able to work in that way, we don't have variables or arrays. A solution can be to create a recursive template, that means a template, which is calling itself. The trick is to call it with parameters, which can be used in the role of variables.
Which Parameters should be given to the recursive call?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:template match="/">
<target>
<singleRows>
<xsl:call-template name="recursive">
<xsl:with-param name="ID" select="0"/>
<xsl:with-param name="POSITION" select="0"/>
<xsl:with-param name="COUNT" select="0"/>
<xsl:with-param name="CONTAINER" select="'single'"/>
</xsl:call-template>
</singleRows>
<doubleRows>
<xsl:call-template name="loop">
<xsl:with-param name="ID" select="0"/>
<xsl:with-param name="POSITION" select="0"/>
<xsl:with-param name="COUNT" select="0"/>
<xsl:with-param name="CONTAINER" select="'double'"/>
</xsl:call-template>
</doubleRows>
</target>
</xsl:template>
<xsl:template name="recursive">
<xsl:param name="ID"/>
<xsl:param name="POSITION"/>
<xsl:param name="COUNT"/>
<xsl:param name="CONTAINER"/>
<xsl:for-each select="//row">
<!--LOGIC-->
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
We start with an outer template, which is calling the recursive with null-values / the values for the container to be filled. Inside the recursive template we implement a loop over all row elements.
This task is quite easy. We can manage that with:
<xsl:for-each select="//row">
<xsl:sort select="id"/>
...
To look at any row only one time we ask for the current position in the loop and compare with the last position + 1
Now we compare the current ID with the last ID to find out, if ID is new or not.
Case the ID is new we call the template (after an output) again with COUNT=1, else we increment the COUNT.
<xsl:for-each select="//row">
<xsl:sort select="id"/>
<xsl:if test="position() = $POSITION+1">
<xsl:choose>
<xsl:when test="id!=$ID">
<!--OUTPUT-->
<xsl:call-template name="recursive">
<xsl:with-param name="ID" select="id"/>
<xsl:with-param name="POSITION" select="position()"/>
<xsl:with-param name="COUNT" select="1"/>
<xsl:with-param name="CONTAINER" select="$CONTAINER"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<!--OUTPUT-->
<xsl:call-template name="recursive">
<xsl:with-param name="ID" select="id"/>
<xsl:with-param name="POSITION" select="position()"/>
<xsl:with-param name="COUNT" select="$COUNT+1"/>
<xsl:with-param name="CONTAINER" select="$CONTAINER"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:for-each>
If we have a new ID in the loop, the old one has to be printed, but only if it was counted only once -> if COUNT=1.
We do that only for the container "single".
We fill the field "id" with the old ID,
the corresponding field "value" must be filled with the last value by a X-Path expression.
Finally we shouldn't forget the last row, which can't be printed by the next row.
<xsl:if test="$COUNT=1 and $CONTAINER='single'">
<row>
<id>
<xsl:value-of select="$ID"/>
</id>
<value>
<xsl:value-of select="../row/value[../id=$ID]"/>
</value>
</row>
</xsl:if>
<xsl:if test="position()=last() and $CONTAINER='single'">
<xsl:copy-of select="."/>
</xsl:if>
The double Output is a bit easier.
We know that there is an old ID in the loop.
If we fould it second time - COUNT=1 - we print it. We do that only for the container "double".
Because we use the current row we can just copy into the target.
<xsl:if test="$COUNT=1 and $CONTAINER='double'">
<xsl:copy-of select="."/>
</xsl:if>
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:template match="/">
<target>
<singleRows>
<xsl:call-template name="recursive">
<xsl:with-param name="ID" select="0"/>
<xsl:with-param name="POSITION" select="0"/>
<xsl:with-param name="COUNT" select="0"/>
<xsl:with-param name="CONTAINER" select="'single'"/>
</xsl:call-template>
</singleRows>
<doubleRows>
<xsl:call-template name="recursive">
<xsl:with-param name="ID" select="0"/>
<xsl:with-param name="POSITION" select="0"/>
<xsl:with-param name="COUNT" select="0"/>
<xsl:with-param name="CONTAINER" select="'double'"/>
</xsl:call-template>
</doubleRows>
</target>
</xsl:template>
<xsl:template name="recursive">
<xsl:param name="ID"/>
<xsl:param name="POSITION"/>
<xsl:param name="COUNT"/>
<xsl:param name="CONTAINER"/>
<xsl:for-each select="//row">
<xsl:sort select="id"/>
<xsl:if test="position() = $POSITION+1">
<xsl:choose>
<xsl:when test="id!=$ID">
<xsl:if test="$COUNT=1 and $CONTAINER='single'">
<row>
<id>
<xsl:value-of select="$ID"/>
</id>
<value>
<xsl:value-of select="../row/value[../id=$ID]"/>
</value>
</row>
</xsl:if>
<xsl:if test="position()=last() and $CONTAINER='single'">
<xsl:copy-of select="."/>
</xsl:if>
<xsl:call-template name="recursive">
<xsl:with-param name="ID" select="id"/>
<xsl:with-param name="POSITION" select="position()"/>
<xsl:with-param name="COUNT" select="1"/>
<xsl:with-param name="CONTAINER" select="$CONTAINER"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:if test="$COUNT=1 and $CONTAINER='double'">
<xsl:copy-of select="."/>
</xsl:if>
<xsl:call-template name="recursive">
<xsl:with-param name="ID" select="id"/>
<xsl:with-param name="POSITION" select="position()"/>
<xsl:with-param name="COUNT" select="$COUNT+1"/>
<xsl:with-param name="CONTAINER" select="$CONTAINER"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
8 | |
7 | |
5 | |
4 | |
4 | |
4 | |
4 | |
3 | |
3 | |
3 |