Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to select the grandparent of a node using xslt

Tags:

select

xslt

xpath

I'm in a situation where I can only test for the child node, but I have to apply tags to the grandparent of this child node.

I've tried using:

<xsl:call-template name="grandparent" select="parent::parent::node()"/>

and:

<xsl:call-template name="grandparent" select="ancestor::node [@nameofgrandparentnode]"/>

But neither works.

The level of the grandparent node is not fixed, so I figure I can't use [@level=#] either. Any ideas on how to select it would be greatly appreciated.

EDIT: -- This part has been posted as a new question:

xslt select grandparent node depending on an attribute value of its grandchild node

Selecting the node using the suggestions from below worked. Thanks! However I also need to test by the attribute of the grandparent or grandchild node.

I've tried:

<xsl:template name"one" match="grandparentnode">
 <Tag1>
  <xsl:apply-templates select="parentNode" />
 </Tag1>
</xsl:template>

<xsl:template name="two" match="grandparentnode[*/*/@grandchildattr='attrValue']">
 <Tag2>
     <xsl:apply-templates select="parentNode" />
 </Tag2>
</xsl:template>

However template "two" always gets called, and "" is always inserted. Even for grandchild nodes whose attribute value is not equal to 'attrValue'.

Am I missing something here?

like image 488
highlightall Avatar asked Sep 09 '10 00:09

highlightall


2 Answers

I've tried using:

<xsl:call-template name="grandparent" select="parent::parent::node()"/> 

and:

<xsl:call-template name="grandparent" select="ancestor::node[@nameofgrandparentnode]"/>

But neither works.

Certainly it will not "work", because the <xsl:call-template> instruction doesn't have a select attribute!

You may pass parameters using <xsl:with-param> children of the <xsl:call-template> instruction, like this:

<xsl:call-template name="compute">
  <xsl:param name="pAncestor" select="someExpression"/>
</xsl:call-template>

For the select attribute of the <xsl:with-param> Use:

For a real grandparent:

../..

for the closest ancestor element with name someName:

ancestor::someName[1]

for the closest ancestor element with name contained in the variable $ancName:

ancestor::*[name()=$ancName][1]
like image 118
Dimitre Novatchev Avatar answered Nov 13 '22 12:11

Dimitre Novatchev


I'm in a situation where I can only test for the child node, but I have to apply tags to the grandparent of this child node.

XSLT isn't procedural programming, so you have to think about things a little differently.

If I understand correctly, you want to inject new tags (additional child elements or attributes?) into your XML, but you want to add it to an ancestor when you find a specific descendant. Your example code appears to try to target the grandparent once you come across the child. Unfortunately, this may be too late.

You may need inject the new element/attribute when engine comes across the element you want to modify (the grandparent in your case), not when it comes across the child that matches the condition (or else you'll end up adding elements or attributes to the child)

Consider this input (grandparent="album", grandchild="label"):

<?xml version="1.0" encoding="UTF-8"?>

<album> 
  <title>Sgt. Pepper's Lonely Hearts Club Band</title>  
  <artist>The Beatles</artist>  
  <year>1967</year>  
  <labels> 
    <label>Parlophone</label>  
    <label>Capitol</label> 
  </labels> 
</album>

I want to modify the album based on whether a certain label is present. Remember the general format for targeting nodes is: target-node[target-condition]. To modify any album elements that have a Capitol label grandchild element, I'd use this:

album[*/label='Capitol']

So consider this stylesheet to add a new attribute and 2 new child elements to album's that match my condition:

<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">  

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

  <xsl:template match="album[*/label='Capitol']"> 
    <xsl:copy> 
      <xsl:apply-templates select="@*"/>
      <xsl:attribute name="new-attribute">A Capitol Record</xsl:attribute>  
      <new-element1/> 
      <xsl:apply-templates select="node()"/>  
      <new-element2/> 
    </xsl:copy> 
  </xsl:template> 

</xsl:stylesheet>

Output (manually formatted):

<?xml version="1.0" encoding="UTF-8"?>
<album new-attribute="A Capitol Record"> 
  <new-element1/> 
  <title>Sgt. Pepper's Lonely Hearts Club Band</title>  
  <artist>The Beatles</artist>  
  <year>1967</year>  
  <labels> 
    <label>Parlophone</label>  
    <label>Capitol</label> 
  </labels> 
  <new-element2/>
</album>

Some sandbox test resources:

  • http://www.whitebeam.org/library/guide/TechNotes/xpathtestbed.rhtm
    • To see what nodes a given XPath expression actually targets
  • http://unindented.org/projects/xslt-tester-applet/run/
    • To try out a stylesheet
like image 24
Bert F Avatar answered Nov 13 '22 12:11

Bert F