Rkka
Rkka

Reputation: 41

flatten hierarchical xml by rearranging nodes

I need to flatten a hierarchical XML structure. Now this probably sounds like a well weathered topic and there are many many links I have poured over. But they are mostly advice on creating hierarchy from a flat structure.

Input XML -

<bom>
    <part>
        <name>a</name>
        <othernodes>abc</othernodes>
        <part>
            <name>b</name>
            <othernodes>abc</othernodes>
            <part>
                <name>e</name>
                <othernodes>abc</othernodes>
            </part>
            <part>
                <name>f</name>
                <othernodes>abc</othernodes>
            </part>
        </part>
        <part>
            <name>c</name>
            <othernodes>abc</othernodes>
            <part>
                <name>g</name>
                <othernodes>abc</othernodes>
            </part>
        </part>
        <part>
            <name>d</name>
            <othernodes>abc</othernodes>
        </part>
    </part>
</bom>

Output sought -

<bom>
    <part>
        <parent/>
        <name>a</name>
        <othernodes>abc</othernodes>
    </part>
    <part>
        <parent>a</parent>
        <name>b</name>
        <othernodes>abc</othernodes>
    </part>
    <part>
        <parent>a</parent>
        <name>c</name>
        <othernodes>abc</othernodes>
    </part>
    <part>
        <parent>a</parent>
        <name>d</name>
        <othernodes>abc</othernodes>
    </part>
    <part>
        <parent>b</parent>
        <name>e</name>
        <othernodes>abc</othernodes>
    </part>
    <part>
        <parent>b</parent>
        <name>f</name>
        <othernodes>abc</othernodes>
    </part>
    <part>
        <parent>c</parent>
        <name>g</name>
        <othernodes>abc</othernodes>
    </part>
</bom>

At first instance I thought this is probably not possible. But then I tried to come up with some sort of xslt. I know recursion here is the key but not sure how to implement. Here's the XSLT I have so far (obviously it does not produce the desired result).

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

  <xsl:output encoding="UTF-8" indent="yes" method="xml" />

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

  <xsl:template match="bom">   
    <xsl:element name="bom">      
      <xsl:apply-templates select="@* | node() [not(child::part)]"/>      
      <xsl:apply-templates select="part"/>
      <!--<xsl:apply-templates select="part/child::part"/>-->
    </xsl:element>
  </xsl:template>

  <xsl:template match="part" >
    <xsl:element name="part">
      <xsl:element name="parent">
        <xsl:value-of select="../part/name"/>
      </xsl:element>
      <xsl:apply-templates select="@* | node() [not(part)]" />
      <xsl:apply-templates select="child::part"/>
    </xsl:element>    
  </xsl:template>
</xsl:stylesheet>

Appreciate if anyone can provide me with some directions on how to approach and solve this problem. Thanks!

Upvotes: 0

Views: 476

Answers (1)

potame
potame

Reputation: 7905

You're almost there, your stylesheet just need some fixtures (and there's no need for recursion here):

  1. The instruction <xsl:apply-templates select="@* | node() [not(child::part)]"/> should use the self:: axis instead, to work properly.
  2. The instruction in template match="part" must be outside the <xsl:element>
  3. the parent name was not correctly retrieved - use <xsl:value-of select="../name"/> instead of <xsl:value-of select="../part/name"/>
  4. think about processing the attributes before outputting any content in your result tree.

This is the corrected version of your stylesheet:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output encoding="UTF-8" indent="yes" method="xml" />

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

    <xsl:template match="bom">
        <xsl:element name="bom">      
            <xsl:apply-templates select="@* | node()"/>      
            <!--<xsl:apply-templates select="part/child::part"/>-->
        </xsl:element>
    </xsl:template>

    <xsl:template match="part" >
        <xsl:element name="part">
            <xsl:apply-templates select="@*"/>
            <xsl:element name="parent">
                <xsl:value-of select="../name"/>
            </xsl:element>
            <xsl:apply-templates select="node() [not(self::part)]" />
        </xsl:element>    
        <xsl:apply-templates select="child::part"/>
    </xsl:template>
</xsl:stylesheet>

This is the result I obtain, the main difference with what you provided in your resides in that the elements are not ordered exactely the same way:

<?xml version="1.0" encoding="UTF-8"?>
<bom>
   <part>
      <parent/>
      <name>a</name>
      <othernodes>abc</othernodes>
   </part>
   <part>
      <parent>a</parent>
      <name>b</name>
      <othernodes>abc</othernodes>
   </part>
   <part>
      <parent>b</parent>
      <name>e</name>
      <othernodes>abc</othernodes>
   </part>
   <part>
      <parent>b</parent>
      <name>f</name>
      <othernodes>abc</othernodes>
   </part>
   <part>
      <parent>a</parent>
      <name>c</name>
      <othernodes>abc</othernodes>
   </part>
   <part>
      <parent>c</parent>
      <name>g</name>
      <othernodes>abc</othernodes>
   </part>
   <part>
      <parent>a</parent>
      <name>d</name>
      <othernodes>abc</othernodes>
   </part>
</bom>

Upvotes: 1

Related Questions