Querying XML: Use Cases - Use case REF: queries based on references. (Page 4 of 5 )
References are an important aspect of XML. This use case describes a database in which references play a significant role and contains several representative queries that exploit these references.
Suppose that the file
census.xml contains an element for each person recorded in a recent census. For each person element, the person’s name, job, and spouse (if any) are recorded as attributes. The
spouseattribute is an IDREF-type attribute that matches the spouse element’s ID-type
nameattribute.
The parent-child relationship among persons is recorded by containment in the element hierarchy. In other words, the element that represents a child is contained within the element that represents the child’s father or mother. Due to deaths, divorces, and remarriages, a child might be recorded under either its father or mother (but not both). In this exercise, the term “children of X” includes “children of the spouse of X.” For example, if Joe and Martha are spouses, Joe’s element contains an element Sam, and Martha’s element contains
an element Dave, then both Joe’s and Martha’s children are considered to be Sam and Dave. Each person in the census has zero, one, or two parents.This use case is based on an input document named
census.xml, with the following DTD:
<!DOCTYPE census [
<!ELEMENT census (person*)>
<!ELEMENT person (person*)>
<!ATTLIST person
name ID #REQUIRED
spouse IDREF #IMPLIED
job CDATA #IMPLIED >
]>
The following census data describes two friendly families that have several intermarriages:
<census>
<person name="Bill" job="Teacher">
<person name="Joe" job="Painter" spouse="Martha">
<person name="Sam" job="Nurse">
<person name="Fred" job="Senator" spouse="Jane">
</person>
</person>
<person name="Karen" job="Doctor" spouse="Steve">
</person>
</person>
<person name="Mary" job="Pilot">
<person name="Susan" job="Pilot" spouse="Dave">
</person>
</person>
</person>
<person name="Frank" job="Writer">
<person name="Martha" job="Programmer" spouse="Joe">
<person name="Dave" job="Athlete" spouse="Susan">
</person>
</person>
<person name="John" job="Artist">
<person name="Helen" job="Athlete">
</person>
<person name="Steve" job="Accountant" spouse="Karen">
<person name="Jane" job="Doctor" spouse="Fred">
</person>
</person>
</person>
</person>
</census>
Question 1. Find Martha's spouse:
<xsl:strip-space elements="*"/>
<xsl:template match="person[@spouse='Martha']">
<xsl:copy>
<xsl:copy-of select="@*"/>
</xsl:copy>
</xsl:template>
Question 2. Find parents of athletes:
<xsl:template match="census">
<xsl:variable name="everyone" select="//person"/>
<result>
<!-- For each person with children -->
<xsl:for-each select="$everyone[person]">
<xsl:variable name="spouse"
select="$everyone[@spouse=current()/@name]"/>
<xsl:if test="./person/@job = 'Athlete' or
$spouse/person/@job = 'Athlete'">
<xsl:copy>
<xsl:copy-of select="@*"/>
</xsl:copy>
</xsl:if>
</xsl:for-each>
</result>
</xsl:template> Question 3. Find people who have the same job as one of their parents:
Try it yourself.
Question 4. List names of parents and children who have the same job, and list
their jobs:
<xsl:template match="census">
<xsl:variable name="everyone" select="//person"/>
<result>
<!-- For each person with children -->
<xsl:for-each select="$everyone[person]">
<xsl:variable name="spouse"
select="$everyone[@spouse=current()/@name]"/>
<xsl:apply-templates select="person[@job = current()/@job]">
<xsl:with-param name="parent" select="@name"/>
</xsl:apply-templates>
<xsl:apply-templates select="person[@job = $spouse/@job]">
<xsl:with-param name="parent" select="$spouse/@name"/>
</xsl:apply-templates>
</xsl:for-each>
</result>
</xsl:template>
<xsl:template match="person">
<xsl:param name="parent"/>
<match parent="{$parent}" child="{@name}" job="{@job}"/>
</xsl:template>
Question 5. List name-pairs of grandparents and grandchildren:
<xsl:template match="census">
<xsl:variable name="everyone" select="//person"/>
<result>
<!-- For each grandchild -->
<xsl:for-each select="$everyone[../../../person]">
<!-- Get the grandparent1 (guaranteed to exist by for each -->
<grandparent name="{../../@name}" grandchild="{@name}"/>
<!-- Get the grandparent2 is grandparent1's spouse if listed -->
<xsl:if test="../../@spouse">
<grandparent name="{../../@spouse}" grandchild="{@name}"/>
</xsl:if>
<!-- Get the names of this person's parent's spouse
(i.e. their mother or father as the case may be) -->
<xsl:variable name="spouse-of-parent" select="../@spouse"/>
<!-- Get parents of spouse-of-parent, if present -->
<xsl:variable name="gp3"
select="$everyone[person/@name=$spouse-of-parent]"/>
<xsl:if test="$gp3">
<grandparent name="{$gp3/@name}" grandchild="{@name}"/>
<xsl:if test="$gp3/@spouse">
<grandparent name="{$gp3/@spouse}" grandchild="{@name}"/>
</xsl:if>
</xsl:if>
</xsl:for-each>
</result>
</xsl:template>
Question 6. Find people with no children:
<xsl:strip-space elements="*"/>
<xsl:template match="census">
<xsl:variable name="everyone" select="//person"/>
<result>
<xsl:for-each select="$everyone[not(./person)]">
<xsl:variable name="spouse"
select="$everyone[@name = current()/@spouse]"/>
<xsl:if test="not ($spouse) or not($spouse/person)">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</result>
</xsl:template>
Question 7. List the names of all Joe's descendants. Show each descendant as an
element with the descendant's name as content and his or her marital status and
number of children as attributes. Sort the descendants in descending order by
number of children and secondarily in alphabetical order by name:
<xsl:variable name="everyone" select="//person"/>
<xsl:template match="census">
<result>
<xsl:apply-templates select="//person[@name='Joe']"/>
</result>
</xsl:template>
<xsl:template match="person">
<xsl:variable name="all-desc">
<xsl:call-template name="descendants">
<xsl:with-param name="nodes" select="."/>
</xsl:call-template>
</xsl:variable>
<xsl:for-each select="exsl:node-set($all-desc)/*">
<xsl:sort select="count(./* | $everyone[@name = current()/@spouse]/*)"
order="descending" data-type="number"/>
<xsl:sort select="@name"/>
<xsl:variable name="mstatus"
select="normalize-space(
substring('No Yes',boolean(@spouse)* 3+1,3))"/>
<person married="{$mstatus}"
nkids="{count(./* | $everyone[@name = current()/@spouse]/*)}">
<xsl:value-of select="@name"/>
</person>
</xsl:for-each>
</xsl:template>
<xsl:template name="descendants">
<xsl:param name="nodes"/>
<xsl:param name="descendants" select="/.."/>
<xsl:choose>
<xsl:when test="not($nodes)">
<xsl:copy-of select="$descendants"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="descendants">
<xsl:with-param name="nodes" select="$nodes[position() > 1] |
$nodes[1]/person | id($nodes[1]/@spouse)/person"/>
<xsl:with-param name="descendants" select="$descendants |
$nodes[1]/person | id($nodes[1]/@spouse)/person"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template> This example accomplishes the query, but it isn’t pretty! The complications come from the need to collect all descendants into a node set so they can be sorted. This forces the use of the node-set extension function. It also means that theid( )function will not help find the spouse because it only works relative to the node’s document. However, the nodes are copies of the original nodes and thus do not have the same document. This situation forces you to go after the spouse elements in a much more cumbersome way by searching for a variable containing all person elements. Contrast this solution to the following XQuery solution:
define function descrip (element $e) returns element
{
let $kids := $e/* union $e/@spouse=>person/*
let $mstatus := if ($e[@spouse]) then "Yes" else "No"
return
<person married={ $mstatus } nkids={ count($kids) }>{ $e/@name/text() }
</person>
define function descendants (element $e)
{
if (empty($e/* union $e/@spouse=>person/*))
then $e
else $e union descendants($e/* union $e/@spouse=>person/*)
}
descrip(descendants(//person[@name = "Joe"])) sortby(@nkids descending, .)
Next: Further Discussion of the W3C XML Query-Use Cases in XSLT >>
More XML Tutorials Articles
More By O'Reilly Media
|
This article is excerpted from chapter nine of the XSLT Cookbook, Second Edition, written by Sal Mangano (O'Reilly; ISBN: 0596009747). Copyright © 2007 O'Reilly Media, Inc. Check it out today at your favorite bookstore. Buy this book now.
|
|