0

I have a working XSLT that groups my elements into columns when a <ColumnBreak> is detected. It is following the sibling recursion concept explained in this link.

XSLT 1.0:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>

<xsl:key name="cell-by-row" match="cell" use="@row" />
<xsl:key name="cell-by-col" match="cell" use="concat(@row, '|', @col)" />

<xsl:template match="/Tree">
    <!-- first-pass -->
    <xsl:variable name="cells">
        <xsl:apply-templates select="Item[1]" mode="sibling">
            <xsl:with-param name="row" select="1"/>
            <xsl:with-param name="col" select="1"/>
        </xsl:apply-templates>  
    </xsl:variable>
    <!-- output -->
    <table border = "1">
        <!-- for each distinct row -->
        <xsl:for-each select="exsl:node-set($cells)/cell[count(. | key('cell-by-row', @row)[1]) = 1]">
            <tr>
                <!-- for each distinct cell in the current row -->
                <xsl:for-each select="key('cell-by-row', @row)[count(. | key('cell-by-col', concat(@row, '|', @col))[1]) = 1]">
                    <td>
                        <!-- get the values in the current cell -->
                        <xsl:for-each select="key('cell-by-col', concat(@row, '|', @col))">
                            <xsl:value-of select="."/>
                            <br/>
                        </xsl:for-each>
                    </td>
                </xsl:for-each>
            </tr>
        </xsl:for-each>
    </table>
</xsl:template>

<xsl:template match="Item" mode="sibling">
    <xsl:param name="row"/>
    <xsl:param name="col"/>
    <cell row="{$row}" col="{$col}">
        <xsl:value-of select="Label"/>
    </cell>
    <xsl:apply-templates select="following-sibling::*[1]" mode="sibling">
        <xsl:with-param name="row" select="$row"/>
        <xsl:with-param name="col" select="$col"/>
    </xsl:apply-templates>  
</xsl:template>

<xsl:template match="ColumnBreak" mode="sibling">
    <xsl:param name="row"/>
    <xsl:param name="col"/>
    <xsl:apply-templates select="following-sibling::*[1]" mode="sibling">
        <xsl:with-param name="row" select="$row"/>
        <xsl:with-param name="col" select="$col + 1"/>
    </xsl:apply-templates>  
</xsl:template>

<xsl:template match="RowBreak" mode="sibling">
    <xsl:param name="row"/>
    <xsl:param name="col"/>
    <xsl:apply-templates select="following-sibling::*[1]" mode="sibling">
        <xsl:with-param name="row" select="$row + 1"/>
        <xsl:with-param name="col" select="1"/>
    </xsl:apply-templates>  
</xsl:template>
</xsl:stylesheet>

XML:

<?xml version="1.0" encoding="utf-8" ?>
<Tree>
  <Item ColumnSpan="2">
    <Label>Item 1</Label>
  </Item>
  <Item>
    <Label>Item 2</Label>
  </Item>
  <Item>
    <Label>Item 3</Label>
  </Item>
  <ColumnBreak />
  <Item>
    <Label>Item 4</Label>
  </Item>
  <Item>
    <Label>Item 5</Label>
  </Item>
  <ColumnBreak />
  <Item>
    <Label>Item 6</Label>
  </Item>
  <Item>
    <Label>Item 7</Label>
  </Item>
</Tree>

Current Output:

Item 1      Item 4      Item 6
Item 2      Item 5      Item 7  
Item 3

Now, if I want to introduce a new attribute called ColumnSpan to the <Item>, the XSLT should be able to expand the column accordingly and shift other columns down when necessary. See ColumnSpan attribute in Item 1 in the above XML.

Expected Results

Item 1 ColumnSpan = 2:

Item 1                  Item 6
Item 2      Item 4      Item 7  
Item 3      Item 5

Item 1 ColumnSpan = 3:

Item 1                  
Item 2      Item 4      Item 6  
Item 3      Item 5      Item 7

Item 2 ColumnSpan = 2:

Item 1      Item 4      Item 6                  
Item 2                  Item 7                  
Item 3      Item 5      

Is this concept doable in XSLT 1.0? Thank you!

mark uy
  • 521
  • 1
  • 6
  • 17
  • 1
    See if this helps: https://stackoverflow.com/a/27217608/3016153 Note that your case is simpler, if you only need to deal with column span. – michael.hor257k May 01 '20 at 08:37
  • thanks for this link. while I’m studying the sample code in the link, i want to ask which one is the proper approach for my case? Option A) Perform sibling recursion first same as in the above XSLT then loop through the cells to adjust the widths, or Option B) Adjust the width during sibling recursion then assign to cells? – mark uy May 02 '20 at 06:45

1 Answers1

1

It's certainly doable, and it's certainly challenging!

The approach that occurs to me is to add an extra parameter to your recursive calls in which you capture the fact that certain (row, column) combinations are no longer available for use, and use this information when allocating the row/column for the next entry.

You're in 1.0 so of course there are very limited choices for how to represent this data structure; one possibility would be a string in which character position ($row*N + $column) is a space if the cell is available, and is "X" if not.

Michael Kay
  • 156,231
  • 11
  • 92
  • 164