Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

List every node in an XML file

Tags:

xslt

xslt-1.0

Simple situation... With any random XML file, I want to create a list of every node that it contains, but without any duplicates! So something like:

<root name="example">
  <child id="1">
    <grandchild/>
  </child>
  <child id="2"/>
  <child id="3"/>
</root>

Is translated to:

/root
/root/@name
/root/child
/root/child/@id
/root/child/grandchild

How to do this, by just using XSLT?

like image 586
Wim ten Brink Avatar asked Dec 05 '25 11:12

Wim ten Brink


2 Answers

Just for fun, without extension function.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="text()"/>
    <xsl:template match="*|@*">
        <xsl:param name="pPath"/>
        <xsl:param name="pNames" select="'&#xA;'"/>
        <xsl:variable name="vPath"
                      select="concat($pPath,'/',
                                     substring('@',
                                               1 div (count(.|../@*) =
                                                      count(../@*))),
                                     name())"/>
        <xsl:variable name="vNames">
            <xsl:if test="not(contains($pNames,
                                       concat('&#xA;',$vPath,'&#xA;')))">
                <xsl:value-of select="concat($vPath,'&#xA;')"/>
            </xsl:if>
            <xsl:apply-templates select="*[1]|@*">
                <xsl:with-param name="pPath" select="$vPath"/>
                <xsl:with-param name="pNames" select="$pNames"/>
            </xsl:apply-templates>
        </xsl:variable>
        <xsl:value-of select="$vNames"/>
        <xsl:apply-templates select="following-sibling::*[1]">
            <xsl:with-param name="pPath" select="$pPath"/>
            <xsl:with-param name="pNames" select="concat($pNames,$vNames)"/>
        </xsl:apply-templates>
    </xsl:template>
</xsl:stylesheet>

Output:

/root
/root/@name
/root/child
/root/child/@id
/root/child/grandchild

Edit: Better example of XSLT/XPath 2.0. This XPath 2.0 line:

string-join(
   distinct-values(
      (//*|//@*)
         /string-join(
            (ancestor::node()/name(),
             if (self::attribute())
                then concat('@',name())
                else name()),
            '/')),
   '&#xA;')

I solved it myself, too! This way:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="fo msxsl">
  <xsl:output method="text" standalone="yes" encoding="UTF-8"/>
  <xsl:param name="Detect">false</xsl:param>
  <xsl:param name="Root"/>
  <xsl:variable name="NewLine" select="'
'"/>
  <xsl:template match="/">
          <xsl:variable name="Nodes">
            <xsl:apply-templates select="/" mode="Loop"/>
          </xsl:variable>
          <xsl:variable name="SortedNodes">
            <xsl:apply-templates select="msxsl:node-set($Nodes)" mode="Nodes">
              <xsl:sort select="." order="ascending" case-order="lower-first" data-type="text"/>
            </xsl:apply-templates>
          </xsl:variable>
          <xsl:apply-templates select="msxsl:node-set($SortedNodes)" mode="Text"/>
  </xsl:template>
  <!-- Elementen. -->
  <xsl:template match="*" mode="Loop">
    <xsl:param name="Node"/>
    <Node>
      <xsl:value-of select="$Node"/>/<xsl:value-of select="name()"/>
    </Node>
    <xsl:apply-templates select="@*" mode="Loop">
      <xsl:with-param name="Node" select="concat($Node, '/', name())"/>
    </xsl:apply-templates>
    <xsl:apply-templates select="*" mode="Loop">
      <xsl:with-param name="Node" select="concat($Node, '/', name())"/>
    </xsl:apply-templates>
  </xsl:template>
  <!-- Attributen. -->
  <xsl:template match="@*" mode="Loop">
    <xsl:param name="Node"/>
    <Node>
      <xsl:value-of select="$Node"/>/@<xsl:value-of select="name()"/>
    </Node>
  </xsl:template>
  <!-- Node. -->
  <xsl:template match="Node" mode="Nodes">
    <xsl:if test="(1=position()) or (preceding-sibling::*[1]/. != .)">
      <Node>
        <xsl:value-of select="."/>
      </Node>
    </xsl:if>
  </xsl:template>
  <xsl:template match="Node" mode="Text"><xsl:value-of select="concat(., $NewLine)"/></xsl:template>
</xsl:stylesheet>

Only 48 lines. :-)

like image 27
Wim ten Brink Avatar answered Dec 08 '25 07:12

Wim ten Brink



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!