Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to include ancestor branch of a node selected via xpath parameter in output of XSLT

After trying for over 8 hours I am hoping someone can help me with this:

Given the following (simplified) XML for a book:

<book>
    <section name="A">
        <chapter name="I">
            <paragraph name="1"/>
            <paragraph name="2"/>
        </chapter>
        <chapter name="II">
            <paragraph name="1"/>          
        </chapter>
    </section>
    <section name="B">
        <chapter name="III"/>
        <chapter name="IV"/>   
    </section>
</book>

I am able to extract any part (section, chapter or paragraph) of the book XML based on a given parameter with the following XSL:

<xsl:param name="subSelectionXPath" required="yes" as="node()"/>

<xsl:template match="/">
    <xsl:apply-templates select="$subSelectionXPath"/>
</xsl:template>

<xsl:template match="*">
    <!-- output node with all children -->
    <xsl:copy-of select="."/>
</xsl:template>

and the parameter $subSelectionXPath with a value like

doc(filename)//chapter[@name='II']

resulting in output:

<chapter name="II">
    <paragraph name="1"/>          
</chapter>

what I want to achieve in addition is to have the the selected XML fragment enclosed by the ancestor XML branch, i.e.:

<book>
    <section name="A">
        <chapter name="II">
            <paragraph name="1"/>          
        </chapter>
    </section>    
</book>

I imagine (and tried) traversing the XML tree and testing whether the current node is an ancestor, something like (pseudo code):

<xsl:if test="node() in $subSelectionXPath/ancestor::node()">
    <xsl:copy>
       <xsl:apply-templates/>
    </xsl:copy>
</xsl:if>

I also experimented with xsl:key but I am afraid my knowledge of XSLT is running to an end here. Any thoughts?

like image 215
Brunsaim Avatar asked Jun 23 '11 18:06

Brunsaim


People also ask

Which XSLT element extracts the value of a selected node?

The <xsl:value-of> element is used to extract the value of a selected node.

What are the output formats for XSLT?

XSLT uses the <xsl:output> element to determine whether the output produced by the transformation is conformant XML (<xsl:output method="xml"/> ), valid HTML (<xsl:output method="html"/> ), or unverified text (< xsl:output method="text"/> ).

What does @* mean in XSLT?

The XPath expression @* | node() selects the union, sorted in document order, of (attribute nodes of the context item and XML child nodes of the context item in sense of the XDM).

Which XSLT element is used to write literal text to the output?

XSLT <xsl:text> The <xsl:text> element is used to write literal text to the output.


1 Answers

It is evident from your code that you are using XSLT 2.0.

This XSLT 2.0 transformation:

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

 <xsl:param name="subSelectionXPath"
  as="node()" select="//chapter[@name='II']"
  />

 <xsl:template match="*[descendant::node() intersect $subSelectionXPath]">
  <xsl:copy>
   <xsl:copy-of select="@*"/>
   <xsl:apply-templates select="*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="*[. intersect $subSelectionXPath]">
  <xsl:copy-of select="."/>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document:

<book>
    <section name="A">
        <chapter name="I">
            <paragraph name="1"/>
            <paragraph name="2"/>
        </chapter>
        <chapter name="II">
            <paragraph name="1"/>
        </chapter>
    </section>
    <section name="B">
        <chapter name="III"/>
        <chapter name="IV"/>
    </section>
</book>

produces exactly the wanted, correct result:

<book>
   <section name="A">
      <chapter name="II">
         <paragraph name="1"/>
      </chapter>
   </section>
</book>

Explanation: We have just two templates:

  1. A template that matches any element whose descendents have non-empty intersection with the $subSelectionXPath node-set. Here we "shallow-copy" the element and apply templates to its children elements.

  2. A template that matches elements that belong to the $subSelectionXPath node-set. Here we copy the whole subtree rooted at that element.

  3. Do note the use of the XPath 2.0 intersect operator.

  4. No explicit recursion.

II. XSLT 1.0 solution:

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

 <xsl:param name="subSelectionXPath"
  select="//chapter[@name='II']"
  />

 <xsl:template match="*">
  <xsl:choose>
   <xsl:when test=
   "descendant::node()
        [count(.|$subSelectionXPath)
        =
         count($subSelectionXPath)
        ]
   ">
   <xsl:copy>
    <xsl:copy-of select="@*"/>
    <xsl:apply-templates select="*"/>
   </xsl:copy>
  </xsl:when>

   <xsl:when test=
   "count(.|$subSelectionXPath)
   =
    count($subSelectionXPath)
   ">
   <xsl:copy-of select="."/>
   </xsl:when>
  </xsl:choose>
 </xsl:template>
</xsl:stylesheet>

When this transformation is applied to the same XML document (shown already above), the same wanted and correct result is produced:

<book>
   <section name="A">
      <chapter name="II">
         <paragraph name="1"/>
      </chapter>
   </section>
</book>

Explanation: This is essentially the XSLT 2.0 solution in which the XPath 2.0 intersect operator is translated to XPath 1.0 using the well-known Kayessian (for @Michael Kay) formula for intersection of two nodesets $ns1 and $ns2:

$ns1[count(.|$ns2) = count($ns2)]
like image 136
Dimitre Novatchev Avatar answered Sep 28 '22 00:09

Dimitre Novatchev