tags:

views:

31

answers:

1

I'm working with an XSL stylesheet, and I'm trying to use the method shown here to store a sorted subtree as a variable. I'm using saxon 8.7 vis the xml-maven-plugin to transform my XML file. Here's the code that I have:

<xsl:variable name="miniDays">
    <xsl:for-each select="//day[position() > $firstPosToShow]">
        <xsl:sort select="@date" order="descending" />
        <xsl:copy-of select=".|@*" />
    </xsl:for-each>
</xsl:variable>

When I run the stylesheet, I get the following error:

Error at xsl:copy-of on line 598 of file:/D:/home/Projects/src/main/xsl/site.xsl:
  XTDE0420: Cannot create an attribute node (date) whose parent is a document node

If I just set the subtree to the variable without sorting, it works, but it's not sorted:

<xsl:variable name="miniDays" select="//day[position() > $firstPosToShow]" />

If I set the copy-of statement's select to ".", it gets past that point, but gives me an error later on when I actually try to use the variable data. Here's how it's being used:

<xsl:for-each select="exsl:node-set($miniDays)">
    <xsl:variable name="in" select="local:calculate-total-in-days(.)" />
    <!-- do some stuff with the var -->
</xsl:for-each>

And the error:

Error on line 676 of file:/D:/home/Projects/src/main/xsl/site.xsl:
  XPTY0004: Required item type of first argument of local:calculate-total-in-days() is element(); supplied value has item type document-node()

The function:

<xsl:function name="local:calculate-total-in-days">
    <xsl:param name="days" as="element()*" />
    <!-- Do some calculations -->
</xsl:function>

Am I using exsl:node-set incorrectly? And what should be in the copy-of's select, "." or ".|@*"?

+1  A: 

There are a number of problems with your code:

  1. <xsl:for-each select="//day[position() > $firstPosToShow]"> . This will select every day element in the document that is at position $firstPosToShow+1 or bigger in the set of day children of its parent! Most probably you want (//day)[position() >= $firstPosToShow]

  2. <xsl:copy-of select=".|@*" /> . This copies the current element, but also copies its attributes. An attribute can be copied only when the parent is an element. This is not the case as the operations inside an untyped variable create a temporary tree (a document) and a document node cannot have attributes. The correct instruction is: <xsl:copy-of select="." />

  3. List itemIn the following code:

the expression exsl:node-set($miniDays) again is of type document-node() and the <xsl:for-each> selects just one (this) node. This explains the raised error, because local:calculate-total-in-days(.) expects an element-argument but is passed a document node.

The correct code is:

<xsl:for-each select="exsl:node-set($miniDays)/*"> 
    <xsl:variable name="in" select="local:calculate-total-in-days(.)" /> 
    <!-- do some stuff with the var --> 
</xsl:for-each>

Also, exslt:node:set() is not required in XSLT 2.0, because there is no RTF type in XSLT 2.0, and in fact is not supported in Saxon 9.x. Therefore, the correct code will be:

<xsl:for-each select="$miniDays/*"> 
    <xsl:variable name="in" select="local:calculate-total-in-days(.)" /> 
    <!-- do some stuff with the var --> 
</xsl:for-each>

Alternatively, you may consider specifying the type of $miniDays explicitly as element()* and this will simplify the code -- it would not be necessary to use $miniDays/* -- just $miniDays

Dimitre Novatchev
Nice answer - thanks! Gave me a lot of stuff to read up on.
Matt McMinn