
XSLT Recipes for Interacting with XML Data
by Jon Udell
August 13, 2003
In last month's column, "The
Document is the Database", I sketched out an approach to building a
web-based application backed by pure XML (and as a matter of fact, XHTML)
data. I've continued to develop the idea, and this month I'll explore some
of the XSLT-related recipes that have emerged.
Oracle's Sandeepan Banerjee, director of product management for Oracle
Server Technologies, made a fascinating comment when I interviewed him
recently. "It's possible," he said, "that developers will want to stay
within an XML abstraction for all their data sources". I suppose my
continuing (some might say obsessive) experimentation with XPath and XSLT
is an effort to find out what that would be like.
It's true that these technologies are still somewhat primitive and
rough around the edges. Some argue that we've got to leapfrog over them to
XQuery or to some XML-aware programming language in order to colonize the
world of XML data. But it seems to me that we can't know where we need to
go until we fully understand where we are. And there's no better reality
check than a practical application. In order to set the stage for this
month's installment, let's review the XHTML data formats for an evolving
application that gathers and displays information about conference
speakers and sessions. Here's the format of the speakers file:
<?xml version="1.0"?>
<body>
<style>
.speaker { margin-bottom: 10px }
.speakerName { font-weight: bold }
.speakerTitle { font-style: italic }
</style>
<speakers>
<div class="speaker" email="jon_udell@infoworld.com">
<div class="speakerName">
Jon Udell
</div>
<div class="speakerTitle">
lead analyst, InfoWorld
</div>
<div class="speakerBio">
<span class="bio"><p>See http://udell.roninhouse.com/bio.html</p></span>
</div>
</div>
</speakers>
</body>
Figure 1. The speakers "database"
And here's the format of the sessions file:
<?xml version="1.0"?>
<body>
<style>
.session { margin-bottom: 10px }
.sessionTitle { font-weight: bold }
.sessionTrack { font-style: italic }
</style>
<sessions>
<div class="session" id="01" day="3" start="10:30" end="12:00">
<div class="sessionTitle">Grade-school CMS lessons</div>
<div class="sessionTrack">general</div>
<div class="speakerEmail">jon_udell@infoworld.com</div>
<div class="speakerEmail">paul@zope-europe.org</div>
<div class="sessionDescription">
<span class="description"><p>Paul's introduction, Jon's keynote</p></span></div>
</div>
</sessions>
</body>
Figure 2. The sessions "database"
More from Jon Udell
The Beauty of REST
Lightweight XML Search Servers, Part 2
Lightweight XML Search Servers
The Social Life of XML
Interactive Microcontent
Note that both of these formats suffer from a syndrome that might be
called "div-itis". I've chosen to maintain a browser-friendly XHTML format
to ensure that the raw data files are always viewable without special
transformation. In fact, that was never completely true: the files contain
some fictitious attributes (e.g. 'id' and 'start' in the speakers file)
that aren't kosher in HTML, and don't display in the browser.
On balance, I still think it's handy to be able to browse the data file
directly. But the techniques I'm exploring in the last column and this one
don't require use of XHTML. Indeed, they'll work more easily when element
names, rather than attributes, carry the semantics.
It's crucial to be able to visualize data. As browsers are increasingly
able to apply CSS stylesheets to arbitrary XML, the XHTML constraint
becomes less important. The Microsoft browser has been able to do
CSS-based rendering of XML for a long time. Now Mozilla can too. Safari
doesn't, yet, but I'll be surprised if it doesn't gain that feature
soon. So while I'm sticking with XHTML for now, that may be a transient
thing. Of more general interest are the ways in which XPath and XSLT can
make XML data (of any flavor) interactive.
An XSLT-driven form generator
Here's a form, driven by both of the data sources, that's used to
update information for a session:

Figure 3. The session update form.
The script that builds the form is based on the Zope XSLT wrapper we
explored last month. It accepts one argument: the id of a session. That id
is used to create a series of form widgets of different types, drawing on
different data sources:
nametypedata source(s)
sessionDaySELECTcurrent session element and ZODB property
sessionTitleINPUTcurrent session element
sessionTrackSELECTcurrent session element and ZODB property
sessionStartSELECTcurrent session element and ZODB property
sessionEndSELECTcurrent session element and ZODB property
sessionSpeakersSELECTcurrent session element and related speaker elements
sessionDescriptionTEXTAREAcurrent session element
Figure 3. Widget types and data sources for the session update form.
Two of these widgets -- sessionTitle and sessionDescription -- are
easily made using XPath expressions to pick out the corresponding data
from the sessions file, as shown in bold Figure 5. But the rest require
more spelunking.
datafile = 'sessions.html'
id = context.REQUEST.form['id']
xsl = '''%s
<xsl:template match="//*[@id='%s']">
<form method="post" action="updateSession">
<div><input type="hidden" name="sessionId" value="{@id}"/></div>
<div>sessionDay: <select name="sessionDay">
%s
</select></div>
<div>sessionTitle: <input size="70"
name="sessionTitle" value="{*[@class='sessionTitle']}" /> </div>
<div>sessionTrack: <select name="sessionTrack">
%s
</select></div>
<div>sessionStart: <select name="sessionStart">
%s
</select></div>
<div>sessionEnd: <select name="sessionEnd">
%s
</select></div>
<div>sessionSpeakers: %s </div>
<div>sessionDescription: <textarea rows="10" cols="70" name="sessionDescription">
<xsl:copy-of select="*[@class='sessionDescription']/*" />
</textarea></div>
<div><input value="updateSession" type="submit"/></div>
</form>
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>'''
xsl = xsl % (context.xsltPreamble(), id,
context.makeList('sessionDays', "@day"),
context.makeList('sessionTracks', "*[@class='sessionTrack']"),
context.timesAsSelectWidget ( context.sessionsAsDict()[id]['start'] ),
context.timesAsSelectWidget ( context.sessionsAsDict()[id]['end'] ),
context.speakersAsSelectWidget(id),
)
result = context.xslt(xsl, datafile)
result = context.adjustSelectWidget ( result )
Figure 5.
Because this is a Zope application, the SELECT widgets for sessionDay
and sessionTrack can use lists of values stored as ZODB properties. That
means an authorized administrator -- a non-programmer, presumably -- can
edit the lists using Zope's through-the-web management interface.