[Blog Map] This blog is inactive. New blog: EricWhite.com/blog
(July 1, 2009 - Updated - OK to normalize empty elements to an element with a self-closing tag.)
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.
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:
staticXElement CloneElement(XElement element)
{
returnnewXElement(element.Name,
element.Attributes(),
element.Nodes().Select(n =>
{
XElement e = n asXElement;
if (e != null)
return CloneElement(e);
return n;
}
)
);
}
staticvoid Main(string[] args)
{
XElement root = newXElement("Root",
newXAttribute("a", 1),
newXElement("Child", 1),
newXElement("Child", 2),
newXComment("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:
staticXElement CloneElement(XElement element)
{
returnnewXElement(element.Name,
element.Attributes().Where(a => !a.IsNamespaceDeclaration),
element.Nodes().Select(n =>
{
if (n isXComment)
returnnull;
XElement e = n asXElement;
if (e != null)
return CloneElement(e);
return n;
}
)
);
}
staticvoid Main(string[] args)
{
XNamespace aw = "http://www.adventureworks.com";
XElement root = newXElement(aw + "Root",
newXAttribute(XNamespace.Xmlns + "aw", aw.NamespaceName),
newXAttribute("a", 1),
newXElement(aw + "Child", 1),
newXElement(aw + "Child", 2),
newXComment("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="http://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="http://www.adventureworks.com">
<Child>1</Child>
<Child>2</Child>
</Root>
Clik here to view.