views:

273

answers:

3

Let's say I'm transforming a multiple-choice quiz from an arbitrary XML format to HTML. Each choice will be represented as an HTML <li> tag in the result document. For each choice, I want to add an HTML class of correct to the <li> if that choice was the correct answer. Additionally, if that choice was the one selected by the user, I want to add a class of submitted to the <li>. Consequently, if the choice is the correct one as well as the submitted one, the <li> should have a class of correct submitted.

As far as I know, white-space separated attribute values aren't a part of the XML data model and thus cannot directly be created via XSLT. However, I have a feeling there's a better way of doing this than littering the code with one conditional for every possible combination of classes (which would be acceptable in this example, but unwieldy in more complex scenarios).

How can I solve this in an elegant way?

Example of Desired Result:

<p>Who trained Obi-Wan Kenobi?</p>
<ul>
 <li>Mace Windu</li>
 <li class="correct submitted">Qui-Gon Jinn</li>
 <li>Ki-Adi-Mundi</li>
 <li>Yaddle</li>
</ul>
+4  A: 

Off the top of my head, you can build up a space-separated list with something like:

<li>
    <xsl:attribute name="class">
        <xsl:if cond="...">correct</xsl:if>
        <xsl:if cond="...">submitted</xsl:if>
  </xsl:attribute>
</li>
Matthew Wilson
That doesn't work (at least not using PHP). There is no white-space in there apart from what surrounds the `if` elements, and that doesn't affect the output (you'd need to use `text` elements for that). Granted, I could use this solution and simply add a space after each possible class name, but that would lead to redundant spaces and seems more cluttered than what should be necessary.
Jakob
I've used it successfully, so maybe it's a difference in XSLT processors as to what they do with the whitespace in the XSLT file itself.
Matthew Wilson
+1  A: 

As far as I know, white-space separated attribute values aren't a part of the XML data model and thus cannot directly be created via XSLT

Unless you are converting to an XML language (which HTML is not, XHTML is), you shouldn't worry about the XML validity of the XSLT ouput. This can be anything, and doesn't need to conform to XML!

mjv
I'm not saying whitespace-separated attribute values are invalid XML, only that there aren't (to my knowledge) XSLT constructs that generate them specifically.
Jakob
+1  A: 

Firstly, there is nothing wrong with whitespace in attribute values in XML: roughly speaking, attribute value normalization converts whitespace characters to spaces and collapses adjacent spaces to a single space when a document is parsed, but whitespace is definitely allowed. EDIT: See below for more on this.

Matthew Wilson's approach fails to include whitespace between the possible values, as you mention in your comment thereto. However, his approach is fundamentally sound. The final piece of the jigsaw is your dislike of redundant spaces: these can be eliminated by use of the normalize-space XPath function.

The following stylesheet puts all the bits together - note that it doesn't do anything with its input document, so for testing purposes you can run it against any XML document, or even against itself, to verify that the output meets your requirements.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
  <xsl:variable name="foo0" select="false()"/>
  <xsl:variable name="bar0" select="true()"/>

  <xsl:variable name="foo1" select="true()"/>
  <xsl:variable name="bar1" select="false()"/>

  <xsl:variable name="foo2" select="true()"/>
  <xsl:variable name="bar2" select="true()"/>

  <xsl:template match="/">

    <xsl:variable name="foobar0">
      <xsl:if test="$foo0"> foo</xsl:if>
      <xsl:if test="$bar0"> bar</xsl:if>
    </xsl:variable>

    <xsl:variable name="foobar1">
      <xsl:if test="$foo1"> foo</xsl:if>
      <xsl:if test="$bar1"> bar</xsl:if>
    </xsl:variable>

    <xsl:variable name="foobar2">
      <xsl:if test="$foo2"> foo</xsl:if>
      <xsl:if test="$bar2"> bar</xsl:if>
    </xsl:variable>

    <li>
      <xsl:attribute name="class">
        <xsl:value-of select="normalize-space($foobar0)"/>
      </xsl:attribute>
    </li>
    <li>
      <xsl:attribute name="class">
        <xsl:value-of select="normalize-space($foobar1)"/>
      </xsl:attribute>
    </li>
    <li>
      <xsl:attribute name="class">
        <xsl:value-of select="normalize-space($foobar2)"/>
      </xsl:attribute>
    </li>

  </xsl:template>
</xsl:stylesheet>

EDIT: Further to the question of spaces separating discrete components within the value of an attribute: The XML Spec defines a number of possible valid constructs as attribute types, including IDREFS and NMTOKENS. The first case matches the Names production, and the second case matches the NMTokens production; both these productions are defined as containing multiple values of the appropriate type, delimited by spaces. So space-delimited lists of values as the value of a single attribute are an inherent component of the XML information set.

NickFitz
Actually, my point about the whitespace-separated attribute values is that XML doesn't recognise them as distinct values—it's all one long string (unless I've overlooked some part of XML). That's the reason I always feel slightly dirty when using class attributes to begin with. That said, I do realize it's a lot easier on us coders to just separate the classes with spaces rather than cram in a bunch of `<class>` child elements everywhere.
Jakob
@Jakob: I've added some more details about space-separated lists of values forming a single attribute value at the end of my reply.
NickFitz