从 XPATH 创建 XML 个节点并将其合并到现有的 XML

Creating XML nodes from XPATH and merging that to existing XML

我需要从 XPATH 创建 XML 个节点并将其合并到现有的 XML。我面临一个问题,即使我从 xpath 指定新生成的 xml 节点应该位于数组的特定位置,它仍然是该数组的顶部元素。请帮助解决这个问题。

输入XML(这里的数据是一个数组):

<?xml version="1.0" encoding="UTF-8"?>
<header>
  <identifier>12345</identifier>
</header>
<data>
  <txCtry>SG</txCtry>
</data>
<data>
  <txCtry>TH</txCtry>
</data>
<data>
  <txCtry>MY</txCtry>
</data>

XSLT:

<?xml version="1.0" encoding="utf-8"?>
<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"
  xmlns:my="http://example.com/my-functions"
  expand-text="yes">
  
<xsl:output omit-xml-declaration="yes" />
<xsl:variable name="vPop" as="element()*">
<item path="/data[2]/txCurr">MYD</item>
 </xsl:variable>
 
 <xsl:variable name="new-nodes">
   <xsl:sequence select="my:subTree($vPop/@path/concat(.,'/',string(..)))"/>
 </xsl:variable>

  <xsl:output method="xml" indent="yes"/>
  <xsl:strip-space elements="*"/>
    
  <xsl:template match="/" name="xsl:initial-template">
    <xsl:sequence select="my:merge(*, $new-nodes/*)"/>
  </xsl:template>


 <xsl:function name="my:merge" as="node()*">
   <xsl:param name="node1" as="node()*"/>
   <xsl:param name="node2" as="node()*"/>
   <xsl:for-each-group select="$node1, $node2" group-by="path()">
     <xsl:copy>
       <xsl:sequence select="my:merge(@*, current-group()[2]/@*)"/>
       <xsl:sequence select="my:merge(node(), current-group()[2]/node())"/>
     </xsl:copy>
   </xsl:for-each-group>
 </xsl:function>

 <xsl:function name="my:subTree" as="node()*">
   
   
  <xsl:param name="pPaths" as="xs:string*"/>

  <xsl:for-each-group select="$pPaths"
    group-by=
        "substring-before(substring-after(concat(., '/'), '/'), '/')">
    <xsl:if test="current-grouping-key()">
     <xsl:choose>
       <xsl:when test=
          "substring-after(current-group()[1], current-grouping-key())">
         <xsl:element name=
           "{substring-before(concat(current-grouping-key(), '['), '[')}">

          <xsl:sequence select=
            "my:subTree(for $s in current-group()
                         return
                            concat('/',substring-after(substring($s, 2),'/'))
                             )
            "/>
        </xsl:element>
       </xsl:when>
       <xsl:otherwise>
        <xsl:value-of select="current-grouping-key()"/>
       </xsl:otherwise>
     </xsl:choose>
     </xsl:if>
  </xsl:for-each-group>
 </xsl:function>
</xsl:stylesheet>

输出XML(此处根据 XSLT 应该创建 txCurr 并将其添加到数据数组的第 2 个位置,但添加到第 0 个位置):

<header>
   <identifier>12345</identifier>
</header>
<data>
   <txCtry>SG</txCtry>
   <txCurr>MYD</txCurr>
</data>
<data>
   <txCtry>TH</txCtry>
</data>
<data>
   <txCtry>MY</txCtry>
</data>

代码的作用是,它首先从 VPop 变量创建一些 XML 片段,对于给定的示例数据,其结果只是 <data<txCurr>MYD</txCurr</data>,即单个 data 元素与单个 txtCurr 元素。下一步然后根据 XPath 3.1 path 函数提供的 XPath,将 XML 片段与输入片段合并。所以你可能想要第二个 data 元素的信息在第一步之后已经消失了,不知何故它希望你确保你的“输入”路径指定并因此创建两个 data 元素(例如 <xsl:variable name="vPop" as="element()*"><item path="/data[1]"/><item path="/data[2]/txCurr">MYD</item></xsl:variable>), 否则整个方法都行不通。

或者第一步需要重写,不仅要根据名称分解和创建元素,还要尝试推断留下哪些索引 out/missing 并创建它们,这是(甚至比目前的方法更)复杂;这是一个基本的,诚然,目前相当复杂的方法:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:map="http://www.w3.org/2005/xpath-functions/map"
  xmlns:array="http://www.w3.org/2005/xpath-functions/array" 
  exclude-result-prefixes="#all"
  xmlns:mf="http://example.com/mf"
  expand-text="yes">

  <xsl:variable name="vPop" as="element()*">
   <item path="/data[2]/txCurr">MYD</item>
 </xsl:variable>
 
 <xsl:variable name="new-nodes">
   <xsl:sequence select="mf:generate-nodes($vPop ! map:entry(@path!string(), string(.)))"/>
 </xsl:variable>

  <xsl:output method="xml" indent="yes"/>
  <xsl:strip-space elements="*"/>
    
  <xsl:template match="/" name="xsl:initial-template">
    <xsl:sequence select="mf:merge($main-input/*, $new-nodes/*)"/>
    <xsl:comment>Run with {system-property('xsl:product-name')} {system-property('xsl:product-version')} {system-property('Q{http://saxon.sf.net/}platform')}</xsl:comment>
  </xsl:template>


 <xsl:function name="mf:merge" as="node()*">
   <xsl:param name="node1" as="node()*"/>
   <xsl:param name="node2" as="node()*"/>
   <xsl:for-each-group select="$node1, $node2" group-by="path()">
     <xsl:copy>
       <xsl:sequence select="mf:merge(@*, current-group()[2]/@*)"/>
       <xsl:sequence select="mf:merge(node(), current-group()[2]/node())"/>
     </xsl:copy>
   </xsl:for-each-group>
 </xsl:function>  


  <xsl:output method="xml" indent="yes" />

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:param name="main-input" as="document-node()" select="parse-xml-fragment($main-fragment)"/>
  
  <xsl:param name="main-fragment" as="xs:string"><![CDATA[<header>
  <identifier>12345</identifier>
</header>
<data>
  <txCtry>SG</txCtry>
</data>
<data>
  <txCtry>TH</txCtry>
</data>
<data>
  <txCtry>MY</txCtry>
</data>]]></xsl:param>

  <xsl:function name="mf:generate-nodes" as="node()*" visibility="public">
        <xsl:param name="xpath-values" as="map(xs:string, item()*)*"/>
        <xsl:for-each-group select="$xpath-values" group-adjacent="
                let $first-step := replace(map:keys(.), '^/?([^/]+)(.*$)', ''),
                    $exp := replace($first-step, '\[[0-9]+\]$', '')
                return
                    $exp">
            <xsl:choose>
                <xsl:when test="current-grouping-key() = ''">
                    <xsl:choose>
                        <xsl:when test="?* instance of node()+">
                            <xsl:sequence select="?*"/>
                        </xsl:when>
                        <xsl:otherwise>
                            <xsl:value-of select="?*"/>
                        </xsl:otherwise>
                    </xsl:choose>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:iterate select="
                            1 to max(current-group() !
                            (let $key := map:keys(.),
                                $first-step := replace($key, '^/?([^/]+)(.*$)', ''),
                                $pos := if (not(ends-with($first-step, ']'))) then
                                    1
                                else
                                    replace($first-step, '^[^\[]+(\[([0-9]+)\])$', '') ! xs:integer(.)
                            return
                                $pos))">
                        <xsl:variable name="exp" select="current-grouping-key()"/>
                        <xsl:variable name="step" as="xs:string*" select="
                                if (. eq 1) then
                                    current-grouping-key()
                                else
                                    (), current-grouping-key() || '[' || . || ']'"/>                      
                        <xsl:variable name="current-grouping-steps"
                            select="current-group()[map:keys(.) ! tokenize(., '/')[normalize-space()][1][. = $step]]"/>
                        <xsl:choose>
                            <xsl:when test="not($exp = '') and not(exists($current-grouping-steps))">                               
                                <xsl:choose>
                                    <xsl:when test="starts-with($exp, 'comment()')">
                                        <xsl:comment/>
                                    </xsl:when>
                                    <xsl:when test="starts-with($exp, 'processing-instruction(')">
                                        <xsl:processing-instruction name="{replace($exp, '^processing-instruction\(([''&quot;]?)([^''&quot;]+)[&quot;'']?\)$', '')}"/>
                                    </xsl:when>
                                    <xsl:when test="starts-with($exp, '@')">
                                        <xsl:attribute name="{substring($exp, 2)}"/>
                                    </xsl:when>
                                    <xsl:when test="$exp">
                                        <xsl:element name="{$exp}"/>
                                    </xsl:when>
                                </xsl:choose>
                            </xsl:when>
                            <xsl:otherwise>
                                <xsl:for-each-group select="$current-grouping-steps"
                                    group-by="replace(map:keys(.), '^/?([^/]+)(.*$)', '')">
                                    <xsl:variable name="name" as="xs:string"
                                        select="replace(current-grouping-key(), '\[[0-9]+\]$', '')"/>
                                    <xsl:choose>
                                        <xsl:when test="starts-with($name, 'comment()')">
                                            <xsl:comment select="?*"/>
                                        </xsl:when>
                                        <xsl:when
                                            test="starts-with($name, 'processing-instruction(')">
                                            <xsl:processing-instruction name="{replace($name, '^processing-instruction\(([''&quot;]?)([^''&quot;]+)[&quot;'']?\)$', '')}" select="?*"/>
                                        </xsl:when>
                                        <xsl:when test="starts-with($name, '@')">
                                            <xsl:attribute name="{substring($name, 2)}" select="?*"
                                            />
                                        </xsl:when>
                                        <xsl:when test="$name">
                                            <xsl:element name="{$name}">
                                                <xsl:sequence
                                                  select="mf:generate-nodes(current-group() ! map:entry(map:keys(.) ! replace(., '^/?[^/]+(.*)', ''), ?*))"
                                                />
                                            </xsl:element>
                                        </xsl:when>
                                        <xsl:otherwise>
                                            <xsl:choose>
                                                <xsl:when test="?* instance of node()+">
                                                  <xsl:sequence select="?*"/>
                                                </xsl:when>
                                                <xsl:otherwise>
                                                  <xsl:value-of select="?*"/>
                                                </xsl:otherwise>
                                            </xsl:choose>
                                        </xsl:otherwise>
                                    </xsl:choose>
                                </xsl:for-each-group>
                            </xsl:otherwise>
                        </xsl:choose>
                    </xsl:iterate>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:for-each-group>
    </xsl:function>
</xsl:stylesheet>