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

我有一个 XML 文件,它是来自网络订单系统的工单。它包含大量订单数据,其中一个值是多个文件路径的分隔字符串。 我想复制完整的 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>

对于包含相同数据的每个文件 URL,预期输出将是一个 xml,除了 属性 值应该是每个副本的单个文件 URL:

<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 循环中使用“result-document”来创建单独的输出文件。

但我无法弄清楚如何将所有内容组合成一个工作 xslt(如果可能的话)来为每个 csv 值创建一个 xml。

实现此目的的一种方法是使用由 xsl:apply-templates 填充并具有 mode 属性的变量。第一步如您所料,但更改生成的文档中的一个元素有点棘手。

在这种方法中,首先,我使用

行对输入文档创建 link
<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()], '')" />

然后我使用一个变量,其值由具有上述 mode="new" 属性的 apply-templates 填充;使用此模式应用于模板;其中之一更改了 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:mode,给定 XSLT 3:

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

此问题在结构上与 中的问题相同,但我不会将其标记为重复问题,因为对于初学者来说,如何将问题的答案转换为您的需求可能并不明显.

该方法的本质是:

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

请注意,这取决于内置模板规则通过未更改的方式复制参数值这一事实(它们实际上表现得像隧道参数)。当然你也可以显式声明为隧道参数

这个问题已经有三个答案,它们都做同样的事情:标记化 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