Search code examples
xsltsaxonxslt-3.0

XSLT for-each-group group-starting-with tail(current-group())


This is a follow up to Martin Honnen's answer on here.

Here was my attempt at his suggestion: XSLT Fiddle link

In spreadsheet form, the data looks like this:

enter image description here

In XML this is the data input:

<VendorFile xmlns="http://etc/VendorFile">
    <Row xmlns="">
        <Meter>123</Meter>
        <State>TX</State>
        <StartDate/>
        <Volume/>
    </Row>
    <Row xmlns="">
        <Meter></Meter>
        <State></State>
        <StartDate>1/1/2024</StartDate>
        <Volume>100</Volume>
    </Row>
    <Row xmlns="">
        <Meter></Meter>
        <State></State>
        <StartDate>1/2/2024</StartDate>
        <Volume>110</Volume>
    </Row>
    <Row xmlns="">
        <Meter></Meter>
        <State></State>
        <StartDate>1/3/2024</StartDate>
        <Volume>107</Volume>
    </Row>
    <Row xmlns="">
        <Meter>456</Meter>
        <State>OK</State>
        <StartDate/>
        <Volume/>
    </Row>
    <Row xmlns="">
        <Meter></Meter>
        <State></State>
        <StartDate>1/1/2024</StartDate>
        <Volume>200</Volume>
    </Row>
    <Row xmlns="">
        <Meter></Meter>
        <State></State>
        <StartDate>1/2/2024</StartDate>
        <Volume>205</Volume>
    </Row>
    <Row xmlns="">
        <Meter></Meter>
        <State></State>
        <StartDate>1/3/2024</StartDate>
        <Volume>210</Volume>
    </Row>
</VendorFile> 

My attempted XSLT is this (I have included the group tags to help understand):

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="#all"
  expand-text="yes">

  <xsl:output method="html" indent="yes" html-version="5"/>

    <xsl:template match="/">
     <output>
        <xsl:for-each-group select="//Row" group-starting-with="Row[normalize-space(Meter) and normalize-space(State)]">
          <group>
              <xsl:for-each select="current-group()">
                  <!-- tail = Returns all but the first item in a sequence. --> 
                   <xsl:apply-templates select="tail(current-group())"/>
                  </xsl:for-each>
             </group>       
        </xsl:for-each-group>
     <output>    
    </xsl:template>
    
<xsl:template match="Row">
    <Row>
    <!--<xsl:apply-templates select="current-group()[1]!(Meter, State), *"/>-->
      <Meter><xsl:value-of select="current-group()[1]!(Meter)"/></Meter>
      <State><xsl:value-of select="current-group()[1]!(State)"/></State>
      <StartDate><xsl:value-of select="StartDate"/></StartDate>
      <Volume><xsl:value-of select="Volume"/></Volume>
    </Row>
</xsl:template>

    
    <!--
    <xsl:template match="Row">
        <xsl:copy>
            <xsl:apply-templates select="current-group()[1]!(Meter, State), *"/>
        </xsl:copy>
    </xsl:template>
  -->

</xsl:stylesheet>

My current output is not what I want, but it's rather close. It seems that the startdate and volumes are occurring 3 times for each group.

<output>
   <group>
      <Row><Meter>123</Meter><State>TX</State>
         <StartDate>1/1/2024</StartDate>
         <Volume>100</Volume>
      </Row>
      <Row><Meter>123</Meter><State>TX</State>
         <StartDate>1/2/2024</StartDate>
         <Volume>110</Volume>
      </Row>
      <Row><Meter>123</Meter><State>TX</State>
         <StartDate>1/3/2024</StartDate>
         <Volume>107</Volume>
      </Row>
      <Row><Meter>123</Meter><State>TX</State>
         <StartDate>1/1/2024</StartDate>
         <Volume>100</Volume>
      </Row>
      <Row><Meter>123</Meter><State>TX</State>
         <StartDate>1/2/2024</StartDate>
         <Volume>110</Volume>
      </Row>
      <Row><Meter>123</Meter><State>TX</State>
         <StartDate>1/3/2024</StartDate>
         <Volume>107</Volume>
      </Row>
      <Row><Meter>123</Meter><State>TX</State>
         <StartDate>1/1/2024</StartDate>
         <Volume>100</Volume>
      </Row>
      <Row><Meter>123</Meter><State>TX</State>
         <StartDate>1/2/2024</StartDate>
         <Volume>110</Volume>
      </Row>
      <Row><Meter>123</Meter><State>TX</State>
         <StartDate>1/3/2024</StartDate>
         <Volume>107</Volume>
      </Row>
      <Row><Meter>123</Meter><State>TX</State>
         <StartDate>1/1/2024</StartDate>
         <Volume>100</Volume>
      </Row>
      <Row><Meter>123</Meter><State>TX</State>
         <StartDate>1/2/2024</StartDate>
         <Volume>110</Volume>
      </Row>
      <Row><Meter>123</Meter><State>TX</State>
         <StartDate>1/3/2024</StartDate>
         <Volume>107</Volume>
      </Row>
   </group>
   <group>
      <Row><Meter>456</Meter><State>OK</State>
         <StartDate>1/1/2024</StartDate>
         <Volume>200</Volume>
      </Row>
      <Row><Meter>456</Meter><State>OK</State>
         <StartDate>1/2/2024</StartDate>
         <Volume>205</Volume>
      </Row>
      <Row><Meter>456</Meter><State>OK</State>
         <StartDate>1/3/2024</StartDate>
         <Volume>210</Volume>
      </Row>
      <Row><Meter>456</Meter><State>OK</State>
         <StartDate>1/1/2024</StartDate>
         <Volume>200</Volume>
      </Row>
      <Row><Meter>456</Meter><State>OK</State>
         <StartDate>1/2/2024</StartDate>
         <Volume>205</Volume>
      </Row>
      <Row><Meter>456</Meter><State>OK</State>
         <StartDate>1/3/2024</StartDate>
         <Volume>210</Volume>
      </Row>
      <Row><Meter>456</Meter><State>OK</State>
         <StartDate>1/1/2024</StartDate>
         <Volume>200</Volume>
      </Row>
      <Row><Meter>456</Meter><State>OK</State>
         <StartDate>1/2/2024</StartDate>
         <Volume>205</Volume>
      </Row>
      <Row><Meter>456</Meter><State>OK</State>
         <StartDate>1/3/2024</StartDate>
         <Volume>210</Volume>
      </Row>
      <Row><Meter>456</Meter><State>OK</State>
         <StartDate>1/1/2024</StartDate>
         <Volume>200</Volume>
      </Row>
      <Row><Meter>456</Meter><State>OK</State>
         <StartDate>1/2/2024</StartDate>
         <Volume>205</Volume>
      </Row>
      <Row><Meter>456</Meter><State>OK</State>
         <StartDate>1/3/2024</StartDate>
         <Volume>210</Volume>
      </Row>
   </group>
</output>

Solution

  • Your main error is changing

    <xsl:for-each-group select="//Row" group-starting-with="Row[normalize-space(Meter) and normalize-space(State)]">
      <xsl:apply-templates select="tail(current-group())"/>
    </xsl:for-each-group>
    

    to

        <xsl:for-each-group select="//Row" group-starting-with="Row[normalize-space(Meter) and normalize-space(State)]">
          <group>
              <xsl:for-each select="current-group()">
                  <!-- tail = Returns all but the first item in a sequence. --> 
                   <xsl:apply-templates select="tail(current-group())"/>
                  </xsl:for-each>
             </group>       
        </xsl:for-each-group>
    

    that way you have inserted a for-each which was not suggested but which for each item in the current group processes the tail of the current group.

    Note also that I have reviewed the original answer and have improved the templates for Rows to

    <xsl:template match="Row">
        <xsl:copy>
            <xsl:apply-templates select="current-group()[1]!(Meter, State), * except (Meter, State)"/>
        </xsl:copy>
    </xsl:template>
    

    I would then suggest to add <xsl:mode on-no-match="shallow-copy"/>.