Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flatten a nested XML using XSLT

Tags:

xml

xslt

I'm having a hard time writing an XSLT to move all nodes having a same attribute value to the same level.

Here's an example:

<root>
    <object Name="1">
        <Property Name="a1" Value="a">
            <Property Name="a1.1" Value="a"/>
            <Property Name="a1.2" Value="a">
                <Property Name="a1.2.1" Value="a"/>
                <Property Name="a1.2.2" Value="a"/>
            </Property>
        </Property>
        <Property Name="b1" Value="b"/>
    </object>
</root>

Currently it is possible to nest properties with value a inside one another (there is no limit for the amount of nodes or nesting level). This model will change to only allow this type of property at the object level. Which should look like this after the transformation (the order of the elements doesn't matter):

<root>
    <object Name="1">
        <Property Name="a1" Value="a"/>
        <Property Name="a1.1" Value="a"/>
        <Property Name="a1.2" Value="a">
        <Property Name="a1.2.1" Value="a"/>
        <Property Name="a1.2.2" Value="a"/>
        <Property Name="b1" Value="b"/>
    </object>
</root>

I've tried solving this with this very similar question, the main difference being that in the example the node won't be copied, but its values will be used. I haven't been able to figure out how to copy the entire node though.

EDIT
The above example is over simplified. The properties will contain sub-elements which will have to be copied as well

<root>
    <object Name="1">
        <Property Name="a1" Value="a">
            <x>x1</x>
            <y>y1</y>
            <z>z1</z>
            <Property Name="a1.1" Value="a">
                <x>x1.1</x>
                <y>y1.1</y>
                <z>z1.1</z>
            </Property>
            <Property Name="a1.2" Value="a">
                <x>x1.2</x>
                <y>y1.2</y>
                <z>z1.2</z>
                <Property Name="a1.2.1" Value="a">
                    <x>x1.2.1</x>
                    <y>y1.2.1</y>
                    <z>z1.2.1</z>
                </Property>
                <Property Name="a1.2.2" Value="a">
                    <x>x1.2.1</x>
                    <y>y1.2.1</y>
                    <z>z1.2.1</z>
                </Property>
            </Property>
        </Property>
        <Property Name="b1" Value="b"/>
    </object>
</root>

Should become this after the transformation:

<root>
    <object Name="1">
        <Property Name="a1" Value="a">
            <x>x1</x>
            <y>y1</y>
            <z>z1</z>
        </Property>
        <Property Name="a1.1" Value="a">
            <x>x1.1</x>
            <y>y1.1</y>
            <z>z1.1</z>
        </Property>
        <Property Name="a1.2" Value="a">
            <x>x1.2</x>
            <y>y1.2</y>
            <z>z1.2</z>
        </Property>
        <Property Name="a1.2.1" Value="a">
            <x>x1.2.1</x>
            <y>y1.2.1</y>
            <z>z1.2.1</z>
        </Property>
        <Property Name="a1.2.2" Value="a">
            <x>x1.2.1</x>
            <y>y1.2.1</y>
            <z>z1.2.1</z>
        </Property>
        <Property Name="b1" Value="b"/>
    </object>
</root>
like image 885
Philippe Avatar asked Aug 17 '26 08:08

Philippe


2 Answers

If you start with the XSLT identity template, all you need is just another template that matches Property that copies the element and attributes, but outputs the child elements after the element, rather that within it.

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

Try this XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes" />
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

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

Note that I am assuming Property elements can only contain other Property elements as children here.

like image 191
Tim C Avatar answered Aug 19 '26 04:08

Tim C


Re your edited question - try it this way:

XSLT 1.0

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

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

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

</xsl:stylesheet>
like image 29
michael.hor257k Avatar answered Aug 19 '26 05:08

michael.hor257k