Sign In/My Account | View Cart  
advertisement


Listen Print Discuss

Transforming XML Schemas
by Eric Gropp | Pages: 1, 2

When an <extension> is encountered, the stylesheet will process the extension's base and local content models in sequence.

<xsl:template match="xs:extension" mode="findChildNodes">
  <xsl:apply-templates select="/xs:schema/xs:complexType[@name=current()/@base]" 
                       mode="findChildNodes"/>
  <xsl:apply-templates select="*" mode="findChildNodes"/>
</xsl:template>

Once the definition of the child element is found, the template determines whether it is a simple type and exempt from any application specific restrictions. If this is true, it builds the table row, and then applies the templates from the childNodeInput mode to construct the input element.

<xsl:template match="xs:element[@name]" mode="findChildNodes">
  <xsl:if test="not(xs:complexType|
                /xs:schema/xs:complexType[@name=current()/@type]|
                xs:annotation/xs:appinfo/frm:readonly)">

    <tr>
      <td>
        <xsl:choose>
          <xsl:when test="xs:annotation/xs:appinfo/frm:label">	
            <xsl:value-of select="xs:annotation/xs:appinfo/frm:label"/>
          </xsl:when>
          <xsl:otherwise>
            <xsl:value-of select="@name"/>
          </xsl:otherwise>	
        </xsl:choose>

      </td>
      <td>
        <xsl:apply-templates select="." mode="childNodeInput">
          <xsl:with-param name="nodeName" select="@name"/>
        </xsl:apply-templates>

      </td>
    </tr>

  </xsl:if>
</xsl:template>

<xsl:template match="*" mode="findChildNodes"/>

Interpreting Simple Type Definitions

The templates of the simpleInputElement mode will walk the schema to find the base type of each simple element and then output the appropriate XHTML form element for the type. If the element's type is a restriction of a base type, it will further modify the XHTML form element with XHTML or custom attributes.

Model of the childNodeInput mode

The first two template rules are designed to match elements typed with anonymous or namespace defined simple types. The priority attribute of the first template is set to zero, so that it will have a lower priority than the template rules matching native WXS types.

<xsl:template match="xs:element[@type]" mode="childNodeInput" priority="0">
  <xsl:param name="nodeName"/>
  <xsl:param name="nodeValue" select="@default"/>
  <xsl:apply-templates 
       select="/xs:schema/xs:simpleType[@name=current()/@type]/xs:restriction"
       mode="childNodeInput">
    <xsl:with-param name="nodeName" select="$nodeName"/>
    <xsl:with-param name="nodeValue" select="$nodeValue"/>
  </xsl:apply-templates>
</xsl:template>

<xsl:template match="xs:element[xs:simpleType]" mode="childNodeInput">
  <xsl:param name="nodeName"/>
  <xsl:param name="nodeValue" select="@default"/>
   <xsl:apply-templates select="xs:simpleType/xs:restriction" mode="childNodeInput">
    <xsl:with-param name="nodeName" select="$nodeName"/>
    <xsl:with-param name="nodeValue" select="$nodeValue"/>
  </xsl:apply-templates>
</xsl:template>

The following template rules are a sample of the templates to match the native WXS types.

<xsl:template match="xs:element[@type='xs:string']|xs:restriction[@base='xs:string']"
              mode="childNodeInput">
  <xsl:param name="nodeName"/>
  <xsl:param name="nodeValue" select="@default"/>
    <xsl:choose>
      <xsl:when test="xs:maxLength">
        <input type="text" name="{$nodeName}" value="{$nodeValue}">
          <xsl:apply-templates select="*" mode="childNodeInput"/>
        </input>					
      </xsl:when>
      <xsl:otherwise>
        <textArea name="{$nodeName}">
          <xsl:apply-templates select="*" mode="childNodeInput"/>
          <xsl:value-of select="$nodeValue"/>
        </textArea>
      </xsl:otherwise>
    </xsl:choose>
</xsl:template>	

<xsl:template match="xs:element[@type='xs:boolean']|xs:restriction[@base='xs:boolean']"
              mode="childNodeInput">
  <xsl:param name="nodeName"/>
  <xsl:param name="nodeValue" select="@default"/>
  <input type="radio" name="{$nodeName}" value="true">
    <xsl:if test="$nodeValue='true'">
      <xsl:attribute name="checked">checked</xsl:attribute>
    </xsl:if>
    Yes
  </input>
  <input type="radio" name="{$nodeName}" value="false">
    <xsl:if test="$nodeValue='false'">
      <xsl:attribute name="checked">checked</xsl:attribute>
    </xsl:if>
    No
  </input>
</xsl:template>

Some restriction facets map directly to XHTML input element attributes. For example <xs:maxLength value="10"> maps to maxsize="10" attribute in an <input> element. Other restriction facets such as <xs:pattern> and <xs:maxInclusive> do not map to XHTML nodes. One approach to ensure conformant input from the user is to add custom attributes to the XHTML <input> element, and use a client side script to validate the user's input using the custom attributes.

<xsl:template match="xs:maxLength" mode="childNodeInput">
  <xsl:attribute name="maxLength">
    <xsl:value-of select="@value"/>
  </xsl:attribute>
</xsl:template>

<xsl:template match="xs:pattern" mode="childNodeInput">
  <xsl:attribute name="validationRegExp">
    <xsl:value-of select="@value"/>
  </xsl:attribute>
</xsl:template>

This stylesheet will map restrictions that have enumeration facets, regardless of the base type, to a <select> XHTML element. WXS annotation elements can useful here, when we need to associate human-readable text with an enumeration facet:

<xs:enumeration value="AK">
  <xs:annotation>
    <xs:documentation>Alaska</xs:documentation>
  </xs:annotation>
</xs:enumeration>
Will be transformed into:

<option value="AK">Alaska</option>

The template rule that matches each enumeration facet checks for annotation information as well as whether the enumeration is the default value.

<xsl:template match="xs:restriction[xs:enumeration]" mode="childNodeInput" 
              priority="1">
  <xsl:param name="nodeName"/>
  <xsl:param name="nodeValue"/>
  <select name="{$nodeName}">
    <xsl:apply-templates select="*" mode="childNodeInput">
      <xsl:with-param name="nodeValue" select="$nodeValue"/>
    </xsl:apply-templates>
  </select>
</xsl:template>

<xsl:template match="xs:enumeration" mode="childNodeInput">
  <xsl:param name="nodeValue"/>
  <option value="{@value}">
    <xsl:if test="@value=$nodeValue">
      <xsl:attribute name="selected">
        selected
      </xsl:attribute>
    </xsl:if>
    <xsl:choose>
      <xsl:when test="xs:annotation/xs:documentation">
        <xsl:value-of select="xs:annotation/xs:documentation"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="@value"/>
      </xsl:otherwise>
    </xsl:choose>
  </option>
</xsl:template>

<xsl:template match="*" mode="childNodeInput"/>

Conclusion

Using WXS as the common resource for data typing in your application can have big payoffs. By allowing components and interfaces to automatically reflect changes to an application's data model, you can greatly increase the reusability and flexibility of a system. XSLT is a useful, largely platform-independent, and highly portable tool for making this possible.


Comment on this articleShare your comments or questions on this article by posting to our forum.
(* You must be a
member of XML.com to use this feature.)
Comment on this Article


Titles Only Titles Only Newest First
  • same thing for relaxNG ?
    2006-11-17 03:20:34 ndeb [Reply]

    I am trying to make a xsl in order to convert a relaxNG schema into a web html form.
    It still lacks a lot of things, and I didn't put the javascript code in it(not difficult). I think it is a good start.


    Here is my work :


    <xsl:template match="rng:start">
    <xsl:apply-templates>
    <xsl:with-param name="pathInXml" select="''"/>
    </xsl:apply-templates>
    </xsl:template>

    <!-- with a ref element we need to look for its define -->

    <xsl:template match="rng:ref[@name]">
    <xsl:param name="pathInXml"/>
    <xsl:param name="nodeName" select="@name"/>
    <xsl:apply-templates select="/rng:grammar/rng:define[@name=$nodeName]">
    <xsl:with-param name="pathInXml" select="$pathInXml"/>
    </xsl:apply-templates>
    </xsl:template>


    <!-- with the three next elements, we just make a recursion on
    the children, passing the parameter pathInXml -->

    <xsl:template match="rng:define[@name]">
    <xsl:param name="pathInXml"/>
    <xsl:apply-templates select="*">
    <xsl:with-param name="pathInXml" select="$pathInXml"/>
    </xsl:apply-templates>
    </xsl:template>

    <!-- with an optional element we add a comment around its children -->

    <xsl:template match="rng:optional">
    <xsl:param name="pathInXml"/>
    <xsl:call-template name="indent">
    <xsl:with-param name="pathInXML" select="$pathInXml"/>
    </xsl:call-template>
    The following parameter(s) is(are) optional : {
    <br/>
    <xsl:apply-templates select="*">
    <xsl:with-param name="pathInXml" select="$pathInXml"/>
    </xsl:apply-templates>
    <xsl:call-template name="indent">
    <xsl:with-param name="pathInXML" select="$pathInXml"/>
    </xsl:call-template>
    }
    <br/>
    </xsl:template>

    <xsl:template match="rng:zeroOrMore|rng:oneOrMore">
    <xsl:param name="pathInXml"/>
    <xsl:call-template name="indent">
    <xsl:with-param name="pathInXML" select="$pathInXml"/>
    </xsl:call-template>
    <input type="button" value="+" onclick="addOne(this)"/>
    <div>
    <xsl:apply-templates select="*">
    <xsl:with-param name="pathInXml" select="$pathInXml"/>
    </xsl:apply-templates>
    </div>
    <br/>
    </xsl:template>

    <!-- with an element markup, we make a recursion on the children,
    adding the current element to the parameter pathInXml -->

    <xsl:template match="rng:element[@name]">
    <xsl:param name="pathInXml"/>
    <xsl:param name="elementName" select="@name"/>
    <xsl:call-template name="indent">
    <xsl:with-param name="pathInXML" select="$pathInXml"/>
    </xsl:call-template>
    <xsl:value-of select="$elementName"/>
    <br/>
    <xsl:apply-templates select="*">
    <xsl:with-param name="pathInXml" select="concat($pathInXml,'/',$elementName)"/>
    </xsl:apply-templates>
    <br/>
    </xsl:template>

    <!-- a choice element will create a dropdown list of choices : -->

    <xsl:template match="rng:choice">
    <xsl:param name="pathInXml"/>
    <br/>
    <xsl:call-template name="indent">
    <xsl:with-param name="pathInXML" select="$pathInXml"/>
    </xsl:call-template>
    <select style="height: 20px; width: 250px"
    onchange="moveOptionContent(this)">
    <xsl:apply-templates select="rng:ref[@name]" mode="choice">
    <xsl:with-param name="pathInXml" select="$pathInXml"/>
    </xsl:apply-templates>
    </select>
    <xsl:apply-templates select="rng:ref[@name]" mode="addHidden">
    <xsl:with-param name="pathInXml" select="$pathInXml"/>
    </xsl:apply-templates>
    <br/>
    </xsl:template>

    <!-- with a ref element in the mode choice, it fills the select element of the choice
    with the different ref values -->

    <xsl:template match="rng:ref[@name]" mode="choice">
    <xsl:param name="pathInXml"/>
    <xsl:param name="nodeName" select="@name"/>
    <option value="{$nodeName}" name="{$pathInXml}/{$nodeName}">
    <xsl:value-of select="$nodeName"/>
    </option>
    </xsl:template>

    <xsl:template match="rng:ref[@name]" mode="addHidden">
    <xsl:param name="pathInXml"/>
    <xsl:param name="nodeName" select="@name"/>
    <div name="{$nodeName}" style="display: none">
    <xsl:apply-templates select="/rng:grammar/rng:define[@name=$nodeName]">
    <xsl:with-param name="pathInXml" select="$pathInXml"/>
    </xsl:apply-templates>
    </div>
    </xsl:template>


    <!-- with an attribute element, we wait for a data markup -->

    <xsl:template match="rng:attribute[@name]">
    <xsl:param name="pathInXml"/>
    <xsl:param name="attributeName" select="@name"/>
    <xsl:call-template name="indent">
    <xsl:with-param name="pathInXML" select="$pathInXml"/>
    </xsl:call-template>
    <xsl:value-of select="$attributeName"/> :
    <xsl:apply-templates select="rng:data">
    <xsl:with-param name="attributeName" select="$attributeName"/>
    <xsl:with-param name="pathInXml" select="$pathInXml"/>
    </xsl:apply-templates>
    <br/>
    <xsl:apply-templates select="a:documentation"/>
    </xsl:template>

    <!-- those nodes are end of the tree : -->

    <xsl:template match="a:documentation">

    <xsl:copy-of select="current()"/>

    <br/>
    </xsl:template>

    <!-- At the end of the tree, we add :attributeName to the pathInXml and make the
    input markup -->

    <xsl:template match="rng:data">
    <xsl:param name="pathInXml"/>
    <xsl:param name="attributeName"/>
    <input type="text" name="{$pathInXml}/@{$attributeName}"/>
    </xsl:template>

    <!-- In order to indent, we split the pathInXML with the / separator and we call
    the function recursively until there is no separator left -->

    <xsl:template name="indent">
    <xsl:param name="pathInXML"/>
    <xsl:if test="contains($pathInXML, '/')">
          
    <xsl:call-template name="indent">
    <xsl:with-param name="pathInXML" select="substring-after($pathInXML,'/')"/>
    </xsl:call-template>
    </xsl:if>
    </xsl:template>


  • Which extensions needed for imports (includes)
    2004-07-21 03:45:44 mattad [Reply]

    Which XSLT source code do I have to add to enable the XSLT script to handle imports (resp. includes) as well?


    Is there somewhere a complete script available? I don't want to re-invent the wheel. I could imagine that a full XSLT script has been developed meanwhile for such a standard task.


    Matt

    • Which extensions needed for imports (includes)
      2004-07-21 09:51:20 Eric Gropp [Reply]

      We've chosen a subset of features to use in our schemas, so we do not have a set of templates that can handle all schema features. However, here's a description of our approach, which I hope may be of some use. I'm sure there are better ones out there, like http://www.idealliance.org/xmlusa/03/call/xmlpapers/03-03-04.994/.03-03-04.html .


      We've handled referenced schema files by creating a node-set of all the schemas. We can then resolve an Element References, Named Types, etc by selecting from the definitions in the schemas in this node set.


      At the top of the stylesheet, we define two XSLT variables: the first variable creates a result set of all the schemas, and the second contains a node set. You'll have to use your favorite equivalent of the node-set extension function. If your XSLT is working off of an instance document, you can use a xsi:schemaLocation attribute as a starting point.



      <xsl:variable name="schemaSetTree">
      <xsl:apply-templates select="/xs:schema" mode="resolveSchemas"/>
      </xsl:variable>
      <xsl:variable name="schemaSet" select="msxsl:node-set($schemaSetTree)"/>



      The "resolveSchemas" mode contains a template that walks through all the tree of included and imported schemas:



      <xsl:template match="xs:schema" mode="resolveSchemas">
      <xsl:param name="targetNamespace" select="@targetNamespace"/>


      <xs:schema targetNamespace="{$targetNamespace}">
      <xsl:copy-of select="*"/>
      </xs:schema>


      <xsl:apply-templates select="document(xs:include/@schemaLocation)/*" mode="resolveSchemas">
      <xsl:with-param name="targetNamespace" select="$targetNamespace"/>
      </xsl:apply-templates>


      <xsl:apply-templates select="document(xs:import/@schemaLocation)/*" mode="resolveSchemas"/>


      </xsl:template>



      For convenience, all included schemas are given a @targetNamespace value equal to the parent namespace-uri.


      You can then use the $schemaSet variable to resolve schema definitions. For example, the complex type definition "Dog" in the namespace "uri:pets" can be found using:


      $schemaSet/xs:schema[@targetNamespace='uri:pets']/xs:complexType[@name='Dog']



      Yours,


      Eric G.
      Portland, OR





  • adding MVC and i18n
    2003-05-12 11:43:15 Erik Ostermueller [Reply]

    I have an idea on how could could add some MVC and i18n to your ideas.


    Instead of storing the 'informative title' in the schema, you could place that information in a separate localized file.


    Further, you can have language specific labels for each schema element/attribute in a separate file using the Schema Adjunct Framework (SAF -- http://www.tibco.com/solutions/products/extensibility/resources/devguide.htm).


    SAF allows you to mark up or annotate a particular schema file from a separate file. It uses XPath to reference the schema.


    So, you could have a single schema, with a separte SAF file for any of multiple languages or even input devices (like PDAs) with different screen sizes/issues.



    Erik Ostermueller

  • Does it work?
    2003-01-29 09:36:42 Rogier Peters [Reply]

    Although I think this is a very good idea, I'm having a hard time getting it to work. It would have been nice to have an the full stylesheet and an example schema (For instance the po.xsd mentioned in the W3 link about NUN) available for download

    • Does it work?
      2003-01-29 12:33:13 Eric Gropp [Reply]

      With the exception of a root stylesheet element and namespace declarations, the sample XSLT code in the article comprises a complete stylesheet, which you should be able to execute.


      To execute it copy all of the sample code into a new XSLT document, with the exception of the enumeration ("AK"="Alaska") example; add namespace declarations for the prefixes "xs" and "xsl"; and add associate the "frm" prefix with any custom namespace.


      As the article mentions, the sample XSLT is limited, and it will not accept NUNs that refer to complex types as used in po.xsd. However russian-doll type schemas will work well with the sample XSLT code, such as the Common Alerting Protocol that recently appeared on the XML cover pages (http://xml.coverpages.org/ni2003-01-06-a.html).


      If you download that schema, and transform it with the XSLT from the article with a "targetNUN" parameter set to "element::alert", you will get an HTML form. You can then further annotate the schema to provide user friendly labels.


      The sample XSLT is far reduced from what you would want to use in a production system, however it contains all of the key elements that we've been using for a variety of applications for 12 months.


      Best of Luck and Happy Transforming!


      -Eric G.





  • how to handle minOccurs and maxOccurs
    2003-01-21 02:03:07 Edwin van der Wal [Reply]

    I am interested how to make HTML-forms that can add children at a specific place where maxOccurs is not reached.


    Kind Greetings,


    Edwin van der Wal
    Edwin@lcn.nl

    • how to handle minOccurs and maxOccurs
      2003-01-29 13:42:47 Eric Gropp [Reply]

      Unfortunately there are too many variables for me to provide a definitive, or even useful, answer to your question. Even if I could, it places too much attention on HTML forms instead of general XML Schema transformation techniques.


      However, your question does raise three issues that I did not cover in the article:


      1. Transforming schemas with instance documents


      To determine whether maxOccurs has been reached, you need some reference to an instance document. How you do that can depend on your transformation engine. We pass the node from the instance document into the XSLT processor as a parameter. This approach allows us to use XSLT to build forms that can both edit elements from instance documents and create new elements.


      2. Dealing with complex elements


      The sample XSLT will build a form from of a complex element definition with simple children, ignoring any complex children. In some cases, you may want to consider a separate tree view for users to add, move, and copy complex children. The appinfo element in the schema also provides an excellent place to define how a complex element appears in a schema with icon image references and the like.


      3. Post back


      The article does not say much about building a new instance element from the user-entered data in the form. However, the "targetElementForm" mode is a useful place to create hidden form elements to hold element order and position information.


      Hope this helps.


      -Eric G.