Joel Bodenmann
Joel Bodenmann

Reputation: 2282

Concatenate parent attributes recursively

Given the following XML:

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

I basically want to flatten the tree while concatenating the name attributes of each parent node element until the last node element:

<package>
    <node name="a-b"/>
    <node name="a-c"/>
    <node name="a-d-e"/>
    <node name="a-d-f"/>
    <node name="a-d-g-h"/>
</package>

What I got working so far is is properly generating a flat list of all node elements using a template and xsl:copy-of:

<xsl:template match="//node">
    <xsl:copy-of select="current()"/>
</xsl:template>

This gives me:

<package>
    <node name="b"/>
    <node name="c"/>
    <node name="e"/>
    <node name="f"/>
    <node name="h"/>
</package>

But I'm not sure how to properly continue from here. My intention was to extend the template and using xsl:attribute and xsl:for-each to concatenate and modify the attribute:

    <xsl:template match="node/@name">
        <xsl:attribute name="name">
            <xsl:for-each select="ancestor::node">
                <xsl:if test="position() > 1">.</xsl:if>
                <xsl:value-of select="@name"/>
            </xsl:for-each>
        </xsl:attribute>
    </xsl:template>

However, this only prints the node's data (if any). What am I missing here?

I have XSLT 2.0 available and I got my inspiration from this SO question.

Upvotes: 1

Views: 362

Answers (2)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243479

I basically want to flatten the tree while concatenating the name attributes of each parent node element until the last node element:

Here is a complete and working XSLT 2.0 solution:

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

  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>

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

  <xsl:template match="node[not(*)]/@name">
    <xsl:attribute name="name" select="string-join(../ancestor-or-self::node/@name, '-')"/>
  </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the provided XML document:

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

the wanted, correct result is produced:

<package>
   <node name="a-b"/>
   <node name="a-c"/>
   <node name="a-d-e"/>
   <node name="a-d-f"/>
   <node name="a-d-g-h"/>
</package>

II. XSLT 1.0 solution:

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

  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>

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

  <xsl:template match="node[not(*)]/@name">
    <xsl:attribute name="name"><xsl:apply-templates select="." mode="gen"/></xsl:attribute>
  </xsl:template>

  <xsl:template match="node/@name" mode="gen">
    <xsl:apply-templates select="../parent::node/@name" mode="gen"/>
    <xsl:if test="../parent::node">-</xsl:if>
    <xsl:value-of select="."/>
  </xsl:template>
</xsl:stylesheet>

when this transformation is applied on the same XML document (above), the same correct result is produced:

<package>
   <node name="a-b"/>
   <node name="a-c"/>
   <node name="a-d-e"/>
   <node name="a-d-f"/>
   <node name="a-d-g-h"/>
</package>

Upvotes: 1

Martin Honnen
Martin Honnen

Reputation: 167571

Use string-join:

<xsl:template match="node">
  <node name="{string-join(ancestor-or-self::node/@name, '-')}"/>
</xsl:template>

Upvotes: 1

Related Questions