Reputation: 69
I have a requirement to create XML nodes from XPATH and merge that to the existing XML. I am facing an issue where even if I am specifying the newly generated xml nodes from xpath should be in a specific position of an array it is still coming as the top element of that array. Kindly help to address this issue.
Input XML (here data is an array):
<?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>
output XML (Here txCurr as per the XSLT should have been created and added to 2nd position of data array but got added in 0th position):
<header>
<identifier>12345</identifier>
</header>
<data>
<txCtry>SG</txCtry>
<txCurr>MYD</txCurr>
</data>
<data>
<txCtry>TH</txCtry>
</data>
<data>
<txCtry>MY</txCtry>
</data>
Upvotes: 0
Views: 120
Reputation: 167436
What the code does, is, it first creates some XML fragment from that VPop
variable and the result from that, for your given sample data, is simply <data<txCurr>MYD</txCurr</data>
, i.e. a single data
element with a single txtCurr
element. The next step then merges the XML fragment with the input fragment, based on the XPaths the XPath 3.1 path
function gives. So the information that you might have wanted the second data
element is already gone after that first step, somehow it expects you to ensure your "input" paths specify and therefore create two data
elements (e.g. <xsl:variable name="vPop" as="element()*"><item path="/data[1]"/><item path="/data[2]/txCurr">MYD</item></xsl:variable>
), otherwise the whole approach can't work.
Or the first step would need to be rewritten not only to break up and create elements based on names but also to try to infer which indices are left out/missing and also create them, something that is (even more) complex than the current approach; here is a basic, and admittedly, currently rather convoluted approach:
<?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(.), '^/?([^/]+)(.*$)', '$1'),
$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, '^/?([^/]+)(.*$)', '$1'),
$pos := if (not(ends-with($first-step, ']'))) then
1
else
replace($first-step, '^[^\[]+(\[([0-9]+)\])$', '$2') ! 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\(([''"]?)([^''"]+)["'']?\)$', '$2')}"/>
</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(.), '^/?([^/]+)(.*$)', '$1')">
<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\(([''"]?)([^''"]+)["'']?\)$', '$2')}" 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(., '^/?[^/]+(.*)', '$1'), ?*))"
/>
</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>
Upvotes: 1