[Update: see below]. A few years ago, Eric van der Vlist put together a proof of concept XML schema language called Examplotron. The clever part of Examplotron is that the schema for a given XML document is that document itself; a document is its own schema. This allows schemas to be designed by writing down example documents (examplotron, get it?) which can then be generalised automatically to produce a RELAX NG schema for those documents and other documents like them. Clever. Now, what if XPath worked like that?
XPath can be used to write patterns or selectors that match or select parts of an XML document for further processing. However, XPath itself is not XML, it is a textual query language that combines the syntax of UNIX file paths with SQL-like predicates. Taking inspiration from Examplotron, we can try defining patterns to match fragments of XML by writing literal fragments of XML. For example, the pattern equivalent to the XPath
foo/bar/baz would be:
<foo> <bar> <baz/> </bar> </foo>
This pattern seems fairly obvious, although it is a bit more verbose than the original XPath. How about a pattern to match a
<h1> element followed by a
<p> element? We might use this to find the first paragraph of an XHTML document, perhaps to apply some special formatting to it.
In this case, the pattern looks a little clearer than the equivalent XPath,
h1/following-sibling::p. On a brief tangent, CSS3 actually has a direct sibling selector to express this:
h1 + p. We have implemented this selector in Prince and it is quite handy for situations like this.
While the above pattern is easy to understand, it’s not clear what we can do with it once it matches some elements. The XPath and the CSS selector match only one element, the
<p>, which can then be transformed by an XSLT template or styled with CSS rules. The pattern on the other hand matches a fragment of XML consisting of two consecutive elements, just like the regular expression
ab matches two consecutive characters. As with regular expressions, the obvious thing to do once you’ve found something that matches the pattern is to replace it with something else:
<ex:search> <h1/> <p/> </ex:search> <ex:replace> <h1/> <p class="first"/> </ex:replace>
Now we have two patterns: we search the document for an instance of the first pattern, and replace it with an instance of the second pattern. This is exactly like the regular expression substitution
s/ab/ac/ which will replace
ac, except that instead of matching characters we’re matching XML elements.
There are some details that I’ve skimmed over in this example, such as what if the elements have existing content or attributes, do we copy them across implicitly? What if we want to match an element that doesn’t have a particular attribute? Some patterns cannot be written explicitly, and we would need to introduce some helper elements and attributes in a separate namespace to express what we want. For example, we might want to throw away paragraph elements that have no content:
<ex:search> <p> <ex:empty/> </p> </ex:search> <ex:replace> <!-- nothing --> </ex:replace>
To finish off, here is an example of a pattern for reversing author names in a citation, so that “Smith John” becomes “John Smith”:
<ex:search> <lastName/> <firstName/> </ex:search> <ex:replace> <firstName/> <lastName/> </ex:replace>
When I tried writing this in XSLT, this is the first solution that occurred to me:
<xsl:template match="lastName[following-sibling::firstName]"> <xsl:copy-of select="following-sibling::firstName"/> <xsl:copy-of select="."/> </xsl:template> <xsl:template match="firstName[preceding-sibling::lastName]"> <!-- nothing --> </xsl:template>
Can anyone think of a simpler way to express this transformation in XSLT?
Boy, did I get that wrong. As Andrew Houghton points out below, the XPath that I gave for selecting a
<p> element immediately following an
<h1> element is insufficiently specific, as it will match all of the
<p> elements that occur after the
<h1>, not just the first. This is equivalent to the CSS indirect sibling selector,
h1 ~ p, not the direct sibling selector,
h1 + p.
The obvious but incorrect solution is to add a position predicate to restrict the XPath to select only the first paragraph, like this:
h1/following-sibling::p. However, this will select a
<p> element that does not directly follow the
<h1>, for example the paragraph in this document, which follows a table:
<h1>Heading</h1> <table>...</table> <p>This paragraph does not immediately follow the heading.</p>
To select only paragraphs that immediately follow headings we need to select the first element that follows the heading and then check that it is a paragraph, like this:
h1/following-sibling::*/self::p. Great! It is a lot more verbose than the CSS
h1 + p selector, but at least it should work now.
Except that in XSLT, it doesn’t work. You see, I forgot that XSLT pattern syntax is a restricted subset of XPath, and can only use the child and attribute axes. This means that you cannot write an XSLT template that matches this XPath:
Instead you need to write it the other way around and place the sibling axis inside a predicate:
Finally, the XSLT templates that I wrote to swap the order of
firstName elements also need similar adjustments to their XPaths:
<xsl:template match="lastName[following-sibling::*/self::firstName]"> <xsl:copy-of select="following-sibling::firstName"/> <xsl:copy-of select="."/> </xsl:template> <xsl:template match="firstName[preceding-sibling::*/self::lastName]"> <!-- nothing --> </xsl:template>
As always, mistakes are the portals of discovery. So, the challenge stands: can anyone write an XSLT transform to swap the order of two elements that is simpler, cleaner, and more obvious than this monstrosity?