1

Similar to this: Duplicate Element x number of times with XSLT

... but a step further.

I'll already have a xml document with each 'record' in once. If each record has a specified number of times it needs to be duplicated (different for each record) would this be possible with XSLT?

so for example:

<?xml version="1.0" standalone="yes"?>
    <Parent>
        <Record name="1">
          <repeat>10</repeat>
            <Child1>Value 1</Child1>
            <Child2>Value 2</Child2>
        </Record>
        <Record name="2">
          <repeat>5</repeat>
            <Child1>Value 1</Child1>
            <Child2>Value 2</Child2>
        </Record>
        <Record name="3">
            <repeat>8</repeat>
            <Child1>Value 1</Child1>
            <Child2>Value 2</Child2>
        </Record>
        <Record name="4">
            <repeat>26</repeat>
            <Child1>Value 1</Child1>
            <Child2>Value 2</Child2>
        </Record>
    </Parent>

Could XLST use the repeat tag to duplicate each record so that record 1 was inserted 10 times, record 2 inserted 5 times and so on ...?

I ask as I may have to set up the school Christmas card print run with each pupil ordering a number of their own design!

Colin Hall
  • 11
  • 1

2 Answers2

1

Using XSLT 3 (as supported by Saxon 9.8 all editions or Altova 2017 and 2018) you can write that as compact as

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:math="http://www.w3.org/2005/xpath-functions/math"
    exclude-result-prefixes="xs math"
    version="3.0">

    <xsl:mode on-no-match="shallow-copy" streamable="yes"/>

    <xsl:output indent="yes"/>

    <xsl:template match="Parent">
        <xsl:copy>
            <xsl:copy-of select="Record!copy-of()!(let $r := . return (1 to repeat)!$r)"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

Or perhaps it is even better to simply process the Record elements with

<xsl:template match="Record">
    <xsl:copy-of select="copy-of()!(let $r := . return (1 to repeat)!$r)"/>     
</xsl:template>

Also, as pointed out rightly in the comments, the copy-of() is not needed for normal processing, only for streaming, so without the streamable="yes" on the xsl:mode the template could be written as

<xsl:template match="Record">
    <xsl:copy-of select="(1 to repeat)!current()"/>     
</xsl:template>

Using XSLT 2 you can use

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs"
    version="2.0">

    <xsl:output indent="yes"/>

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

    <xsl:template match="Record">
        <xsl:copy-of select="for $n in 1 to repeat return current()"/>     
    </xsl:template>

</xsl:stylesheet>
Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
1

If are using XSLT 1.0, then upgrade to XSLT 2.0...

But if this is not a possibility, then in XSLT 1.0 you can use a recursive named template

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

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

    <xsl:template match="Record">
        <xsl:call-template name="Repeat">
            <xsl:with-param name="repeat" select="repeat" />
        </xsl:call-template>
    </xsl:template>

    <xsl:template match="repeat" />

    <xsl:template name="Repeat">
        <xsl:param name="repeat" />

        <xsl:call-template name="Identity" />
        <xsl:if test="$repeat > 1">
            <xsl:call-template name="Repeat">
                <xsl:with-param name="repeat" select="$repeat - 1" />
            </xsl:call-template>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

For example purposes, my example includes removing the repeat from the output.

In XSLT 2.0, you could do this..

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

    <xsl:output method="xml" indent="yes" />
    <xsl:strip-space elements="*" />

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

    <xsl:template match="Record">
        <xsl:variable name="Record" select="." />
        <xsl:for-each select="1 to xs:integer(repeat)">
            <xsl:apply-templates select="$Record" mode="Repeat" />
        </xsl:for-each>
    </xsl:template>

    <xsl:template match="repeat" />

    <xsl:template match="*" mode="Repeat">
        <xsl:call-template name="Identity" />
    </xsl:template>
</xsl:stylesheet>
Tim C
  • 70,053
  • 14
  • 74
  • 93