0

I am trying to transform where the output is returned as xpaths for each element in the xml.

Here is my sample xml

<NodeRoot>
    <NodeA class="3">
        <NodeB xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true">
            <NodeC abc="1">103</NodeC>
            <NodeD>103</NodeD>
        </NodeB>
    </NodeA>
    <NodeA class="1">
        <NodeGroup xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true">
            <NodeC name="z" asc="2">103</NodeC>
        </NodeGroup>
    </NodeA>
</NodeRoot>

My XSL

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" encoding="utf-8" media-type="text/plain"/>

    <xsl:template match="@*|text()|comment()|processing-instruction()"/>
    <xsl:template match="@xsi:nil" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>

    <xsl:template match="*">
        <xsl:for-each select="ancestor-or-self::*">
            <xsl:value-of select="concat('/',local-name(.))"/>
        </xsl:for-each>
        <xsl:text>&#xA;</xsl:text>
        <xsl:apply-templates/>
    </xsl:template>
</xsl:stylesheet>

This gives me the below output

/NodeRoot
/NodeRoot/NodeA
/NodeRoot/NodeA/NodeB
/NodeRoot/NodeA/NodeB/NodeC
/NodeRoot/NodeA/NodeB/NodeD
/NodeRoot/NodeA
/NodeRoot/NodeA/NodeGroup
/NodeRoot/NodeA/NodeGroup/NodeC

The xpaths are returned but the order in which they are returned is not what I want. Currently, the order is parent followed by all its child. What I want is that the order is in a way that I get results based on depth of nodes. So first root (level 0) should be returned followed by its immediate children (level 1 elements), followed by level 2 child nodes and so on.

Expected outcome

/NodeRoot
/NodeRoot/NodeA
/NodeRoot/NodeA
/NodeRoot/NodeA/NodeB
/NodeRoot/NodeA/NodeGroup
/NodeRoot/NodeA/NodeB/NodeC
/NodeRoot/NodeA/NodeB/NodeD
/NodeRoot/NodeA/NodeGroup/NodeC

So basically

All level 0 elements
All level 1 elements
.
.
.
All level n elements

1 Answer 1

1

So instead of a recursive template process all elements sorted by the ancestor count:

<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
   <xsl:output method="text" encoding="utf-8" media-type="text/plain"/>

   <xsl:template match="/">
       <xsl:apply-templates select="//*">
           <xsl:sort select="count(ancestor::*)"/>
       </xsl:apply-templates>
   </xsl:template>

    <xsl:template match="*">
        <xsl:for-each select="ancestor-or-self::*">
            <xsl:value-of select="concat('/',local-name(.))"/>
        </xsl:for-each>
        <xsl:text>&#xA;</xsl:text>
    </xsl:template>
</xsl:transform>

http://xsltransform.hikmatu.com/jyyiVhh

A simple <xsl:apply-templates select="//*"/> would select all elements inside the document for processing in document order, however with the nested <xsl:sort select="count(ancestor::*)"/> the processing order is changed and the ones with the lowest ancestor count are processed first. See https://www.w3.org/TR/xslt-10/#section-Applying-Template-Rules which says "The selected set of nodes is processed in document order, unless a sorting specification is present" and https://www.w3.org/TR/xslt-10/#sorting which says "When a template is instantiated by xsl:apply-templates ..., the current node list list consists of the complete list of nodes being processed in sorted order".

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks this works. One clarification though ... when I debug the xslt, I can see that the transformation happens level wise, but from my understanding, first the xpath is created and then it is sorted based on the position. But how does the transformation change the order of output? A bit more explanation would be great.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.