0

I'm using XSLT 1.0. I have <RowBreak> and <ColumnBreak> elements in my XML file. RowBreak will group my data by row, while ColumnBreak will transform to columns. For as long as there's no match for ColumnBreak or RowBreak, the display should continue downwards.

Here is my XML:

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

The expected output is this:

Item 1  Item 3
Item 2  Item 4
        Item 5

Item 6  Item 8
Item 7

Item 9
Item 10

EDIT: Here's my updated XSLT. I still couldn't figure out how to do the nested grouping.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="html" indent="yes"/>
  <xsl:key name="rowGroups" match="Tree/*[not(self::RowBreak)]" use="count(preceding-sibling::RowBreak)" />

  <xsl:template match="Tree">
    <xsl:variable name="rowGroupings" select="*[not(self::RowBreak)][generate-id() = generate-id(key('rowGroups', count(preceding-sibling::RowBreak))[1])]" />
    <xsl:variable name="position" select="position()" />
    <table>
      <xsl:for-each select="$rowGroupings">
        <xsl:variable name="rowId" select="generate-id()"/>
        <xsl:variable name="colGroupings" select="$rowGroupings[not(self::ColumnBreak)][generate-id()=$rowId][1]" />
        <tr>
          <td>
            <xsl:for-each select="$colGroupings">
              <xsl:sort select="$colGroupings[preceding-sibling::ColumnBreak]" order="descending" />
              <xsl:if test="position() = 1">
                <xsl:variable name="maxRows" select="$colGroupings[preceding-sibling::ColumnBreak]" />
                <xsl:call-template name="ColumnGroupTemplate">
                  <xsl:with-param name="maxRows" select="$maxRows" />
                  <xsl:with-param name="colGroupings" select="$colGroupings" />
                </xsl:call-template>
              </xsl:if>
            </xsl:for-each>
          </td>
        </tr>
      </xsl:for-each>
    </table>
  </xsl:template>

  <xsl:template name="ColumnGroupTemplate">
    <xsl:param name="maxRows" />
    <xsl:param name="colGroupings" />
    <xsl:for-each select="$maxRows">
      <xsl:variable name="position" select="position()" />
      <tr>
        <xsl:for-each select="$colGroupings">
          <xsl:variable name="group" select="$colGroupings[preceding-sibling::ColumnBreak]" />
          <td>
            <xsl:value-of select="normalize-space($colGroupings[position() = $position])" />
          </td>
        </xsl:for-each>
      </tr>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

How do I achieve the expected output using Muenchian grouping? Thanks!

mark uy
  • 521
  • 1
  • 6
  • 17

1 Answers1

1

If you apply this stylesheet against your input XML

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">
    <xsl:strip-space elements="*"/>
    <xsl:output method="html" version="5.0" />


    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="Tree">
        <table>
            <!-- process the first row -->
            <xsl:call-template name="process_first_group">
                <xsl:with-param name="group" select="*[not(preceding-sibling::RowBreak) 
                    and not(self::RowBreak)]"/>
            </xsl:call-template>
            <xsl:apply-templates select="RowBreak"/>
        </table>
    </xsl:template>

    <!-- succeeding rows that start with RowBreak -->
    <xsl:template match="RowBreak">
        <xsl:variable name="row_ID" select="generate-id()"/>
        <xsl:variable name="prec_Col" select="count(preceding-sibling::ColumnBreak)"/>
        <xsl:call-template name="process_other_group">
            <xsl:with-param name="group" select="following-sibling::*[not(self::RowBreak)]
                [preceding-sibling::RowBreak[1]
                [generate-id()=$row_ID]
                ]"/>
            <xsl:with-param name="row_ID" select="$row_ID"/>
            <xsl:with-param name="prec_Col" select="$prec_Col"/>
        </xsl:call-template>
    </xsl:template>

    <xsl:template name="process_first_group">
        <xsl:param name="group"/>
        <xsl:for-each select="$group[self::Item[not(preceding-sibling::ColumnBreak)]]">
            <xsl:variable name="pos" select="position()"/>
            <tr>
                <td>
                    <xsl:value-of select="."/>
                </td>
                <xsl:for-each select="following-sibling::ColumnBreak">
                    <xsl:variable name="col_ID" select="generate-id()"/>
                    <xsl:apply-templates select="$group[self::Item
                        [preceding-sibling::ColumnBreak[1]
                        [generate-id()=$col_ID]
                        ]
                        [position()=$pos]
                        ]"/>
                </xsl:for-each>
            </tr>
        </xsl:for-each>
    </xsl:template>

    <xsl:template name="process_other_group">
        <xsl:param name="group"/>
        <xsl:param name="row_ID"/>
        <xsl:param name="prec_Col"/>
        <xsl:for-each select="$group[self::Item[count(preceding-sibling::ColumnBreak)=$prec_Col]]">
            <xsl:variable name="pos" select="position()"/>
            <tr>
                <td>
                    <xsl:value-of select="."/>
                </td>
                <xsl:for-each select="$group[self::ColumnBreak[preceding-sibling::RowBreak[generate-id()=$row_ID]]]">
                    <xsl:variable name="col_ID" select="generate-id()"/>
                    <xsl:apply-templates select="following-sibling::Item[preceding-sibling::ColumnBreak[1][generate-id()=$col_ID] and preceding-sibling::RowBreak[1][generate-id()=$row_ID]][position()=$pos]"/>
                </xsl:for-each>
            </tr>
        </xsl:for-each>
    </xsl:template>

    <xsl:template match="Item">
        <td><xsl:apply-templates/></td>
    </xsl:template>

    <xsl:template match="Label">
        <xsl:apply-templates/>
    </xsl:template>

</xsl:stylesheet>

you will get

<table>
   <tr>
      <td>Item 1</td>
      <td>Item 3</td>
   </tr>
   <tr>
      <td>Item 2</td>
   </tr>
   <tr>
      <td>Item 4</td>
      <td>Item 6</td>
      <td>Item 8</td>
   </tr>
   <tr>
      <td>Item 5</td>
      <td>Item 7</td>
   </tr>
   <tr>
      <td>Item 9</td>
   </tr>
   <tr>
      <td>Item 10</td>
   </tr>
</table>

See it in action https://xsltfiddle.liberty-development.net/3NzcBuj/1

Joel M. Lamsen
  • 7,143
  • 1
  • 12
  • 14
  • Thanks for this. I'll do some tweaking in your code as one use case is failing. For instance, there's no in the XML, and a exists between Item 5 and Item 6. The expected output should be two columns, with Item 1-5 in first column, and Item 6-10 in second column. – mark uy Dec 19 '18 at 01:09
  • I have added some modifications but this still needs to be improved. https://xsltfiddle.liberty-development.net/3NzcBuj/2 – Joel M. Lamsen Dec 19 '18 at 08:51
  • Thanks Joel, but I still couldn't make it work. I've updated my XSLT and scenario as well. – mark uy Dec 19 '18 at 10:16
  • 1
    Please try this one. https://xsltfiddle.liberty-development.net/3NzcBuj/3 – Joel M. Lamsen Dec 20 '18 at 01:58
  • Thank you Joel M. Lamsen, I appreciate your help. It's already working for the above XML, but I updated your latest fiddle's XML, the result gets duplicated for some. https://xsltfiddle.liberty-development.net/3NzcBuj/8 – mark uy Dec 20 '18 at 02:08
  • 1
    Yeah, I forgot to say that that is working on the given input XML. However, the logic is there already, you just have to modify the script yourself. – Joel M. Lamsen Dec 20 '18 at 02:19
  • Yes, thank you so much. I'll use your code as my base. – mark uy Dec 20 '18 at 02:21