Search code examples
xsltxslt-2.0

How do I match on any node that itself or any child has an attribute with a value in XSLT Template?


Say I have XML data like this:

<root>
    <subs>
        <sub>
            <values>
                <value attribute="a">1</value>
                <value attribute="a">2</value>
                <value attribute="c">3</value>
                <value attribute="c">4</value>
            </values>
        </sub>
        <subOther>
            <otherValues attribute="c">
                <otherValue attribute="a">1</value>
                <otherValue attribute="a">2</value>
                <otherValue attribute="b">3</value>
                <otherValue attribute="a">4</value>
            </otherValues>
        </subOther>
    </subs>
</root>

I am trying to create an XSLT template that matches all the nodes in the path to /root/subs/subOther/otherValues/otherValue[attribute="b"].

So far, this is the closest I have gotten:

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

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

    <xsl:template match="//*[ancestor-or-self::[@attribute='b']]">
        <xsl:copy>
            <xsl:apply-templates select="node()" />
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

But that throws an error saying there is an unexpected token [. I have tried several combinations but they either don't match anything at all, match too much (i.e. everything), or they throw some sort of error.

Edit: I updated the example and expected to be a little more clear. Also note that this is a highly-simplified XML. In my actual file the attribute in question can be at any leaf node on any valid element for that level, so I have to use a more generic path using * and unknown paths with //. So, for instance, one of the value elements could be the one with attribute="b" and it would trigger the same result.

Edit 2: The expected result is to select the nodes that have a path that lead to any left-child w/ an attribute that is equal to a specific value. In my XSD schema there's a total of about 100 possible leaf nodes spread all over the place. The use case is that the attribute in question marks which data elements have had changes, and I need to basically create a "diff" where the full file is whittled down to only nodes where the results are only those items that have changed and their parents. In the small example above, attrubute="b" is the indication I need to copy that node, and thus I would expect this exact result:

<root> <!-- Copied because part of the path -->
    <subs> <!-- Copied because part of the path -->
        <sub> <!-- Copied because part of the path -->
            <values> <!-- Copied because part of the path -->
                <value attribute="b">3</value> <!-- Copied because it matches the attribute -->
            </values>
        </sub>
    </subs>
</root>

I hope that makes better sense. Also, I fixed the typo on the xsl:stylesheet being self-closing.


Solution

  • It looks like you have changed the identity template to ignore elements (the change will also drop attributes and text nodes), and added a template to copy the elements you need.

    I think you need to reverse your logic. Instead of thinking about things you want to copy, think of it as removing things you don't want to copy.

    So, you have the identity template to do the generic copying of elements, and have a second template to remove the things you don't want (the elements which don't have a "b" attribute either on its self or its descendants).

    Try this XSLT

    <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output method="xml" indent="yes" />
        <xsl:strip-space elements="*" />
    
        <!--IDENTITY TEMPLATE -->
        <xsl:template match="@*|node()">
          <xsl:copy>
            <xsl:apply-templates select="@*|node()" />
          </xsl:copy>
        </xsl:template>
    
        <xsl:template match="*[not(descendant-or-self::*[@attribute = 'b'])]" />
    </xsl:stylesheet>
    

    See it in action at http://xsltfiddle.liberty-development.net/ncntCS6