Get first child node in XSLT using local-name()
Asked Answered
H

3

6

Assume we have this simple xml ...

 <books>   
    <book>
       <author/>
       <title/>
    </book>
    <book>
       <author/>
       <title/>
    </book>
 </books>

I'm using this xpath to get the elements of the first book instance.

//books[1]/*

Returns

<author/>
<title/>

And that works fine, but I have to get it working using local-name(). I've tried the following but none of these work...

//*[local-name()='books']/*

this returns repeating author and title elements, not good, I only need them from the first child

//*[local-name()='books'][0]/*

this does not return anything

Basically, I want to create a CSV file, so the first line in the output will be a header listing the book attribute names followed by the arbitrary data values. I only need to get the header part working.

author,title
john,The End is Near
sally,Looking for Answers
Haplo answered 8/11, 2011 at 23:3 Comment(4)
Your query //books[1]/* should return two book elements, not an author and a title element. So there is some confusion somewhere.Elegance
Nope, it returns just the first one with my Xalan 1.0 XSLT transformerHaplo
Yeah, that was my bad in the OP, I am using [1]-based, thx for the infoCon
@RaffiM: Are you aware of the fact that you have accepted a generally incorrect answer?Cursorial
A
12

The path expression you say works for you

//books[1]/*

generates a list of all child nodes of the first (and only in this case) occurrence of any <books> node. Because, in your data, the only occurrence of <books> is at the root, it is the same as

/books/*

which returns two <book> nodes, and so you are wrong to say that it returns only one node.

It is hard to know what you need, as if you are always applying local-name to the root node then you do not need to know its name and can access it with just /*, so you would want simply

/*/*[1]

However to access the first child node of a <books> node anywhere in the document you would write

//*[local-name()='books']/*[1]

You should be careful to confine your context as much as possible, as starting the XPath expression with // will force a search of the entire document, which is pointless and time-consuming if the node in question is always at the root.

Aid answered 9/11, 2011 at 1:54 Comment(1)
See my edits above int he original post for more details on what I'm trying to accomplish, sorry for the lack of additional detail. I tried using /*/*[1], it's printing "author,title" twice, i just need it to print once.Haplo
C
13

This is a FAQ -- the XPath [] operator has higher precedence (priority) than the // pseudo-operator.

So:

//someElemName[1]

selects every element named someElemName that is the first child of its parent -- and, depending on the XML document, there can be more than one such elements.

To change this, one must use brackets.

Use:

(//*[local-name() = 'book'])[1]/*

Also note: In XPath positions are 1-based, not 0-based.

XSLT-based verification:

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

 <xsl:template match="/">
  <xsl:copy-of select=
  "(//*[local-name() = 'book'])[1]/*"/>
 </xsl:template>
</xsl:stylesheet>

when this transformation is applied on the following XML document:

<books>
    <book num="1">
        <author num="1"/>
        <title num="1"/>
    </book>
    <book num="2">
        <author num="2"/>
        <title num="2"/>
    </book>
</books>

the wanted nodes are selected and copied to the output:

<author num="1"/>
<title num="1"/>
Cursorial answered 9/11, 2011 at 3:34 Comment(1)
@RaffiM: Was my answer useful to you?Cursorial
A
12

The path expression you say works for you

//books[1]/*

generates a list of all child nodes of the first (and only in this case) occurrence of any <books> node. Because, in your data, the only occurrence of <books> is at the root, it is the same as

/books/*

which returns two <book> nodes, and so you are wrong to say that it returns only one node.

It is hard to know what you need, as if you are always applying local-name to the root node then you do not need to know its name and can access it with just /*, so you would want simply

/*/*[1]

However to access the first child node of a <books> node anywhere in the document you would write

//*[local-name()='books']/*[1]

You should be careful to confine your context as much as possible, as starting the XPath expression with // will force a search of the entire document, which is pointless and time-consuming if the node in question is always at the root.

Aid answered 9/11, 2011 at 1:54 Comment(1)
See my edits above int he original post for more details on what I'm trying to accomplish, sorry for the lack of additional detail. I tried using /*/*[1], it's printing "author,title" twice, i just need it to print once.Haplo
H
0

I have to meet same concerns. I solved as follows:

//*[local-name()='MYNODENAME' and position()=X]

Have a nice day.

Hubsher answered 7/10, 2014 at 7:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.