Home > Back-end >  Converting a flat xml to a tree hierarchy based on IDs of inner elements
Converting a flat xml to a tree hierarchy based on IDs of inner elements

Time:11-11

My starting XML consists of an unordered flat list of catalogues containing an element that each has their own ID, and a child ID, if they have one.

My starting XML looks like this:

<root>

<catalogue>
    <item>
        <id>item2</id>
        <child>
            <id>item3</id>
        </child>
    </item>
</catalogue>

<catalogue>
    <item>
        <id>item1</id>
        <child>
            <id>item2</id>
        </child>
    </item>
</catalogue>

<catalogue>
    <item>
        <id>item3</id>
    </item>
</catalogue>

</root>

And I need to convert it to a nested tree hierarchy, where the corresponding child item is written inside of its parent item.

My resulting XML should look like this:

<root>

<catalogue>
    <item>
        <id>item1</id>
        <child>
            <id>item2</id>
        </child>
    </item>
    <catalogue>
        <item>
            <id>item2</id>
            <child>
                <id>item3</id>
            </child>
        </item>
        <catalogue>
            <item>
                <id>item3</id>
            </item>
        </catalogue>
    </catalogue>
</catalogue>

</root>

I've gotten somewhat close to getting the solution, but I can't get the resulting XML file to not contain unnecessary, duplicate elements.

In the following XML file, the top and bottom catalogues inside the root are duplicate unwanted entries. I need only the large, middle catalogue.

My current result XML looks like this:

<root>

<catalogue>
    <item>
        <id>item2</id>
        <child>
            <id>item3</id>
        </child>
    </item>
    <catalogue>
        <item>
            <id>item3</id>
        </item>
    </catalogue>
</catalogue>

<catalogue>
    <item>
        <id>item1</id>
        <child>
            <id>item2</id>
        </child>
    </item>
    <catalogue>
        <item>
            <id>item2</id>
            <child>
                <id>item3</id>
            </child>
        </item>
        <catalogue>
            <item>
                <id>item3</id>
            </item>
        </catalogue>
    </catalogue>
</catalogue>

<catalogue>
    <item>
        <id>item3</id>
    </item>
</catalogue>

</root>

My current result XML is being created with this XSLT file:

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

<xsl:template match="catalogue">
    <xsl:copy>
        <xsl:apply-templates select="node() | @*"/>
        <xsl:for-each select="/root/catalogue[item/id/text() = current()/item/child/id/text()]">
            <xsl:apply-templates select="."/>
        </xsl:for-each>
    </xsl:copy>
</xsl:template>

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

</xsl:stylesheet>

I've been trying to find a solution to this for far too long, so I hope someone can help me.

CodePudding user response:

Maybe like this:

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="*"/>

<xsl:key name="cat" match="catalogue" use="item/id" />

<xsl:template match="/root">
    <xsl:copy>
        <!-- start with items that are not children of any item -->
        <xsl:apply-templates select="catalogue[not(item/id = //child/id)]"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="catalogue">
    <xsl:copy>
        <xsl:copy-of select="item"/>
        <!-- recurse with current item's children -->
        <xsl:apply-templates select="key('cat', item/child/id)"/>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>
  • Related