Project

Profile

Help

Sample JS code for a XMLDOM Serializer with global namespace declarations

Added by Philip Fearon about 10 years ago

When using Saxon-CE to create an XML DOM there can be a number of namsepace related issues when it comes to serializing this DOM. It becomes especially difficult to control where namespace declarations occur, some browsers even succeed in duplicating namespace declarations for the same prefix - creating invalid XML. I've written an alternative JavaScript XML serializer as a workaround for this and I'm posting it here in the hope that it may be useful to others.

The xmlToString function is accessed via a NamespacedXML object with a constructor that requires a 'namespace-binder' object as its only argument. The namespace-binder contains a set of properties describing the URI/prefix bindings to declare at the document element.

Here is some sample code that calls the function:


function serializeDeltaXML(xml) {
    var namespacex = {
        "http://www.deltaxml.com/ns/well-formed-delta-v1": "deltaxml",
        "http://www.deltaxml.com/ns/xml-namespaced-attribute": "dxx",
        "http://www.deltaxml.com/ns/non-namespaced-attribute" : "dxa",
        "http://dita.oasis-open.org/architecture/2005/": "ditaarch"
    };
    var nsXML = new NamespacedXML(namespacex);
    return nsXML.xmlToString(xml);
}

And here is the coded for the function:


function NamespacedXML(namespaces) {
    var result = "";
    var started = false;
    var parent = this;
    var attrRegex = new RegExp("[\"&<]", "g");
    var elementRegex = new RegExp("[&<]", "g");

    this.xmlToString = function (xml) {
        if (xml.nodeType == 10) {
            // dtd document type declaration
            result+= "';
        }
        if (xml.nodeType == 9) {
            // document type
            var nodes = xml.childNodes;
            for (var i = 0; i < nodes.length; i++) {
                parent.xmlToString(nodes[i]);
            }
        } else if (xml.nodeType == 1) {
            // element type
            addStartTag(xml);
            var nodes = xml.childNodes;
            for (var i = 0; i < nodes.length; i++) {
                parent.xmlToString(nodes[i]);
            }
            addEndTag(xml);
        } else if (xml.nodeType == 3) {
            addTextNode(xml);
        } else if (xml.nodeType == 8) {
            addCommentNode(xml);
        } else {
            console.log("unexpected node type! " + xml.nodeType);
        }
        return result;
    }
    function addStartTag(xmlNode) {
        result += "<" + xmlNode.nodeName;
        if (!started) {
            addNamespaceDeclarations();
            started = true;
        }
        addAttributes(xmlNode);
        result += ">";
    }
    function addNamespaceDeclarations() {
        for (var property in namespaces) {
            if (namespaces.hasOwnProperty(property)) {
                var prefix = namespaces[property];
                if (prefix.length > 0) {
                    prefix = ":" + prefix;
                }
                result += " xmlns" + prefix + '="' + property + '"';
            }
        }
    }

    function addEndTag(xmlNode) {
        result += "" + xmlNode.nodeName + ">";
    }

    function addAttributes(xmlNode) {
        var attrs = xmlNode.attributes;
        for (var a = 0; a < attrs.length; a++) {
            addAttribute(attrs[a]);
        }
    }

    function addAttribute(xmlAttr) {
        var attrName = xmlAttr.nodeName;
        if (attrName != "xmlns" && attrName.indexOf("xmlns:") != 0) {
            var attrValue = xmlAttr.value;
            attrValue = attrValue.replace(attrRegex, replacer);
            result += " " + attrName + '="' + attrValue + '"';
        }
    }

    function addTextNode(xmlNode) {
        var textValue = xmlNode.nodeValue;
        result += textValue.replace(elementRegex, replacer);
    }

    function addCommentNode(xmlNode) {
        result += "";
    }

    replacer = function (match, offset, string) {
        if (match == "&") {
            return "&";
        } else if (match == "\"") {
            return """;
        } else if (match == "<") {
            return "<";
        } else {
            return match;
        }
    }
}

Note: This function works by supressing all pre-existing xmlns declarations within the DOM and just allowing global namespace declarations on the document element. As such, cases where xmlns prefixes need to be redefined are not catered for here.


Please register to reply