If you've every dealt with a Mac OS X property list XML file (.plist), such as that produced when exporting an iTunes playlist file, then you've maybe noticed the structure is a pain to work with, especially with respect to XSL transformations.
Consider an example:
<plist version="1.0">
<dict>
<key>Major Version</key><integer>1</integer>
<key>Minor Version</key><integer>1</integer>
<key>Application Version</key><string>7.3.1</string>
...
The xpath expression to select the Application Version looks something like:
/plist/dict/string[preceding-sibling::key[1] = 'Application Version']
All those selectors using "preceding-sibling" are a pain to write and less-than-clear. I think it's helpful to first transform the plist into a more useful form first. What if we could make the above plist snippet look like the following:
<?xml version="1.0"?>
<plist version="1.0">
<dict>
<entry key="Major Version">
<integer>1</integer>
</entry>
<entry key="Minor Version">
<integer>1</integer>
</entry>
<entry key="Application Version">
<string>7.3.1</string>
</entry>
...
With this new form, we can now rewrite our selector as:
/plist/dict/entry[@key = 'Application Version']/string
It's more concise, easier to write, and clear about what is actual going on in the code. I like it. We can use XSL to transform any plist XML file into this new form. The transform is as follows:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:apply-templates mode="element" />
</xsl:template>
<xsl:template mode="element" match="dict">
<xsl:copy>
<xsl:for-each select="@*">
<xsl:attribute name="{name()}">
<xsl:value-of select="." />
</xsl:attribute>
</xsl:for-each>
<xsl:apply-templates mode="dict" select="key" />
</xsl:copy>
</xsl:template>
<xsl:template mode="dict" match="key">
<xsl:element name="entry">
<xsl:attribute name="key">
<xsl:value-of select="." />
</xsl:attribute>
<xsl:apply-templates mode="element" select="following-sibling::*[1]" />
</xsl:element>
</xsl:template>
<xsl:template mode="element" match="*" >
<xsl:copy>
<xsl:for-each select="@*">
<xsl:attribute name="{name()}">
<xsl:value-of select="." />
</xsl:attribute>
</xsl:for-each>
<xsl:apply-templates mode="element" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
In Mac OS X, you can apply this transform right at the command line with the xsltproc command.