Querying XML: Use Cases - Use case PARTS: recursive parts explosion.
(Page 3 of 5 )
This use case illustrates how a recursive query might can construct a hierarchical document of arbitrary depth from flat structures stored in a database.
This use case is based on a “parts explosion” database that contains information about how parts are used in other parts.
The input to the use case is a “flat” document in which each different part is represented by a<part>element withpartidand name attributes. Each part may or may not be part of a larger part; if so, thepartidof the larger part is contained in apartof attribute. This input document might be derived from a relational database in which each part is represented by a table row withpartidas primary key andpartofas a foreign key referencingpartid.
The challenge of this use case is to write a query that converts the “flat” representation of the parts explosion, based on foreign keys, into a hierarchical representation in which part containment is represented by the document structure.
The input data set uses the following DTD:
<!DOCTYPE partlist[ <!ELEMENT partlist (part*)> <!ELEMENT part EMPTY> <!ATTLIST part partid CDATA #REQUIRED partof CDATA #IMPLIED name CDATA #REQUIRED> ]>
Although thepartidandpartofattributes could have been of type ID and IDREF, respectively, in this schema they are treated as character data, possibly materialized in a straightforward way from a relational database. Eachpartofattribute matches exactly onepartid. Parts having nopartofattribute are not contained in any other part.
The output data conforms to the following DTD:
<!DOCTYPE parttree [ <!ELEMENT parttree (part*)> <!ELEMENT part (part*)> <!ATTLIST part partid CDATA #REQUIRED name CDATA #REQUIRED> ]>
Sample data conforming to that DTD might look like this:
<?xml version="1.0" encoding="ISO-8859-1"?> <partlist> <part partid="0" name="car"/> <part partid="1" partof="0" name="engine"/> <part partid="2" partof="0" name="door"/> <part partid="3" partof="1" name="piston"/> <part partid="4" partof="2" name="window"/> <part partid="5" partof="2" name="lock"/> <part partid="10" name="skateboard"/> <part partid="11" partof="10" name="board"/> <part partid="12" partof="10" name="wheel"/> <part partid="20" name="canoe"/> </partlist> Question 1. Convert the sample document from "partlist" to "parttree" format (see the DTD section for definitions). In the result document, part containment is represented by containment of one <part> element inside another. Each part that is not part of any other part should appear as a separate top-level element in the output document: <xsl:template match="partlist"> <parttree> <!-- Start with the part that is not part of anything --> <xsl:apply-templates select="part[not(@partof)]"/> </parttree> </xsl:template>
<xsl:template match="part"> <part partid="{@partid}" name="{@name}"> <xsl:apply-templates select="../part[@partof = current()/@partid]"/> </part> </xsl:template> It turns out that this sort of transformation is easier to code and understand in XSLT than in XQuery. For comparison, here is the XQuery solution offered by the W3C paper: define function one_level (element $p) returns element { <part partid="{ $p/@partid }" name="{ $p/@name }" > { for $s in document("partlist.xml")//part where $s/@partof = $p/@partid return one_level($s) } </part> }
<parttree> { for $p in document("partlist.xml")//part[empty(@partof)] return one_level($p) } </parttree>
Even without a detailed understanding of XQuery, you should be able to see that the XQuery solution is needed to explicitly implement the recursion while XSLT’sapply-templatesand pattern matching allow a more declarative solution. Granted, the difference is not that dramatic, but I find XSLT more elegant for this type of problem.