user3796454
user3796454

Reputation: 69

How to loop through common id values in XSLT

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

Answers (1)

matthias_h
matthias_h

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() &lt; $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

Related Questions