satish b
satish b

Reputation: 392

xsl:apply-templates not iterating over each node given by select

I'm using Saxon HE 9-4-0-3J. My intent is to use the select attribute on xsl:apply-templates to iterate over each <RECORD> element, but only a single <RECORD> element in the xml is being processed.

xml

<ENVELOPE>
<PRODUCT>
    <HEAD><FLAG>No</FLAG></HEAD>
    <BODY>
        <RECORD><Value>9</Value></RECORD>
        <RECORD><Value>10</Value></RECORD>
        <MISC>9</MISC>
    </BODY>
</PRODUCT>

xslt 2.0

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/ENVELOPE">
<Interface>
   <Data><xsl:apply-templates select="PRODUCT"/></Data>
</Interface>
</xsl:template>

<xsl:template match="PRODUCT">
   <xsl:choose>
      <xsl:when test="HEAD/FLAG='Yes'">
         <xsl:attribute name="Match">Affirmative</xsl:attribute>
         <xsl:apply-templates select="BODY/RECORD" mode="affr"/>
      </xsl:when>
      <xsl:when test="HEAD/FLAG='No'">
         <xsl:attribute name="Match">Negative</xsl:attribute>
         <xsl:apply-templates select="BODY/RECORD" mode="neg"/>
      </xsl:when>
   </xsl:choose>
</xsl:template>

<xsl:template match="RECORD" mode="affr">
   <xsl:attribute name="Value"><xsl:value-of select="Value"/></xsl:attribute>
</xsl:template>

<xsl:template match="RECORD" mode="neg">
   <xsl:attribute name="Value"><xsl:value-of select="Value"/></xsl:attribute>
</xsl:template>

</xsl:stylesheet>

Output

<Interface>
      <Data Match="Negative" Value="9"/> <!-- this line doesn't appear, I want it to -->
      <Data Match="Negative" Value="10"/>
</Interface>

Upvotes: 1

Views: 410

Answers (2)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243479

The problem is that you are creating two attributes with the same name -- Value. In a wellformed XML document an element cannot have more than one attribute with the same name.

To quote the W3C XSLT 2.0 Specification (this is exactly the same in XSLT 1.0, too):

9. If an attribute A in the result sequence has the same name as another attribute B that appears later in the result sequence, then attribute A is discarded from the result sequence.

So, whenever XSLT generates for an element more than one attributes with the same name, only the last attribute is copied to the output -- and this is what you see.

Here is a short and simple solution (only two templates, no conditionals, no parameters):

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:variable name="vDoc" select="/"/>

 <xsl:variable name="vMatch" select=
 "('Affirmative', 'Negative')[2 - number($vDoc/*/*/HEAD/FLAG eq 'Yes')]"/>

 <xsl:template match="/*">
  <Interface>
    <xsl:apply-templates select="*/BODY/RECORD"/>
  </Interface>
 </xsl:template>

 <xsl:template match="RECORD">
  <Data Match="{$vMatch}" Value="{Value}"/>
 </xsl:template>
</xsl:stylesheet>

when this transformation is applied on the provided XML document:

<ENVELOPE>
    <PRODUCT>
        <HEAD>
            <FLAG>No</FLAG>
        </HEAD>
        <BODY>
            <RECORD>
                <Value>9</Value>
            </RECORD>
            <RECORD>
                <Value>10</Value>
            </RECORD>
            <MISC>9</MISC>
        </BODY>
    </PRODUCT>
</ENVELOPE>

the wanted, correct result is produced:

<Interface>
   <Data Match="Negative" Value="9"/>
   <Data Match="Negative" Value="10"/>
</Interface>

Upvotes: 2

Jim Garrison
Jim Garrison

Reputation: 86774

The <DATA...> output tag is generated when you match <ENVELOPE...>, so there can only be one of these output. Any <xsl:attribute> directives merely add to this output tag, with later values overwriting earlier ones.

You must explicitly generate the DATA tags you want output, as in:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">

  <xsl:output method="xml" indent="yes"/>
  <xsl:template match="/ENVELOPE">
    <Interface>
      <xsl:apply-templates select="PRODUCT"/>
    </Interface>
  </xsl:template>

  <xsl:template match="PRODUCT">
    <xsl:variable name="flag">
      <xsl:choose>
        <xsl:when test="HEAD/FLAG='Yes'">Affirmative</xsl:when>
        <xsl:when test="HEAD/FLAG='No'">Negative</xsl:when>
      </xsl:choose>
    </xsl:variable>

    <xsl:apply-templates select="BODY/RECORD">
      <xsl:with-param name="flag"><xsl:value-of select="$flag"/></xsl:with-param>
    </xsl:apply-templates>

  </xsl:template>

  <xsl:template match="RECORD">
    <xsl:param name="flag"/>
    <Data Value="{Value}" Match="{$flag}"/>
  </xsl:template>

</xsl:stylesheet>

Upvotes: 2

Related Questions