Search code examples
xslt-2.0xslt-grouping

Using for-each-group with having a condition in group-by


I want to group the items by id using for-each-group and only group if they are in different source, if source is same dont group even if it has same id

<items>
    <item id="123" source="loc1">
        <price>12</price>
        <description>test</description> 
    </item>

    <item id="123" source="loc2">
        <price>122</price>
        <description>test</description> 
    </item>

    <item id="234" source="loc1">
        <price>566</price>
        <description>test</description> 
    </item>

    <item id="456" source="loc2">
        <price>222</price>
        <description>desc</description> 
    </item>

    <item id="456" source="loc2">
        <price>312</price>
        <description>desc</description> 
    </item>

    <item id="768" source="loc1">
        <price>212</price>
        <description>desc</description> 
    </item>

    <item id="768" source="loc2">
        <price>934</price>
        <description>desc</description> 
    </item>
  </items>

And out put be some thing like this

<items>
    <group>
         <item id="123" source="loc1">
            <price>12</price>
            <description>test</description> 
         </item>

         <item id="123" source="loc2">
            <price>122</price>
            <description>test</description> 
         </item>
    </group>

    <group>
        <item id="234" source="loc1">
           <price>566</price>
           <description>test</description> 
        </item>
    </group>

    <group>
        <item id="456" source="loc2">
           <price>222</price>
           <description>desc</description> 
        </item>
    </group>

     <group>
        <item id="456" source="loc2">
           <price>222</price>
           <description>desc</description> 
        </item>
    </group>

    <group>
       <item id="768" source="loc1">
          <price>212</price>
          <description>desc</description> 
       </item>

       <item id="768" source="loc2">
           <price>934</price>
           <description>desc</description> 
       </item>
    </group>
</items>

Update

To group by id

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="#all"
    version="3.0">

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:output method="xml" indent="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:template match="items">
    <xsl:copy>
        <xsl:for-each-group select="item" group-by="@id">
            <group>
                <xsl:apply-templates select="current-group()"/>
            </group>
        </xsl:for-each-group>

    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

Solution

  • I think your requirement can be implemented by using group-by="@id" and then inside using a check count(distinct-values(current-group()/@source)) = count(current-group()) to decide whether to wrap all items in the current-group() into a single group element wrapper or whether to wrap each item of its own:

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        exclude-result-prefixes="#all"
        version="3.0">
    
      <xsl:mode on-no-match="shallow-copy"/>
    
      <xsl:output method="xml" indent="yes"/>
    
    
      <xsl:template match="items">
          <xsl:copy>
              <xsl:for-each-group select="item" group-by="@id">
                  <xsl:choose>
                      <xsl:when test="count(distinct-values(current-group()/@source)) = count(current-group())">
                          <group>
                              <xsl:apply-templates select="current-group()"/>
                          </group>
                      </xsl:when>
                      <xsl:otherwise>
                          <xsl:for-each select="current-group()">
                              <group>
                                  <xsl:apply-templates select="."/>
                              </group>
                          </xsl:for-each>
                      </xsl:otherwise>
                  </xsl:choose>
              </xsl:for-each-group>
          </xsl:copy>
      </xsl:template>
    
    </xsl:stylesheet>
    

    Online example at https://xsltfiddle.liberty-development.net/pPqsHTR, not that I corrected the last two item elements in your input sample to have a source attribute instead of the name they have in your post.