Search code examples
xpathxslt-2.0

XSLT 2.0 group-by issue to include parent node


I am trying to group the input XML as per country code. The grouping function is working but I'm not able get all the nodes. Issue: I'm missing other two nodes of a specific country. Please review my code. Using current-group() only shows child nodes and misses wd:ID node.

Thanks! HB

Input XML

<wd:Report_Data xmlns:wd="urn:com.xyz">
<wd:Report_Entry>
    <wd:Worker>
        <wd:Worker wd:Descriptor="Mark">
        </wd:Worker>
        <wd:CF_Country_code_2>DE</wd:CF_Country_code_2>
    </wd:Worker>
    <wd:ID wd:type="Employee_ID">12</wd:ID>
</wd:Report_Entry>
<wd:Report_Entry>
    <wd:Worker>
        <wd:Worker wd:Descriptor="Jack">
        </wd:Worker>
        <wd:CF_Country_code_2>DE</wd:CF_Country_code_2>
    </wd:Worker>
    <wd:ID wd:type="Employee_ID">34</wd:ID>
</wd:Report_Entry>
<wd:Report_Entry>
    <wd:Worker>
        <wd:Worker wd:Descriptor="Miguel">
        </wd:Worker>
        <wd:CF_Country_code_2>ES</wd:CF_Country_code_2>
    </wd:Worker>
    <wd:ID wd:type="Employee_ID">67</wd:ID>
</wd:Report_Entry>
<wd:Report_Entry>
    <wd:Worker>
        <wd:Worker wd:Descriptor="Chris">
        </wd:Worker>
        <wd:CF_Country_code_2>ES</wd:CF_Country_code_2>
    </wd:Worker>
    <wd:ID wd:type="Employee_ID">89</wd:ID>
</wd:Report_Entry>

My XSLT code

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:wd="urn:com.xyz">
<xsl:template match="wd:Report_Data">
    <ROOT>
        <xsl:for-each-group select="wd:Report_Entry/wd:Worker"
            group-by="wd:CF_Country_code_2">
            <wd:Report_Data>
                <xsl:copy-of select=".." />
            </wd:Report_Data>
        </xsl:for-each-group>
    </ROOT>
</xsl:template>

Expected output:

<ROOT>
<wd:Report_Data xmlns:wd="urn:com.xyz">
    <wd:Report_Entry>
        <wd:Worker>
            <wd:Worker wd:Descriptor="Mark">
            </wd:Worker>
            <wd:CF_Country_code_2>DE</wd:CF_Country_code_2>
        </wd:Worker>
        <wd:ID wd:type="Employee_ID">12</wd:ID>
    </wd:Report_Entry>
    <wd:Report_Entry>
        <wd:Worker>
            <wd:Worker wd:Descriptor="Jack">
            </wd:Worker>
            <wd:CF_Country_code_2>DE</wd:CF_Country_code_2>
        </wd:Worker>
        <wd:ID wd:type="Employee_ID">34</wd:ID>
    </wd:Report_Entry>
</wd:Report_Data>


<wd:Report_Data xmlns:wd="urn:com.xyz">
    <wd:Report_Entry>
        <wd:Worker>
            <wd:Worker wd:Descriptor="Miguel">
            </wd:Worker>
            <wd:CF_Country_code_2>ES</wd:CF_Country_code_2>
        </wd:Worker>
        <wd:ID wd:type="Employee_ID">67</wd:ID>
    </wd:Report_Entry>
    <wd:Report_Entry>
        <wd:Worker>
            <wd:Worker wd:Descriptor="Chris">
            </wd:Worker>
            <wd:CF_Country_code_2>ES</wd:CF_Country_code_2>
        </wd:Worker>
        <wd:ID wd:type="Employee_ID">89</wd:ID>
    </wd:Report_Entry>
</wd:Report_Data>


Solution

  • You could change the grouping to

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xpath-default-namespace="urn:com.xyz"
        exclude-result-prefixes="#all"
        version="3.0">
    
      <xsl:mode on-no-match="shallow-copy"/>
    
      <xsl:output indent="yes"/>
    
      <xsl:template match="/">
          <Root>
              <xsl:for-each-group select="Report_Data/Report_Entry" group-by="Worker/CF_Country_code_2">
                  <xsl:copy select="..">
                      <xsl:apply-templates select="current-group()"/>
                  </xsl:copy>
              </xsl:for-each-group>          
          </Root>
      </xsl:template>
    
    </xsl:stylesheet>
    

    https://xsltfiddle.liberty-development.net/93dFeq1

    That is XSLT 3 as supported since Saxon 9.8 all editions or Altova XML 2017 R3.

    In XSLT 2 you can use <xsl:copy select=".."> but you can use <xsl:element name="{name(..)}" namespace="{namespace-uri(..)}"> instead.

    And of course instead of declaring the identity transformation as the base processing with xsl:mode you can spell it out as a template

      <xsl:template match="@* | node()">
        <xsl:copy>
          <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
      </xsl:template>