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>