0

See the XML code of a table below. My task is to add an attribute (MOREROWS) to every ENTRY element which has one or more consecutive following rows with the text "##rowspan##" in the ENTRY element with the same COLNAME value.

The MOREROWS attribute should contain the number of following consecutive rows satisfying the desired condition.

I've been experimenting with for-each-group, for-each and count, but haven't managed to find a solution yet. My main problem is to ignore the gaps. After all I want to count only the consecutive following rows satisfying the condition, and not all the following rows satisfying the condition.

Ideally I would want to use count as that seems like the logical choice here since I don't need to apply templates but rather just want a number.

Any help will be appreciated.

Current table:

<TABLE>
  <ROW>
    <ENTRY COLNUM="1" COLNAME="col1">
      <CONTENT>42</CONTENT>
    </ENTRY>
    <ENTRY COLNUM="2" COLNAME="col2">
      <CONTENT>155</CONTENT>
    </ENTRY>
    <ENTRY COLNUM="3" COLNAME="col3">
      <CONTENT/>
    </ENTRY>
    <ENTRY COLNUM="4" COLNAME="col4">
      <CONTENT>148</CONTENT>
    </ENTRY>
  </ROW>
  <ROW>
    <ENTRY COLNUM="1" COLNAME="col1">
      <CONTENT>147</CONTENT>
    </ENTRY>
    <ENTRY COLNUM="2" COLNAME="col2">
      <CONTENT>##rowspan##</CONTENT>
    </ENTRY>
    <ENTRY COLNUM="3" COLNAME="col3">
      <CONTENT/>
    </ENTRY>
    <ENTRY COLNUM="4" COLNAME="col4">
      <CONTENT>##rowspan##</CONTENT>
    </ENTRY>
  </ROW>
  <ROW>
    <ENTRY COLNUM="1" COLNAME="col1">
      <CONTENT>147</CONTENT>
    </ENTRY>
    <ENTRY COLNUM="2" COLNAME="col2">
      <CONTENT>##rowspan##</CONTENT>
    </ENTRY>
    <ENTRY COLNUM="3" COLNAME="col3">
      <CONTENT/>
    </ENTRY>
    <ENTRY COLNUM="4" COLNAME="col4">
      <CONTENT>300</CONTENT>
    </ENTRY>
  </ROW>
  <ROW>
    <ENTRY COLNUM="1" COLNAME="col1">
      <CONTENT>1814</CONTENT>
    </ENTRY>
    <ENTRY COLNUM="2" COLNAME="col2">
      <CONTENT/>
    </ENTRY>
    <ENTRY COLNUM="3" COLNAME="col3">
      <CONTENT/>
    </ENTRY>
    <ENTRY COLNUM="4" COLNAME="col4">
      <CONTENT>1905</CONTENT>
    </ENTRY>
  </ROW>
  <ROW>
    <ENTRY COLNUM="1" COLNAME="col1">
      <CONTENT>147</CONTENT>
    </ENTRY>
    <ENTRY COLNUM="2" COLNAME="col2">
      <CONTENT>##rowspan##</CONTENT>
    </ENTRY>
    <ENTRY COLNUM="3" COLNAME="col3">
      <CONTENT/>
    </ENTRY>
    <ENTRY COLNUM="4" COLNAME="col4">
      <CONTENT>##rowspan##</CONTENT>
    </ENTRY>
  </ROW>
</TABLE>

Desired result:

<TABLE>
  <ROW>
    <ENTRY COLNUM="1" COLNAME="col1">
      <CONTENT>42</CONTENT>
    </ENTRY>
    <ENTRY COLNUM="2" COLNAME="col2"** MOREROWS="2"**>
      <CONTENT>155</CONTENT>
    </ENTRY>
    <ENTRY COLNUM="3" COLNAME="col3">
      <CONTENT/>
    </ENTRY>
    <ENTRY COLNUM="4" COLNAME="col4"** MOREROWS="1"**>
      <CONTENT>148</CONTENT>
    </ENTRY>
  </ROW>
  <ROW>
    <ENTRY COLNUM="1" COLNAME="col1">
      <CONTENT>147</CONTENT>
    </ENTRY>
    <ENTRY COLNUM="2" COLNAME="col2">
      <CONTENT>##rowspan##</CONTENT>
    </ENTRY>
    <ENTRY COLNUM="3" COLNAME="col3">
      <CONTENT/>
    </ENTRY>
    <ENTRY COLNUM="4" COLNAME="col4">
      <CONTENT>##rowspan##</CONTENT>
    </ENTRY>
  </ROW>
  <ROW>
    <ENTRY COLNUM="1" COLNAME="col1">
      <CONTENT>147</CONTENT>
    </ENTRY>
    <ENTRY COLNUM="2" COLNAME="col2">
      <CONTENT>##rowspan##</CONTENT>
    </ENTRY>
    <ENTRY COLNUM="3" COLNAME="col3">
      <CONTENT/>
    </ENTRY>
    <ENTRY COLNUM="4" COLNAME="col4">
      <CONTENT>300</CONTENT>
    </ENTRY>
  </ROW>
  <ROW>
    <ENTRY COLNUM="1" COLNAME="col1">
      <CONTENT>1814</CONTENT>
    </ENTRY>
    <ENTRY COLNUM="2" COLNAME="col2"** MOREROWS="1"**>
      <CONTENT/>
    </ENTRY>
    <ENTRY COLNUM="3" COLNAME="col3">
      <CONTENT/>
    </ENTRY>
    <ENTRY COLNUM="4" COLNAME="col4"** MOREROWS="1"**>
      <CONTENT>1905</CONTENT>
    </ENTRY>
  </ROW>
  <ROW>
    <ENTRY COLNUM="1" COLNAME="col1">
      <CONTENT>147</CONTENT>
    </ENTRY>
    <ENTRY COLNUM="2" COLNAME="col2">
      <CONTENT>##rowspan##</CONTENT>
    </ENTRY>
    <ENTRY COLNUM="3" COLNAME="col3">
      <CONTENT/>
    </ENTRY>
    <ENTRY COLNUM="4" COLNAME="col4">
      <CONTENT>##rowspan##</CONTENT>
    </ENTRY>
  </ROW>
</TABLE>

2 Answers 2

1

I think this can be solved with xsl:for-each-group group-adjacent e.g. (in XSLT 3)

  <xsl:template match="ROW/ENTRY[not(CONTENT = '##rowspan##')]">
    <xsl:copy>
      <xsl:apply-templates select="@*"/>
      <xsl:for-each-group select="../following-sibling::ROW/ENTRY[@COLNAME = current()/@COLNAME]" group-adjacent="CONTENT = '##rowspan##'">
        <xsl:if test="position() = 1 and current-grouping-key()">
          <xsl:attribute name="MOREROWS" select="count(current-group())"/>
        </xsl:if>
      </xsl:for-each-group>
    </xsl:copy>
  </xsl:template>
  
  <xsl:mode on-no-match="shallow-copy"/>

Example online fiddle.

It might be a bit more efficient to group once to store the adjacent ENTRYs in a map:

  <xsl:variable name="groups" as="map(xs:string, xs:integer)*">
    <xsl:map>
      <xsl:for-each select="TABLE/ROW[1]/ENTRY">
        <xsl:variable name="pos" select="position()"/>
        <xsl:for-each-group select="../following-sibling::ROW/ENTRY[$pos]" group-adjacent="CONTENT = '##rowspan##'">
          <xsl:if test="current-grouping-key()">
            <xsl:map-entry key="current-group()[1]/../preceding-sibling::ROW[1]//ENTRY[$pos]/generate-id()" select="count(current-group())"/>
          </xsl:if>
        </xsl:for-each-group>
      </xsl:for-each>      
    </xsl:map>
  </xsl:variable>
  
  <xsl:template match="ROW/ENTRY[not(CONTENT = '##rowspan##') and map:contains($groups, generate-id(current()))]">
    <xsl:copy>
      <xsl:apply-templates select="@*"/>
      <xsl:attribute name="MOREROWS" select="$groups(generate-id(current()))"/>
    </xsl:copy>
  </xsl:template>
  
  <xsl:mode on-no-match="shallow-copy"/>

To use the map functions you need the stylesheet to declare xmlns:map="http://www.w3.org/2005/xpath-functions/map".

Sign up to request clarification or add additional context in comments.

Comments

1

Another way you could look at it:

XSLT 3.0

<xsl:stylesheet version="3.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:key name="ctd" match="tail" use="@head-id" />

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

<xsl:variable name="all-tails">
    <xsl:for-each select="/TABLE/ROW/ENTRY[CONTENT='##rowspan##']">
        <tail head-id="{generate-id((../preceding-sibling::ROW/ENTRY[@COLNAME=current()/@COLNAME][not(CONTENT='##rowspan##')])[last()])}"/>
    </xsl:for-each>
</xsl:variable>

<xsl:template match="ENTRY">
    <xsl:variable name="my-tails" select="key('ctd', generate-id(), $all-tails)" />
    <xsl:copy>
        <xsl:apply-templates select="@*"/>
        <xsl:if test="$my-tails">
            <xsl:attribute name="MOREROWS" select="count($my-tails)"/>
        </xsl:if>
        <xsl:apply-templates/>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.