In my previous post I showed how to use the W3C DOM API to create XML with namespaces, using namespace aware methods like createElementNS or setAttributeNS of the W3C DOM Level 2 and 3 Core API. While MSXML (all versions including the latest, MSXML 6) implements the W3C DOM Level 1 Core API it does not implement any of the methods used in the previous post, like createElementNS or setAttributeNS. Instead it has a single method createNode that takes as its first argument the node type, as its second argument the qualified name and as its third argument the namespace you want to create a node in. This post shows how to use that method to create XML with namespaces with MSXML and JavaScript. The examples use MSXML 3 but later versions of MSXML (e.g. 4, 5, 6) expose exactly the same API.
Let's assume you want to create the following XML document with JavaScript and the MSXML DOM API:
<root xmlns="http://example.com/ns1"><foo><bar>foobar</bar></foo></root>
The key to doing that properly is to undestand the following: in the
XML markup there is an XML default namespace declaration attribute
xmlns="http://example.com/ns1" on the "root" element that is in scope
for the "root" element and all its descendant elements (e.g. the "foo"
and the "bar" element) meaning all three elements, the "root" element,
the "foo" element and the "bar" element are in that namespace
http://example.com/ns1. To create that XML document programmatically
you have to create all three elements in that namespace. Thus to create those three elements, you need to call createNode three times, each time passing in the node type (1 for element node), the element name (e.g. 'root') and the namespace (e.g. 'http://example.com/ns1'):
var doc = new ActiveXObject('Msxml2.DOMDocument.3.0');
var ns1 = 'http://example.com/ns1';
var root = doc.createNode(1, 'root', ns1);
var foo = doc.createNode(1, 'foo', ns1);
var bar = doc.createNode(1, 'bar', ns1);
bar.appendChild(doc.createTextNode('foobar'));
foo.appendChild(bar);
root.appendChild(foo);
doc.appendChild(root);
That creates an XML DOM document that, when serialized, looks as the above document. Here is an example
showing that. As you can see, the DOM code did not need to create the
default namespace declaration attribute at all, nevertheless, when the document is serialized by accessing the xml property, the serialization adds it.
Let's look at a further example that includes elements and attributes in two namespaces:
<root xmlns="http://example.com/ns1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://example.com/n1 schema.xsd">
<foo>
<bar>foobar</bar>
</foo>
</root>
[Note: for better reading I have inserted whitespace in the XML
markup but the code I will show will focus on creating the elements and
attributes only, not the whitespace.] In the above XML sample we now
have two additional attributes, one namespace declaration attribute
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" and one attribute
in that namespace, the xsi:schemaLocation attribute. Again we do not
have to create any namespace declaration attributes at all, it suffices
to use the previous code, add a call to createNode and pass in
2 as the node type for attribute, the qualified name of the attribute (e.g. 'xsi:schemaLocation') and the namespace (e.g. 'http://www.w3.org/2001/XMLSchema-instance') to create that attribute and use setAttributeNode to add the attribute to the 'root' element:
var doc = new ActiveXObject('Msxml2.DOMDocument.3.0');
var ns1 = 'http://example.com/ns1';
var xsi = 'http://www.w3.org/2001/XMLSchema-instance';
var root = doc.createNode(1, 'root', ns1);
var schemaLocation = doc.createNode(2, 'xsi:schemaLocation', xsi);
schemaLocation.nodeValue = 'http://example.com/n1 schema.xsd';
root.setAttributeNode(schemaLocation);
var foo = doc.createNode(1, 'foo', ns1);
var bar = doc.createNode(1, 'bar', ns1);
bar.appendChild(doc.createTextNode('foobar'));
foo.appendChild(bar);
root.appendChild(foo);
doc.appendChild(root);
Here is an example
showing the result. Again, serialization (i.e. accessing the the xml property) creates all necessary
namespace declaration attributes, as long as elements and attributes
have been created in the namespaces they belong to. The only reason to
create a namespace declaration attribute explicitly is to enforce its
output on an element where the namespace is not used. Let's look at a
further example:
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<script type="text/ecmascript" xlink:href="foo.js"/>
</svg>
In that XML document the XLink namespace
http://www.w3.org/1999/xlink is defined on the root element, the 'svg'
element, although the namespace is only used on the 'script' child
element's attribute xlink:href. This time, if we want to ensure the
namespace declaration attribute appears on the 'svg' element, we have
to explicitly set it on that element (and need to know that namespace
declaration attributes are per definition in the namespace
http://www.w3.org/2000/xmlns/):
var doc = new ActiveXObject('Msxml2.DOMDocument.3.0');
var svgNs = 'http://www.w3.org/2000/svg';
var xlinkNs = 'http://www.w3.org/1999/xlink';
var root = doc.createNode(1, 'svg', svgNs);
var xlink = doc.createNode(2, 'xmlns:xlink', 'http://www.w3.org/2000/xmlns/');
xlink.nodeValue = xlinkNs;
root.setAttributeNode(xlink);
var script = doc.createNode(1, 'script', svgNs);
script.setAttribute('type', 'text/ecmascript');
var href = doc.createNode(2, 'xlink:href', xlinkNs);
href.nodeValue = 'foo.js';
script.setAttributeNode(href);
root.appendChild(script);
doc.appendChild(root);
Here is an example showing the result. If we did not set the namespace declaration
attribute on the 'svg' element then the resulting serialized document
would nevertheless be namespace well-formed XML, only serialization
would add the namespace declaration on the 'script' element.
So keep two things in mind when creating XML with namespaces
programmatically with the MSXML DOM API: you need to create each element
and attribute in the namespace it belongs to, passing in the node type, the qualified name and the namespace
URI to the createNode method. And you do not
need to create namespace declaration attribute explicitly, unless you
want to enforce its appearance on an element where the namespace is not
used (like the root element of your document).
The first rule is also of importance when you want to add elements
or attributes to an already loaded document. Assuming we have loaded
the following document
<root xmlns="http://example.com/ns1">
<foo/>
</root>
and want to add a 'bar' element in the same namespace as the other
elements then often people assume they can create a 'bar' element with
createElement('bar') and add it to the root element and that it then
takes on the namespace of the root element. That is not the case
however, createElement('bar') creates a 'bar' element in no namespace
and when you insert that as a child of the above 'root' element and
serialize the serialization will add <bar xmlns=""/> to ensure the
created element is serialized in no namespace. So to properly add a
'bar' element in the same namespace as the 'root' element you again
need to use createNode and pass in the namespace URI:
var bar = doc.createNode(1, 'bar', doc.documentElement.namespaceURI);
doc.documentElement.appendChild(bar);