The Idea
One of the ideas we've had whilst developing XForms over the years has been to try and generate a form from a set of XML instance data. The following article takes a look at one attempt at doing this.
The main idea was that for every leaf node (a node with no children) in a set of XML instance data, we wanted to create an XForms input control and for every non-leaf node we'd create an XForms group control. The result would be that we'd have a very simple XForm that contained controls bound to the instance data.
The generation of the simple XForm we decided would be done with XSLT.
The Example
As always, we start with some example instance data, in this case representing a dummy set of 'account' information (note that it's not a mixed namespace document, which keeps things a bit simpler):
<account account_id="XYZ0123456"> <client_details> <client client_id="C0000001"> <title>Mr</title> <first_name>Joe</first_name> <surname>Bloggs</surname> <date_of_birth>1950-07-15</date_of_birth> <gender>M</gender> <address> <line_1>10 High Street</line_1> <line_2>Averageville</line_2> <line_3>Madeupshire</line_3> <line_4>MU994RF</line_4> </address> <contact> <daytime_phone>07987654321</daytime_phone> <evening_phone>01234567890</evening_phone> <email>joe.bloggs@example.co.uk</email> </contact> </client> <client client_id="C0000002"> <title>Mrs</title> <first_name>Jane</first_name> <surname>Bloggs</surname> <date_of_birth>1952-09-01</date_of_birth> <gender>F</gender> <address> <line_1>10 High Street</line_1> <line_2>Averageville</line_2> <line_3>Madeupshire</line_3> <line_4>MU994RF</line_4> </address> <contact> <daytime_phone>07987123456</daytime_phone> <evening_phone>01234567890</evening_phone> <email>jane.bloggs@example.co.uk</email> </contact> </client> </client_details> <contact_preferences> <primary_account_contact_ref>C0000002</primary_account_contact_ref> <contact_via_phone>false</contact_via_phone> <contact_via_email>true</contact_via_email> </contact_preferences> </account>
The next part of the process is to start on writing the XSLT that will transform the data into an XForm.
We're outputting an (X)HTML document so the first xsl:template element we need matches the instance data's root element and inserts the html element (with some namespace declarations), the head and body tags into the output stream.
<xsl:template match="/"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:xf="http://www.w3.org/2002/xforms" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" > <head> ... </head> <body> ... </body> ... </html> </xsl:template>
Also in this xsl:template we add the contents of the head element. I'm going to be using the Ubiquity XForms javascript library so the contents contain a script tag that links to that, plus the HTML title tag and some basic inline CSS. Obviously, if you are using a different XForms processor then you'll omit the script tag, and add whatever is needed for your processor:
... <head> <title><xsl:value-of select="$form_title"/></title> <script type="text/javascript" src="ubiquity-xforms-0.7.1/build/dist/src/ubiquity-loader.js">/**/</script> <style type="text/css"> xf\:group, xf\:input{ display: block; margin-bottom: 10px; } xf\:label{ width: 200px; } .repeated-group{ border: 1px solid #000; } </style> </head> ...
Notice that the contents of the title tag is an XSL param. There's a number of xsl:params defined above the first template which let us influence the output of the translation (they've all got default values). This one's just letting us set the document's title.
Still in the initial xsl:template I now need to insert the code that will generate the XForm model, instance and form controls. As I'm using the Ubiquity XForms library, the xf:model element and its children are required to be placed within the documents body tag.
... <body> <xsl:element name="xf:model"> <xsl:attribute name="id"><xsl:value-of select="$xf_model_id" /></xsl:attribute> <xsl:choose> <xsl:when test="$xf_instance_inline = 'true'"> <xf:instance id="{$xf_instance_id}"> <xsl:apply-templates mode="inst-data" /> </xf:instance> </xsl:when> <xsl:otherwise> <xf:instance id="{$xf_instance_id}" src="{$xf_instance_src}" /> </xsl:otherwise> </xsl:choose> </xsl:element> </body> ...
As you can see, we make use of a few more XSLT parameters. One sets the value of the xf:model/@id attribute, one is used to choose whether or not we want to include the instance data inline in the generated form or link the xf:instance to it in a separate file utilising the xf:instance/@src attribute. In the example I'm describing here, we'll have the instance data inline in the form.
When including the instance data inline we take advantage of the @mode attribute of xsl:apply-templates which lets us process a node (or set of nodes) more than once.
We need to process the instance data once to copy it into the transform output stream as children of the xf:instance element so that it can be used as XForm instance data, and again to generate the form controls (we could also process it again to generate a set of xf:bind elements too).
To copy through the data for use as XForms instance data requires a single xsl:template:
... <xsl:template match="@* | node()" mode="inst-data"> <xsl:copy> <xsl:apply-templates select="@* | node()" mode="inst-data" /> </xsl:copy> </xsl:template> ...
We've now inserted all the code that should now generate the XForms model element and it's children. We need to add code that generates the XForms controls which is the semi-tricky bit. To do this we process the instance data again using a different @mode (the context node of the xsl:template we're in is still '/'):
... <xsl:apply-templates mode="xf-controls"> <xsl:with-param name="xpath_prefix">/</xsl:with-param> </xsl:apply-templates> ...
We now need to add a number of xsl:templates that match nodes in different situations in the input document so that the right XForms control is output. The rules we're using are that non-leaf nodes are output as xf:group controls and leaf nodes are output as xf:input controls.
The simplest new xsl:template we need is for attribute nodes. They are all leaf nodes and always translated to xf:inputs:
... <xsl:template match="@*" mode="xf-controls"> <xf:input ref="@{name(.)}"> <xf:label>(@<xsl:value-of select="name(.)"/>)</xf:label> </xf:input> </xsl:template> ...
Now we need to match and process remaining nodes from the input document. The problem here is that for each of the nodes matched we need to work out if they are a leaf node or not. This is done with further xsl:templates, using the @match attribute and an XPath expression to differentiate leaf from non-leaf by seeing if the node currently being processed has any descendants or attributes.
Firstly an xsl:template for non-leaf nodes:
... <xsl:template match="*[not(contains(name(.), ':')) and (count(descendant::*) > 0 or count(attribute::*) > 0)]" mode="xf-controls"> <xsl:param name="xpath_prefix"></xsl:param> <xsl:variable name="current_node_name" select="name(.)" /> <xsl:variable name="current_node_position" select="count(preceding-sibling::*[name() = $current_node_name]) + 1" /> <xsl:call-template name="non-leaf-node"> ... </xsl:call-template> </xsl:template> ...
Then leaf nodes:
... <xsl:template match="*[not(contains(name(.), ':')) and (count(descendant::*) = 0 and count(attribute::*) = 0)]" mode="xf-controls"> <xsl:param name="xpath_prefix"></xsl:param> <xsl:variable name="current_node_name" select="name(.)" /> <xsl:variable name="current_node_position" select="count(preceding-sibling::*[name() = $current_node_name]) + 1" /> <xsl:call-template name="non-leaf-node"> ... </xsl:call-template> </xsl:template> ...
Each of the xsl:templates sets a couple of variable values that works out where the current node is in the document (if its got siblings of the same name, etc.) and calls a named xsl:template with these values. The called templates are then responsible for outputting the correct XForm control that references the correct piece of XForms instance data.
Here's the named template that decides what's output for a non-leaf node (the leaf template is similar but outputs an input control instead):
<xsl:template name="non-leaf-node"> <xsl:param name="ref_value" /> <xsl:param name="has_siblings_of_same_name" /> <xsl:param name="position" /> <xsl:param name="label_value" /> <xsl:choose> <xsl:when test="$has_siblings_of_same_name"> <xf:group ref="{$ref_value}[{$position}]" class="repeated-group"> <xsl:if test="$xf_group_labels = 'true'"> <xf:label>(<xsl:value-of select="$label_value"/>)</xf:label> </xsl:if> <xsl:apply-templates select="@* | node()" mode="xf-controls" /> </xf:group> </xsl:when> <xsl:otherwise> <xf:group ref="{$ref_value}"> <xsl:if test="$xf_group_labels = 'true'"> <xf:label>(<xsl:value-of select="$label_value"/>)</xf:label> </xsl:if> <xsl:apply-templates select="@* | node()" mode="xf-controls" /> </xf:group> </xsl:otherwise> </xsl:choose> </xsl:template>
One of the important things in the above template is the value of the xf:group/@ref attribute. If there's more than one node with the same name at any level of the input instance data, the generated controls need to reference the different nodes using the node position in the document, otherwise all the controls will show the same information.
Also, by identifying controls that have siblings of the same name -- by adding @class="repeated-group" -- we could run further transforms on the output XForm and replace these controls with things like xf:repeat controls.
There's a zip file attached that contains the instance data, XSLT and a batch file that runs the transform. (You'll need the Saxon XSLT engine, and will need to edit the .bat file for use on your machine. There are more details in the readme file.)
If you don't want to download and the attachment and the transform yourself, the generated XForm can be seen here.
The Conclusion
Whilst this is by no means perfect, it shows that it is possible to take a piece of XML instance data and very quickly get to a working XForm from it. As I mentioned, further transforms on the generated form could achieve even more functionality.
| Attachment | Size |
|---|---|
| instance-to-xform.zip | 5.11 KB |

Hello Alex! This was an
Hello Alex!
This was an excellent article. Keep up the great work.
Although instance documents are a great start, generating a selection list from a single instance is not possible. You can also easily convert instance documents to XML Schemas using XML IDEs such as oXygen.
An alternative approach is to generate an XForms documents directly and dynamically from an XML Schema. This allows you to regenerate the form whenever the XML Schema changes. This is called "model-drive" development since the XForms artifacts are generated directly from the model and can be kept in sync.
The problem with generating XForms from XML Schemas is that XML Schemas come with so many variations that it becomes a difficult task to process them in general.
An alternative is to use XML Schema standards like the US National Information Exchange Model (NIEM.gov).
Here is an article on this:
http://www.ibm.com/developerworks/library/x-xformsniem/
One other technique is to use XQuery to transform XML Schemas. These techniques are described here:
http://en.wikibooks.org/wiki/XRX/XForms_Generator
I look forward to reading your other articles.
- Dan
Hi Alex !!! Thanks for your
Hi Alex !!!
Thanks for your article ...Actually we are doing the same xforms research with plain xforms code with Firefox plug-in.
Developed some sample applications and tested , it's working fine in Firefox ....
In terms of validation part the Schema approach is nice instead of the XSLT , because the code lengthy and all matched tag should need in template section .....
Keep it up your work in XForms
Thanks and Regards
Rajamani Marimuthu
Junior Research Fellow
Open Technology Centre
India.
Thanks for taking the time to
Thanks for taking the time to read and comment Dan.
With regards to the generating forms from XML Schemas, I believe one of my colleagues, Phil Booth, was writing a 'schema analyser' to achieve exactly that. Hopefully sometime in the future he'll contribute an article documenting his experiences.
Cheers,
Alex
Thank you for this
Thank you for this interesting article.
It wouldn't be difficult to integrate it in XSLTForms because the nodeset() XPath extension function is available in almost every browser (except FF2).
Of course, it would be easier to generate an XForms document from an XML Schema. Because it would even nicer with type and repeat recognition so controls would be more pertinent and triggers could be added to add/remove an item.
Alain Couthures