Introduction
XmlDocument
objects are easy to use for navigating the DOM and copying, modifying, or inserting nodes. However, they can also use a large amount of memory to store the entire DOM of a large XML string in memory. Because of this, the XmlReader
and XmlWriter
classes are used for stream based manipulation of an XML string.
Using the code
The insert
method illustrates the process of reading two strings of XML with the XmlTextReader
from the System.Xml
namespace to ensure they are both valid XML. The source XML is read to ensure its validity and then stored in a StringBuilder
object. The target XML is read, but the root element is stripped off and the name and attributes and stored in string variables. The inner XML from the target document is read and stored in another StringBuilder
object. Finally the XmlTextWriter
is used to write out the root element and its attributes with the source XML inserted before the inner XML from the target document.
Start off with the insert
method declaration.
private string insert(string sourceXml, string targetXml)
{
...
}
First, the XmlTextReader
and XmlTextWriter
objects must be created and initialized with the sourceXml string.
System.Text.StringBuilder sb = new System.Text.StringBuilder();
System.IO.StringReader stringReader =
new System.IO.StringReader(sourceXml);
System.IO.StringWriter stringWriter =
new System.IO.StringWriter(sb);
System.Xml.XmlTextReader xmlReader =
new System.Xml.XmlTextReader(stringReader);
System.Xml.XmlTextWriter xmlWriter =
new System.Xml.XmlTextWriter(stringWriter);
string strValidSourceXml = String.Empty;
string strValidTargetXml = String.Empty;
string strRootName = String.Empty;
string[,] arrRootAtts = null;
The source XML is read, validated, and written into the StringBuilder
object by parsing each node with the XmlReader
and subsequently writing each node to the StringBuilder
with the XmlWriter
.
try
{
while(xmlReader.Read())
{
xmlWriter.WriteNode(xmlReader,true);
}
}
catch(System.Xml.XmlException e)
{
MessageBox.Show(this,
"Error parsing source XML\n\nMessage: " + e.Message,
"Parser Error",MessageBoxButtons.OK,MessageBoxIcon.Error);
return String.Empty;
}
strValidSourceXml = sb.ToString();
After reading the source, the readers and writers must be reset to parse the target XML.
sb = new System.Text.StringBuilder();
stringReader = new System.IO.StringReader(targetXml);
stringWriter = new System.IO.StringWriter(sb);
xmlReader = new System.Xml.XmlTextReader(stringReader);
xmlWriter = new System.Xml.XmlTextWriter(stringWriter);
Then the root element is broken into its components - name and attributes. The inner XML from the root is read into a string variable.
try
{
while(xmlReader.Read())
{
strRootName = xmlReader.Name;
if(xmlReader.HasAttributes)
{
int i = 0;
arrRootAtts = new string[xmlReader.AttributeCount,2];
while(xmlReader.MoveToNextAttribute())
{
arrRootAtts[i,0] = xmlReader.Name;
arrRootAtts[i,1] = xmlReader.Value;
i++;
}
}
strValidTargetXml = xmlReader.ReadInnerXml();
}
}
catch(System.Xml.XmlException e)
{
MessageBox.Show(this,
"Error parsing target XML\n\nMessage: " + e.Message,
"Parser Error",MessageBoxButtons.OK,MessageBoxIcon.Error);
return String.Empty;
}
The root node and its attributes are written out to a StringBuilder
using the XmlWriter
. The validated XML from the source and the inner XML from the target are written as children of the root node.
xmlWriter.WriteStartElement(strRootName);
if (arrRootAtts != null)
{
for (int i = 0; i < arrRootAtts.GetLength(0); i++)
{
xmlWriter.WriteAttributeString(arrRootAtts[i,0],
arrRootAtts[i,1]);
}
}
xmlWriter.WriteRaw(strValidTargetXml + strValidSourceXml);
xmlWriter.WriteEndElement();
Finally, the method returns the string from the StringBuilder
object containing the combined XML as a string.
return sb.ToString();

Memory Savings
Be aware that the StringBuilder
and the strings themselves carry some memory overhead, so it's often worth taking time to compare the two approaches for typical strings of XML and determine if the memory savings is worth the extra development effort.
In my testing, for combining two strings of XML, using the XmlReader
resulted in 1/2 the memory consumption of the XmlDocument
approach.
While the XmlDocument
object is certainly more intuitive for inserting nodes into a string of XML, the XmlReader
and XmlWriter
objects can be utilized for quickly parsing and combining large strings of XML with less memory overhead than using the XmlDocument
and XmlDocumentFragment
objects to combine two documents.
History