Sunday, July 18, 2010

How To Use InsertAfter() in XmlDocument and Copy Nodes from another XmlDocument

Symptom:

You tried to insert an element (actually it has to be an XmlNode) using InsertAfter() or InsertBefore() method as doing so results in an exception with "The reference node is not a child of this node."

You also want to do the same but take a node from one XmlDocument to another.

Personally I feel that the Xml classes in the .NET Framework is not very intuitive. If you approach them as if they are regular List or Tree class type collection, then you will get a bunch of exceptions.

In terms of InsertAfter() type operations, this is a Node based operation and you normally cannot run InsertAfter() against the XmlDocument object. You must at least go down to the first child of the document, then invoke InsertAfter() at the node with the new node data (to be inserted) and the reference node location. This is most puzzling to me but that's what you need to do.

In terms of copying the data from a node in one document to another, all the nodes created or inserted in one XmlDocument have the membership to that XmlDocument. This is why new nodes must be created from the originating document using CreateElement() type calls. If you are familiar with creating a new row from the DataTable class, then you also know that you have to add that row to the table later is similar to this approach.

To confuse the matter, there are Clone type functions in XmlNode (see note below*). In this context they seem to be of no use (again that's not intuitive to me, since I tend to think if you clone it, I would say you created a detached instance.)

In actuality though, it is an error condition if you simply copy the node to another XmlDocument. This is also separate from where the node resides in overall tree of the nodes. ImportNode() simply buys the membership in another XmlDocument without reserving exactly where your own seat is.

Another important point to remember is that the Node (or Element) you insert to another XmlDocument should be the one returned from the ImportNode() method and not the one you passed to ImportNode(). If you try to insert the node that you passed to the ImportNode() method, you will get an exception "The node to be inserted is from a different document context."

XmlNode useThisNode = doc.ImortNode( myOriginalNode );


Solution Example:

The following code example demonstrates how InsertAfter() should (or at least could) be used, then add yet another node from another XmlDocument (created in-line in the code).

 XmlDocument Insertion Example
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.XPath;
namespace XMLDocumentExample
{
class Program
{
static void Main(string[] args)
{
// === EXAMPLE 1: How To Use InsertAfter in XMLDocuments ========================
XmlDocument doc1 = new XmlDocument();
// This XPathNavigator creates a convenient way to Write out the tree on the console.
// we don't use it for nothing else in this example.
XPathNavigator nav1 = doc1.CreateNavigator();
// This creates the root document, which all XML documents will need.
// i.e. <doc1></doc1>
XmlElement e = doc1.CreateElement("doc1");
doc1.AppendChild(e);
// Everything in thid document are the child of the root. I will purposely make a
// Mistake here so that I don't have doc1.2.0 element. I will demonstrate inserting
// doc1.2.0 using InsertAfter.
/*
*
* <doc1>
<doc1.0.0 />
<doc1.1.0 />
<doc1.3.0 />
</doc1>
*
*/
XmlElement c = doc1.CreateElement("doc1.0.0");
e.AppendChild(c);
c = doc1.CreateElement("doc1.1.0");
e.AppendChild(c);
c = doc1.CreateElement("doc1.3.0");
e.AppendChild(c);
Console.WriteLine(nav1.OuterXml); // Will show you you have doc1.0.0, doc1.1.0 and doc1.3.0 now.
// Now we will try to add the missing doc1.2.0 and just as a bonus I will attach doc1.2.1 as the
// child of doc1.2.0
// First let's get to the node AFTER where we will insert doc1.2.0, so that will be doc1.1.0
// Note you can either use /doc1/doc1.1.0 or //doc1.1.0 as the XPath query.
XmlNode nod1_1_0 = doc1.SelectSingleNode("//doc1.1.0");
// This block of code assembles doc1.2.0 node and its child doc1.2.1
XmlElement n2 = doc1.CreateElement("doc1.2.0");
XmlElement n2c = doc1.CreateElement("doc1.2.1");
n2.AppendChild(n2c);
// This is the most tricky important part. You have to ask the parent node to InsertAfter, and not
// directly with doc1.
//XmlNode myParent = nod1_1_0.ParentNode;
//myParent.InsertAfter(n2, nod1_1_0);
XmlNode mp = doc1.FirstChild;
mp.InsertAfter(n2, nod1_1_0);
// The result would look like this:
/*
<doc1>
<doc1.0.0 />
<doc1.1.0 />
<doc1.2.0>
<doc1.2.1 />
</doc1.2.0>
<doc1.3.0 />
</doc1>
* */
// Write the tree out on the console.
Console.WriteLine(nav1.OuterXml);
// === EXAMPLE 2: Copy A Node from One XMLDocument to Another ==============
// Create the Second XmlDocument, and then copy a node in the second
// document into the first document.
XmlDocument doc2 = new XmlDocument();
doc2.AppendChild(doc2.CreateElement("doc2"));
doc2.FirstChild.AppendChild(doc2.CreateElement("doc2.0.0"));
XPathNavigator nav2 = doc2.CreateNavigator();
Console.WriteLine(nav2.OuterXml);
XmlNode node_2_0_0 = doc2.SelectSingleNode("//doc2.0.0");
// Note that this does not append the node, it simply says that
// a foreign node can now belong to the first document.
XmlNode importableNode = doc1.ImportNode(node_2_0_0, true);
doc1.SelectSingleNode("/doc1/").AppendChild(importableNode);
Console.WriteLine(nav1.OuterXml);
Console.ReadLine();
}
}
}



*Note: Cloning of a node is required when you need to make a copy of a node and insert that back into the same XmlDocument. It appears that you cannot make a reference to a node and insert that (of course, if you can do that then there is a side effect of both node data changing if you edit the content of one node.)


No comments: