DekGenius.com
[ Team LiB ] Previous Section Next Section

Recipe 17.6 Detecting Changes to an XML Document

Problem

You need to inform one or more classes or components that a node in an XML document has been inserted, removed, or had its value changed.

Solution

In order to track changes to an active XML document, subscribe to the events published by the XmlDocument class. XmlDocument publishes events for node creation, insertion, and removal for both the pre- and post-conditions of these actions. In the following example, we have a number of event handlers defined in the same scope as the DetectXMLChanges method, but they could just as easily be callbacks to functions on other classes that are interested in the manipulation of the live XML document.

DetectXMLChanges loads an XML fragment we define in the method, wires up the event handlers for the node events, adds, changes, and removes some nodes to trigger the events, then writes out the resulting XML:

public static void DetectXMLChanges( )
{
    string xmlFragment = "<?xml version='1.0'?>" +
        "<!-- My sample XML -->" +
        "<?pi myProcessingInstruction?>" +
        "<Root>" + 
        "<Node1 nodeId='1'>First Node</Node1>" +
        "<Node2 nodeId='2'>Second Node</Node2>" +
        "<Node3 nodeId='3'>Third Node</Node3>" +
        @"<Node4><![CDATA[<>\&']]></Node4>" +
        "</Root>"; 

    XmlDocument doc = new XmlDocument( );
    doc.LoadXml(xmlFragment);

    //Create the event handlers.
    doc.NodeChanging += new XmlNodeChangedEventHandler(NodeChangingEvent);
    doc.NodeChanged += new XmlNodeChangedEventHandler(NodeChangedEvent);
    doc.NodeInserting += new XmlNodeChangedEventHandler(NodeInsertingEvent);
    doc.NodeInserted += new XmlNodeChangedEventHandler(NodeInsertedEvent);
    doc.NodeRemoving += new XmlNodeChangedEventHandler(NodeRemovingEvent);
    doc.NodeRemoved += new XmlNodeChangedEventHandler(NodeRemovedEvent);

    // Add a new element node.
    XmlElement elem = doc.CreateElement("Node5");
    XmlText text = doc.CreateTextNode("Fifth Element");
    doc.DocumentElement.AppendChild(elem);
    doc.DocumentElement.LastChild.AppendChild(text);

    // Change the first node
    doc.DocumentElement.FirstChild.InnerText = "1st Node";

    // remove the fourth node
    XmlNodeList nodes = doc.DocumentElement.ChildNodes;
    foreach(XmlNode node in nodes)
    {
        if(node.Name == "Node4")
        {
            doc.DocumentElement.RemoveChild(node);
            break;
        }
    }

    // write out the new xml
    Console.WriteLine(doc.OuterXml);
}

These are the event handlers from the XmlDocument along with one formatting method, WriteNodeInfo, that takes an action string and gets the name and value of the node being manipulated. All of the event handlers invoke this formatting method, passing the corresponding action string:

private static void WriteNodeInfo(string action, XmlNode node)
{
    if (node.Value != null)
    {
        Console.WriteLine("Element: <{0}> {1} with value {2}", 
            node.Name,action,node.Value);
    }
    else
        Console.WriteLine("Element: <{0}> {1} with null value", 
            node.Name,action);
}

public static void NodeChangingEvent(object source, XmlNodeChangedEventArgs e)
{
    WriteNodeInfo("changing",e.Node);
}

public static void NodeChangedEvent(object source, XmlNodeChangedEventArgs e)
{
    WriteNodeInfo("changed",e.Node);
}

public static void NodeInsertingEvent(object source, XmlNodeChangedEventArgs e)
{
    WriteNodeInfo("inserting",e.Node);
}

public static void NodeInsertedEvent(object source, XmlNodeChangedEventArgs e)
{
    WriteNodeInfo("inserted",e.Node);
}

public static void NodeRemovingEvent(object source, XmlNodeChangedEventArgs e)
{
    WriteNodeInfo("removing",e.Node);
}

public static void NodeRemovedEvent(object source, XmlNodeChangedEventArgs e)
{
    WriteNodeInfo("removed",e.Node);
}

The DetectXmlChanges method results in the following output:

Element: <Node5> inserting with null value
Element: <Node5> inserted with null value
Element: <#text> inserting with value Fifth Element
Element: <#text> inserted with value Fifth Element
Element: <#text> changing with value First Node
Element: <#text> changed with value 1st Node
Element: <Node4> removing with null value
Element: <Node4> removed with null value
<?xml version="1.0"?><!-- My sample XML --><?pi myProcessingInstruction?><Root><
Node1 nodeId="1">1st Node</Node1><Node2 nodeId="2">Second Node</Node2><Node3 nod
eId="3">Third Node</Node3><Node5>Fifth Element</Node5></Root>

Discussion

With an XmlDocument, you can traverse both forward and backward on the XML stream, as well as use XPath navigation to find nodes. If you are just reading XML and not modifying it, and you have no need for traversing backward through the nodes, you should avoid using XmlDocument, since XmlTextReader is faster for reading and XmlTextWriter is faster for writing (both have less overhead than XmlDocument). The .NET Framework team did a nice job of giving XML processing flexibility, but if you use a class with more functionality than you need, you will pay the resulting performance penalty.

See Also

See the "XmlDocument Class" and "XmlNodeChangedEventHandler Class" topics in the MSDN documentation.

    [ Team LiB ] Previous Section Next Section