Question

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?

Was it helpful?

Solution 2

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>

OTHER TIPS

One possible solution is to traverse the tree and keep track of the appropriate output as you go. For instance, when this XSLT:

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

  <xsl:template match="/*">
    <xsl:apply-templates mode="children">
      <xsl:with-param name="pName" select="name()"/>
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="*[*]" mode="children">
    <xsl:param name="pName"/>
    <xsl:variable name="vNewName" select="concat($pName, '/', name())"/>
    <xsl:value-of select="concat('/', $vNewName, '/&#10;')"/>
    <xsl:apply-templates mode="children">
      <xsl:with-param name="pName" select="$vNewName"/>
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="*[not(*)]" mode="children">
    <xsl:param name="pName"/>
    <xsl:value-of select="concat('/', $pName, '/', name(), '/&#10;')"/>
  </xsl:template>

</xsl:stylesheet>

...is applied to the provided XML:

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

...the wanted result is produced:

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

Explanation:

  • Template #1 matches the root node and instructs the processor to apply templates to all of its child nodes while passing alone a $pName parameter that, at the moment, contains the name of the root node. Note the use of mode="children"; I use this so future templates with more generic matching don't include this root node.
  • Template #2 matches all nodes that have children (and are using mode="children"). Upon finding such a node, the processor outputs a line of text that combines the carried along $pName parameter, a forward slash, and the name of the current element. Finally, the template mimics Template #1 by applying templates to all child nodes and passing along a parameter - this time, however, the parameter contains the concatenated text created up to and including this node.
  • Template #3 matches all nodes that don't have children (and, again, are using mode="children"). Upon finding such a node, the processor outputs a line of text that does something fairly similar to Template #2.
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top