I have a partially working XSLT that groups my elements by columns according to column break. My elements are displayed in one column until a <ColumnBreak>
is encountered, which then makes the succeeding items rendered in a new column to the right.
Now I want to extend this XSLT to achieve the concept of column span based on the width of my items, so I followed the sample implementation from this topic. Furthermore, if a region is already occupied in the next column due to the width span of the previous element, the succeeding elements following the ColumnBreak
shall be displayed below the already occupied region.
XML:
<?xml version="1.0" encoding="utf-8" ?>
<Group>
<Items>
<Item>
<Display>Item 1</Display>
</Item>
<Item>
<Display>Item 2</Display>
<Width>2</Width>
</Item>
<Item>
<Display>Item 3</Display>
</Item>
<ColumnBreak />
<Item>
<Display>Item 4</Display>
</Item>
<Item>
<Display>Item 5</Display>
</Item>
<ColumnBreak />
<Item>
<Display>Item 6</Display>
</Item>
<Item>
<Display>Item 7</Display>
</Item>
</Items>
</Group>
XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl"
exclude-result-prefixes="msxsl xsi">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Group">
<xsl:apply-templates select="Items"/>
</xsl:template>
<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="Items">
<div>
<xsl:variable name="cells">
<xsl:apply-templates select="*[1]" mode="item-sibling">
<xsl:with-param name="row" select="1"/>
<xsl:with-param name="col" select="1"/>
<xsl:with-param name="index" select="1"/>
</xsl:apply-templates>
</xsl:variable>
<xsl:call-template name="generate-rows">
<xsl:with-param name="cells" select="$cells" />
<xsl:with-param name="current-row" select="1"/>
</xsl:call-template>
</div>
</xsl:template>
<xsl:template name="generate-rows">
<xsl:param name="cells" />
<xsl:param name="current-row" />
<xsl:param name="last-row" />
<xsl:for-each select="exsl:node-set($cells)/cell[count(. | key('cell-by-row', @row)[1]) = 1]">
<xsl:for-each select="key('cell-by-row', @row)[count(. | key('cell-by-col', concat(@row, '|', @col))[1]) = 1]">
<xsl:for-each select="key('cell-by-col', concat(@row, '|', @col))">
<xsl:if test="not(@empty)">
<div row="{@row}" col="{@col}" index="{@index}" width="{@width}">
<xsl:apply-templates select="." mode="item">
</xsl:apply-templates>
</div>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
<xsl:template match="*" mode="item-sibling">
<xsl:param name="row"/>
<xsl:param name="col"/>
<xsl:param name="index"/>
<xsl:param name="previous-cells" select="some-dummy-node-to-make-this-a-node"/>
<xsl:variable name="width">
<xsl:call-template name="width-template" />
</xsl:variable>
<xsl:variable name="colliding-cell" select="exsl:node-set($previous-cells)/cell[@row = $row and @col < $col and @index > $index and @width + @col > $col]" />
<xsl:choose>
<xsl:when test="$colliding-cell">
<xsl:apply-templates select="self::node()" mode="item-sibling">
<xsl:with-param name="row" select="$row"/>
<xsl:with-param name="col" select="$col"/>
<xsl:with-param name="index" select="$colliding-cell/@index + 1"/>
<xsl:with-param name="previous-cells" select="$previous-cells"/>
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="current-cell">
<cell row="{$row}" col="{$col}" index="{$index}" width="{$width}">
<xsl:copy-of select="exsl:node-set(.)"/>
</cell>
</xsl:variable>
<xsl:variable name="new-cells">
<xsl:copy-of select="$previous-cells"/>
<xsl:copy-of select="$current-cell"/>
</xsl:variable>
<xsl:copy-of select="$current-cell"/>
<xsl:apply-templates select="following-sibling::*[1]" mode="item-sibling">
<xsl:with-param name="row" select="$row"/>
<xsl:with-param name="col" select="$col"/>
<xsl:with-param name="index" select="$index + 1"/>
<xsl:with-param name="previous-cells" select="$new-cells"/>
</xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="ColumnBreak" mode="item-sibling">
<xsl:param name="row"/>
<xsl:param name="col"/>
<xsl:param name="index"/>
<xsl:param name="previous-cells"/>
<xsl:apply-templates select="following-sibling::*[1]" mode="item-sibling">
<xsl:with-param name="row" select="$row"/>
<xsl:with-param name="col" select="$col + 1"/>
<xsl:with-param name="index" select="1" />
<xsl:with-param name="previous-cells" select="$previous-cells"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template name="width-template">
<xsl:choose>
<xsl:when test="Width">
<xsl:value-of select="Width" />
</xsl:when>
<xsl:otherwise>1</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="Item" mode="item">
<xsl:value-of select="Display" />
</xsl:template>
</xsl:stylesheet>
EXPECTED RESULT*:
Scenario 1: Item 2 Width = 2
Scenario 2: Item 2 Width = 2, Item 4 Width = 2
*For illustration purposes, I used HTML table in my scenario screenshots, but basically the items need not to be precisely aligned by row because my elements may vary by height. The ultimate goal is to group them into columns.
Current Output:
My current XSLT produces a raw matrix which shows the column assignment of the element and its corresponding width. How do I convert this matrix to a layout of HTML <div>
that will look similar to the expected results above?
My XSLT Fiddle: https://xsltfiddle.liberty-development.net/6pS26mX/1
Thank you!