Translate

Archives

XSLT Variables in Attribute Match Predicates

I tripped over the issue of using a variable as a predicate in an attribute match again today. This has happened to me before and I should know better but it have been a while since I had to do any serious XSLT1 coding. I now mostly work with XSLT2. Since there is not much information on the Internet about this issue, I decided to explain the issue in this post.

Consider the following simple XML document:

<?xml version="1.0"?>
<employees>
     <employee id="333">
          <name>Ciara</name>
      </employee>
      <employee id="334">
          <name>Christina</name>
      </employee> 
      <employee id="335">
          <name>Kenneth</name>
      </employee>
      <employee id="336">
          <name>Robert</name>
      </employee>
</employees>


Suppose I want to output the name (and only the name) of the employee who’s ID is 334. Here is a simple stylesheet which does that:

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

  <xsl:output method="text"/>

  <xsl:template match="/">
      <xsl:apply-templates select="//employee[@id='334']" />
  </xsl:template>

  <xsl:template match="employee">
      <xsl:value-of select="name" />
  </xsl:template>

</xsl:stylesheet>


The transformation outputs Christina as expected.

However what I would like to do is make the stylesheet more general and be able to pass in an employee id from the command line so that I do not have to keep editing the stylesheet. Something like the following:

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

  <!-- xsltproc: pass in on command line as -param empid "'value'" -->
  <xsl:param name="empid"/>

  <xsl:output method="text"/>

  <xsl:template match="/">
      <xsl:apply-templates select="//employee[@id=$empid]" />
  </xsl:template>

  <xsl:template match="employee">
      <xsl:value-of select="name" />
  </xsl:template>

</xsl:stylesheet>


Again, this works as expected and Christina is outputted.

Now, consider the case where I want to change the name of employee 334 to Joanna and output a new employee file (XML document). Here is a simple stylesheet which does that:

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

   <!-- xsltproc: pass in on command line as -param empid "'value'" -->
   <xsl:param name="empid"/>

   <xsl:output method="xml" indent="yes"/>

   <xsl:template match="employee[@id='334']" >
    <employee id="334">
        <name>Joanna</name>
    </employee>
   </xsl:template>

   <xsl:template match="node()|@*">
       <xsl:copy>
          <xsl:apply-templates select="@*|node()"/>
       </xsl:copy>
   </xsl:template>

</xsl:stylesheet>


The output from the transformations is (after I have tidied up the whitespace):

<?xml version="1.0"?>
<employees>
    <employee id="333">
        <name>Ciara</name>
    </employee>
    <employee id="334">
         <name>Joanna</name>
    </employee> 
    <employee id="335">
        <name>Kenneth</name>
    </employee>
    <employee id="336">
        <name>Robert</name>
    </employee>
</employees>


Now I would like to do something similar to what I did in the second example above and make the previous stylesheet more general by passing in an employee id and new name to the stylesheet which in turn would generate a new employee file (XML document).

Something like the following:

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

   <!-- xsltproc: pass in on command line as -param empid "'value'" -->
   <xsl:param name="empid"/>
   <xsl:param name="newname"/>

   <xsl:output method="xml" indent="yes"/>

   <xsl:template match="employee[@id=$empid]" >
       <employee id="334">
           <name><value-of select="$newname"/></name>
       </employee>
   </xsl:template>

   <xsl:template match="node()|@*">
       <xsl:copy>
          <xsl:apply-templates select="@*|node()"/>
       </xsl:copy>
   </xsl:template>

</xsl:stylesheet>


Unfortunately I get the following error messages when I try to perform the transformation:

$ xsltproc -param empid "'334'" -param newname "'Joanna'" employee.xsl employee.xml
XPath error : Undefined variable
compilation error: file employee.xsl line 10 element template
Failed to compile predicate
$ 


It took me quite a while to figure out that the message about an undefined variable was a red herring.and that the error message that I needed to focus on was actually the Failed to compile predicate message. Finally a bell went off in the back of my scull and I realized that I was dealing with a variable in match predicate issue.

The controlling text in the XSLT1 specification is in the first paragraph of Section 5.3.
XLST1 Section 5.3
Here it states that it is an error for the value of a match attribute to contain a VariableReference. A VariableReference is essentially anything starting with a ‘$’.

Here is the reworked stylesheet:

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

   <!-- xsltproc: pass in on command line as -param empid "'value'" -->
   <xsl:param name="empid"/>
   <xsl:param name="newname"/>

   <xsl:output method="xml" indent="yes"/>

   <xsl:template match="//employee">
       <xsl:if test="@id=$empid">
           <xsl:element name="employee" >
               <xsl:attribute name="id" ><xsl:value-of select="$empid"/></xsl:attribute>
               <name><xsl:value-of select="$newname" /></name>
           </xsl:element>
       </xsl:if>
       <xsl:if test="not(@id=$empid)">
           <xsl:copy-of select="." />
       </xsl:if>
   </xsl:template>

   <xsl:template match="node()|@*">
       <xsl:copy>
           <xsl:apply-templates select="@*|node()"/>
       </xsl:copy>
   </xsl:template>

</xsl:stylesheet>


This stylesheet now works as intended and the output from the transformation is as shown below (after whitespace cleanup):

[fpm@ultra ~]$ xsltproc -param empid "'334'" -param newname "'Joanna'" employee.xsl employee.xml
<?xml version="1.0"?>
<employees>
    <employee id="333">
        <name>Ciara</name>
    </employee>
    <employee id="334"><name>Joanna</name></employee> 
    <employee id="335">
        <name>Kenneth</name>
    </employee>
    <employee id="336">
        <name>Robert</name>
    </employee>
</employees>


If you are using Saxon 9, the following command line will perform the transformation without error using the last stylesheet provided you change the XSL version number on the stylesheet to 2.0:

java -jar /usr/share/java/saxon.jar -s:employee.xml -xsl:employee.xsl newname=Joanna empid=334


Interestingly, I could find no mention of a variable in attribute match predicate restriction in the XSLT2 specification. So I decided to see if my original stylesheet with the attribute match predicate variable would work in XSLT2. It works! Here is the stylesheet I used to check this hypothesis:

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

   <!-- xsltproc: pass in on command line as -param empid "'value'" -->
   <xsl:param name="empid"/>
   <xsl:param name="newname"/>

   <xsl:output method="xml" indent="yes"/>

   <xsl:template match="employee[@id=$empid]" >
       <employee id="334">
           <name><value-of select="{$newname}"/></name>
       </employee>
   </xsl:template>

   <xsl:template match="node()|@*">
       <xsl:copy>
           <xsl:apply-templates select="@*|node()"/>
       </xsl:copy>
   </xsl:template>

</xsl:stylesheet>


Note the use of an attribute value template (AVT) after the name literal result element.

Here is command line and the output using the above stylesheet with Saxon9 EE:

$ java -jar /usr/share/java/saxon.jar -s:employee.xml -xsl:employee2.xsl newname="Joanna" empid=334
<?xml version="1.0" encoding="UTF-8"?>
<employees>
    <employee id="333">
        <name>Ciara</name>
    </employee>
    <employee id="334">
      <name>
         <value-of select="Joanna"/>
      </name>
   </employee> 
    <employee id="335">
        <name>Kenneth</name>
    </employee>
    <employee id="336">
        <name>Robert</name>
    </employee>
</employees>$ 


Looks like the restriction on attribute match predicate variables was silently relaxed in XSLT2.

So the message to take away from this post is that you cannot do the following in XSLT1:

    <xsl:template match="//something[. = $value]" >

;
and for the most part you can’t use variables to contain expressions or patterns; you can only use them to hold strings, numbers, booleans and node-sets..

Enjoy!

Comments are closed.