Saturday, December 31, 2011

Simplify with an XML data model - Part 3


Part 3: Allowing binding to missing XML nodes.

You might notice that if you try to bind to an element that does not exist in the model, you will get an error in the call to evaluate. This adds some complexity and burden so we will make a function that ensures the bound path exists in the XML DOM. Ensuring the xpath makes development much easier.
Notice that in the previous examples the xml model was initialized to this:
    var xmlString = "<root><address><street number=\"123\">Main Street</street><state>MA</state></address></root>";
But it would be much easier to start with just an empty document:
    var xmlString = "<root/>";
To accomplish this we will update our code that returns a node based on XPath, and ensure that the path exists by creating it recursively if it does not.
    //evaluate the xpath on the xmlDom and return a node ensuring that the path exists
    function getNodeEnsurePath(xpath, xmlDom) {
        try {
            // Evaluate the XPath to get the node value
            var xpathResult = xmlDom.evaluate(xpath, xmlDom, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
            var node = xpathResult.singleNodeValue;

            //if the node don't exist, make it!
            if (node == null) {

                //get the node name from the xpath
                var splitXpath = xpath.split('/');
                var currElementName = splitXpath.pop();

                //use recursion to get the parent node
                var parent = getNodeEnsurePath(splitXpath.join("/"), xmlDom);

                //Create the missing node and attach it to the parent
                if (currElementName.charAt(0) == '@') {
                    //it's an attribute
                    node = xmlDom.createAttribute(currElementName.substr(1));
                    parent.setAttributeNode(node);
                } else if (currElementName.indexOf("[") > -1) {
                    //it's an array[n]
                    //get the index
                    var index = currElementName.substring(currElementName.indexOf("[") + 1, currElementName
                            .indexOf("]"));
                    currElementName = currElementName.substring(0, currElementName.indexOf("["));

                    //get count of existing elements and create an array of nodes
                    var countXpath = "count(" + splitXpath.join("/") + "/" + currElementName + ")";
                    var totalExisting = xmlDom.evaluate(countXpath, xmlDom, null, XPathResult.NUMBER_TYPE, null).numberValue;
                    var numNodesToBuild = index - totalExisting;
                    for ( var i = 0; i < numNodesToBuild; i++) {
                        //create elements for the node any all missing preceeding siblings
                        node = xmlDom.createElement(currElementName)
                        parent.appendChild(node);
                    }
                } else {
                    //just a normal element
                    node = xmlDom.createElement(currElementName);
                    parent.appendChild(node);
                }
            }
            return node;
        } catch (e) {
            outputError(e);
        }
    }
Now we need to switch out the calls to getNode and replace them with our new function:
    // Updates the XML DOM from the element
    function setXmlValue(element, xpath, xmlDom) {
        //Get the node from the model
        var node = getNodeEnsurePath(xpath, xmlDom);
    ...


    function setFormValue(element, xpath, xmlDom){
        //Get the node from the model
        var node = getNodeEnsurePath(xpath, xmlDom);
    ...
That's it, now we can make a hearty XML DOM just from the XPath in the binding. This removes the redundancy of needing placeholder structure in your XML.
You can view this example here.
As always you can download all the source for these examples as a zip.
Remember you will need the Sarrissa library.

Next we will get some validation working with Schematron and browser side XSLT!
Simplify with an XML data model - Part 4

No comments:

Post a Comment