tags:

views:

182

answers:

3

Im not entirely sure if this is possible with XSLT and my lecturer had no idea what i was talking about so if anyone could help, thanks!!!

I have an XML file with a list of authors, each with a unique id, and a list of books, each with a child element with an IDREF as its value. The file is laid out like this:

<library>
  <authors>
    <author id="a001">
       <name>Joyce</name>
    </author>
  </authors>
  <books>
    <book id="b001">
      <name>Illiad</name>
      <authorID>a001</authorID>
    </book>
  </books>
</library>

I am trying to write out a list of all books and, by using the authorID value, get information on the Author.

XSLT:

<?xml version="1.0" encoding="UTF-8"?>

<html xmlns="http://www.w3.org/1999/xhtml"&gt;
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <title>XSLT Test</title>
</head>

<body>
    <xsl:for-each select="//book">
     <div style="display:block; background-color:#999; padding:2px; margin:2px;">
         <h2><xsl:value-of select="name"/></h2>
            <p><xsl:value-of select="synopsis"/></p>
            <ul>
             <li>Author: <xsl:value-of select="//author[@id='X']/name"/></li>
            </ul>
        </div>
    </xsl:for-each>
</body>
</html>

By replacing X with a value, eg a001, I can get Joyce back but how can i use the authorID value here instead, so that the system scales up nicely?

A: 

You can use a variable

<xsl:variable name="author" select="authorID/text()" />

and then use it in the predicate

<xsl:value-of select="//author[@id=$author]/name"/>
pablochan
A: 
<xsl:template match="book">
    <xsl:variable name="authorID" select="@authorID"/>

    <div>
        <h2><xsl:value-of select="name"/></h2>
        <p><xsl:value-of select="synopsis"/></p> 
        <xsl:for-each select="/library/authors/author[@id=$authorID]">
            <ul>
                <li>Author: <xsl:value-of select="name"/></li>
            </ul>
        </xsl:for-each>
    </div>
</xsl:template>
Roland Bouman
+5  A: 

Avoid the // operator like the plague. It has really bad performance characteristics, especially when you nest it like you do (for-each //, value-of //).

Use canonical XPath expressions, like this. They perform much better:

<xsl:for-each select="/library/books/book">
  <div style="display:block; background-color:#999; padding:2px; margin:2px;">
    <h2><xsl:value-of select="name"/></h2>
    <p><xsl:value-of select="synopsis"/></p>
    <ul>
      <li>
        <xsl:text>Author: </xsl:text>
        <!-- use the current() function to access the current node (book) -->
        <xsl:value-of select="
          /library/authors/author[@id=current()/authorID]/name
        "/>
      </li>
    </ul>
  </div>
</xsl:for-each>

Better yet, declare an XSL key:

<xsl:key name="kAuthorById" match="author" use="@id" />

and access it with the key() function:

<xsl:value-of select="key('kAuthorById', authorID)/name" />

This method has by far the best performance, especially when the input documents get bigger.

You should also avoid <xsl:for-each>. Try to write templates and use template matching instead:

<xsl:template match="library">
  <body>
    <xsl:apply-templates select="books" />
  </body>
</xsl:template>

<xsl:template match="books">
  <h1>List of Books</h1>
  <xsl:apply-templates select="book" />
</xsl:template>

<xsl:template match="book">
  <div style="display:block; background-color:#999; padding:2px; margin:2px;">
    <h2><xsl:value-of select="name"/></h2>
    <p><xsl:value-of select="synopsis"/></p>
    <ul>
      <li>Author: <xsl:value-of select="key('kAuthorById', authorID)/name"/></li>
    </ul>
    </div>
</xsl:template>
Tomalak
+1 for `key()`, I really should use that one more often myself.
Roland Bouman