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

42,509

Solution 1

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.

Solution 2

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"/>
Share:
42,509
raffian
Author by

raffian

Applications architect and code slinger since 2000, my background is full stack Java. Reach me at 72616666692e6970616440676d61696c2e636f6d My other passion is landscape photography, check it out if you're interested!

Updated on October 11, 2020

Comments

  • raffian
    raffian over 3 years

    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