Click here to Skip to main content
15,893,401 members
Articles / Programming Languages / XML
Article

XML sub-tree processing in .NET Framework 1.1

Rate me:
Please Sign up or sign in to vote.
3.38/5 (5 votes)
25 Jan 20063 min read 30.7K   260   9  
An article on how to process XML sub-trees in .NET Framework 1.1.

Introduction

This article will show you how to process XML sub-trees when using XmlReaders and XmlWriters 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 XmlSerializers.

To make it more difficult to tamper with message content and to gain some performance, I opted by not using XmlDocuments, instead, I used XmlReaders and XmlWriters.

Because the messages were being handed out to XmlSerializers (and instances of other classes), I was afraid something could happen to the message content because XmlReaders and XmlWriters were, some times, out of my control. I had to build something that would guarantee the integrity of the messages (and the XmlReaders and XmlWriters 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.

C#
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.

C#
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.

C#
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.

C#
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.

C#
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.

C#
public override void Close()
{
    this.CompleteAll();
    this.currentState = System.Xml.WriteState.Closed;
    this.Flush();
}

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior) Paulo Morgado
Portugal Portugal

Comments and Discussions

 
-- There are no messages in this forum --