views:

407

answers:

2

I'd like to add an element to a xml document and I'd like to pass as a parameter the path to the element.

sample.xml file:

<?xml version="1.0"?>
<stuff>
  <element1>
    <foo>2</foo>
<bar/>
  </element1>
  <element2>
<subelement/>
<bar/>
   </element2>
   <element1>
     <foo/>
 <bar/>
   </element1>
 </stuff>

Using:

xalan.exe -p myparam "element1" sample.xml addelement.xslt

I'd like the following result:

<?xml version="1.0"?>
<stuff>
  <element1>
    <foo>2</foo>
    <bar/>
    <addedElement/>
  </element1>
  <element2>
<subelement/>
<bar/>
   </element2>
   <element1>
     <foo/>
 <bar/>
     <addedElement/>
   </element1>
 </stuff>

I've manage to write addelement.xslt, when hardcoding the path it works, but when I try to use parameter myparam in the match attribute I get:

XPathParserException: A node test was expected.
pattern = '$myparam/*[last()]' Remaining tokens are:  ('$' 'myparam' '/' '*' '[' 'last' '(' ')' ']') (addelement.xslt, line 12, column 42)

addelement.xslt

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;

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

<xsl:template match="element1/*[last()]">
    <xsl:copy-of select="."/>
<addedElement></addedElement>
</xsl:template>

</xsl:stylesheet>

addelement.xslt with hardcoded path replaced

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;

<xsl:param name="myparam"/>

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

<xsl:template match="$myparam/*[last()]">
    <xsl:copy-of select="."/>
<addedElement></addedElement>
</xsl:template>

</xsl:stylesheet>

Thanks for helping

+1  A: 

I don't think you can use variables/paramaters in matching templates like you have coded. Even this doesn't work

<xsl:template match="*[name()=$myparam]/*[last()]">

Instead, try changing the first matching template to as follows, so that the parameter check is inside the template code, not as part of the match statement.

<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
        <xsl:if test="local-name() = $myparam">
            <addedElement/>
        </xsl:if>
    </xsl:copy>
</xsl:template>
Tim C
Do you mean that variable/parameters are not a valid XPath expression ?However, I can't make it work with your solution.
David
First of all, XSLT 1.0 does not allow the use of variables/parameters in match patterns at all.XSLT 2.0 does allow it but if you use Xalan, an XSLT 1.0 processor, you can't use XSLT 2.0 features.And secondly, if you have a variable or parameter of type string with an element name then you can't use it to construct XPath expressions with it dynamically. That is not how variable/parameters work, neither in XPath or in other languages. If the variable/parameter value is of type string then you can use it anywhere where a string is allowed, as Tim has done.
Martin Honnen
Thanks Martin, do you know any command line based XSLT 2.0 processor ?
David
Saxon 9 (http://saxon.sourceforge.net/), Gestalt (http://gestalt.sourceforge.net), AltovaXML Tools (http://www.altova.com/altovaxml.html) can all be used from the command line.
Martin Honnen
I'm confused, I _can_ make it work with Tim's solution both with Xalan and Saxon 9. With Xalan, it was not working because I had to use single quotes around the value of the param in the command line.Since Tim's solution works also into Xalan, I conclude that Tim's solution is XSLT 1.0 compatible.By the way, I am not sure that I understand your first comment. Correct me if I'm wrong: XSLT 2.0 allows variable/parameters in match patterns but they can't be use to construct XPath expression ? Then, how can they be used in match patterns ?
David
Tim has posted two snippets, the first one uses $myparam in a match pattern, that is not allowed in XSLT 1.0 and thus any XSLT 1.0 processor should complain about such code. Whether Xalan does complain I have not tested. That snippet is however fine as XSLT 2.0 code as XSLT 2.0 allows using variables/parameters in match patterns.The second snippet that Tim posted is fine as XSLT 1.0 code.As for using a variable of type string in a match pattern or an XPath expression, you can use them anywhere where a string value could be used.Your first attempt, match="$myparam/*[last()]", is not allowed.
Martin Honnen
A: 

Here is how you could do that with XSLT 1.0:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;

<xsl:param name="n" select="'element1'"/>

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

<xsl:template match="*/*[last()]">
  <xsl:choose>
    <xsl:when test="local-name(..) = $n">
      <xsl:copy-of select="."/>
      <addedElement></addedElement>
    </xsl:when>
    <xsl:otherwise>
      <xsl:copy>
        <xsl:apply-templates select="@* | node()"/>
      </xsl:copy>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

</xsl:stylesheet>
Martin Honnen