Sarissa to the Rescue
February 23, 2005
Client-side XML processing. Today's browsers do cover the basics and some of them go even further, offering support for XHTML, SVG, XSLT, XPath , XLink, validation using W3C XML Schema, and more. This article will introduce you to basic cross-browser XML development with the aid of Sarissa, an ECMAScript library designed to stop those nasty incompatibilities before they get too close.
Getting Started
Using XML on the client enables you to do things you've never done before, especially when it comes to control of structured information and delivering an enhanced user experience. Let's go over some typical examples.
Your favorite designer could very well be hiding this from you: it is possible to
update
only parts of a web page with data coming from a request to your server without refreshing
the page and without scripting between those troublesome iframe
elements. The
request can be the result of user interaction handled by your script. Using the XMLHttpRequest
object, you can perform requests over HTTP and obtain the XML response as a DOM-compatible
object. You can then process that object further, if you want, before finally injecting
it
into the document using plain DOM methods, adding to your usability and saving bandwidth.
Use of XML on the client side is often driven by server-side requirements. For example, a high number of concurrent requests involving XSLT-based transformations sounds like trouble for any server. A transformation requires three tree structures in memory, one for each of the source, transform, and result documents. Outsourcing the transformation process to capable clients is a little like outsourcing the process of furniture assembly to the clients themselves. They get what they want more quickly and pay less while you save resources without losing the sale. After the assembly, clients keep the screwdriver for future use much the same way a browser will cache your XSLT document.
Another use case may involve sending structured information to the server application. To perform this, you can create a DOM document programmatically (even by parsing an XML string), perhaps processing it further, and finally submit it to the server.
You get the idea. This can go on to complex applications with rich UIs, for example a web-based XML editor based on XSLT, DOM, and CSS.
But let's go over the basics first.
Basic Training
The real issue in client-side XML development is browser incompatibility around implementations and extensions of the W3C DOM. The Sarissa library hides these incompatibilities for you, also adding some useful utilities into the mix.
A typical script block dealing with XML starts with instantiating a DOM document.
With
Sarissa, getting a new XMLDocument
object is done by calling a 'static'
method:
// Get a browser specific DOM Document object var oDomDoc = Sarissa.getDomDocument(); // more DOM code here
In standards-based browsers, this block is equal to
document.implementation.createDocument
. In IE, Sarissa just uses the most
recent MSXML ProgId
to construct an ActiveX-based object as appropriate.
Additionally, you can pass two string parameters to that factory method, which correspond
to
a namespace URI and a local name respectively. Those are used by Sarissa to create
a root
element and add it to the newly constructed XMLDocument
:
// construct a document containing // <foo xmlns="http://myserver/ns/uri" /> var oDomDoc = Sarissa.getDomDocument("http://myserver/ns/uri","foo");
You can also populate the Document using an XML string. The above line is equal to
var oDomDoc = Sarissa.getDomDocument("http://myserver/ns/uri","foo"); // populate the DOM Document using an XML string oDomDoc.loadXML("<foo xmlns='http://myserver/ns/uri' />");
How about loading an XML document from a URL? Just copy the above XML, paste it in a new file on your server and load it like this:
var oDomDoc = Sarissa.getDomDocument("http://myserver/ns/uri","foo"); // set loading method to synchronous oDomDoc.async = false; // populate the DOM Document using a remote file oDomDoc.load("path/to/my/file.xml"); // report any XML parsing errors if(oDomDoc.parseError != 0){ // construct a human readable // error description alert(Sarissa.getParseErrorText(oDomDoc);); }else{ // show loaded XML alert(Sarissa.serialize(oDomDoc);); };
We first load the remote file using the load
method of a
XMLDocument
using synchronous loading, meaning that the if
branch will only be executed after the load
method returns. Then we check for a
parsing error. If an error exists, the user sees the result of a call to
Sarissa.getParseErrorText
, which provides a string with a human-readable
description of the error. If there is no error, the user sees the XML string serialization
of the document returned from Sarissa.serialize
. This is like IE's
xml
property of DOM Nodes, with the difference being that it works for
everyone.
More Tricks
The XMLHttpRequest
object, available by one name or another in every major
browser by now, is used when you simply need more control over the request to the
remote
server, like specifying the HTTP method and headers. You can use it to load the same
XML
file as above like:
var xmlHttp = new XMLHttpRequest(); // specify HTTP method, file URL and // whether to use asynchronous loading xmlHttp.open("GET", "path/to/my/file.xml", false); // perform the actual request xmlHttp.send(null); // show result alert(Sarissa.serialize(xmlHttp.responseXML));
What we've done here is create a new XMLHttpRequest
object and configure
it to request the specified URL using HTTP GET asynchronously. We then perform the
actual
request and when that returns, we serialize the response XML which is available via
the
responseXML
property.
To perform XSLT transformations, two XMLDocument
objects are needed, one for
the XSLT transform and one for the source document. Supposing we have obtained those
as
and xslDoc
respectively, we
can perform the transformation using an xmlDoc
: XSLTProcessor
// create an instance of XSLTProcessor var processor = new XSLTProcessor(); // configure the processor to use our stylesheet processor.importStylesheet(xslDoc); // transform and store the result as a new doc var resultDocument = processor.transformToDocument(xmlDoc); // show transformation results alert(Sarissa.serialize(resultDocument));
Here we create a new processor and load our stylesheet to it using the
importStylesheet
method. It is worth noting that a single configured instance
of XSLTProcessor
can be re-used to transform more than one source document. We
don't have to load the stylesheet each time. Then we store the transformation result
into a new XMLDocument
object and display its serialization to the user.
You may be aware that IE has added the transformNode
and
transformNodeToObject
methods in its implementation of the
XMLDocument
object. Sarissa does implement those methods for Mozilla but they
are deprecated. The use of the XSLTProcessor
is recommended as it provides a
more efficient way to transform multiple documents and set XSLT parameters. A last
word on
XSLT -- Right now XSLT and XPath stuff are not supported for Konqueror and Safari,
although
this is expected to change.
Injections
Playing with XML programmatically is cool, but we usually want to modify our page
using
the resulting markup. Suppose we want to inject an XML node bound as fooNode
in
our document as a child of an element with an id value of 'targetNode'
:
document.getElementById('targetNode').appendChild(document.importNode(fooNode, true));
It is possible to get into trouble with this code. Although it's the most efficient
way to
append the node in the document, it could result to an error if, for example, the
node you
are trying to append is a document node. Serializing with Sarissa.serialize
and
setting the innerHTML
of the target element is always an option, but I would
suggest doing it properly using DOM instead.
The Final Touch
So all of this is great but it's still too much code to write, and probably too error-prone, especially if you are a code completion wimp like me. Moreover, the case becomes worse if you want to use XSLT on the client where applicable. To do that, you need to work on both end points:
-
your server must be able to transform the XML before sending it, or leave the transformation to the client. This can be dependent on the URL requested or an HTTP parameter.
-
The client must know if it is able to perform the transformation and ask the XML as appropriate.
IS_ENABLED_XSLTPROC
is a Boolean constant you can check in your logic to figure out what to do.
With these two issues addressed, you could result in something like
// set an HTTP parameter depending on whether you want // the transformation on the server or not var clientTransform = Sarissa.IS_ENABLED_XSLTPROC; // now construct the URL as appropriate var url = 'path/to/file?sent-as-is=' + clientTransforml;
So we have constructed the URL we want thanks to a Sarissa constant that tells us
whether
our client is able to use a transformer. Now, supposing we still want to append the
result
targetElement
as with our previous example and with an instance of
XSLTProcessor
at hand (which may be null), we perform this with just one
line:
Sarissa.updateContentFromURI(url, targetElement, processor);
This will work if the URL does point to an HTTP server. If you cannot have access
to one,
just use your filesystem instead; you will need to load or build the source document
manually and call Sarissa.updateContentFromNode
with it.
Client-side XML can open new doors for your applications. Using Sarissa, it can be easy as well. Sarissa includes a lot more and the code can even guide you in writing your own reusable components. Give it a try and let me know what you are up to. Maybe next time I'll show you how to build a browser-based XML editor.
Resources:
|