Search code examples
xsltxslt-2.0xslt-grouping

group-starting-with not working as I expect it to


I am trying to convert a cals table to a series of lists and I am struggling with grouping.

The table consists of a couple of heading rows (thead/row), and a bunch of body rows (tbody/row). There are 2 types of body row: a) a standard data row, and b) a straddle heading row which 'break up' the table visually delineating each section.

I want to create a new list for each section and so am using

<xsl:for-each-group group-starting-with="row/entry/hd4" select="table/tgroup/tbody/row">

I have tried various options in the group-starting-with attribute including

entry/@nameend
entry[@nameend]
entry/hd4
entry[hd4]

From my background reading on this, I note that the value should be a pattern, and not a condition, so I'm guessing the predicates are wrong.

<anch fac="yes">
   <p/>
   <calstable>
      <table frame="none">
         <tgroup cols="4" colsep="0" rowsep="0">
            <colspec colname="1" colnum="1" colwidth="39pt" align="center"/>
            <colspec colname="2" colnum="2" colwidth="39pt" align="center"/>
            <colspec colname="3" colnum="3" colwidth="45pt" align="center"/>
            <colspec colname="4" colnum="4" colwidth="33pt" align="center"/>
            <thead>
               <row valign="bottom">
                  <entry align="center">Anchorage</entry>
                  <entry align="center">Lat.</entry>
                  <entry align="center">Long.</entry>
                  <entry align="center">Draft</entry>
               </row>
               <row valign="bottom">
                  <entry align="center">No.</entry>
                  <entry align="center">(S)</entry>
                  <entry align="center">(E)</entry>
                  <entry align="center">(m.)</entry>
                  <entry> </entry>
               </row>
            </thead>
            <tbody>
               <row>
                  <entry align="left" namest="1" nameend="4"> 
                     <hd4>Outer</hd4> 
                  </entry>
               </row>
               <row>
                  <entry>O1</entry>
                  <entry>17° 57.0&#8242;</entry>
                  <entry>122° 04.0&#8242;</entry>
                  <entry>9.5</entry>
               </row>
               <row>
                  <entry>O2</entry>
                  <entry>17° 56.0&#8242;</entry>
                  <entry>122° 04.0&#8242;</entry>
                  <entry>9.5</entry>
               </row>
               <row>
                  <entry>O3</entry>
                  <entry>17° 55.0&#8242;</entry>
                  <entry>122° 04.0&#8242;</entry>
                  <entry>7.0</entry>
               </row>
               <row>
                  <entry align="left" namest="1" nameend="4"> 
                     <hd4>Gantheaume Bay</hd4> 
                  </entry>
               </row>
               <row>
                  <entry>G1</entry>
                  <entry>17° 56.5&#8242;</entry>
                  <entry>122° 10.0&#8242;</entry>
                  <entry>8.0</entry>
               </row>
               <row>
                  <entry>G2</entry>
                  <entry>17° 55.0&#8242;</entry>
                  <entry>122° 10.0&#8242;</entry>
                  <entry>8.0</entry>
               </row>
               <row>
                  <entry>G2</entry>
                  <entry>17° 56.5&#8242;</entry>
                  <entry>122° 09.5&#8242;</entry>
                  <entry>8.0</entry>
               </row>
            </tbody>
         </tgroup>
      </table>
   </calstable>
</anch>

and the xslt as follows

<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    version="2.0"
>    
    <xsl:output indent="yes" method="xml" standalone="yes"/>

    <xsl:template match="/">
        <anch>
            <xsl:apply-templates select="anch/calstable" mode="cals-to-list"/>
        </anch>
    </xsl:template>

    <xsl:template match="calstable[//thead[count(row) = 2]]" mode="cals-to-list">
        <xsl:for-each-group group-starting-with="row/entry/hd4" select="table/tgroup/tbody/row">
            <list>
                <xsl:apply-templates select="current-group()" mode="cals-to-list"/>
            </list>
        </xsl:for-each-group>
     </xsl:template>

    <xsl:template match="tbody/row[entry[@nameend]]" mode="cals-to-list">
        <xsl:variable name="lhtype">
            <xsl:choose>
                <xsl:when test="entry/hd2">2</xsl:when>
                <xsl:when test="entry/hd3">3</xsl:when>
                <xsl:when test="entry/hd4">4</xsl:when>
                <xsl:otherwise>2</xsl:otherwise>
            </xsl:choose>
        </xsl:variable>
        <lh lhtype="{$lhtype}">
           <xsl:apply-templates select="entry[@nameend]" mode="cals-to-list"/>
        </lh>
    </xsl:template>


    <xsl:template match="tbody/row[not(entry[@nameend])]" mode="cals-to-list">
        <li>
            <xsl:apply-templates select="entry" mode="cals-to-list"/>
        </li>
    </xsl:template>

    <xsl:template match="row/entry[@nameend]" mode="cals-to-list">
        <xsl:value-of select="*/text()"/>
    </xsl:template>

    <xsl:template match="row/entry[not(@nameend)]" mode="cals-to-list">
        <xsl:variable name="pos" select="position()"/>
        <xsl:variable name="thead-rows" select="count(ancestor::*[local-name()='tgroup']/thead/row)"/>
        <xsl:variable name="top-line" select="normalize-space(ancestor::*[local-name()='tgroup']/thead/row[position() != $thead-rows]/entry[$pos]/text())"/>
        <xsl:variable name="bot-line" select="normalize-space(ancestor::*[local-name()='tgroup']/thead/row[$thead-rows]/entry[$pos]/text())"/>
        <xsl:variable name="unit" select="normalize-space(substring-before(substring-after($bot-line,'('),')'))"/>
        <xsl:value-of select="$top-line"/><xsl:text> </xsl:text><xsl:value-of select="."/><xsl:text> </xsl:text><xsl:value-of select="$unit"/><xsl:text> </xsl:text>
    </xsl:template>



 </xsl:stylesheet>

This is what I get

<anch xmlns:xs="http://www.w3.org/2001/XMLSchema">
   <list>
      <lh lhtype="4">Outer</lh>
      <li>Anchorage O1  Lat. 17° 57.0′ S Long. 122° 04.0′ E Draft 9.5 m. </li>
      <li>Anchorage O2  Lat. 17° 56.0′ S Long. 122° 04.0′ E Draft 9.5 m. </li>
      <li>Anchorage O3  Lat. 17° 55.0′ S Long. 122° 04.0′ E Draft 7.0 m. </li>
      <lh lhtype="4">Gantheaume Bay</lh>
      <li>Anchorage G1  Lat. 17° 56.5′ S Long. 122° 10.0′ E Draft 8.0 m. </li>
      <li>Anchorage G2  Lat. 17° 55.0′ S Long. 122° 10.0′ E Draft 8.0 m. </li>
      <li>Anchorage G2  Lat. 17° 56.5′ S Long. 122° 09.5′ E Draft 8.0 m. </li>
   </list>
</anch>

But I would expect the list to break and restart a new list just before the Gantheaume Bay entry.

Can someone explain where I've gone wrong and what the correct pattern should be?

TIA


Solution

  • You are selecting row elements with the xsl:for-each-group and so the group-starting-with must also be a row element (representing the first one each in group).

    Change your xsl:for-each-group to this...

    <xsl:for-each-group group-starting-with="row[entry/hd4]" select="table/tgroup/tbody/row">