XSLT(2.0 或 3.0)方法将存储在 xml 中的每个逗号分隔值的整个 xml 复制到单独的 xml 文件中
Posted
技术标签:
【中文标题】XSLT(2.0 或 3.0)方法将存储在 xml 中的每个逗号分隔值的整个 xml 复制到单独的 xml 文件中【英文标题】:XSLT (2.0 or 3.0) way of duplicating whole xml for each comma separated value stored in xml into separate xml files 【发布时间】:2021-11-27 02:38:57 【问题描述】:我有一个 XML 文件,它是来自网络订单系统的 workOrder。它包含大量订单数据,其中一个值是多个文件路径的分隔字符串。 我想复制完整的 XML 并为每个 fileURL 输出一个 xml,并用每个 fileURL 换出值(每个 xml 中的单个文件路径)。原因是稍后使用的工作流系统读取文件的路径并将其拾取并将 xml 关联为元数据以进行进一步处理,但每个文件需要一个 xml。
输入XML(包含存储路径的部分):
<rootNode>
...
<properties>
<property>
<name label='fileURL'>fileurl</name>
<value>\\nas02\Order\O10346_OP176786_X1.pdf, \\nas02\Order\Weborder\O10346_OP176789_X2.pdf, \\nas02\Order\Weborder\O10346_OP176795_X3.pdf, \\nas02\Order\Weborder\O10346_OP176796_X1.pdf,
</value>
</property>
</properties>
</technicalSpec>
...
</rootNode>
预期的输出将是每个包含相同数据的 fileURL 的一个 xml,除了属性值应该是每个副本的单个 fileURL:
<rootNode>
...
<properties>
<property>
<name label='fileURL'>fileurl</name>
<value>\\nas02\Order\O10346_OP176786_X1.pdf
</value>
</property>
</properties>
</technicalSpec>
...
</rootNode>
我知道如何将 csv 字符串放入变量中:
<xsl:variable name="csv" select="//property[name='fileurl']/value"></xsl:variable>
我发现我可以为值做一个 for-each 循环:
<xsl:for-each select="tokenize($csv, ',')">
我还发现了如何复制整个 xml 内容:
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
而且我知道我可以在 for-each 循环中使用“结果文档”来创建单独的输出文件。
但我无法弄清楚如何将所有内容组合成一个有效的 xslt(如果可能的话)以为每个 csv 值创建一个 xml。
【问题讨论】:
每个结果文档的名称和文件路径应该是什么?我想您不想保存扩展名为 .pdf 的 XML 文档? 【参考方案1】:这个问题在结构上与Split google XML items by param value 中的问题相同,但我不会将其标记为重复,因为对于初学者来说,如何将问题的答案转换为您的需要可能并不明显。
这种方法的本质是:
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="/">
<xsl:variable name="root" select="/*"/>
<xsl:for-each select="tokenize(//value, ',')!normalize-space()">
<xsl:result-document href="position().xml">
<xsl:apply-templates select="$root">
<xsl:with-param name="current-file" select="."/>
</xsl:apply-templates>
</xsl:result-document>
</xsl:for-each>
</xsl:template>
<xsl:template match="value">
<xsl:param name="current-file"/>
<value>$current-file</value>
</xsl:template>
请注意,这取决于内置模板规则通过未更改的方式复制参数值(它们的行为实际上类似于隧道参数)。当然也可以显式声明为隧道参数。
【讨论】:
谢谢。正如您所说,这与给出的第一个答案非常相似。您建议的两个答案都对我有用。感谢您的帮助。【参考方案2】:在给定 XSLT 3 的情况下,我会使用隧道参数和 xsl:mode
:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="main-root" select="/"/>
<xsl:for-each select="tokenize(rootNode/technicalSpec/properties/property[name[@label = 'fileURL' and . = 'fileurl']]/value, ',s*')[normalize-space()]">
<xsl:result-document href="substring-before(., '.pdf').xml">
<xsl:apply-templates select="$main-root/*">
<xsl:with-param name="url" select="." tunnel="yes"/>
</xsl:apply-templates>
</xsl:result-document>
</xsl:for-each>
</xsl:template>
<xsl:template match="property[name[@label = 'fileURL' and . = 'fileurl']]/value">
<xsl:param name="url" tunnel="yes"/>
<xsl:copy>$url</xsl:copy>
</xsl:template>
<xsl:mode on-no-match="shallow-copy"/>
</xsl:stylesheet>
【讨论】:
与我的回答几乎相同,只是您对输入 XML 中的省略号的解释与我理解的方式不同。 谢谢。这个工作正常。我做了一些小改动,以更具体地适合我需要作为磁盘输出的文件名。【参考方案3】:这个问题已经有三个答案,它们都做同样的事情:标记value
元素,为每个标记创建一个result-document
并应用具有当前标记的模板作为用于模板匹配的参数value
.
我会建议一种不同的方法,我认为它更简单:
XSLT 3.0
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
expand-text="yes">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="/">
<xsl:variable name="root" select="."/>
<xsl:analyze-string select="//value" regex="(.+?)($|, )">
<xsl:matching-substring>
<xsl:result-document href="position().xml">
<xsl:apply-templates select="$root/*"/>
</xsl:result-document>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:template>
<xsl:template match="value">
<xsl:copy>regex-group(1)</xsl:copy>
</xsl:template>
</xsl:stylesheet>
请注意,我假设了一个正确分隔的字符串,格式为:
<value>\\nas02\Order\O10346_OP176786_X1.pdf, \\nas02\Order\Weborder\O10346_OP176789_X2.pdf, \\nas02\Order\Weborder\O10346_OP176795_X3.pdf, \\nas02\Order\Weborder\O10346_OP176796_X1.pdf</value>
演示(模拟):https://xsltfiddle.liberty-development.net/3MP42Pb
【讨论】:
【参考方案4】:实现此目的的一种方法是使用由xsl:apply-templates
填充的变量,并带有mode
属性。第一步正如您所怀疑的那样,但更改结果文档中的一个元素有点棘手。
在这种方法中,首先,我用行链接到输入文档
<xsl:variable name="doc" select="/" />
制作了带有路径的文件名的副本 - 正如您已经建议的那样:
<xsl:variable name="csv" select="//property[name='fileurl']/value" />
这里应用了xsl:for-each
。
作为输出文件名,我简单地选择了$csv
字符串的当前部分(本次迭代)的最后一部分:
<xsl:variable name="result-name" select="string-join(tokenize(., '\\')[position() = last()], '')" />
然后我使用一个变量,其值由apply-templates
填充,并带有提到的mode="new"
属性;使用此模式应用于模板;其中之一更改了xsl:param
给出的参数中设置的值:
<xsl:variable name="new-doc">
<xsl:apply-templates select="$doc" mode="new">
<xsl:with-param name="nam" select="normalize-space(.)" />
</xsl:apply-templates>
</xsl:variable>
现在,具有mode="new"
属性的两个模板被执行。
最后,将变量用xsl:result-document
写入对应的文档:
<xsl:result-document encoding="UTF-8" href="$result-name">
<xsl:copy-of select="$new-doc" />
</xsl:result-document>
整个样式表可能如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="doc" select="/" />
<xsl:variable name="csv" select="//property[name='fileurl']/value" />
<xsl:template match="/">
<xsl:for-each select="tokenize($csv, ',')">
<xsl:variable name="result-name" select="string-join(tokenize(., '\\')[position() = last()], '')" />
<xsl:variable name="new-doc">
<xsl:apply-templates select="$doc" mode="new">
<xsl:with-param name="nam" select="normalize-space(.)" />
</xsl:apply-templates>
</xsl:variable>
<xsl:result-document encoding="UTF-8" href="$result-name">
<xsl:copy-of select="$new-doc" />
</xsl:result-document>
</xsl:for-each>
</xsl:template>
<xsl:template match="value" mode="new">
<xsl:param name="nam" />
<value><xsl:value-of select="$nam" /></value>
</xsl:template>
<!-- identity template -->
<xsl:template match="node()|@*" mode="new">
<xsl:param name="nam" />
<xsl:copy>
<xsl:apply-templates select="node()|@*" mode="new">
<xsl:with-param name="nam" select="$nam" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
【讨论】:
<xsl:variable name="doc" select="/" />
不会“制作输入文档的副本”。创建$new-doc
变量只是为了将其内容复制到输出的目的是什么?为什么不直接写入输出?可能还有更多,但这两个跳出来了。
是的。这不是一个副本,而是一个链接。我改变了那个。从this answer at SO 获得灵感。是的,这个变量可以被优化掉。以上是关于XSLT(2.0 或 3.0)方法将存储在 xml 中的每个逗号分隔值的整个 xml 复制到单独的 xml 文件中的主要内容,如果未能解决你的问题,请参考以下文章
BizTalk Server 2016 映射中是不是支持 XSLT 2.0 或 3.0?
XSLT 3.0 - 无法在 XSLT 3.0 xml-to-json() 中获取对象数组
XSLT 3.0 - 在 XSLT 3.0 xml-to-json() 中出现错误“重复键值”