Transforming XML Schemas
A W3C XML Schema (WXS) document contains valuable information that can be used throughout a system or application, but the complexity that WXS allows can make this difficult in practice. XSLT, however, can concisely and efficiently manipulate WXS documents in order to perform a number of tasks, including creating HTML input forms, generating query interfaces, documenting data structures and interfaces, and controlling a variety of user interface elements.
As an example, this article describes an XSLT document which creates an XHTML form based on the WXS definition of a complex element. For brevity and clarity, the article omits several WXS and XHTML form aspects, including attribute definitions, keys, imported/included schemas, and qualified name issues. How these additional features are implemented can depend greatly on your use of WXS and on your application. However, building a stylesheet that handles every possible WXS feature can be quite an effort and may often be unnecessary.
Much of the information -- occurrence constraints, data types,
special restrictions, and enumerations -- needed to build an XHTML
form is already contained in a WXS document. Missing bits such as
label text and write restrictions can be added into WXS's
<annotation> element.
The stylesheet will perform four distinct tasks:
- Find the definition of the target complex element that we want.
- Build a form element for the target element.
- Find the definitions of the target element's valid children.
- Build an input element for each of the simple child elements.
In order to do this, the stylesheet will apply different template
rules to similar WXS elements depending on the task at hand. To
make this possible, the stylesheet will use a separate mode for each task.
The modes are default, targetElementForm,
findChildNodes, and childNodeInput.
Using a separate mode for each task will also help when modifying,
expanding, or deciphering this stylesheet.
Seek and Transform I
When looking someone up in the phonebook, unique names are very helpful: "Smith, Julian Forsythe" is better than "Smith, J". Similarly we need to name the target element uniquely. This can be tricky because an element name is not necessarily unique; multiple locally defined elements or attributes can share the same name. So for inspiration, we can look to NUNs (Normalized Universal Names), which are path statements used to uniquely identify components of a schema. NUNs made their first appearance in the Schema Formal Description Working Draft. The W3C recently issued a formal draft of NUNs under the new name Schema Component Descriptors (SCD).
For this exercise we will use a simplified form of NUN based on
element names to identify the target element and pass it
into the stylesheet as a parameter.
So, for example, the NUN for the definition of the global
<actionItem> element would be
"element::actionItem", and the NUN for an element
<employee>, local to the global
<workGroup> element, would be
"element::workGroup/element::employee".
This article will preface the description of a set of template
rules with a model that is based on UML
Activity Diagrams. In these diagrams template rules are shown
as states; modes are shown as composite states;
<apply-templates> actions are shown as
arrows; branching by template rule match patterns are shown as
hollow circles; and other logical branches and the XSLT entry
point are shown as solid circles.
The first template to fire, match='xs:schema', will
extract the first element name from the NUN string. The first
element name in the NUN will always be a global element, therefore
its definition will be a child of the
<xs:schema> element.
<xsl:param name="targetNUN"/>
<xsl:template match="xs:schema">
<xsl:variable name="NUNModified" select="concat($targetNUN,'/')"/>
<xsl:variable name="NUNToken"
select="substring-after(substring-before($NUNModified,'/'),'element::')"/>
<xsl:apply-templates select="xs:element[@name=$NUNToken]">
<xsl:with-param name="NUNRemain" select="substring-after($NUNModified,'/')"/>
</xsl:apply-templates>
</xsl:template>
The other templates in the mode will then recursively parse each element name in the NUN, drilling down through the nested local element definitions until all names from the NUN have been parsed and the target element has been found.
<xsl:template match="xs:element[@name]">
<xsl:param name="NUNRemain"/>
<xsl:choose>
<xsl:when test="string-length($NUNRemain)=0">
<xsl:apply-templates select="." mode="targetElementForm"/>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="xs:complexType/*">
<xsl:with-param name="NUNRemainder"
select="substring-after($NUNRemain,'/')"/>
<xsl:with-param name="NUNToken"
select="substring-after(substring-before($NUNRemain,'/')
,'element::')"/>
</xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="xs:sequence|xs:choice|xs:all">
<xsl:param name="NUNRemain"/>
<xsl:param name="NUNToken"/>
<xsl:apply-templates
select="xs:sequence|xs:choice|xs:all|xs:element[@name=$NUNToken]">
<xsl:with-param name="NUNRemain" select="$NUNRemain"/>
<xsl:with-param name="NUNToken" select="$NUNToken"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="*"/>
Powers of Annotation
Now that the stylesheet has found the target element and has begun
creating a form, it needs an informative title. WXS's
<annotation> element lets us associate
human-friendly information and almost any kind of application
specific information we want in the schema. A detailed
description of the data contained in a simple element can be
placed into <documentation>. For our form, we
can also use the <appinfo> element to indicate
whether an element is read-only and how it should be labeled.
The targetElementForm mode contains a single template
rule, which builds the form container. It then enters the
findChildNodes mode by selecting the anonymous
or named complex type definitions for the element.
<xsl:template match="xs:element[@name]" mode="targetElementForm">
<b>New <xsl:value-of select="xs:annotation/xs:appinfo/frm:label"/></b><br/>
<xsl:copy-of select="xs:annotation/xs:documentation/node()"/>
<form>
<input type="hidden" name="%%elementNUN"
value="{/xs:schema/@targetNamespace}#{$targetNUN}"/>
<table>
<xsl:apply-templates select="*|/xs:schema/xs:complexType[@name=current()/@type]"
mode="findChildNodes"/>
<tr><td colspan="2">
<input type="submit" value="Save Changes"/>
</td></tr>
</table>
</form>
</xsl:template>
<xsl:template match="*" mode="targetElement"/>
WXS's <annotation> element will also be useful
later on when building XHTML input elements for the target
element's children.
Seek and Transform II
The next step is to find the definitions for the target element's child elements. Fortunately, with a bit of recursion, XSLT can easily handle the complex content models that WXS makes possible.
The templates of the findChildNodes mode will
recursively walk through the content model of the target element
and apply the template rules to locate the definitions of its
child nodes. These few templates can handle named types,
sequences, group references, and type extensions. Though not
shown in these examples, a stylesheet could use these templates to
track
occurrence constraints and determine whether a child element
is required or optional.
The first template matches content model elements that the stylesheet will just step through.
<xsl:template match="xs:complexType|xs:complexContent|
xs:sequence|xs:all|xs:group[@name]"
mode="findChildNodes">
<xsl:apply-templates select="*" mode="findChildNodes"/>
</xsl:template>
References to global group and element definitions are resolved by
the following two templates. Included and imported schemas are
not shown in this example. However, one method for handling
referenced WXS documents is to build a node set of the referenced
WXS documents' <xs:schema> elements with the
help of XSLT's document()
function. You can then use your favorite XSLT 1.0 processor's
node-set() extension function (.NET,
MSXML,
XALAN,
SAXON,
EXSL)
to assign the node set to a global variable, which you can use in
place of /xs:schema in the following templates.
<xsl:template match="xs:group[@ref]" mode="findChildNodes">
<xsl:apply-templates select="/xs:schema/xs:group[@name=current()/@ref]"
mode="findChildNodes"/>
</xsl:template>
<xsl:template match="xs:element[@ref]" mode="findChildNodes">
<xsl:apply-templates select="/xs:schema/xs:element[@name=current()/@ref]"
mode="findChildNodes"/>
</xsl:template>
Pages: 1, 2 |