The problem
As users fill out a form you created, the data they enter is stored an XML instance. You would like to enable users:
- To download that XML document containing the data they entered so far.
- To upload an XML document in the same format, which they maybe saved earlier.
The solution
See this walk-through of the running example:
XForms doesn't provide a built-in mechanism for users to download an XML instance as an XML file, or to replace an XML instance with an XML file they provide. This is because your data in XML instances is by default "private": unless you are exposing the content of some nodes to the user through XForms controls, you don't necessarily want users to be able to see or change this data. In fact, in Orbeon Forms, instances are kept on the server, and only the parts that need to be shows to users are sent to the browser.
However, there are cases where you want users to be able to download or upload an instance in the form of an XML document. You can do this relatively easily in Orbeon Forms through some extensions.
Download an instance
Every time you want users to download a file, you can use an
<xforms:output ref="..." appearance="xxforms:download">. The node you point to must either contain the URL to a file (typically to a temporary file you generated), or must contain the base64 encoded file. In this case, since the file is relatively small, we'll use the latter. Inside the
<xforms:output>, so the file can be recognized by the browser you can specify the media type of the file with
<xforms:mediatype> and the file name with
<xforms:filename>. To host the base64 encoded file, the example defines an instance:
<xforms:instance id="download">
<serialized mediatype="application/xml" filename="instance.xml"/>
</xforms:instance>
And an <xforms:output> pointing to that instance:
<xforms:output ref="instance('download')" appearance="xxforms:download">
<xforms:label>Download instance</xforms:label>
<xforms:mediatype ref="@mediatype"/>
<xforms:filename ref="@filename"/>
</xforms:output>
All you have left to do is to populate instance('download')with the base64 data. You do this when users indicate that they are interested in downloading the instance by clicking on "Upload / download XML data" with the <xforms:setvalue> below. The XPath expression uses two Saxon extensions: saxon:serialize() and saxon:string-to-nase64Binary(). You could evaluate this XPath expression in an <xforms:bind calculate="..."/> instead of an <xforms:setvalue/>, but the downside is that it would be repeatedly evaluated, thus hindering performance.
<xforms:setvalue ev:event="xxforms-dialog-open" ref="instance('download')"
value="saxon:string-to-base64Binary(saxon:serialize(instance('main-instance'), 'xml'), 'UTF-8')"/>
Upload an instance
Every time you want users to upload a file, use an
<xforms:upload ref="...">.
The example defines an instance for this: the content of the document element (
<serialized>) will contain the base64 encoded file users uploaded; the
mediatype and
filename attribute will respectively contain the mediatype and file name (which this example doesn't use).
<xforms:instance id="upload">
<serialized mediatype="" filename=""/>
</xforms:instance>
The
<xforms:upload> control is very similar to the
<xforms:output> you have seen in the previous section:
<xforms:upload ref="instance('upload')">
<xforms:label>Select file:</xforms:label>
<xforms:filename ref="@filename"/>
<xforms:mediatype ref="@mediatype"/>
</xforms:upload>
The file is uploaded when the instance to which the <xforms:upload> is bound to is submitted. Here, the instance is never submitted to any external service; you just want instance('upload') to be updated with the base64 encoded file, so you use a submission that doesn't send the data anywhere, using the test: scheme. You can think of this as being similar to sending data to /dev/null on UNIX:
<xforms:submission id="upload-submission" ref="instance('upload')" validate="false"
relevant="false" method="post" replace="none" resource="test:">
You run this submission when a file is selected by users by adding the following inside the <xforms:upload>:
<xforms:send ev:event="xforms-select" submission="upload-submission"/>
When the submission is done, you want to replace instance('main-instance') with the data uploaded by users. But first, you need to decode the base64 with saxon:base64Binary-to-string(). This gives you the serialized XML, which you'll want to parse with saxon:parse(). You run all this from an <xforms:insert>. As it points to the root element of main-instance, the whole instance is replaced by what is returned by the origin expression:
<xforms:insert ev:event="xforms-submit-done" nodeset="instance('main-instance')"
origin="saxon:parse(saxon:base64Binary-to-string(xs:base64Binary(instance('upload')), 'UTF-8'))"/>
Run it and get the source