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.
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.
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.
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.
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");