Converting Schematron to XML Schemas

This article first appeared in a blog on O'Reilly on November 13, 2007.

Noah Mendelsohn on XML-DEV got me thinking again about the obvious dual of the recent blogs here: how to convert Schematron into XSD. Putting aside the natural question of why we might want to do this, here’s my stab at an answer.

Because Schematron is more powerful and more general than XSD, and because it uses different abstractions (phases and patterns rather than a grammar), it is not possible to convert every arbitrary Schematron schema into a useful schema in XSD.

However, it is certainly possible to devise some conventions which allow translation to some extent.

I’ll leave out the abstract declarations, but here is an example for HTML of how it might look. First lets treat the different types of complex content as if they were just like facets.

<sch:pattern role="Declarations">
<sch:rule context="xhtml:html"  role="element-declaration">
   <sch:rule extends="container"  role="element-content-type" />
   <sch:rule extends="metadata" role="attribute-group-reference" />
</sch:rule>

<sch:rule context="xhtml:head"  role="element-declaration">
   <sch:rule extends="container"  role="element-content-type" />
   <sch:rule extends="metadata" role="attribute-group-reference" />
</sch:rule>

<sch:rule context="xhtml:meta"  role="element-declaration">
   <sch:rule extends="empty"  role="element-content-type" />
</sch:rule>

<sch:rule context="xhtml:p"  role="element-declaration">
   <sch:rule extends="mixed"  role="element-content-type" />
   <sch:rule extends="metadata" role="attribute-group-reference" />
   <sch:rule extends="css" role="attribute-group-reference" />
</sch:rule>

<sch:rule context="xhtml:span"  role="element-declaration">
   <sch:rule extends="text"    role="element-content-type" /> <!-- for example-->
   <sch:rule extends="css" role="attribute-group-reference" />
</sch:rule>

<sch:rule context="xhtml:img"  role="element-declaration">
   <sch:rule extends="empty"  role="element-content-type" />
   <sch:rule extends="css" role="attribute-group-reference" />
</sch:rule>

<sch:rule context="xhtml:img/@width"  role="attribute-declaration">
   <sch:rule extends="dimension-type"  role="simple-type" />
</sch:rule>

<sch:rule context="xhtml:img/@height"  role="attribute-declaration">
   <sch:rule extends="dimension-type"  role="attribute-simple-type" />
</sch:rule>

</sch:pattern>

This gives us the framework. In this pattern, we use abstract rules for a mix-in style of multiple inheritance.

You should be able to see how each of these can be mechanically converted into partial XSD declarations for elements and attributes, such as

<xsd;element name="html">
   <xsd:complexType mixed="false" >
      &xsd;:group ref="open" />
     &attributeGroup; ref="metadata" />
   <xsd:complexType>
</xsd:element>

<xsd;element name="meta">
   <xsd:complexType mixed="false" >
     &attributeGroup; ref="open" />
   <xsd:complexType>
</xsd:element>

<xsd;element name="head">
   <xsd:complexType mixed="false" >
      &xsd;:group ref="open" />
     &attributeGroup; ref="metadata" />
   <xsd:complexType>
</xsd:element>

<xsd;element name="p">
   <xsd:complexType mixed="true" >
      &xsd;:group ref="open" />
     &attributeGroup; ref="metadata" />
     &attributeGroup; ref="cvv" />
   <xsd:complexType>
</xsd:element>

<xsd;element name="span">
   <xsd:complexType mixed="true" >
      &xsd;:attributeGroup ref="css" />
   <xsd:complexType>
</xsd:element>

<xsd;element name="img">
   <xsd:complexType mixed="false" >
       <attribute name="height" type="dimension-type" />
       <attribute name="width" type="dimension-type" />
   <xsd:complexType>
</xsd:element>

So far so good. To get better control of optionality of attributes, we could extend a pattern with a different name, I suppose. But the idea is the same: we use the sch:role attribute to provide the metadata needed to allow an effective transformation.

What about content models? In the version above, any element with subelements just has an “open” content model, presumably declared with some wildcard.

This is where abstract patterns can come in. First lets alter the schema we generate to make groups with the same name as the element.

<xsd;element name="html">
   <xsd:complexType mixed="false" >
      &xsd;:group ref="html" />
     &attributeGroup; ref="metadata" />
   <xsd:complexType>
</xsd:element>

<xsd;element name="meta">
   <xsd:complexType mixed="false" >
     &attributeGroup; ref="open" />
   <xsd:complexType>
</xsd:element>

<xsd;element name="head">
   <xsd:complexType mixed="false" >
      &xsd;:group ref="head" />
     &attributeGroup; ref="metadata" />
   <xsd:complexType>
</xsd:element>

<xsd;element name="p">
   <xsd:complexType mixed="true" >
      &xsd;:group ref="p" />
     &attributeGroup; ref="metadata" />
     &attributeGroup; ref="cvv" />
   <xsd:complexType>
</xsd:element>

<xsd;element name="span">
   <xsd:complexType mixed="true" >
      &xsd;:attributeGroup ref="css" />
   <xsd:complexType>
</xsd:element>

<xsd;element name="img">
   <xsd:complexType mixed="false" >
       <attribute name="height" type="dimension-type" />
       <attribute name="width" type="dimension-type" />
   <xsd:complexType>
</xsd:element>

We could have:

<sch:pattern is-a="container"   role="group-declaration">
  <sch:param  name="context" value="xhtml:html"/>
  <sch:param  name="content-model" value="( xhtml:head?, xhtml:body )"/>
  <sch:param  name="required-children" value="xhtml:body"/>
  <sch:param  name="optional-children" value="xhtml:head"/>
</sch:pattern>

<sch:pattern is-a="container"   role="group-declaration">
  <sch:param  name="context" value="xhtml:head"/>
  <sch:param  name="content-model" value="( xhtml:title, xhtml:meta*, (xhtml:style | xhtml:script)* " />
  <sch:param  name="required-children" value="xhtml:title"/>
  <sch:param  name="optional-children" value="xhtml:meta | xhtml:style | xhtml:script"/>
</sch:pattern>

<sch:pattern is-a="container"   role="group-declaration">
  <sch:param  name="context" value="xhtml:p"/>
  <sch:param  name="content-model" value="( i | b | span | img )* "/>
  <sch:param  name="required-children" value=""/>
  <sch:param  name="optional-children" value="i | b | span | img"/>
</sch:pattern>

The purpose of abstract patterns is to allow patterns to be parameterized to bring out and name the parts of the patterns of interest in the schema-developer’s world-view. Create your own schema language!

In this case, we have three uses of abstract patterns, and the role attribute tells our notional converter program that we want to generate a xsd:group from these. (We are giving the element content model in the conventional syntax, so some converter would have to translate between syntaxes, it is just mechanical.) The final two parameters give (actually repeat) the information in the content model in a way that would be more conducive for use in an XPath.

So our convert program would take these and generate

<xsd:group name="html">
   <xsd:sequence>
      <xsd:element ref="head" minOccurs="0" />
      <xsd:element ref="body" minOccurs="0" />
  </xsd:sequence>
</xsd:group>

and so on. Of course, there would be many other approaches possible. But you should be able to see from this example how Schematron can, in fact, be highly declarative, if that is what you want.