-1

Trying to transform XML to HTML tables. I can find plenty of examples on creating an XSLT style sheet for XML where the format is like this:

<catalog>
  <cd>
    <title>Empire Burlesque</title>
    <artist>Bob Dylan</artist>
    <country>USA</country>
    <company>Columbia</company>
  </cd>
</catalog>

But what if the format is like this, as it is in my dataset?

<DataObjects>
  <ObjectSelect>
    <mdNm>my-catalog</mdNm>
    <meNm>Catalog Selection A1</meNm>
  </ObjectSelect>
  <DataInstances>
    <DataInstance>
      <instanceId>Cd</instanceId>
        <field name='title'>Empire Burlesque</field>
        <field name='artist'>Bob Dylan</field>
        <field name='country'>USA</field>
        <field name='company'>Columbia</field>
    </DataInstance>
    <DataInstance>
      <instanceId>Movie</instanceId>
        <field name='title'>Casablanca</field>
        <field name='director'>Michael Curtiz</field>
        <field name='genre'>Drama</field>
    </DataInstance>
  </DataInstances>
</DataObjects>

Each InstanceId has a single entry, with unique field names, and I'm trying to get create a table output for each unique InstanceId, so a "Cd" table, a "Movie" table, and so on. Each field would be a new row, the first column would be what is in name=, and the value would be in the second column of the row. I think once I figure out how to grab the value from name="" and put it as a row name, I'll be able to figure out the rest. All the tutorials talk about using value-of, but that only works if the format is like the first example given.

An example XSLT that I'm using as a starting point. It does not like the field name= format.

<?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>
    <h2>Catalog Selection A1</h2>
    <table border="1">
      <tr bgcolor="#9acd32">
        <th>Name</th>
        <th>Value</th>
      </tr>
      <xsl:for-each select="DataObjects/DataInstances/DataInstance/instanceId/Cd">
      <tr>
        <td>Title</td>
        <td><xsl:value-of select="title" /></td>
      </tr>
      <tr>
        <td>Artist</td>
        <td><xsl:value-of select="artist" /></td>
      </tr>
      <tr>
        <td>Country</td>
        <td><xsl:value-of select="country" /></td>
      </tr>
      <tr>
        <td>Company</td>
        <td><xsl:value-of select="company" /></td>
      </tr>
      </xsl:for-each>
      <xsl:for-each select="DataObjects/DataInstances/DataInstance/instanceId/Movie">
      <tr>
        <td>Title</td>
        <td><xsl:value-of select="title" /></td>
      </tr>
      <tr>
        <td>Director</td>
        <td><xsl:value-of select="director" /></td>
      </tr>
      <tr>
        <td>Genre</td>
        <td><xsl:value-of select="genre" /></td>
      </tr>
      </xsl:for-each>
    </table>
  </body>
  </html>
</xsl:template>
</xsl:stylesheet>

The output I'm trying to generate is a basic html table like this,

<html>
  <body>
    <h2>Catalog Selection A1</h2>
    <table border="1">
      <tr bgcolor="#9acd32">
        <th>Name</th>
        <th>Value</th>
      </tr>
      <tr>
        <td>Title</td>
        <td>Empire Burlesque</td>
      </tr>
      <tr>
        <td>Artist</td>
        <td>Bob Dylan</td>
      </tr>
      <tr>
        <td>Country</td>
        <td>USA</td>
      </tr>
      <tr>
        <td>Company</td>
        <td>Columbia</td>
      </tr>
    </table>
    <table border="1">
      <tr bgcolor="#9acd32">
        <th>Name</th>
        <th>Value</th>
      </tr>
      <tr>
        <td>Title</td>
        <td>Casablanca</td>
      </tr>
      <tr>
        <td>Director</td>
        <td>Michael Curtiz</td>
      </tr>
      <tr>
        <td>Genre</td>
        <td>Drama</td>
      </tr>
    </table>
  </body>
</html>
Danny Z
  • 113
  • 1
  • 6
  • While asking an XSLT question you need to provide a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example): (1) Input XML. (2) Your logic, and XSLT that tries to implement it. (3) Desired output, based on the sample XML in the #1 above. (4) XSLT processor and its conformance with the XSLT standards: 1.0, 2.0, 3.0, or 4.0. – Yitzhak Khabinsky Nov 29 '22 at 23:54
  • I wish I knew what you were asking me for. I provided a sample of the XML format I'm working with, the desired output. As for logic and XSLT I pointed to the value-of method. I will update with an example of that. As for 4, I'm not sure what you mean. – Danny Z Nov 30 '22 at 02:28
  • Please add the **exact** output (as code) you expect to get in the given example. Also, unless you know in advance which `instanceId` values will appear in the input, this is (also) a [grouping](https://stackoverflow.com/tags/xslt-grouping/info) problem - and grouping is done differently according to the XSLT version that your processor supports. If you don't know the answer to this, find out: https://stackoverflow.com/a/25245033/3016153 – michael.hor257k Nov 30 '22 at 02:52
  • I'm using browser to process, is this a bad idea? I need users to see the formatted output in Edge and/or Chrome. I do know the exact instanceId. I will have 9 groups to parse and display. I've limited to 2 in order to simplify the question. I will be able to define each instanceId and each field name. – Danny Z Nov 30 '22 at 02:54
  • Browsers support XSLT 1.0 only. Whether it's a bad idea depends on what you're aiming at. Certainly for testing, you would be better off using an environment that allows you to see what you're doing and provides error messages. --- I am still unclear how you want to display multiple CDs, for example. Do you really want a separate table for each? – michael.hor257k Nov 30 '22 at 03:07
  • I'll never have more than 1 of the same instanceId, so in this example, only 1 cd, 1 movie, ever. – Danny Z Nov 30 '22 at 03:11
  • I added something else, ideally I can display the value of to an

    tag in the output.

    – Danny Z Nov 30 '22 at 03:17

1 Answers1

0

I would suggest you do something along the lines of:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/DataObjects">
    <html>
        <body>
            <h2>
                <xsl:value-of select="ObjectSelect/meNm" />
            </h2>
            <h3>Cds</h3>
            <xsl:for-each select="DataInstances/DataInstance[instanceId='Cd']">
                <xsl:call-template name="table"/>
            </xsl:for-each>
            <h3>Movies</h3>
            <xsl:for-each select="DataInstances/DataInstance[instanceId='Movie']">
                <xsl:call-template name="table"/>
            </xsl:for-each>
        </body>
    </html>
</xsl:template>

<xsl:template name="table">
    <table border="1">
        <tr>
            <th>Name</th>
            <th>Value</th>
        </tr>
        <xsl:for-each select="field">
            <tr>
                <td>
                    <xsl:value-of select="@name" />
                </td>
                <td>
                    <xsl:value-of select="." />
                </td>
            </tr>
        </xsl:for-each> 
    </table>
</xsl:template>

</xsl:stylesheet>

This minimizes code duplication by (1) using a named template to produce a table for any kind of instanceId and (2) processing each field to a row with name and value. However this also means that it will produce a row for every field in the input. If that's not what you want, then more work is required.


Added:

If you only have one of each kind, and you don't mind preserving the original order, then you can do simply:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/DataObjects">
    <html>
        <body>
            <h2>
                <xsl:value-of select="ObjectSelect/meNm" />
            </h2>
            <xsl:for-each select="DataInstances/DataInstance">
                <table border="1">
                    <tr>
                        <th>Name</th>
                        <th>Value</th>
                    </tr>
                    <xsl:for-each select="field">
                        <tr>
                            <td>
                                <xsl:value-of select="@name" />
                            </td>
                            <td>
                                <xsl:value-of select="." />
                            </td>
                        </tr>
                    </xsl:for-each> 
                </table>
            </xsl:for-each>
        </body>
    </html>
</xsl:template>

</xsl:stylesheet>
michael.hor257k
  • 113,275
  • 6
  • 33
  • 51
  • I like this a lot. So I can selectively choose which instanceIds to show in the first example, or show them all in the second. In both cases, if I understand this right, this will produce a row for every field. If I want to only show specific fields, I would need to filter them out ahead of time. This is doable, but would be good to know of a way to do this in the transform. Could I create an array for the fields I want to display? – Danny Z Nov 30 '22 at 03:55
  • It's a little awkward in XSLT 1.0 You could simply name them in a predicate. e.g. ``. If you want to maintain them in a separate list, then it gets more complicated. Note also that if you have a different list for each kind, then you won't be able to take all the shortcuts I have taken. – michael.hor257k Nov 30 '22 at 04:07
  • Probably easier for me to filter on the front end before the XML file is created then. You've been a big help. – Danny Z Nov 30 '22 at 04:14