tags:

views:

156

answers:

5

I have the following XML:

<parent>
   <pet>
      <data>
         <birthday/>
      </data>
   </pet>
   <pet>
      <data>
         <birthday/>
      </data>
   </pet>
</parent> 

And now I want to select the first birthday element via parent//birthday[1] but this returns both birthday elements because bothof them are the first child of their parents. How can I only select the first birthday element of the entire document no matter where it is located. I've tried parent//birthday[position()=1] but that doesn't work either.

Cheers

A: 

try

//birthday[position()=1]

// finds nodes no matter where there are in the hierarchy

you could also do

pet[position()=1]/data/birthday
carillonator
That gives me the same result, I still get both birthday elements
Benjamin
That doesn't work for me. It returns 2 elements.
Darrel Miller
True but the problem is that when I run my xpath expression I only know that there must a couple of birthday elements in the document, I don't anything about pet
Benjamin
It still doesn't work with position()=1 because you are testing the position in the original document not in the resulting node-set
Darrel Miller
//pet[position()=1]/data/birthday works for me
Matt Ellen
@Darrell you're right.
carillonator
-1 because it is simply wrong. Well, actually the second XPath isn't, but I can't give -0.5 ;-) Will remove down-vote when corrected.
Tomalak
+1  A: 

Ok, I admit this is horrendous and there must be a better way, but it appears to work.

/*/*[descendant::birthday and not(preceding-sibling::*[descendant::birthday])]

I look for all elements at the second level in the tree that have a descendant element called birthday that do not have a preceding sibling element that has a birthday element as a descendant.

Darrel Miller
if there were alternating `pet` elements with and without `birthday` descendants, this would match all those with a `birthday`, not just the first.
carillonator
No it wouldn't because the preceding-sibling axis looks at all preceding siblings, not just the one prior.
Darrel Miller
A: 
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;

    <xsl:template match="/">
        <xsl:variable name="birthdays" select="//birthday"/>
        <xsl:value-of select="$birthdays[1]"/>
    </xsl:template>

</xsl:stylesheet>
igor
+5  A: 

You mean (note the parentheses!)

(/parent/pet/data/birthday)[1]

or, shorter but less descriptive

(/*/*/*/birthday)[1]

or, better because semantically correct:

/parent/pet[1]/data/birthday

or, if not all pets have birthday entries:

/parent/pet[data/birthday][1]/data/birthday

In any case, avoid using // because it is computationally expensive (it recursively tests every node in the document). When the document structure is fixed and known, it is easy to avoid.

If you work from a context node, you can abbreviate the expression by making it relative to that context node.

Brief explanation:

  • /parent/pet/data/birthday[1] selects all <birthday> nodes that are the first in their respective parents (the <data> nodes), throughout the document
  • (/parent/pet/data/birthday)[1] selects all <birthday> nodes, and of those (that's what the parentheses do, they create an intermediary node-set), it takes the first one
Tomalak
Thank you. I knew there had to be an easier way.
Darrel Miller
>avoid using //I'm assuming he has to use it considering the wording here:>How can I only select the first birthday element of the entire document no matter where it is located
igor
@igor: I was making an educated guess based on the sample XML input structure. Birthday elements that are not descendants of pet elements seemed improbable to me in this case.
Tomalak
A: 

FYI: you can visualize the results of the various Xpath queries with the (free) XPathVisualizer tool. Works on Windows only.

alt text

Cheeso