Dances with XML
XML. It seems that most developers either love it or hate it. However you feel about it, XML is wide-spread enough that every Delphi developer is likely to reckon with it some time during their career.
Delphi provides 3 processor implementations (called DOM vendors) to choose from, all housed under a common API (units xmldom, XMLIntf and XMLDoc. The 3 stock vendors are:
- Xerces XML;
- ADOM XML v4; and
Xerces works on Win32 and Linux targets.ADOM is an all-native code solution so it works on all platforms. MSXML is the default, and naturally it works only on Win32. I don’t know if Xerces or MSXML will work on Win64. Perhaps a reader can advise? For the rest of this entry, I am going to assume that the reader is/(will be) using the IXMLDocument with the MSXML vendor, targeting the Win32 platform.
Any one who has used Delphi’s stock library for navigating through XML documents will be struck by one disconcerting observation: With our expectations spoiled by ease at which we Delphi programmers can manipulate strings, records, objects, interfaces and our own cleverly defined library API’s, it takes a disproportionately large amount of code to achieve only small things in terms of XML navigation. And if you want robust code that handles missing or unexpected structures in your XML document, OMG, it’s so much code to write! I am not going to provide demonstration code to prove my point – I leave that as an exercise to the reader.
At this point, some readers will be thinking “Use a library with fluent API!”. Yes, this helps to a degree, but not enough. And to date, there is no FOSS or commercial publicly available Delphi XML processor library with a fluent API, that can both read and write to the document. With the stock libraries, navigation of XML documents is like wrestling a crocodile. It shouldn’t be like that. It should be like dancing on air. And it can be. The answer is XPATH.
XPATH is a simple language to describe the selection of sequences of nodes within an XML document, with respect to a focal point and an XPATH expression. XPATH is to XML what SQL is to relational databases. With a little help from a small library unit, we can leverage the power of XPATH within Delphi and make the experience of navigating documents robustly, like dancing on air. With the MSXML vendor, we are sadly limited to XPATH 1.0 – no XPATH 2.0 available from Microsoft. But XPATH 1.0 should be enough for most needs. The remainder of this entry will be a series of small problems and a demonstration of how simple it is to solve with XPATH.
Use case 1: “Hide the Sausage”
<places> <!-- Where is that sausage hiding? --> <under-the-bed /> <in-the-closet> <sausage snag-count='3'' /> </in-the-closet> <sofa /> <larder> <sausage snag-count='1' /> </larder> <laundry-room /> </places>
In the following sample problems we have declared:
var Root: IXMLNode; // The document node for the above XML document loaded into IXMLDocument. Run : IXMLNode; // Just a loop variable.
Find all the hiding places for sausages!
for Run in XFocus(Root) / 'places/*[sausage]' do PutNode( Run)
Is there any place with exactly one sausage?
if '*/*/sausage[@snag-count=1]' in XFocus(Root) then Put(' Yes, there is.') else Put(' No, there isn''t.')
How many sausages are there in the house? Don’t assume the limited structure in Use Case 1. The document has an arbitary structure and those sausages can be hiding any-where at any level in the document.
Sum := 0; for Run in XFocus(Root) / '//sausage/@snag-count' do Inc( Sum, StrToIntDef( Run.Text, 0))
Note, although valid XPATH, we can’t do the following …
Sum := 0; for Node in XFocus( Root) / ′sum(//Sausage/@snag-count)′ do Sum := Node.Value
.. because of a limitation imposed by our current vendor (MSXML) that we can only use XPATH that returns node-sets, not atomic values.
Where are those hiding places again? Produce a HTML list sorted by sausage count ascending.
Whilst not technically challenging, solving this using IXMLDocument might take a page or two code. With a little help from a good friend of XPATH, namely XSLT, this looks line a one liner …
Put( (XFocus(Root) * ppTransform.Content).XML.Text)
…well…, not quiet. The ppTransform.Content mentioned above is this string. Its an XSLT transform. But all-in-all its not much code for what it does.
<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'> <xsl:output method='xml' indent='yes' omit-xml-declaration='yes' /> <xsl:template match='/'> <ul> <xsl:apply-templates select='*/*[sausage]'> <xsl:sort select='sum( sausage/@snag-count)' data-type='number' order='ascending' /> </xsl:apply-templates> </ul> </xsl:template> <xsl:template match='*'> <li> <xsl:value-of select='name()'/> </li> </xsl:template> </xsl:stylesheet>
If from these samples you are not impressed by the power and simplicity of XPATH in Delphi to solve navigational problems, then this is probably a picture of you. (Below left).