Reputation: 59
I've the following source data:
<DATA>
<ITEM>
<ID>28</ID>
<BRAND>TGR</BRAND>
<ACC_NO>143080</ACC_NO>
<ACC_DESCR>account description</ACC_DESCR>
</ITEM>
<ITEM>
<ID>28</ID>
<BRAND>TGR</BRAND>
<ACC_NO>272180</ACC_NO>
<ACC_DESCR>account description</ACC_DESCR>
</ITEM>
<ITEM>
<ID>32</ID>
<BRAND>TGR</BRAND>
<ACC_NO>159880</ACC_NO>
<ACC_DESCR>account description</ACC_DESCR>
</ITEM>
</DATA>
Which i want to transform into:
<DATA>
<ITEM>
<ID>28</ID>
<BRAND>TGR</BRAND>
<ACCOUNTS>
<ACCOUNT>
<ACC_NO>143080</ACC_NO>
<ACC_DESCR>account description</ACC_DESCR>
<ACCOUNT>
<ACCOUNT>
<ACC_NO>272180</ACC_NO>
<ACC_DESCR>account description</ACC_DESCR>
<ACCOUNT>
<ACCOUNTS>
</ITEM>
<ITEM>
<ID>32</ID>
<BRAND>TGR</BRAND>
<ACCOUNTS>
<ACCOUNT>
<ACC_NO>159880</ACC_NO>
<ACC_DESCR>account description</ACC_DESCR>
<ACCOUNT>
<ACCOUNTS>
</ITEM>
</DATA>
Applying the following transformation:
<xsl:template match="/">
<xsl:for-each select="/DATA/ITEM">
<ITEM>
<ID>
<xsl:value-of select="ID"/>
</ID>
<BRAND>
<xsl:value-of select="BRAND"/>
<BRAND>
<ACCOUNTS>
<ACCOUNT>
<AccountNumber>
<xsl:value-of select="ACC_NO"/>
<AccountNumber>
<AccountDescription>
<xsl:value-of select="ACC_DESCR"/>
<AccountDescription>
</ACCOUNT>
</ACCOUNTS>
</ITEM>
</xsl:for-each>
</xsl:template>
I will get ACCOUNTS list inside ITEM, but still not merging ITEM's with same ID. Basically i'll get always a collection with a single element, instead of all the accounts under same ID.
Any suggestion how can i do it and produce the desired output?
Thanks
Upvotes: 1
Views: 873
Reputation: 122364
In XSLT 2.0 you simply replace the outer for-each
with a for-each-group
and then add an inner for-each
to create one ACCOUNT
per group member:
<xsl:template match="/">
<xsl:for-each-group select="/DATA/ITEM" group-by="ID">
<ITEM>
<ID>
<xsl:value-of select="ID"/>
</ID>
<BRAND>
<xsl:value-of select="BRAND"/>
<BRAND>
<ACCOUNTS>
<xsl:for-each select="current-group()">
<ACCOUNT>
<AccountNumber>
<xsl:value-of select="ACC_NO"/>
<AccountNumber>
<AccountDescription>
<xsl:value-of select="ACC_DESCR"/>
<AccountDescription>
</ACCOUNT>
</xsl:for-each>
</ACCOUNTS>
</ITEM>
</xsl:for-each-group>
</xsl:template>
In 1.0 the most efficient approach is a technique known as Muenchian grouping - define a key giving the grouping criteria and then use a trick with generate-id
to pull out just the first instance of a given ID as a proxy for the group as a whole.
<xsl:key name="itemById" match="ITEM" use="ID" />
<xsl:template match="/">
<xsl:for-each select="/DATA/ITEM[
generate-id() = generate-id(key('itemById', ID)[1])]">
<ITEM>
<ID>
<xsl:value-of select="ID"/>
</ID>
<BRAND>
<xsl:value-of select="BRAND"/>
<BRAND>
<ACCOUNTS>
<xsl:for-each select="key('itemById', ID)">
<ACCOUNT>
<AccountNumber>
<xsl:value-of select="ACC_NO"/>
<AccountNumber>
<AccountDescription>
<xsl:value-of select="ACC_DESCR"/>
<AccountDescription>
</ACCOUNT>
</xsl:for-each>
</ACCOUNTS>
</ITEM>
</xsl:for-each>
</xsl:template>
key('itemById', ID)[1]
gives you the first ITEM
element which has the same ID
value as the current one we're testing, and then comparing the generate-id
values of the two allows you to determine whether or not they are the same node.
(Personally I would approach the problem using templates rather than for-each
but the principle remains the same)
Upvotes: 1