Have you ever wanted to change the default namespace of an XmlDocument in .NET. It's not as easy as it might be.
The problem
Sometimes, particularly in middleware systems, you need to pass a source document to a target system. The document may not need any changes, but one system will apply namespace declarations in the document and the other won't work with them, or one won't add them and the other will require them.
There are a few main approaches you can take:
- XSL Transforms
- Regular expression replacements (or other string manipulations)
- XmlNode traversal methods (DOM or XmlReader)
For such a simple replacement process, XSL Transforms are overkill. Regular expressions suffer in being fairly difficult to write well and to work with different encodings, processing instructions and comments. Not impossible though. The third option is my preferred solution.
The third option has two variations. The first, creating a new XmlDocument by traversing the original document is fairly heavy as a second XmlDocument is built in memory. Using the XmlReader allows for a lightweight and efficient method, suitable for handling large documents.
A solution framework
The following solution has a few sections to it:
- The creation of an appropriate XmlReader
- The creation of an appropriate XmlWriter
- Processing of nodes prior to the document element node (XML declaration if present and comments)
- Processing of the document element and descendent nodes
- Processing of nodes after the document element node (mainly comments)
- Clean up
The code that follows uses string I/O. You are more likely to be using a stream as input and output.
The solution
Part 1: Creating an XmlReader
string sourceXml =
"<?xml version=\"1.0\" ?>" +
"<!-- comment -->" +
"<elementName b='doc value'>" +
"<subelement a='a value'>" +
"test data" +
"</subelement>" +
"<!-- comment -->" +
"</elementName>" +
"<!-- comment -->";
StringReader sreader = new StringReader(sourceXml);
XmlReader xreader = XmlReader.Create(sreader);
Note that the source XML does not include a default namespace. The purpose of this exercise is to add one.
Part 2: Creating an XmlWriter
StringWriter swriter = new StringWriter();
XmlWriterSettings wsettings = new XmlWriterSettings();
wsettings.OmitXmlDeclaration = true;
wsettings.ConformanceLevel = ConformanceLevel.Document;
wsettings.Indent = true;
wsettings.NewLineOnAttributes = true;
XmlWriter xwriter = XmlWriter.Create(swriter, wsettings);
The XmlWriterSettings is being used to configure the output, in this case preventing the XmlDeclaration from being output.
Part 3: Processing of nodes prior to the document element
A well-formed XML document has exactly one root element node, however it may have comments and other nodes prior to the element node. These need to be copied verbatim to the output, and that is what the following code does:
xreader.Read();
while (! xreader.EOF)
{
// look for an XML element node
if (xreader.NodeType == XmlNodeType.Element)
break;
// otherwise write the content to the
// output. This also reads the next node
xwriter.WriteNode(xreader, true);
}
Note that using the WriteNode() method is an efficient way of handling large amounts of content, and it is also used in the following sections.
Part 4: Processing the document element node
The document element consists of one of the following:
- An empty element
- doesn't have any contained nodes but may have attributes
- A matching pair of start and end element tags
- may have attributes
- may have any internal structure
In this example, a default namespace is added to the node. It is straightforward to do the reverse and remove a default namespace. The steps are as follows:
- Create an appropriate start element
- Copy in the attributes
- Process any descendent content
- Close the element
Here's the code:
if (!xreader.EOF)
{
// process the document element
xwriter.WriteStartElement(
String.Empty, // no prefix so the default namespace
xreader.LocalName, // same local name
"urn:tempuri.org/schema"); // the namespace
// copy in all attributes
xwriter.WriteAttributes(xreader, true);
// check if it is an empty element
if (xreader.IsEmptyElement)
{
// move passed the document element
xreader.Read();
}
else
{
// move to descendent content
xreader.Read();
while (!xreader.EOF)
{
// an end element must close the
// document element
if (xreader.NodeType == XmlNodeType.EndElement)
{
// move past the document element
xreader.Read();
break;
}
// write the entire node to the output
// and move to the following node
xwriter.WriteNode(xreader, true);
}
}
// close the document element
xwriter.WriteEndElement();
}
Step 5: Processing subsequent nodes
The steps are identical to the initial nodes:
while (! xreader.EOF)
{
xwriter.WriteNode(xreader, true);
}
Step 6: Clean up
There are two main operations to complete as part of clean up:
- Flushing the output to the stream using XmlWriter.Flush()
- Closing the various streams, readers and writers
These steps are left to the reader.
Versions
- Microsoft .NET Framework 2.0
Metadata