Reputation: 69
I recently started working on XSLT. I have a requirement of grouping node values based on id field. However id field could be a duplicate value. For example, lets say below is the XML structure.
<ProductResults>
<ProductResult>
<ProductId>1000</ProductId>
<Location>Bangalore</Location>
<ModuleNumber>02</ModuleNumber>
<StoreId>1234<StoreId>
</ProductResult>
<ProductResult>
<ProductId>2000</ProductId>
<ModuleNumber>03</ModuleNumber>
<Location>Bangalore</Location>
<StoreId>1234<StoreId>
</ProductResult>
<ProductResult>
<ProductId>1000</ProductId>
<ModuleNumber>01</ModuleNumber>
<Location>Mumbai</Location>
<StoreId>1234<StoreId>
</ProductResult>
<ProductResult>
<ProductId>4000</ProductId>
<ModuleNumber>02</ModuleNumber>
<Location>Kolkata</Location>
<StoreId>1234<StoreId>
</ProductResult>
<ProductResult>
<ProductId>1000</ProductId>
<ModuleNumber>03</ModuleNumber>
<Location>Chennai</Location>
<StoreId>1234<StoreId>
</ProductResult>
</ProductResults>
If you notice, the ProductId 1000 is repeating thrice. I am supposed to write XSLT to produce below output.
{
"StoreId": "1234",
"Locations": [
{
"ProductId": "1000",
"Locations": [
{
"Location": "Bangalore",
"ModuleNumber": "02"
},
{
"Location": "Mumbai",
"ModuleNumber": "01"
},
{
"Location": "Chennai",
"ModuleNumber": "03"
}
]
},
{
"ProductId": "2000",
"Locations": [
{
"Location": "Bangalore",
"ModuleNumber": "03"
}
]
},
{
"ProductId": "4000",
"Locations": [
{
"Location": "Kolkata",
"ModuleNumber": "02"
}
]
}
]
}
Since the ProductId field is repeating, I cannot use foreach loop directly which creates extra code block for same ProductId . Any suggestions on this?.
Thanks in advance.
Upvotes: 0
Views: 413
Reputation: 11416
The following XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes"
version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="@*|node()">
<xsl:apply-templates select="@*|node()" />
</xsl:template>
<xsl:template match="@*|node()">
<xsl:variable name="uniqueProducts"
select="count(//ProductId[not(. = following::ProductId)])"/>
{
"StoreId": <xsl:value-of select="ProductResult/StoreId"/>,
"Locations": [
<xsl:for-each select="//ProductId[not(. = following::ProductId)]">
<xsl:sort select="."/>
{
"ProductId": "<xsl:value-of select="."/>,
"Locations": [
<xsl:call-template name="location">
<xsl:with-param name="productId" select="."/>
</xsl:call-template>
]
<xsl:if test="position() < $uniqueProducts">,</xsl:if>
</xsl:for-each>
]}
</xsl:template>
<xsl:template name="location">
<xsl:param name="productId"/>
<xsl:for-each select="//ProductResult[ProductId=$productId]">
{
"Location": "<xsl:value-of select="Location"/>",
"ModuleNumber": "<xsl:value-of select="ModuleNumber"/>"
}
<xsl:if test="position() != last()">,</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
when applied to the following (corrected, as you didn't close the StoreId
in your example)
Input XML:
<ProductResults>
<ProductResult>
<ProductId>1000</ProductId>
<Location>Bangalore</Location>
<ModuleNumber>02</ModuleNumber>
<StoreId>1234</StoreId>
</ProductResult>
<ProductResult>
<ProductId>2000</ProductId>
<ModuleNumber>03</ModuleNumber>
<Location>Bangalore</Location>
<StoreId>1234</StoreId>
</ProductResult>
<ProductResult>
<ProductId>1000</ProductId>
<ModuleNumber>01</ModuleNumber>
<Location>Mumbai</Location>
<StoreId>1234</StoreId>
</ProductResult>
<ProductResult>
<ProductId>4000</ProductId>
<ModuleNumber>02</ModuleNumber>
<Location>Kolkata</Location>
<StoreId>1234</StoreId>
</ProductResult>
<ProductResult>
<ProductId>1000</ProductId>
<ModuleNumber>03</ModuleNumber>
<Location>Chennai</Location>
<StoreId>1234</StoreId>
</ProductResult>
</ProductResults>
produces the following
Output XML:
{
"StoreId": 1234,
"Locations": [
{
"ProductId": "1000,
"Locations": [
{
"Location": "Bangalore",
"ModuleNumber": "02"
}
,
{
"Location": "Mumbai",
"ModuleNumber": "01"
}
,
{
"Location": "Chennai",
"ModuleNumber": "03"
}
]
,
{
"ProductId": "2000,
"Locations": [
{
"Location": "Bangalore",
"ModuleNumber": "03"
}
]
,
{
"ProductId": "4000,
"Locations": [
{
"Location": "Kolkata",
"ModuleNumber": "02"
}
]
]}
Note that this is only an example for the grouping, with e.g. an additional for-each loop it would be possible to adjust this to take care for more than a single StoreId.
For simplification I've just wrote this for the one StoreId
provided using <xsl:value-of select="ProductResult/StoreId"/>
instead of checking for different StoreId
values and processing all of them.
For detailed references you can check this article by Jeni Tennison http://www.jenitennison.com/xslt/grouping/muenchian.xml for Muenchian Grouping, and you can e.g. have a look at http://www.dpawson.co.uk/xsl/sect2/N4486.html for XSLT Grouping.
Update: Just adjusted first posted XSLT, and for explanation how this works - the <xsl:for-each select="//ProductId[not(. = following::ProductId)]">
loop processes all unique ProductId
values, calling the <xsl:template name="location">
with the current ProductId
as parameter and generating the output for every location that matches the ProductId
. Though the updated approach doesn't use Muenchian Grouping, it may still be of value for you so I'll keep the reference.
Upvotes: 0