Reputation: 18950
I have an XML like:
<ast>
<group>
<Set>
<location line="1" column="22" offset="22"/>
<group>
<Id value="foo">
<location line="1" column="31" offset="31"/>
</Id>
</group>
<group>
<Function>
<location line="1" column="22" offset="22"/>
<end-location line="1" column="49" offset="49"/>
<group>
<Id value="a">
<location line="1" column="35" offset="35"/>
</Id>
<Id value="b">
<location line="1" column="37" offset="37"/>
</Id>
</group>
<group>
<Return>
<location line="1" column="40" offset="40"/>
<Number value="0">
<location line="1" column="47" offset="47"/>
</Number>
</Return>
</group>
</Function>
</group>
</Set>
...
</group>
</ast>
which I process with this template:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="text()" /><!-- remove blanks -->
<xsl:template match="Set[group[position()=1]/Id][group[position()=2]/Function]">
<function-def>
<xsl:attribute name="name">
<xsl:value-of select="group[position()=1]/Id/@value" />
<xsl:text>(</xsl:text>
<xsl:apply-templates select="group[position()=2]/Function" />
<xsl:text>)</xsl:text>
</xsl:attribute>
<xsl:copy-of select="group/Function/location" />
<xsl:copy-of select="group/Function/end-location" />
</function-def>
</xsl:template>
<xsl:template match="Function/group[position()=1]/Id">
<xsl:value-of select="@value" />
<xsl:if test="position() != last()"><xsl:text>,</xsl:text></xsl:if>
</xsl:template>
</xsl:stylesheet>
however the condition position() != last()
on the last template is not working. Why?
The output renders as:
<?xml version="1.0"?>
<function-def name="foo(a,b,)">...
while it should be:
<?xml version="1.0"?>
<function-def name="foo(a,b)">...
Upvotes: 0
Views: 2577
Reputation: 70618
It is working, but not in the way you think....
In your first template, you have this xsl:apply-templates
<xsl:apply-templates select="group[position()=2]/Function" />
But you have no template matching Function
, and so XSLT's built-in template rules kicks in, which is this...
<xsl:template match="*|/">
<xsl:apply-templates/>
</xsl:template>
This will select the Group
elements, for which again there is no template. Now, when it does <xsl:apply-templates/>
this will select all child nodes, which includes the empty text nodes used to indent the XML.
The problem is when you are testing position() = last()
you are testing the position of the element in the set of all the child nodes that have been selected, which includes the text nodes. There is an empty text node after the last id
, so id
may be the last id
element, but it is not the last child node.
One solution, is to tell XSLT to strip out empty text nodes, so that id
then does become the last child node
<xsl:strip-space elements="*" />
Alternatively, you can add a template matching group
, and explicitly select only id
nodes
<xsl:template match="Function/group[position()=1]">
<xsl:apply-templates select="Id" />
</xsl:template>
Upvotes: 2