Home > Software design >  Is there a way to copy XML nodes n times based on an element in the XML using XSLT?
Is there a way to copy XML nodes n times based on an element in the XML using XSLT?

Time:11-27

I'd like to duplicate some nodes in my XML file. This file is meant to be sent towards a printing engine. It considers a purchase order with some lines, and for each line, a number of labels needs to be printed. The number is dependent on the number of items that will be received for that purchase order. Therefore I'd like to duplicate the XML node for that specific line n times, n equal to the number of copies specified in the specific line.

My source XML:

<?xml version="1.0" encoding="utf-8"?>
<report>
    <header>
        <purchaseorder>KER123456</purchaseorder>
    </header>
    <lines>
        <line>
            <copies>2</copies>
            <item>item1</item>
        </line>
        <line>
            <copies>3</copies>
            <item>item2</item>
        </line>
    </lines>
</report>

The requested result:

<report>
    <header>
        <purchaseorder>KER123456</purchaseorder>
    </header>
    <lines>
        <line>
            <item>item1</item>
        </line>
        <line>
            <item>item1</item>
        </line>
        <line>
            <item>item2</item>
        </line>
        <line>
            <item>item2</item>
        </line>
        <line>
            <item>item2</item>
        </line>
    </lines>
</report>

I already fiddled with an XSLT example I found on Stack Overflow: Duplicate element x number of times with XSLT

But unfortunately I couldn't get it to work.

My XSLT experiment:

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

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

    <xsl:template match="copies">
        <xsl:variable name="copies" select="../copies"/>
        <xsl:copy-of select="."/>
        <xsl:for-each select="1 to .">
            <xsl:apply-templates select="$copies" mode="replicate"/>
        </xsl:for-each>
    </xsl:template>

    <xsl:template match="line" mode="replicate">
        <line>
            <xsl:apply-templates select="@* except @name|node()"/>
        </line>
    </xsl:template>
    <xsl:template match="line"/>

</xsl:stylesheet>

CodePudding user response:

First you need a well-formed XML input, with a single root element.

Then you can do simply:

XSLT 2.0

<xsl:stylesheet version="2.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="line">
    <xsl:variable name="item" select="item"/>
    <xsl:for-each select="1 to copies">
        <line>
            <xsl:copy-of select="$item"/>
        </line>
    </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

Demo: https://xsltfiddle.liberty-development.net/eiorv1b

  • Related