有没有办法使用 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 次?的主要内容,如果未能解决你的问题,请参考以下文章
Marklogic xml 转换中的 XSLT 3.0 支持