« Red | [dandruff::main] | Testimony »

Playing with siblings

Boris presented me with a problem yesterday: how to extract certain values from a plist XML document.

This isn't nice, semantically meaningful XML - for example:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" 
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
  <dict>
    <key>Date</key>
    <date>2003-02-10T16:14:16Z</date>
    <key>HTML</key>
    <string>Hello!</string>
    <key>Identifier</key>
    <integer>10</integer>
    <key>Message</key>
    <string>Hello!</string>
    <key>Outgoing</key>
    <integer>1</integer>
    <key>Type</key>
    <integer>1</integer>
  </dict>
  <dict>
    <key>Date</key>
    <date>2003-02-10T16:14:25Z</date>
    <key>Identifier</key>
    <integer>10</integer>
    <key>Message</key>
    <string>Salut!</string>
    <key>Outgoing</key>
    <integer>0</integer>
    <key>Type</key>
    <integer>1</integer>
  </dict>
</array>
</plist>

From this, I've been asked to extract 'date', the contents of 'string' just after a key which contains 'Message', and the contents of 'integer' just after a key which contains 'Outgoing'. So an extract from the above example would look something like:

Date: 2003-02-10T16:14:16Z
Message: Hello!
Outgoing: 1

Having to rush out to Rigoletto, I first implemented a couple of really bad solutions in Perl (#1 and #2) - I got as far as printing the results as text and not HTML (which is a small step away hence not a big deal). However, I didn't like the look of these.

So, after getting my head a bit more around XSL - trying to work out how on earth I'm supposed to select the value of an element based on the value a sister element - this is what I got, with the main part looking like this:

<xsl:template match="/plist/array">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
  <head>
    <title>Plist to HTML</title>
  </head>
  <body>
    <xsl:apply-templates select="*" />
   </body>
</html>
</xsl:template>

<xsl:template match="dict">
  <p>
  <strong>Date:</strong> <xsl:value-of select="date" /><br />
  <xsl:apply-templates select="key" />
  </p>
</xsl:template>

<xsl:template match="key">
  <xsl:if test="text()='Message'">
    <strong>Message:</strong> <xsl:value-of select="following-sibling::string" /><br />
  </xsl:if>

  <xsl:if test="text()='Outgoing'">
    <strong>Outgoing:</strong> <xsl:value-of select="following-sibling::integer" />
  </xsl:if>
</xsl:template>

Within the <dict> container, we print the value of <date> as we see it. Then we do something special at the level of <key>. When we see a <key> which has 'Message' in its text, we just print the next <string> we see, using the 'following-sibling' axis. Same goes for 'Outgoing'. Here's the dirty evidence, done using PHP.

You know, it worries me a little bit when the solution looks so simple. :)

Btw, here's a friendlier link to this entry.

Posted by sniffles at February 14, 2003 01:54 PM