Search code examples
xmlxslttreexmlnodexmlspy

Print the tree of XML using XSL


I've the following XML

<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type='text/xsl' href='parser.xsl'?>
<NVS>
    <A>
        <F>007</F>
    </A>
    <A>-002</A>
    <B>--003</B>
    <C>
        <D>------005</D>
    </C>
    <E>-006</E>
</NVS>

And I would like to print a tree for each node such as :

/NVS/A/
/NVS/A/F/
/NVS/A/
/NVS/B/
/NVS/C/
/NVS/C/D
/NVS/E/

I tried some XSL but I can't afford the right result. The best XSL is that :

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html" encoding="ISO-8859-1" doctype-public="-//W3C//DTD XHTML//EN" doctype-system="http://www.w3.org/TR/2001/REC-xhtml11-20010531" indent="yes"/>
    <xsl:template match="/*">
        <html>
            <body>
                <xsl:for-each select=".">/<xsl:value-of select="."/>
                    <br/>
                </xsl:for-each>
            </body>
        </html>
    </xsl:template>
</xsl:stylesheet>

And I also tried the "for-each", such as:

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html" encoding="ISO-8859-1" doctype-public="-//W3C//DTD XHTML//EN" doctype-system="http://www.w3.org/TR/2001/REC-xhtml11-20010531" indent="yes"/>
    <xsl:template match="/*/*">
        <xsl:for-each select=".">/<xsl:value-of select="."/>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

But not better. And also I just prin the value whereas I would like only the name of the node.

Any idea?


Solution

  • Another option is to use the ancestor-or-self axis to go back up the tree.

    XML Input

    <NVS>
        <A>
            <F>007</F>
        </A>
        <A>-002</A>
        <B>--003</B>
        <C>
            <D>------005</D>
        </C>
        <E>-006</E>
    </NVS>
    

    XSLT 1.0

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output method="text"/>
        <xsl:strip-space elements="*"/>
    
        <xsl:template match="text()"/>
    
        <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 select="node()"/>
        </xsl:template>
    
    </xsl:stylesheet>
    

    Output

    /NVS
    /NVS/A
    /NVS/A/F
    /NVS/A
    /NVS/B
    /NVS/C
    /NVS/C/D
    /NVS/E
    

    You can also easily modify it to give an exact path by adding the position in a predicate when that element exists more than once at a given level. For example, there are two A elements that are children of /NVS.

    XSLT 1.0

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output method="text"/>
        <xsl:strip-space elements="*"/>
    
        <xsl:template match="text()"/>
    
        <xsl:template match="*">
            <xsl:for-each select="ancestor-or-self::*">
                <xsl:value-of select="concat('/',local-name())"/>
                <xsl:if test="(preceding-sibling::*|following-sibling::*)[local-name()=local-name(current())]">
                    <xsl:value-of select="concat('[',count(preceding-sibling::*[local-name()=local-name(current())])+1,']')"/>
                </xsl:if>
            </xsl:for-each>
            <xsl:text>&#xA;</xsl:text>
            <xsl:apply-templates select="node()"/>
        </xsl:template>
    
    </xsl:stylesheet>
    

    Output (using same input as above)

    /NVS
    /NVS/A[1]
    /NVS/A[1]/F
    /NVS/A[2]
    /NVS/B
    /NVS/C
    /NVS/C/D
    /NVS/E
    

    Also, if you don't want the path to the root element output, just add this template:

    <xsl:template match="/*">
        <xsl:apply-templates/>
    </xsl:template>