从 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\(([''"]?)([^''"]+)["'']?\)$', '')}"/>
</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\(([''"]?)([^''"]+)["'']?\)$', '')}" 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>
我需要从 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\(([''"]?)([^''"]+)["'']?\)$', '')}"/>
</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\(([''"]?)([^''"]+)["'']?\)$', '')}" 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>