0

Hello I was struggling with this for the past days and I could not find a good answer nor solution. I have an XML file with a list of objects like this:

<?xml version="1.0" encoding="UTF-8"?>
<LineItems>
    <TableName>Lines</TableName>
    <TableTerm>Lines</TableTerm>
    <LineItems>
        <Class>A Class</Class>
    </LineItems>
    <LineItems>
        <Number>1234</Number>
    </LineItems>
    <LineItems>
        <Description>G</Description>
    </LineItems>
    <LineItems>
        <Class>B Class</Class>
    </LineItems>
    <LineItems>
        <Number>5678</Number>
    </LineItems>
    <LineItems>
        <Description>F</Description>
    </LineItems>
    <ColumnMetadata>
        <Name>Class</Name>
        <Term>Class</Term>
    </ColumnMetadata>
    <ColumnMetadata>
        <Name>Number</Name>
        <Term>No</Term>
    </ColumnMetadata>
    <ColumnMetadata>
        <Name>Description</Name>
        <Term>Description</Term>
    </ColumnMetadata>
</LineItems>

I am applying the following transformaiton:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
    <xsl:variable name="columns" select="count(LineItems/ColumnMetadata)" />
    <xsl:variable name="items" select="count(LineItems/LineItems)" />
    <xsl:variable name="rows" select="$items div $columns" />
    <table border="1">
        <thead >
            <tr bgcolor="#9acd32">
                <xsl:for-each select="LineItems/ColumnMetadata">
                    <th style="padding: .3em 0;">
                        <xsl:value-of select="Term" />
                    </th>
                </xsl:for-each>
            </tr>
        </thead>
        <tbody style="text-align: center;">
            <xsl:for-each select="(//LineItems)[position()&lt;=$rows]">
            <xsl:variable name="i" select="position() - 1"/>
            <tr>
                <xsl:for-each select="(//*)[position()&lt;=$columns]">
                    <xsl:variable name="j" select="position()+($columns*$i)"/>
                    <td style="padding: .3em 0;">
                        <xsl:value-of select="LineItems/LineItems[$j]" />
                    </td>
                </xsl:for-each>
            </tr>
            </xsl:for-each>
        </tbody>
    </table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

Finally the desire output for this case would be:

<table>
  <thead>
    <tr>
      <th>Class</th>
      <th>No</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
        <td>A Class</td>
        <td>1234</td>
        <td>G</td>
    </tr>
    <tr>
        <td>B Class</td>
        <td>5678</td>
        <td>F</td>
    </tr>
  </tbody>
</table>

This case is a case of MxN table, I cna know how many columns from nodes. So, to sumarise:

  • The actual list that have to be transformed as a table is all inside root <LineItems>.
  • I don't know how many items (rows) I am going to get, but I can calculate them dividing amount of <LineItems> nodes ($items) by amount of <ColumnMetadata> nodes ($columns)
  • Nodes like <Class>, <Number> and <Description>, are columns in table, but they can have other names, they are dynamic and can be 5, 6... Many columns.

If I transform above XML with XSL in online tools I only get the header row of the table (and inspecting HTML, I can see 2 rows for 2 items, but empty). If I use Visual Studio transformation tool, I not only get header row, I also get first 2 columns of table (in this example Class values) but not the values in the rest of them. I really don't understand what is going on and why do I get different results using different tools.

Thanks in advance

Nahuel
  • 53
  • 6
  • I struggled a bit to understand what you are trying to achieve. Can you please add the expected output or the ones you're getting already. – Lingamurthy CS Jul 24 '15 at 05:18
  • The big question about your input is whether the line items follow a **regular** pattern. In your example, there are 3 columns and 2 rows - and the line items enumerate the 6 cell values in a neat left-to-right, top-to-bottom order (or at least so it seems). If that's always the case (i.e. an empty cell will be represented by an empty line item element), then this is quite easy. Otherwise it can get complex. – michael.hor257k Jul 24 '15 at 11:38
  • @LingamurthyCS I added the desired output. Thanks. – Nahuel Jul 26 '15 at 21:35
  • @michael.hor257k once the XML is created, lineitmes folllow a regular pattern. But I don't know before XML is created names nor column quantity nor items. – Nahuel Jul 26 '15 at 21:36

1 Answers1

1

lineitmes folllow a regular pattern.

Then I believe it could be simply:

XSLT 1.0

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

<xsl:template match="/LineItems">
    <xsl:variable name="columns" select="count(ColumnMetadata)"/>
    <table border="1">
        <thead >
            <tr>
                <xsl:for-each select="ColumnMetadata">
                    <th>
                        <xsl:value-of select="Term"/>
                    </th>
                </xsl:for-each>
            </tr>
        </thead>
        <tbody>
            <xsl:for-each select="LineItems[position() mod $columns = 1]">
                <tr>
                    <xsl:for-each select=". | following-sibling::LineItems[position() &lt; $columns]">
                        <td>
                            <xsl:value-of select="*"/>
                        </td>
                    </xsl:for-each>
                </tr>
            </xsl:for-each>
        </tbody>
    </table>
</xsl:template>

</xsl:stylesheet>
michael.hor257k
  • 96,733
  • 5
  • 30
  • 46