Reputation: 129
i have to duplicate the xml payload into as many xml payloads based on a specific id, e.g., userid
<ns2:Details xmlns:ns2="ns">
<ns2:var1>AA0511201143</ns2:var1>
<ns2:var2>PARCEL</ns2:var2>
<ns2:var3>04/04/2011</ns2:var3>
<ns2:var4>Organization</ns2:var4>
<ns2:UserId>46</ns2:UserId>
<ns2:UserId>237</ns2:UserId>
</ns2:Details>
i need the output as
<ns2:Details>
<ns2:var1>AA0511201143</ns2:var1>
<ns2:var2>PARCEL</ns2:var2>
<ns2:var3>04/04/2011</ns2:var3>
<ns2:var4>Organization</ns2:var4>
<ns2:UserId>46</ns2:UserId>
</ns2:Details>
<ns2:Details>
<ns2:var1>AA0511201143</ns2:var1>
<ns2:var2>PARCEL</ns2:var2>
<ns2:var3>04/04/2011</ns2:var3>
<ns2:var4>Organization</ns2:var4>
<ns2:UserId>237</ns2:UserId>
</ns2:Details>
is this possible
Update: The below answer that was given is working fine, but there's a small catch I failed to mention. If the userid is the same and it's repeating, then the same xml payload should be displayed. For this I tried the following to get the unique elements of userid
<xsl:param name="userId" select="ns0:UserId[generate-id(.)=generate-id(key('k', ns0:UserId)[1])]"/>
but this is not working and also tried using above
..[generate-id(.)=generate-id(key('k', ns0:UserId)[1])]
at template level also it is not working
Am I missing something?
Update : i made a small modification to the above code, instead of working at xsl:param, i have used it at xsl:apply-template
before modification (provided as answer to me) <xsl:apply-templates select="//ns2:Details/ns2:UserId"/> after modification <xsl:apply-templates select="//ns2:Details/ns2:UserId[generate-id(.)=generate-id(key('myUserId', .)[1])]"/>
my mistake i was using ns2:userid instead of "."
full xsl code ---
<xsl:output method="xml" indent="yes"/> <xsl:key name="k" match="ns2:UserId" use="text()"/> <xsl:key name="myUserId" match="ns2:UserId" use="."/> <xsl:template match="/"> <ns2:Root> <xsl:apply-templates select="//ns2:Details/ns2:UserId[generate-id(.)=generate-id(key('myUserId', .)[1])]"/> </ns2:Root> </xsl:template>
<xsl:template match="//ns2:Details"> <xsl:param name="userId" select="ns2:UserId"/> <ns2:Details> <xsl:copy-of select="key('k', $userId)[1]"/> <!-- displays UserId values--> <xsl:copy-of select="./*[name() != 'ns2:UserId']"/> <!-- displays other values--> </ns2:Details> </xsl:template>
<xsl:template match="ns2:UserId"> <xsl:apply-templates select=".."> <xsl:with-param name="userId" select="."/> </xsl:apply-templates> </xsl:template>
Please, validate it. this too is working for me...
Upvotes: 7
Views: 3052
Reputation: 1
There are two more compact versions on XSLT 1.0 with same result.
Based on generate-id()
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns2="ns">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="k1" match="/ns2:Details/ns2:UserId" use="."/>
<xsl:key name="k2" match="/ns2:Details/ns2:UserId" use="generate-id() = generate-id(key('k1',.)[1])"/>
<xsl:template match = "/">
<xsl:for-each select="key('k2',true())">
<ns:Details>
<xsl:copy-of select="../node()[not(self::ns2:UserId)]"></xsl:copy-of>
<xsl:copy-of select="."/>
</ns:Details>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Based on preceding-sibling
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns2="ns">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="k" match="/ns2:Details/ns2:UserId" use="not(node() = preceding-sibling::ns2:UserId/node())"/>
<xsl:template match = "/">
<xsl:for-each select="key('k',true())">
<ns:Details>
<xsl:copy-of select="../node()[not(self::ns2:UserId)]"></xsl:copy-of>
<xsl:copy-of select="."/>
</ns:Details>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Upvotes: 0
Reputation: 21
IN Addition to that in XSLT 2.0 it is simple Instead of writing multiple temlates with apply-templates,see the below code with single template it will give same result.
<xsl:for-each-group select="*:Details" group-by="*:UserId">
<xsl:comment select="current-grouping-key()"/>
<ns2:Details>
<xsl:for-each select="current-group()">
<xsl:element name="ns2:UserId">
<xsl:value-of select="current-grouping-key()"/>
</xsl:element>
<xsl:copy-of select="*[name() != 'ns2:UserId']"/>
</xsl:for-each>
</ns2:Details>
</xsl:for-each-group>
</xsl:template>
Upvotes: 0
Reputation: 24826
One way to achieve the wanted result is to use the Identity Transformation and override the ns2:Details
node.
In the overriding template, you can use the repetition instruction xsl:for-each
to iterate over all UserId
.
To manage duplicate UserId
you can make use of the well known predicate coming from the Menuchian method of grouping.
Because we'll use the identity transformation, the way all thing is generated is much more simple.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns2="ns">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="UserId" match="ns2:UserId" use="."/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ns2:Details">
<xsl:for-each select="ns2:UserId
[generate-id()
= generate-id(key('UserId',.)[1])]">
<ns2:Details>
<xsl:copy-of select="../@*"/>
<xsl:apply-templates select="../node()
[not(self::ns2:UserId)]"/>
<xsl:apply-templates select="."/>
</ns2:Details>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When this transform is applied to the input provided in the question, the following fragment is obtained:
<ns2:Details xmlns:ns2="ns">
<ns2:var1>AA0511201143</ns2:var1>
<ns2:var2>PARCEL</ns2:var2>
<ns2:var3>04/04/2011</ns2:var3>
<ns2:var4>Organization</ns2:var4>
<ns2:UserId>46</ns2:UserId>
</ns2:Details>
<ns2:Details xmlns:ns2="ns">
<ns2:var1>AA0511201143</ns2:var1>
<ns2:var2>PARCEL</ns2:var2>
<ns2:var3>04/04/2011</ns2:var3>
<ns2:var4>Organization</ns2:var4>
<ns2:UserId>237</ns2:UserId>
</ns2:Details>
This output is obtained even when duplicates of UserId are present in the input document.
Upvotes: 0
Reputation: 56162
Supposed XML:
<ns2:Details xmlns:ns2="ns2">
<ns2:var1>AA0511201143</ns2:var1>
<ns2:var2>PARCEL</ns2:var2>
<ns2:var3>04/04/2011</ns2:var3>
<ns2:var4>Organization</ns2:var4>
<ns2:UserId>46</ns2:UserId>
<ns2:UserId>237</ns2:UserId>
<ns2:UserId>46</ns2:UserId>
</ns2:Details>
XSLT:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns2="ns2"
>
<xsl:output method="xml" indent="yes"/>
<xsl:key name="k" match="ns2:UserId" use="text()"/>
<xsl:template match="/">
<root>
<xsl:apply-templates select="//ns2:Details/ns2:UserId[not(node() = preceding-sibling::node())]"/>
</root>
</xsl:template>
<xsl:template match="//ns2:Details">
<xsl:param name="userId" select="ns2:UserId"/>
<ns2:Details>
<xsl:copy-of select="key('k', $userId)[not(node() = preceding-sibling::node())]"/>
<xsl:copy-of select="./*[name() != 'ns2:UserId']"/>
</ns2:Details>
</xsl:template>
<xsl:template match="ns2:UserId">
<xsl:apply-templates select="..">
<xsl:with-param name="userId" select="."/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
Output XML:
<?xml version="1.0" encoding="utf-8"?>
<root xmlns:ns2="ns2">
<ns2:Details>
<ns2:UserId>46</ns2:UserId>
<ns2:var1>AA0511201143</ns2:var1>
<ns2:var2>PARCEL</ns2:var2>
<ns2:var3>04/04/2011</ns2:var3>
<ns2:var4>Organization</ns2:var4>
</ns2:Details>
<ns2:Details>
<ns2:UserId>237</ns2:UserId>
<ns2:var1>AA0511201143</ns2:var1>
<ns2:var2>PARCEL</ns2:var2>
<ns2:var3>04/04/2011</ns2:var3>
<ns2:var4>Organization</ns2:var4>
</ns2:Details>
</root>
Upvotes: 4
Reputation: 243449
This transformation (short, only two templates, no xsl:for-each
, no modes):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns2="ns">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kIdByVal" match="ns2:UserId" use="."/>
<xsl:template match="/">
<xsl:apply-templates select=
"ns2:Details/ns2:UserId
[generate-id()=generate-id(key('kIdByVal',.)[1])]
"/>
</xsl:template>
<xsl:template match="ns2:UserId">
<ns2:Details>
<xsl:copy-of select=
"../node()
[not(self::ns2:UserId
[not(generate-id()=generate-id(current()))])
]"/>
</ns2:Details>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document (containing redundant ns2:UserId
elements):
<ns2:Details xmlns:ns2="ns">
<ns2:var1>AA0511201143</ns2:var1>
<ns2:var2>PARCEL</ns2:var2>
<ns2:var3>04/04/2011</ns2:var3>
<ns2:var4>Organization</ns2:var4>
<ns2:UserId>46</ns2:UserId>
<ns2:UserId>237</ns2:UserId>
<ns2:UserId>46</ns2:UserId>
</ns2:Details>
produces exactly the wanted, correct result:
<ns2:Details xmlns:ns2="ns">
<ns2:var1>AA0511201143</ns2:var1>
<ns2:var2>PARCEL</ns2:var2>
<ns2:var3>04/04/2011</ns2:var3>
<ns2:var4>Organization</ns2:var4>
<ns2:UserId>46</ns2:UserId>
</ns2:Details>
<ns2:Details xmlns:ns2="ns">
<ns2:var1>AA0511201143</ns2:var1>
<ns2:var2>PARCEL</ns2:var2>
<ns2:var3>04/04/2011</ns2:var3>
<ns2:var4>Organization</ns2:var4>
<ns2:UserId>237</ns2:UserId>
</ns2:Details>
Explanation: Muenchian grouping, xsl:copy-of
, use of current()
Upvotes: 1
Reputation: 60414
The following stylesheet handles duplicates:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns2="ns2">
<xsl:output method="xml" indent="yes" />
<xsl:key name="byUserId" match="ns2:UserId" use="." />
<xsl:template match="/">
<root>
<xsl:apply-templates
select="ns2:Details/ns2:UserId
[generate-id()=generate-id(key('byUserId', .)[1])]" />
</root>
</xsl:template>
<xsl:template match="ns2:UserId">
<xsl:apply-templates select=".." mode="out">
<xsl:with-param name="userId" select="." />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="ns2:Details" mode="out">
<xsl:param name="userId" select="''" />
<xsl:copy>
<xsl:apply-templates select="node()|@*" mode="out" />
<xsl:copy-of select="$userId"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ns2:UserId" mode="out" />
<xsl:template match="node()|@*" mode="out">
<xsl:copy>
<xsl:apply-templates select="node()|@*" mode="out" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
On this input:
<ns2:Details xmlns:ns2="ns2">
<ns2:var1>AA0511201143</ns2:var1>
<ns2:var2>PARCEL</ns2:var2>
<ns2:var3>04/04/2011</ns2:var3>
<ns2:var4>Organization</ns2:var4>
<ns2:UserId>46</ns2:UserId>
<ns2:UserId>237</ns2:UserId>
<ns2:UserId>46</ns2:UserId>
</ns2:Details>
Produces:
<root xmlns:ns2="ns2">
<ns2:Details>
<ns2:var1>AA0511201143</ns2:var1>
<ns2:var2>PARCEL</ns2:var2>
<ns2:var3>04/04/2011</ns2:var3>
<ns2:var4>Organization</ns2:var4>
<ns2:UserId>46</ns2:UserId>
</ns2:Details>
<ns2:Details>
<ns2:var1>AA0511201143</ns2:var1>
<ns2:var2>PARCEL</ns2:var2>
<ns2:var3>04/04/2011</ns2:var3>
<ns2:var4>Organization</ns2:var4>
<ns2:UserId>237</ns2:UserId>
</ns2:Details>
</root>
Upvotes: 0
Reputation: 10754
Yes it is possible. You can loop through using a for-each loop using for-each ns2:UserID node.
Upvotes: 0