XPath ancestor and descendant in XSL copy-of

16,711

Solution 1

And I have the following XSL:

<xsl:template match="/"> 
    <xsl:copy-of select="ancestor::criterion/> 
</xsl:template>

which produces nothing!

As it should!

ancestor::criterion

is a relative expression, which means that it is evaluated off the current node (matched by the template). But the current node is the document node /.

So, the above is equivalent to:

/ancestor::criterion

However, by definition the document node / has no parents (and that means no ancestors), so this XPath expression doesn't select any node.

I expected it to produce:

<file> 
    <criteria> 
    </criteria> 
</file>

What you probably wanted was:

//criterion/ancestor::*

or

//*[descendant::criterion]

The last two XPath expressions are equivalent and select all elements that have a criterion descendant.

Finally, to produce the output you wanted, here is one possible solution:

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

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

 <xsl:template match="root | criterion | format"/>
</xsl:stylesheet>

When this transformation is applied on the provided XML document, the wanted output is produced:

<file>
<criteria>
</criteria>
</file>

Solution 2

ancestor is for selecting nodes that are higher (closer to the root) in the XML document. descendant is for selecting nodes that are lower (children) in the XML document.

In your example, ancestor::criterion selects nothing because the current node is / (meaning the root of the document - <file> in this case), as indicated by match="/". The root node has no ancestors so the ancestor axis does nothing.

To get every <criterion> element, you should use the descendant axis:

<xsl:template match="/">
  <xsl:copy-of select="descendant::criterion"/>
</xsl:template>

Or its shortcut //:

<xsl:template match="/">
  <xsl:copy-of select="//criterion"/>
</xsl:template>

That will return the following:

<criterion>AAA</criterion>

Using a loop or another template you can get all three of them:

<xsl:template match="/">
  <file>
    <xsl:apply-templates select="//criterion"/>
  </file>
</xsl:template>
<xsl:template match="criterion">
  <xsl:copy-of select="."/>
</xsl:template>

This will produce the following:

<file>
  <criterion>AAA</criterion>
  <criterion>BBB</criterion>
  <criterion>CCC</criterion> 
</file>

If you want to get the <file> element, too, it's a bit more complicated. XPath specifies nodes and simple copies will not copy the elements that contain the elements you select. I can clarify this point more if you're still confused.

Share:
16,711

Related videos on Youtube

developer
Author by

developer

Updated on June 04, 2022

Comments

  • developer
    developer almost 2 years

    I am new to XPath, and from what I have read in some tutorials about axes, I am still left wondering how to implement them. They aren't quite behaving as I had expected. I am particularly interested in using ancestor and descendant axes.

    I have the following XML structure:

    <file>
        <criteria>
            <root>ROOT</root>
            <criterion>AAA</criterion>
            <criterion>BBB</criterion>
            <criterion>CCC</criterion> 
        </criteria>
        <format>
            <sort>BBB</sort>
        </format>
    </file>
    

    And I have the following XSL:

    <xsl:template match="/">
        <xsl:copy-of select="ancestor::criterion/>
    </xsl:template>
    

    which produces nothing!

    I expected it to produce:

    <file>
        <criteria>
        </criteria>
    </file>
    

    Can someone explain ancestor and descendant axes to me in a more helpful way than the tutorials I have previously read?

    Thanks!

  • Dimitre Novatchev
    Dimitre Novatchev almost 14 years
    You say that: <xsl:template match="/"> <xsl:copy-of select="//criterion"/> </xsl:template> will return the following: <criterion>AAA</criterion> This is not so. The result will be: ` <criterion>AAA</criterion> <criterion>BBB</criterion> <criterion>CCC</criterion> `
  • developer
    developer almost 14 years
    Thank you for your response. //criterion/ancestor::* and //*[descendant::criterion] give me weird results, and while the fully coded solution you provided does work and produce expected results, I still need a less hard-coded solution (this above solution requires the knowledge of root and format.. but there may be others I do not know about..) Also, thanks for explanation of descendant and ancestor axes :)
  • Dimitre Novatchev
    Dimitre Novatchev almost 14 years
    @iHeartGreek: Why do you expect an XPath expression that selects specific nodes to select other nodes? In case you have another problem, just ask another question. You haven't asked the question that is not answered. :)
  • Flynn1179
    Flynn1179 almost 14 years
    Just a point, but descendant::criterion is NOT equivalent to //criterion; the latter will find all criterion nodes in the entire document. The correct shortcut is .//criterion.

Related