4

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.

Nimantha
  • 6,405
  • 6
  • 28
  • 69
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219

1 Answers1

7

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

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
  • 1
    Thanks for the tip. I had the same problem, now with your help I got a nice `.xml` with proper namespace. – junpet Feb 14 '20 at 16:39