Hiko Haieto
Hiko Haieto

Reputation: 433

Flattening attributes of nested XML elements using XSLT

I am trying to flatten a tree of nested XML elements by combining the values of an attribute using XSLT. For instance, if I have the following input:

<node value="a">
    <node value="b">
        <node value="c">
            <node value="d">
            </node>
        </node>
        <node value="e">
            <node value="f">
            </node>
            <node value="g">
                <node value="h">
                </node>
            </node>
        </node>
    </node>
</node>

Then these would be the "flattened" results I would like to be able to get:

a/b/c/d
a/b/e/f
a/b/e/g/h

All I currently have been able to achieve is outputting a record only on the most deeply nested occurrences of node "node" with the "value" attribute:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:csv="csv:csv">
    <xsl:output method="text" encoding="utf-8" />
    <xsl:template match="text()|@*"/>
    <xsl:template match="node[@value]">
        <xsl:if test="not(descendant::node[@value])">
            <xsl:value-of select="@value"/>
            <xsl:text>&#xa;</xsl:text>
        </xsl:if>
        <xsl:apply-templates/>
    </xsl:template>
</xsl:stylesheet>

As you may have gathered from my description and the xsl:if test, a potential complication is that some instances of the "node" element may not have the "value" attribute and so this must be explicitly checked. How can I update this stylesheet to achieve the desired result?

Upvotes: 1

Views: 451

Answers (1)

Martin Honnen
Martin Honnen

Reputation: 167716

With XSLT 2 or 3 it becomes

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="3.0">

  <xsl:output method="text"/>

  <xsl:template match="/">
      <xsl:value-of select="descendant::node[@value and not(descendant::node[@value])]!string-join(ancestor-or-self::node/@value, '/')" separator="&#10;"/>
  </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/b4GWVh/0

With XSLT 1 I would use

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">

  <xsl:output method="text"/>

  <xsl:template match="/">
      <xsl:apply-templates select="descendant::node[@value and not(descendant::node[@value])]"/>
  </xsl:template>

  <xsl:template match="node">
      <xsl:apply-templates select="ancestor-or-self::node/@value"/>
      <xsl:text>&#10;</xsl:text>
  </xsl:template>

  <xsl:template match="@value">
      <xsl:if test="position() > 1">/</xsl:if>
      <xsl:value-of select="."/>
  </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/b4GWVh/1

Upvotes: 2

Related Questions