[XSL] Obtaining an attribute from self or ancestor

E

Ebenezer

Hello!

Let's suppose we have an XML with some nested NODE nodes:

<root attr="first">
<node id="1" attr="mike">
<node id="2" />
<node id="3" attr="dave" />
</node>
<node id="4">
<node id="5" attr="peter" />
</node>
</root>

What I want to achieve is to have an XSL that:
- takes a $id variable externally from a PHP script
- finds the node with @id=$id
- outputs the value "@attr" attribute IF PRESENT, otherwise the parent's
"@attr" attribute IF PRESENT, otherwise the grandparent's... recursively
traversing from parent to parent, until an @attr value is found

This won't work for some nodes:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0">
<xsl:eek:utput method="html" />
<xsl:template match="root">
<xsl:apply-templates select="//node[@id=$id]" />
</xsl:template>
<xsl:template match="node">
<xsl:choose>
<xsl:when test="not(@attr)"><xsl:apply-templates ".." /></xsl:when>
<xsl:eek:therwise><xsl:value-of select="@attr" /></xsl:eek:therwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
 
M

Martin Honnen

Ebenezer said:
What I want to achieve is to have an XSL that:
- takes a $id variable externally from a PHP script
- finds the node with @id=$id
- outputs the value "@attr" attribute IF PRESENT, otherwise the parent's
"@attr" attribute IF PRESENT, otherwise the grandparent's... recursively
traversing from parent to parent, until an @attr value is found

This won't work for some nodes:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0">
<xsl:eek:utput method="html" />

You need to define the id parameter
said:
<xsl:template match="root">
<xsl:apply-templates select="//node[@id=$id]" />
</xsl:template>
<xsl:template match="node">
<xsl:choose>
<xsl:when test="not(@attr)"><xsl:apply-templates ".."
/></xsl:when>

That is not the correct syntax, you need
<xsl:apply-templates select=".."/>
Also it is not clear what you want to do if you do not find a 'node'
ancestor element with an 'attr' attribute.
If you walk up to the 'root element then the template matching that will
lead to infinite recursion. So perhaps you should better use
<xsl:apply-templates select="parent::node"/>
Or if you want to walk up to the 'root' element as well you need a mode e.g.
<xsl:apply-templates select=".." mode="attribute-check"/>

and then you need to add that mode parameter to the template e.g.


<xsl:template match="root">
<xsl:apply-templates select="//node[@id=$id]"
mode="attribute-check" />
</xsl:template>
<xsl:template match="node | root" mode="attribute-check">
<xsl:choose>
<xsl:when test="not(@attr)"><xsl:apply-templates
select="parent::*" /></xsl:when>
<xsl:eek:therwise><xsl:value-of select="@attr" /></xsl:eek:therwise>
</xsl:choose>
</xsl:template>
 
E

Ebenezer

Martin Honnen ha scritto:
You need to define the id parameter
<xsl:param name="id"/>

Are you sure? I don't know if it's a standard behaviour, but other
stylesheet's I've made take the parameters with no effort or definition...
That is not the correct syntax, you need
<xsl:apply-templates select=".."/>

Sorry, that was my mistake in pasting the code, it's with "select" indeed...
Also it is not clear what you want to do if you do not find a 'node'
ancestor element with an 'attr' attribute.
If you walk up to the 'root element then the template matching that will
lead to infinite recursion. So perhaps you should better use
<xsl:apply-templates select="parent::node"/>

Yes! There was an infinite recursion indeed but I didn't find a clue on
it...
Or if you want to walk up to the 'root' element as well you need a mode
e.g.
<xsl:apply-templates select=".." mode="attribute-check"/>

and then you need to add that mode parameter to the template e.g.

This info was really valuable. Thanks a lot and excuse me for the multipost.
 
P

Pavel Lepin

Ebenezer said:
<root attr="first">
<node id="1" attr="mike">
<node id="2" />
<node id="3" attr="dave" />
</node>
<node id="4">
<node id="5" attr="peter" />
</node>
</root>

What I want to achieve is to have an XSL that:
- takes a $id variable externally from a PHP script
- finds the node with @id=$id
- outputs the value "@attr" attribute IF PRESENT,
otherwise the parent's "@attr" attribute IF PRESENT,
otherwise the grandparent's... recursively traversing from
parent to parent, until an @attr value is found

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="id"/>
<xsl:template match="/">
<xsl:value-of select=
"(//node[@id=$id]/ancestor-or-self::*/@attr)[last()]"
/>
</xsl:template>
</xsl:stylesheet>

HTH, HAND.
 
D

Dimitre Novatchev

Actually, this can be achieved with a single XPath one-liner (even without
XSLT being involved).

Use:

//node[@id = $id]/ancestor-or-self::*[@attr][1]/@attr


Cheers,
Dimitre Novatchev
 
R

Richard Tobin

Dimitre Novatchev said:
Actually, this can be achieved with a single XPath one-liner (even without
XSLT being involved).

Use:

//node[@id = $id]/ancestor-or-self::*[@attr][1]/@attr

Or, if you find it clearer not to repeat @attr:

(//node[@id = $id]/ancestor-or-self::*/@attr)[last()]

-- Richard
 
D

Dimitre Novatchev

Richard Tobin said:
Dimitre Novatchev said:
Actually, this can be achieved with a single XPath one-liner (even without
XSLT being involved).

Use:

//node[@id = $id]/ancestor-or-self::*[@attr][1]/@attr

Or, if you find it clearer not to repeat @attr:

(//node[@id = $id]/ancestor-or-self::*/@attr)[last()]

This may be "clearer" but risks to be less efficient.

Using [1] in a reverse axis is a strong optimization hint and a clever XPath
engine will not traverse the whole way up -- it wil just stop on the first
self-or-ancestor element found that has an "attr" attribute.

Cheers,
Dimitre Novatchev
 
R

Richard Tobin

(//node[@id = $id]/ancestor-or-self::*/@attr)[last()]
[/QUOTE]
This may be "clearer" but risks to be less efficient.

Using [1] in a reverse axis is a strong optimization hint and a clever XPath
engine will not traverse the whole way up -- it wil just stop on the first
self-or-ancestor element found that has an "attr" attribute.

True, but unlikely to be significant when the axis is ancestor-or-self,
because XML documents tend to be much shallower than they are wide.
For preceding-sibling it would be much more likely to make a difference.

-- Richard
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,968
Messages
2,570,153
Members
46,699
Latest member
AnneRosen

Latest Threads

Top