XSL - How to select all ancestors upto the root, but not the sibling nodes

51 Views Asked by At

I have an xml payload

<Root>

    <Header>
        <Name>B</Name>
    </Header>
    <Item>
        <Id>10</Id>
        <Description>Item10</Description>
    </Item>
    <Item>
        <Id>20</Id>
        <Description>Item20</Description>
    </Item>
    <Package>
        <Id>A</Id>
    </Package>
    <Package>
        <Id>B</Id>
    </Package>
    <Package>
        <Id>C</Id>
    </Package>
</Root>

For each Package, I am trying to get output of the entire message of all ancestors of the current Package upto its root, but not its following sibling nodes (other packages).

Desired Output:

`

<Root>

    <Header>
        <Name>B</Name>
    </Header>
    <Item>
        <Id>10</Id>
        <Description>Item10</Description>
    </Item>
    <Item>
        <Id>20</Id>
        <Description>Item20</Description>
    </Item>
    <Package>
        <Id>A</Id>
    </Package>

</Root>
<Root>

    <Header>
        <Name>B</Name>
    </Header>
    <Item>
        <Id>10</Id>
        <Description>Item10</Description>
    </Item>
    <Item>
        <Id>20</Id>
        <Description>Item20</Description>
    </Item>

    <Package>
        <Id>B</Id>
    </Package>

</Root>
<Root>

    <Header>
        <Name>B</Name>
    </Header>
    <Item>
        <Id>10</Id>
        <Description>Item10</Description>
    </Item>
    <Item>
        <Id>20</Id>
        <Description>Item20</Description>
    </Item>

    <Package>
        <Id>C</Id>
    </Package>
</Root>
`

But I am getting 3 messages including all packages in each.

Here is my code


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

<xsl:template match="/">
<Messages>
        <xsl:for-each select="//Package">
            <xsl:copy-of select="/*[not(following-sibling::*)]" />
        </xsl:for-each>
    </Messages>
</xsl:template>

What I am doing wrong? Thank you for your advice.

2

There are 2 best solutions below

3
michael.hor257k On

To get the result you show (not the result you describe), you could do simply:

XSLT 1.0

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

<xsl:template match="/Root">
    <xsl:variable name="common" select="*[not(self::Package)]" />
    <xsl:for-each select="Package">
        <Root>
            <xsl:copy-of select="$common"/>
            <xsl:copy-of select="."/>
        </Root>
    </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

But do note that this result is an XML fragment, not a well-formed XML document, because it lacks a single root element.

0
Conal Tuohy On

This statement doesn't do what you think it does:

<xsl:copy-of select="/*[not(following-sibling::*)]" />

That XPath expression begins with a / which is an absolute reference to the root of the document. So the result of the expression doesn't depend on the context node (a Package element) which has been established by the enclosing xsl:for-each iterator.

What the expression actually returns is the root element /*, so long as it has no following sibling elements (which is always true by definition since an XML document must have exactly one root element).

Hence the stylesheet simply returns several copies of the entire input document, one for each iteration of //Package in the document.