Saturday, October 25, 2008

Apple sucks at XML

OK, I've officially had it with Apple. Steve Jobs may have style down cold, but his programmers were smoking something fierce when they designed the XML format for their so-called Property list. Don't let that Wikipedia page fool you on the apparent simplicity of the format. Take a look at one of Apple's own samples. Still not convinced? How about a real-world use-case: the emoticon definition file for an Adium theme, a portion of which is reproduced below:
<plist version="1.0">
<dict>
<key>AdiumSetVersion</key>
<integer>1</integer>
<key>Emoticons</key>
<dict>
<key>amazing.png</key>
<dict>
<key>Equivalents</key>
<array>
<string>=-o</string>
<string>=-O</string>
<string>:-o</string>
<string>:-O</string>
</array>
<key>Name</key>
<string>Surprised</string>
</dict>
<key>anger.png</key>
<dict>
<key>Equivalents</key>
<array>
<string>&gt;:o</string>
<string>:-@</string>
<string>:@</string>
<string>X(</string>
</array>
<key>Name</key>
<string>Angry</string>
</dict>
<key>bad_egg.png</key>
<dict>
<key>Equivalents</key>
<array>
<string>&gt;-[</string>
<string>&gt;-(</string>
</array>
<key>Name</key>
<string>Nervous</string>
</dict>
(...snip...)
</dict>
</dict>
</plist>
Do you see what the problem is? For those of you playing at home, here's a hint: how would you write an XPath expression to obtain the "equivalents" of a given image file?

Yes, it's not impossible to grab a value for a given key, but did they have to make it so hard when XML can express the same idea in a much easier format? Or, rather, did they have to be so lazy when writing the code that serializes these property lists to/from XML?

In any case, if you ever have the need to process an XML file created by an Apple program, the following stylesheet will (likely) help restore your sanity. Simply pre-process the XML with my stylesheet and then your XML code or stylesheet will be much easier to write (and read!):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:output method="xml" encoding="utf-8" indent="yes" />

<xsl:template match="* | @* | node()">
<xsl:copy>
<!-- if the previous sibling is a 'key' element -->
<xsl:if test="name(preceding-sibling::*[position()=1]) = 'key'">
<xsl:attribute name="key">
<xsl:value-of select="preceding-sibling::key[position()=1]/text()" />
</xsl:attribute>
</xsl:if>
<xsl:apply-templates select="* | @* | node()" />
</xsl:copy>
</xsl:template>

<xsl:template match="key" />

</xsl:stylesheet>
For an example, let's take another look at the sample XML I showed earlier and compare that with the XML sexiness that is generated by applying my stylesheet against it (some spacing was added to the "after" version to better illustrate how they compare to each other):
BeforeAfter
<plist version="1.0">
<dict>
<key>AdiumSetVersion</key>
<integer>1</integer>
<key>Emoticons</key>
<dict>
<key>amazing.png</key>
<dict>
<key>Equivalents</key>
<array>
<string>=-o</string>
<string>=-O</string>
<string>:-o</string>
<string>:-O</string>
</array>
<key>Name</key>
<string>Surprised</string>
</dict>
<key>anger.png</key>
<dict>
<key>Equivalents</key>
<array>
<string>&gt;:o</string>
<string>:-@</string>
<string>:@</string>
<string>X(</string>
</array>
<key>Name</key>
<string>Angry</string>
</dict>
<key>bad_egg.png</key>
<dict>
<key>Equivalents</key>
<array>
<string>&gt;-[</string>
<string>&gt;-(</string>
</array>
<key>Name</key>
<string>Nervous</string>
</dict>
(...snip...)
</dict>
</dict>
</plist>
<plist version="1.0">
<dict>

<integer key="AdiumSetVersion">1</integer>

<dict key="Emoticons">

<dict key="amazing.png">

<array key="Equivalents">
<string>=-o</string>
<string>=-O</string>
<string>:-o</string>
<string>:-O</string>
</array>

<string key="Name">Surprised</string>
</dict>

<dict key="anger.png">

<array key="Equivalents">
<string>&gt;:o</string>
<string>:-@</string>
<string>:@</string>
<string>X(</string>
</array>

<string key="Name">Angry</string>
</dict>

<dict key="bad_egg">

<array key="Equivalents">
<string>&gt;-[</string>
<string>&gt;-(</string>
</array>

<string key="Name">Nervous</string>
</dict>
(...snip...)
</dict>
</dict>
</plist>

...isn't that a sight for sore eyes? You're welcome.