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