有没有办法使用 XSLT 根据 XML 中的元素复制 XML 节点 n 次?

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

我想在我的 XML 文件中复制一些节点。该文件旨在发送给打印引擎。它考虑了一个有几行的采购订单,对于每一行,需要打印一些标签。该数量取决于将为该采购订单接收的项目数量。因此,我想将该特定行的 XML 节点复制 n 次,n 等于特定行中指定的副本数。

我的来源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>

请求的结果:

<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>

我已经摆弄了一个在 Stack Overflow 上找到的 XSLT 示例: Duplicate element x number of times with XSLT

但不幸的是我无法让它工作。

我的 XSLT 实验:

<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>

首先,您需要一个格式正确的 XML 输入,具有单个根元素。

那么你可以简单地做:

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>

演示:https://xsltfiddle.liberty-development.net/eiorv1b


已添加:

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="line" name="generate-lines">
    <xsl:param name="n" select="copies"/>
    <xsl:if test="$n > 0">
        <xsl:copy>
            <xsl:copy-of select="item"/>
        </xsl:copy>
        <!-- recursive call -->
        <xsl:call-template name="generate-lines">
            <xsl:with-param name="n" select="$n - 1"/>
        </xsl:call-template>
    </xsl:if>
</xsl:template>

</xsl:stylesheet>

XSLT 1.0

这是我的 XSLT 1.0 解决方案。在 XSLT 1.0 中,您不能在 select 参数中使用带有范围的 xsl:for-each 循环。相反,我对模板进行了递归调用。

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

<xsl:template match="line">
    <xsl:call-template name="block-generator">
        <xsl:with-param name="N" select="copies"/>
    </xsl:call-template>
</xsl:template>

<xsl:template name="block-generator">
    <xsl:param name="N"/>
    <xsl:param name="i" select="0"/>
    <xsl:if test="$N > $i">
        <line>
            <xsl:copy-of select="item"/>
        </line>
        <xsl:call-template name="block-generator">
            <xsl:with-param name="N" select="$N"/>
            <xsl:with-param name="i" select="$i + 1"/>
        </xsl:call-template>
    </xsl:if>
</xsl:template>

</xsl:stylesheet>