Partager via


Manually Cloning LINQ to XML Trees

There are a variety of circumstances where you want to clone a LINQ to XML tree while making modifications to the cloned tree.  It’s possible to write a very small amount of recursive code to do this.  I’ve posted elsewhere about normalizing a LINQ to XML tree, and in that post, I use the coding approach that I present here.

This blog is inactive.
New blog: EricWhite.com/blog

Blog TOC(July 1, 2009 - Updated - OK to normalize empty elements to an element with a self-closing tag.) 

You can, of course, clone an XML tree using the XElement or XDocument constructors, however you have no control over the cloning process.

The gist of the technique is to write a recursive function that clones an element.  In its simplest form, here is the code to clone a tree:

static XElement CloneElement(XElement element)
{
return new XElement(element.Name,
element.Attributes(),
element.Nodes().Select(n =>
{
XElement e = n as XElement;
if (e != null)
return CloneElement(e);
return n;
}
)
);
}

static void Main(string[] args)
{
XElement root = new XElement("Root",
new XAttribute("a", 1),
new XElement("Child", 1),
new XElement("Child", 2),
new XComment("This is a comment.")
);

XElement root2 = CloneElement(root);
Console.WriteLine(root);
Console.WriteLine("==========");
Console.WriteLine(root2);
Console.WriteLine();
}

The default behavior of CloneElement is to normalize an empty element (<Tag></Tag>) to a self-closing element (<Tag/>).  This is correct behavior - the XML specification basically states that the two forms of markup are equivalent.  Further, if an element is declared to be EMPTY, only a self-closing tag will do, whereas any empty element can be converted to a self-closing tag, so it's always safe to convert to self-closing.  This is the default behavior of LINQ to XML.

We can modify the CloneElement method to customize the cloned tree.  If, say, we want to remove all namespace attributes and remove all comment nodes, we can write it like this:

static XElement CloneElement(XElement element)
{
return new XElement(element.Name,
element.Attributes().Where(a => !a.IsNamespaceDeclaration),
element.Nodes().Select(n =>
{
if (n is XComment)
return null;
XElement e = n as XElement;
if (e != null)
return CloneElement(e);
return n;
}
)
);
}

static void Main(string[] args)
{
XNamespace aw = "https://www.adventureworks.com";
XElement root = new XElement(aw + "Root",
new XAttribute(XNamespace.Xmlns + "aw", aw.NamespaceName),
new XAttribute("a", 1),
new XElement(aw + "Child", 1),
new XElement(aw + "Child", 2),
new XComment("This is a comment.")
);

XElement root2 = CloneElement(root);
Console.WriteLine(root);
Console.WriteLine("==========");
Console.WriteLine(root2);
}

This produces the following output:

<aw:Root xmlns:aw="https://www.adventureworks.com" a="1">
<aw:Child>1</aw:Child>
<aw:Child>2</aw:Child>
<!--This is a comment.-->
</aw:Root>
==========
<Root a="1" xmlns="https://www.adventureworks.com">
<Child>1</Child>
<Child>2</Child>
</Root>

Comments

  • Anonymous
    January 30, 2009
    I always wondered why the standard XLINQ API doesn't have some sort of visitor for creating a new tree from the old one. With that, and some creative use of predicates, I think we could actually get almost on par with XSLT for tree rewriting (I still prefer it to XLINQ because of the conciseness and clarity of XSLT transforms which only need to modify a few specific nodes out of many). By the way, I've posted here under the alias 'int19h' previously. Now it's new job, new email, and new blog account :)

  • Anonymous
    February 16, 2009
    In certain scenarios, it is important to be able to compare two XML trees for equivalence. For example,

  • Anonymous
    April 15, 2010
    How can i clone a document without cloning default namespace? Or, ideally, can I change or remove default namespace without cloning?

  • Anonymous
    April 15, 2010
    Hi Leo, Here is a brand-new blog post on this: http://blogs.msdn.com/ericwhite/archive/2010/04/16/controlling-namespace-serialization-of-linq-to-xml.aspx -Eric