738560
738560

Reputation: 129

How to Duplicate xml elements

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

Answers (7)

Vladimir
Vladimir

Reputation: 1

There are two more compact versions on XSLT 1.0 with same result.

  1. 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>
    
  2. 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

Vema Reddy
Vema Reddy

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

Emiliano Poggi
Emiliano Poggi

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

Kirill Polishchuk
Kirill Polishchuk

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

Dimitre Novatchev
Dimitre Novatchev

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

Wayne
Wayne

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

Fishcake
Fishcake

Reputation: 10754

Yes it is possible. You can loop through using a for-each loop using for-each ns2:UserID node.

Upvotes: 0

Related Questions