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

Posted

技术标签:

【中文标题】有没有办法使用 XSLT 基于 XML 中的元素复制 XML 节点 n 次?【英文标题】:Is there a way to copy XML nodes n times based on an element in the XML using XSLT? 【发布时间】:2022-01-09 06:07:55 【问题描述】:

我想在我的 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>

【问题讨论】:

【参考方案1】:

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>

【讨论】:

【参考方案2】:

首先你需要一个格式良好的 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>

【讨论】:

糟糕,我确实创建了一个错误的 XML,抱歉。为了在这个论坛上更容易阅读,我删除了原始 XML 文件,但我删除的有点太多了。我已经编辑了我的帖子,感谢您的努力! XSLT 模板就像一个魅力!不幸的是,我需要在其中实现此 XSLT 的解决方案不支持 XSLT 2.0。所以我需要将 xsl:for-each 循环转换成别的东西......我会管理的! 我在我的答案中添加了一个 XSLT 1.0 解决方案。顺便说一句,xsl:for-each 不是“循环”。一个递归命名模板,就像上面的那样。

以上是关于有没有办法使用 XSLT 基于 XML 中的元素复制 XML 节点 n 次?的主要内容,如果未能解决你的问题,请参考以下文章

使用 xslt 将 xml 复杂节点元素拆分为多个节点

有没有办法使用 xsl 更改 xml 中元素的名称?

Marklogic xml 转换中的 XSLT 3.0 支持

使用 XSLT 进行空前缀转换的 XML [重复]

如何使用 XSLT 替换 XML 节点名称中的字符 - 更改根元素

使用元素名称将输入 XML 传递给 XSLT 中的变量