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:

  1. The creation of an appropriate XmlReader
  2. The creation of an appropriate XmlWriter
  3. Processing of nodes prior to the document element node (XML declaration if present and comments)
  4. Processing of the document element and descendent nodes
  5. Processing of nodes after the document element node (mainly comments)
  6. 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:

  1. An empty element
    • doesn't have any contained nodes but may have attributes
  2. A matching pair of start and end element tags
    1. may have attributes
    2. 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:

  1. Create an appropriate start element
  2. Copy in the attributes
  3. Process any descendent content
  4. 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:

  1. Flushing the output to the stream using XmlWriter.Flush()
  2. Closing the various streams, readers and writers

These steps are left to the reader.

Versions

  • Microsoft .NET Framework 2.0

Metadata


Bookmark with :
Digg It! DZone StumbleUpon Technorati Reddit Del.icio.us Newsvine Furl Blinklist
posted @ Thursday, May 22, 2008 11:25 AM | in .NET Software Development

Comments

No comments posted yet.

Post Comment

Title *
Name *
Email
Url
Comment *  


Please add 4 and 5 and type the answer here: