i'm trying to add an "index" attribute to all XML nodes using recursion in C#. My problem is that when i try to add an attribute to a node that does not have child nodes, it fails with a null reference. for example, for a simple XML file (html) i want it to be like that:
<div index="1">
<div index="1.1">
<h2 index="1.1.1">some text1</h2>
<h2 index="1.1.2">some text</h2>
</div>
</div>
<div index="2">
<table index="2.1">
<tr index="2.1.1">
<td index="2.1.1.1">some cell</td>
<td index="2.1.1.2">some cell</td>
</tr>
<tr index="2.1.2">
<td index="2.1.2.1">some cell</td>
</tr>
</table>
</div>
<div index="3">
<h1 index="3.1">some text</h1>
</div>
My function now looks like this:
public static string TraverseNodes(XmlNode node,XmlDocument xmlDoc,bool isChild)
{
int i = 1;
foreach (XmlNode subNode in node)
{
var child = subNode.ChildNodes[i];
if (subNode.ChildNodes[i] != null)
{
XmlAttribute typeAttr = xmlDoc.CreateAttribute("realIndex");
typeAttr.Value = (isChild ? (i+ ".") : "") + (i + 1);
subNode.Attributes.Append(typeAttr);
}
i++;
TraverseNodes(subNode, xmlDoc, isChild);
}
return PrintXml(xmlDoc);
}
maybe my whole approach is wrong. i'd be glad for any help.
I'd use LINQ to XML for this. My first somewhat inefficient way of doing this would be:
foreach (var element in doc.Descendants())
{
int indexInLevel = element.ElementsBeforeSelf().Count() + 1;
var parent = element.Parent;
string prefix = parent == null ? "" : (string) parent.Attribute("index") + ".";
element.SetAttributeValue("index", prefix + indexInLevel);
}
Note that that will make your root element have an index of "1". It relies on the fact that Descendants
traverses in document order, so the parent of any element will already have had an index attribute set before it traverses down to the children.
Now I said that's fairly inefficient, because it needs to count all the earlier siblings each time. You could make it more efficient with recursion instead, and be a bit more flexible too:
public void AssignIndexes(XElement element, string prefix, int index)
{
string value = prefix + index;
element.SetAttributeValue("index", value);
value += "."; // As the prefix for all children
int subindex = 1;
foreach (var child in element.Elements())
{
AssignIndexes(child, value, subindex++);
}
}
public void AssignIndexesToChildren(XElement element)
{
int subindex = 1;
foreach (var child in element.Elements())
{
AssignIndexes(child, "", subindex++);
}
}
Now you can call AssignIndexesToChildren(doc.Root)
and it will ignore the root element, but create "1" for the first child, etc.
See more on this question at Stackoverflow