XRX/XForms Generator
Motivation
[edit | edit source]You have an XML Schema and you want to automatically generate an XForms application directly from the XML Schema.
Method
[edit | edit source]We will write an XQuery that will take the URL of an XML Schema as an input and will generate the XForms edit form as an output.
Note: Some of the functions in this example depend on use of ISO/IEC element naming conventions to "guess" the correct control to place on the XForms application. As an alternative you can add tags to the annotations of the XML Schema to always generate the correct XForms controls from an XML Schema. The downside to this option is that you will be forced to use a text editor rather then a graphical editor for your XML Schemas.
Steps
[edit | edit source]Here are the steps used to generate the XForms application
- Parse the XML Schema file looking for a document root
- Start to generate xf:groups for each complex element in the XML Schema
- For each simpleType, guess at the appropriate XForms control to use. Use hints from the XML data type or the element name to guess the correct control type. Leverage ISO/IEC 11179 metadata registry naming conventions and Representation terms whenever possible. Use xf:input for simple text fields, but use xf:select1 for enumerated values. For example, use the textarea control if the suffix of the element name is text, description or note.
- Generate any business rules in the bindings section of the XForms model. Although future XForms clients may be able to automatically infer rules from the XML Schema, today many XForms clients do not fully support XML Schemas. So, to generate a list of all the required fields, a set of binding elements must be generated. Luckily this is only a few lines of XQuery code.
- Generate an instance document for the initial values of the form
We will leverage a large collection of XQuery functions to keep our main XQuery simple. A separate module will be used to store all these functions.
The resulting XForms application can then be customized with specific behavior. Once this is done you must regenerate the XForms application if the XML Schema changes. Merging the customizations and the new XML Schema can be done using an XML merge tool.
Sample Functions
[edit | edit source]Looking for textarea
[edit | edit source]The following function uses the some construct of XQuery. It is very similar to a for loop, but instead of returning output for each item in a sequence, it only returns a Boolean true or false if one item in a sequence satisfies some criteria. In this case, the criteria is if the suffix of the element name ends in text, note or description, we will use a textarea.
declare function schema-to-xforms:is-textarea($element-name as xs:string) as xs:boolean {
(: if the element name has any of the following suffix we map it to the input element type :)
let $textarea-suffixes :=
<items>
<item>text</item>
<item>note</item>
<item>description</item>
</items>
let $lower-case-element-name := lower-case($element-name)
return
some $type in $textarea-suffixes/item
satisfies ends-with($lower-case-element-name, $type)
};
Adding Date Bindings
[edit | edit source]To get our XForms application to automatically put calendar pickers in the user interface we need to bind each of the elements with an XML Schema date type.
Here is a sample bindings for date type:
<xf:model>
...
<xf:bind nodeset="//TaskStartDate" type="xs:date"/>
<xf:bind nodeset="//TaskEndDate" type="xs:date"/>
...
</xf:model>
The XQuery to generate these from all the elements in an XML Schema is very simple if you use the appropriate naming conventions and end each element with the suffix "Date". The query to generate all the date bindings is just to use the XQuery function ends-with() such as the following:
for $element in $schema//xs:element[ends-with(lower-case(@name), 'date')]
return
<xf:bind nodeset="//{string($element/@name)}" type="xs:date"/>
This will also need to be done for attributes if you store dates in attributes.
Generating Boolean Bindings
[edit | edit source]In the same way, you can also look for all elements that end with the suffix "indicator" to turn input controls with the words "true" and "false" into checkboxes.
for $element in $schema//xs:element[ends-with(lower-case(@name), 'indicator')]
return
<xf:bind nodeset="//{string($element/@name)}" type="xs:boolean"/>
Looking for required fields
[edit | edit source]You can get a list of all the non-optional elements in an XML Schema by just looking for all elements that are not complex and do not permit zero occurrences. If an element is complex, it will have a child element of complexType. Our XPath expression has to remove these by adding a predicate with a not() function: xs:element[not(xs:complexType). We must then use a boolean and statement to exclude all elements that do not have a minOccurs='0' attribute.
for $element in $schema//xs:element[not(xs:complexType) and not(@minOccurs='0')]
return
<xf:bind nodeset="//{string($element/@name)}" required="true()"/>
Sample Nesting with xs:group Element
[edit | edit source]You can use the group element to keep track of the context of the data in your form.
For example if the form instance had the following structure:
<root>
<sub-node>
<sub-sub-node>
<fname>John</fname>
<lname>Doe</fname>
</sub-sub-node>
</sub-node>
</root>
then you would generate the following xf:group element with nodeset attribute set to the correct context:
<xf:group nodeset="/root/sub-node/sub-sub-node">
<xf:label class="group-label">contact</xf:label>
<xf:input ref="fname">
<xf:label>Name: </xf:label>
<xf:input>
<xf:input ref="lname">
<xf:label>Name: </xf:label>
<xf:input>
</xf:group>
You can also style the group using a bounding box similar to the way that the HTML fieldset is styled.
Resources
[edit | edit source]- Sample XQuery Module
- Presentation on XForms autogeneration Part 1
- Presentation on XForms autogeneration Part 2 - repeating structures
References
[edit | edit source]- HTML to XForms in XSLT 1 A 2006 article written by John Clark on using XSLT to transform XML Schemas into XForms.