1

I have an XML file of movies two examples of which seen below -- there are MANY movie elements in mediaList, and I want my XSLT to transform it into an HTML table for other people to view.

<mediaList>
 <movie id="1299349" dateCreated="2014-04-11" lastModified="2014-04-12">
    <title>
        <titleSort>Armageddon</titleSort>
    </title>
    <director>
        <personTerm type="code" authority="lccn">no98124072</personTerm>
        <personTerm type="text">Bay, Michael, 1964-</personTerm>
    </director>
    <genre>Action and adventure</genre>
    <genre>Disaster</genre>
    <writer>
        <personTerm type="code" authority="lccn">n91119795</personTerm>
        <personTerm type="text">Hensleigh, Jonathan</personTerm>
    </writer>
    <writer>
        <personTerm type="code" authority="lccn">no98124254</personTerm>
        <personTerm type="text">Abrams, J. J. (Jeffrey Jacob), 1966-</personTerm>
    </writer>
    <screenplay href="http://endeavor.flo.org/vwebv/holdingsInfo?bibId=578717">PN1997 .A73 1997a</screenplay>
    <language>
        <languageTerm type="code" authority="iso639-2">eng</languageTerm>
        <languageTerm type="text">English</languageTerm>
    </language>
    <year>1998</year>
    <callNumber href="http://endeavor.flo.org/vwebv/holdingsInfo?bibId=1299349">[DVD] PN1995.9 .A3 B39 1999</callNumber>
</movie>
<movie id="1324917" dateCreated="2014-04-13">
    <title>
        <titleSort>Police Story 2</titleSort>
    </title>
    <director>
        <personTerm type="code" authority="lccn">no96039667</personTerm>
        <personTerm type="text">Cheng, Long, 1954-</personTerm>
    </director>
    <genre>Martial arts</genre>
    <genre>Buddy</genre>
    <writer>
        <personTerm type="code" authority="lccn">no96039667</personTerm>
        <personTerm type="text">Cheng, Long, 1954-</personTerm>
    </writer>
    <writer>
        <personTerm type="code" authority="lccn">no2005066079</personTerm>
        <personTerm type="text">Tang, Edward, 1946-</personTerm>
    </writer>
    <language>
        <languageTerm type="code" authority="iso639-3">yue</languageTerm>
        <languageTerm type="text">Yue Chinese</languageTerm>
    </language>
    <year>1988</year>
    <callNumber href="http://endeavor.flo.org/vwebv/holdingsInfo?bibId=1324917">[DVD] PN1995.9 .A3 C52 2006</callNumber>
</movie>
</Medialist

Right now, my XSLT looks like this:

 <?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:output method="html"/>
    <xsl:template match="/">
        <html>
            <head>
                <link rel="stylesheet" type="text/css" href="mediaList.css"/>
                <title/>
            </head>
            <body>
                <table>

                    <tr>
                        <th>Title</th>
                        <th>Director</th>
                        <th>Genre</th>
                        <th>Writer</th>
                        <th>Screenplay</th>
                        <th>Source</th>
                        <th>Language</th>
                        <th>Year</th>
                        <th>Call Number</th>
                    </tr>

                    <xsl:for-each select="mediaList/movie">
                        <tr>
                            <td>
                                <xsl:apply-templates select="./title"/>
                            </td>
                            <td>
                                <xsl:apply-templates select="./director/personTerm[@type='text']"/>
                            </td>
                            <xsl:choose>
                                <xsl:when test="count(./genre) > 1">
                                    <td>
                                        <xsl:call-template name="recursiveGenre">
                                            <xsl:with-param name="numberG" select="count(./genre)"/>
                                        </xsl:call-template>
                                        <xsl:value-of select="./genre[1]"/>
                                    </td>
                                </xsl:when>
                                <xsl:otherwise>
                                    <td>
                                        <xsl:value-of select="./genre"/>
                                    </td>
                                </xsl:otherwise>
                            </xsl:choose>

                            <xsl:choose>
                                <xsl:when test="count(./writer) > 1">
                                    <td>
                                        <xsl:call-template name="recursive">
                                            <xsl:with-param name="number" select="count(./writer)"/>
                                        </xsl:call-template>
                                        <xsl:value-of select="writer[1]/personTerm[@type='text']"/>
                                    </td>
                                </xsl:when>
                                <xsl:otherwise>
                                    <td>
                                        <xsl:value-of select="writer/personTerm[@type='text']"/>
                                    </td>
                                </xsl:otherwise>
                            </xsl:choose>

                            <td>
                                <xsl:apply-templates select="screenplay"/>
                            </td>
                            <td>
                                <xsl:apply-templates select="source"/>
                            </td>
                            <td>
                                <xsl:value-of select="./language/languageTerm[@type='text']"/>
                            </td>
                            <td>
                                <xsl:value-of select="./year"/>
                            </td>
                            <td>
                                <xsl:apply-templates select="callNumber"/>
                            </td>
                        </tr>
                    </xsl:for-each>
                </table>
            </body>
        </html>
    </xsl:template>

    <xsl:template match="title">
        <xsl:if test="./nonSort">
            <xsl:value-of select="./nonSort"/>
            <xsl:text> </xsl:text>
        </xsl:if>
        <xsl:value-of select="./titleSort"/>
    </xsl:template>

    <xsl:template name="recursive">
        <xsl:param name="number"/>
        <xsl:if test="$number > 1">
            <xsl:value-of select="writer[$number]/personTerm[@type='text']"/>
            <xsl:text> / </xsl:text>
            <xsl:call-template name="recursive">
                <xsl:with-param name="number" select="$number - 1"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>

    <xsl:template name="recursiveGenre">
        <xsl:param name="numberG"/>
        <xsl:if test="$numberG > 1">
            <xsl:value-of select="genre[$numberG]"/>
            <xsl:text> / </xsl:text>
            <xsl:call-template name="recursiveGenre">
                <xsl:with-param name="numberG" select="$numberG - 1"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>

    <xsl:template match="screenplay | source | callNumber">
        <a>
            <xsl:attribute name="href">
                <xsl:value-of select="./@href"/>
            </xsl:attribute>
            <xsl:value-of select="."/>
        </a>
    </xsl:template>

</xsl:stylesheet>

So while this one is a bit clunky, it's working alright -- it produces a nice table anchored by titles and provides relevant information about each title in subsequent cells.

But what I'm really trying to do is show the data in different ways. If, for instance I want to group the titles by genre so that the output would look like this:

     <table>

                    <tr>
                        <th>Genre</th>
                        <th>Title</th>
                        <th>Director</th>
                        <th>Writer</th>
                        <th>Screenplay</th>
                        <th>Source</th>
                        <th>Language</th>
                        <th>Year</th>
                        <th>Call Number</th>
                    </tr>
    <tr><td>Action and Adventure</td>
<td>Some Action/Comedy Title</td>
...
</tr>
<tr><td>Action and Adventure</td>
<td>Some Action/Comedy Title - The Return</td>
...
</tr>
<tr><td>Action and Adventure</td>
<td>Revenge of the Action/Comedy Title</td>
...
</tr>
<tr><td>Comedy</td>
<td>Some Action/Comedy Title</td>
...
<tr><td>Comedy</td>
<td>Some Action/Comedy Title - The Return</td>
</tr>
<tr><td>Comedy</td>
<td>Revenge of the Action/Comedy Title</td>
...
</tr>
</table>

I'd also want to be able to view it by any other of the "repeated value" columns, like language (many of the movie elements have more than one language element), Writer (same deal)

Is there any way to have the table re-generate on a click by containing all types of transformation inside the single XSLT file? Would I need separate XSLT files for each "type" of table? Is there a better method to do what I'm doing?

Any and all advice would be much appreciated -- I'm still pretty new at this, i'm in my first programming class and we're doing XML/XSLT.

user3530461
  • 83
  • 2
  • 9
  • Hello, I just saw that it is XSL 2.0. I only work with 1.0 so far - sorry I can't really help with grouping. Please try to use instead of (http://stackoverflow.com/questions/6342902/for-loops-vs-apply-templates). For grouping search on the board. In XSLT 1.0 there is Muenchian grouping - I think there is an easier solution though in XSLT 2.0. Best regards, Peter – Peter Apr 16 '14 at 08:06

1 Answers1

1

I am going to give an XSLT 1.0 solution here (which will still work in XSLT 2.0) as the solution is fairly generic and simple to add new grouping parameters.

In XSLT 1.0, you use a technique called Muenchian Grouping to do grouping, and involves creating keys to lookup nodes. For "genre" you could define the key like so:

<xsl:key name="genre" match="genre" use="." />

And for language, you could do this

<xsl:key name="language" match="language" use="languageTerm[@type='text']" />

However, in this particular instance, it will be easier if the name of the key exactly matches the element name, and the "use" attribute is just ".", so for this answer the key will be defined like so

<xsl:key name="languageTerm" match="languageTerm[@type='text']" use="." />

For you XSLT you would also define a parameter to say on what you want to group

<xsl:param name="groupBy" select="'genre'" />

Here the value will need to match the key name (and the name of the element being matched).

Then, to get the distinct values for each group, the expression would be as follows:

<xsl:apply-templates select="movie//*[local-name() = $groupBy]
                             [generate-id() = generate-id(key($groupBy, .)[1])]" />

(It is the second xpath condition which is the grouping clause).

Within in the template that matches this, which is effectively your header row, you could then get all movie elements for the "group", like so

<xsl:apply-templates select="key($groupBy, .)/ancestor::movie" />

Here is the full XSLT in this case:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:output indent="yes"/>
    <xsl:output method="html" indent="yes" />

    <xsl:key name="genre" match="genre" use="." />
    <xsl:key name="languageTerm" match="languageTerm[@type='text']" use="." />

    <xsl:param name="groupBy" select="'genre'" />

    <xsl:template match="/*">
        <xsl:apply-templates select="movie//*[local-name() = $groupBy][generate-id() = generate-id(key($groupBy, .)[1])]" />
    </xsl:template>

    <xsl:template match="movie//*">
        <h1><xsl:value-of select="." /></h1>
        <ul>
            <xsl:apply-templates select="key($groupBy, .)/ancestor::movie" />
        </ul>
    </xsl:template>

    <xsl:template match="movie">
        <li><xsl:value-of select="title" /></li>
    </xsl:template>
</xsl:stylesheet>

To group by language, change the parameter to "languageTerm". Hopefully it should not be too hard to add different groupings.

Note that, as mentioned, this is really an XSLT 1.0. Usually, in XSLT 2.0, you should be using the xsl:for-each-group to do grouping. I just used XSLT 1.0 here as it is arguably easier to add new groups, simply by adding a new xsl:key for each one.

Tim C
  • 70,053
  • 14
  • 74
  • 93