Introduction
This article will show you how to process XML sub-trees when using XmlReader
s and XmlWriter
s in the .NET Framework 1.1.
Background
Recently, in an application, I had the need to log all XML messages exchanged with service providers.
Being services implemented over the POX/REST (Plain Old XML/REpresentational State Transfer) protocol, I had the disadvantage of not being able to take advantage of the SOAP implementation in the .NET framework but, because I had to do it myself, I would have some control over the messages.
Part of the message content belonged to the protocol implemented by the service provider (something like SOAP), and only a small part of it was service specific content. The service clients use DTOs (Data Transfer Objects) which were serialized using XmlSerializer
s.
To make it more difficult to tamper with message content and to gain some performance, I opted by not using XmlDocument
s, instead, I used XmlReader
s and XmlWriter
s.
Because the messages were being handed out to XmlSerializer
s (and instances of other classes), I was afraid something could happen to the message content because XmlReader
s and XmlWriter
s were, some times, out of my control. I had to build something that would guarantee the integrity of the messages (and the XmlReader
s and XmlWriter
s being used to process them).
XML sub-tree processing
The requirement looked simple; guarantee that each element of the XmlReader
or XmlWriter
was treated as a sub-tree and seen by other code as a complete XML document.
XML sub-tree reading
To implement XML sub-tree reading, I opted on building a XmlReader
derived class (XmlSubtreeReader
) that would receive, on instantiation of its objects, an instance of an XmlReader
derived class from where its current element will be used as the root of a new XmlReader
.
I opted also to implement IDisposable
to take advantage of the use of using
(Using
in Visual Basic) blocks and to be compliant with the implementation of the XmlReader
in the .NET framework 2.0.
public class XmlSubtreeReader : System.Xml.XmlReader,
System.IDisposable
{
public XmlSubtreeReader(System.Xml.XmlReader
wrappedReader) : base()
{
...
}
}
To guarantee that the recently created XmlSubtreeReader
doesn't compromise the integrity of the wrapped XmlReader
, on its instantiation, the wrapped XmlReader
's current depth is stored and used to guarantee that the wrapped XmlReader
's depth doesn't go bellow its initial value.
public override bool Read()
{
switch (this.readerState)
{
case XmlSubtreeReader.State.Initial:
{
this.readerState =
XmlSubtreeReader.State.Interactive;
this.ProcessNamespaces();
return true;
}
case XmlSubtreeReader.State.Interactive:
{
break;
}
case XmlSubtreeReader.State.PopNamespaceScope:
{
this.namespaceManager.PopScope();
goto case
XmlSubtreeReader.State.ClearNamespaceAttributes;
}
case XmlSubtreeReader.State.ClearNamespaceAttributes:
{
this.namespaceAttributeCount = 0;
this.readerState =
XmlSubtreeReader.State.Interactive;
break;
}
case XmlSubtreeReader.State.Error:
case XmlSubtreeReader.State.EndOfFile:
case XmlSubtreeReader.State.Closed:
default:
{
return false;
}
}
this.currentNamespaceAttribute = -1;
this.wrappedReader.MoveToElement();
if ((this.wrappedReader.Depth == this.initialDepth) &&
((this.wrappedReader.NodeType ==
System.Xml.XmlNodeType.EndElement)
|| ((this.wrappedReader.NodeType ==
System.Xml.XmlNodeType.Element)
&& this.wrappedReader.IsEmptyElement)))
{
this.readerState = XmlSubtreeReader.State.EndOfFile;
return false;
}
if (this.wrappedReader.Read())
{
this.ProcessNamespaces();
return true;
}
return false;
}
On the other hand, when closing the XmlSubtreeReader
, the wrapped XmlReader
should be skipped until its initial depth is reached.
public override void Close()
{
if (this.readerState !=
XmlSubtreeReader.State.Closed)
{
while (this.readerState !=
XmlSubtreeReader.State.EndOfFile)
{
this.Skip();
}
this.wrappedReader.Skip();
this.readerState = XmlSubtreeReader.State.Closed;
}
}
XML sub-tree writing
To implement XML sub-tree writing, I opted on building a XmlWriter
derived class (XmlSubtreeWriter
) that would receive, on instantiation of its objects, an instance of an XmlWriter
derived class from where its current element will be used as the root of a new XmlWriter
.
For the same reasons as with the reader, I opted on implementing IDisposable
.
public class XmlSubtreeWriter : System.Xml.XmlReader,
System.IDisposable
{
public XmlSubtreeWriter(System.Xml.XmlWriter
wrappedWriter) : base()
{
...
}
}
To guarantee that the recently created XmlSubtreeWriter
doesn't compromise the integrity of the wrapped XmlWriter
, on its instantiation, the wrapped XmlWriter
's current depth is stored and used to guarantee that the wrapped XmlWriter
's depth doesn't go bellow its initial value.
public override void WriteStartElement(string prefix,
string localName, string ns)
{
CheckWrite();
this.depth++;
this.wrappedWriter.WriteStartElement(prefix, localName, ns);
this.currentState = this.wrappedWriter.WriteState;
}
The CheckWrite
method is called at the beginning of every write start method to validate if the XmlSubtreeWriter
is in a valid state for writing.
In the same way, the CheckWriteEndElement
method is called in the beginning of every write end method to validate if the XmlSubtreeWriter
has reached its root.
As in sub-tree reading, when closing the XmlSubtreeWriter
, the wrapped XmlWriter
should end every element until its initial depth is reached.
public override void Close()
{
this.CompleteAll();
this.currentState = System.Xml.WriteState.Closed;
this.Flush();
}