Search code examples
winapimsxmlixmldomdocument

How do I add a namespace to an msxml DOMDocument?


How do I add a schema to an IXMLDOMDocument?

For example, I want to generate the XML:

<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
   <Relationship Id="rId1" Type="Frob" Target="Grob"/>
</Relationships>

I can construct the DOMDocument60 object (pseudo-code):

DOMDocument60 doc = new DOMDocument60();

IXMLDOMElement relationships = doc.appendChild(doc.createElement("Relationships"));

IXMLDOMElement relationship = relationships.appendChild(doc.createElement("Relationship"));
   relationship.setAttribute("Id", "rId1");
   relationship.setAttribute("Type", "Frob");
   relationship.setAttribute("Target", "Grob");

Now comes the question of how to add the namespace.

How to add the namespace?

If I do the obvious solution, setting an attribute on the Relationships node called xmlns:

<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">

through something like:

relationships.setAttribute("xmlns", 
      "http://schemas.openxmlformats.org/package/2006/relationships");

When the document is saved, it causes the resulting xml to be wrong:

<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
   <Relationship Id="rId1" Type="Frob" Target="Grob" xmlns=""/>
</Relationships>

It places empty xmlns attributes on every other element. In this little test document it only misapplies the xmlns to one element. In the real world there are dozens, or a few million other elements with an empty xmlns attribute.

namespaceURI property

I tried setting the namespaceURI property of the Relationships element:

relationshps.namespaceURI := "http://schemas.openxmlformats.org/package/2006/relationships"; 

but the property is read-only.

schemas Property

The document does have a schemas property, which gets or sets an XMLSchemaCache object. But it requires an actual schema document. E.g. trying to just set a schema doesn't work:

schemas = new XMLSchemaCache60();
schemas.add('', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
doc.schemas := schemas;

But that tries to actually load the schema url, rather than not loading the schema because it isn't a URI.

Perhaps I have to randomly try other things:

schemas = new XMLSchemaCache60();
schemas.add('http://schemas.openxmlformats.org/spreadsheetml/2006/main', null);
doc.schemas := schemas;

But that causes no xmlns to be emitted.

Rather than trying to build an XML document the correct way, I could always use a StringBuilder to build the XML manually, and then have parse it into an XML Document object.

But I'd rather do it the right way.


Solution

  • The trick is to realize the W3C DOM Level 2 and 3 have a method createElementNS 🕗:

    Creates an element with the specified namespace URI and qualified name.

    Syntax

    element = document.createElementNS(namespaceURI, qualifiedName);

    However MSXML 6 only supports DOM Level 1.

    Fortunately, W3C DOM Level 1 did have a method to create an element with a namespace: createNode🕗:

    Creates a node using the supplied type, name, and namespace.

    HRESULT createNode(VARIANT Type, BSTR name, BSTR namespaceURI, out IXMLDOMNode node);
    

    Thus my solution is that i have to change:

    relationships: IXMLDOMElement = doc.createElement("Relationships"); 
    

    into:

    const NODE_ELEMENT: Integer = 1;
    const ns: string = "http://schemas.openxmlformats.org/package/2006/relationships";
    
    relationships: IXMLDOMElement = doc.createNode(NODE_ELEMENT, "Relationships", namespace); 
    

    A sucky part is that every element must be created in that namespace:

    function AddElementNS(IXMLDOMNode parentNode, String tagName, String namespaceURI): IXMLDOMElement;
    {
       doc: IXMLDOMDocument = parentNode as IXMLDOMDocument;
       if (doc == null) 
          doc = parentNode.ownerDocument;
    
       if (namespaceURI <> "")
          Result = doc.createNode(NODE_ELEMENT, tagName, namespaceURI)
       else
          Result = doc.createElement(tagName);
    
       parentNode.appendChild(Result);
    }
    
    relationships: IXMLDOMElement = AddElementNS(doc, "Relationships", ns);
    
    relationship: IXMLDOMElement = AddElementNS(relationships, "Relationship", ns);
       relationship.setAttribute("Id", "rId1");
       relationship.setAttribute("Type", "Frob");
       relationship.setAttribute("Target", "Grob");       
    

    Bonus Reading