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?
The <xsl:value-of> element is used to extract the value of a selected node.
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"/> ).
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).
XSLT <xsl:text> The <xsl:text> element is used to write literal text to the output.
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:
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.
A template that matches elements that belong to the $subSelectionXPath
node-set. Here we copy the whole subtree rooted at that element.
Do note the use of the XPath 2.0 intersect
operator.
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)]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With