Jousting with XML – Adding Attributes to Existing Nodes

Home » SharePoint Development » Jousting with XML – Adding Attributes to Existing Nodes

Jousting with XML – Adding Attributes to Existing Nodes

Posted on

This isn’t technically a SharePoint-related post as the issue is one of pure XML manipulation but I ran across it while building a custom solution in SharePoint and it took hours to figure it out so I thought it merited some attention. When working with XML, as is often the case in SharePoint (especially when using web services), there is often a need to modify some fragment of XML to support a resultant operation. In this particular instance, I had an XML fragment similar to the following:

<Books>
<Book name="My Book" id="book1">
<Chapters>
<Chapter name="Chapter 1" />
<Chapter name="Chapter 2" />
<Chapter name="Chapter 3" />
</Chapters>
</Book>
</Books>

In order to populate a TreeView and from there construct a link for each node with the proper parameters, I needed to add the ID of the parent Book object to each one of the Chapter nodes as a new attribute. Sounds simple enough. I started with something like this (error trapping and null checks omitted for brevity):

 

XmlDocument doc = new XmlDocument();
doc.LoadXml(booksxml);
XmlNode bookNode = doc.DocumentElement.SelectSingleNode("Book");
string bookId = bookNode.Attributes["id"].Value;

XmlAttribute parentBookId = doc.CreateAttribute("bookid");
parentBookId.Value = bookId;

XmlNodeList chapterNodes = bookNode.SelectNodes("Chapters/Chapter");

foreach (XmlNode chapterNode in chapterNodes)
{
   chapterNode.Attributes.Append(parentBookId);
}

Easy, right? Not so fast. Under no circumstances would the new "bookid" attribute get added to each Chapter node in the resultant XML string. I tried all kinds of things, including converting the node to an XmlElement and using the SetAttribute() method, moving the value assignment into the node itself, and so on, but nothing seemed to work. For whatever reason it just ignored my new attribute and moved right along. What was really frustrating was that in debug mode I could see the new attribute being added to each node as I stepped through it. But when the code ran the final output was unchanged.

After much searching around and cursing XML in general, I tried what seemed a silly bit of code:

 

XmlDocument doc = new XmlDocument();
doc.LoadXml(booksxml);
XmlNode bookNode = doc.DocumentElement.SelectSingleNode("Book");
string bookId = bookNode.Attributes["id"].Value;

XmlNodeList chapterNodes = bookNode.SelectNodes("Chapters/Chapter");

foreach (XmlNode chapterNode in chapterNodes)
{
   XmlAttribute parentBookId = chapterNode.OwnerDocument.CreateAttribute("bookid");
   parentBookId.Value = bookId;
   chapterNode.Attributes.Append(parentBookId);
}

Voila! It worked. Moving the creation of the new attribute element into the context of the current node did the trick. That’s a bit cumbersome, to say the least, especially as my actual code contained several nested collections of child nodes, so I had to repeat the process for each node in the subsequent collections, each time changing the variable name of the new attribute but leaving the actual attribute name ("bookid") the same. I’ve since tested it in several variations (.NET Framework 3.5) and found that this is the only method that seems to work. My final XML looked just like I wanted it to:

<Books>
<Book name="My Book" id="book1">
<Chapters>
<Chapter name="Chapter 1" bookid="book1" />
<Chapter name="Chapter 2" bookid="book1" />
<Chapter name="Chapter 3" bookid="book1" />
</Chapters>
</Book>
</Books>

I’d love to know what’s going on here. Why is a globally declared (in the context of the XML document itself) attribute not viable while a locally declared (for each node) attribute that’s created in the context of the parent document perfectly feasible? It sure makes for some ugly code and the test routines aren’t very clean. If anyone has some insight to share please enlighten me. On the other hand, for once it wasn’t some weird SharePoint-only thing, so I guess I should be thankful for that…