Combining the XmlReader and XmlWriter classes for simple streaming transformations

The XmlReader and XmlWriter classes can often be combined to provide simple streaming transformations rather than resorting to XSLT which requires a the document to be loaded into memory. This class combination is often faster and uses less memory, although it requires more code and is less flexible in the types of transformations possible. However for many scenarios it is ideal. Say for example you wanted to add a new element in several repeating places to an existing document. The XmlWriter.WriteNode method is useful in pulling from an XmlReader and pushing to an XmlWriter to achieve this, but it does have a limitation in that it writes the current node and all its children to the XmlWriter without providing more fine-grained control.

The code below shows a method called WriteShallowNode, which writes individual nodes to the XmlWriter from the XmlReader. In this way you can change individual nodes during the transformation process.

//This method is useful for streaming transformation with the

//XmlReader and the XmlWriter. It pushes through single nodes in the stream

static void WriteShallowNode( XmlReader reader, XmlWriter writer )

{

if ( reader == null )

{

throw new ArgumentNullException("reader");

}

if ( writer == null )

{

throw new ArgumentNullException("writer");

}

switch ( reader.NodeType )

{

case XmlNodeType.Element:

writer.WriteStartElement( reader.Prefix, reader.LocalName, reader.NamespaceURI );

writer.WriteAttributes( reader, true );

if ( reader.IsEmptyElement )

{

writer.WriteEndElement();

}

break;

case XmlNodeType.Text:

writer.WriteString( reader.Value );

break;

case XmlNodeType.Whitespace:

case XmlNodeType.SignificantWhitespace:

writer.WriteWhitespace(reader.Value);

break;

case XmlNodeType.CDATA:

writer.WriteCData( reader.Value );

break;

case XmlNodeType.EntityReference:

writer.WriteEntityRef(reader.Name);

break;

case XmlNodeType.XmlDeclaration:

case XmlNodeType.ProcessingInstruction:

writer.WriteProcessingInstruction( reader.Name, reader.Value );

break;

case XmlNodeType.DocumentType:

writer.WriteDocType( reader.Name, reader.GetAttribute( "PUBLIC" ), reader.GetAttribute( "SYSTEM" ), reader.Value );

break;

case XmlNodeType.Comment:

writer.WriteComment( reader.Value );

break;

case XmlNodeType.EndElement:

writer.WriteFullEndElement();

break;

}

}

Performing simple transformations with this method is very straightforward. The code below (written for .NET v2.0) reads the movies.xml document and for those dvd elements whose attribute genre is an action type, a new publisher element is written to the output.

Input document:

<?xml version="1.0"?>

<dvdstore>

<dvd genre="action" publicationdate="1990" >

<title>T2</title>

<review>This film was <b>impressive</b> in its creativity.</review>

<stats>

<price>8.99</price>

<id>123-456</id>

</stats>

</dvd>

….

</dvdstore >

static void AddElementWithWriteShallowNode()

{

XmlWriterSettings settings = new XmlWriterSettings();

settings.Indent = true;

using (XmlReader reader = XmlReader.Create("movies.xml"))

{

using (XmlWriter writer = XmlWriter.Create(Console.Out, settings))

{

while (reader.Read())

{

if (reader.IsStartElement("dvd") &&

reader.GetAttribute("genre") == "action")

{

//Write the dvd element

WriteShallowNode(reader, writer);

//Now add a new publisher element to the output

writer.WriteElementString("publisher",

"metal.sword.com", "Samurai Films");

}

else

WriteShallowNode(reader, writer);

}

}

}

}

Output document:

<?xml version="1.0"?>

<dvdstore>

<dvd genre="action" publicationdate="1990" >

<p:publisher xmlns:p='metal.sword.com'>Samurai Films</p:publisher>

<title>T2</title>

<review>This film was <b>impressive</b> in its creativity.</review>

<stats>

<price>8.99</price>

<id>123-456</id>

</stats>

</dvd>

….

</dvdstore >

Without this method you have to use the XmlWriter WriteXXX methods to re-write the XML document to the output if all you wanted to do was inject a single publisher element into the tree.

For an excellent and complete application example of using this approach see Dan Wahlin’sGenerate Dynamic Maps and Flight Routes with XML and SVG