Reputation:
As in, if I have an XML Document
<root a="value">
<item name="first">
x
<foo name = "firstgrandchild">There is nothing here</foo>
y
<foo name = "secondgrandchild">There is something here</foo>
</item>
<item name="second">
xy
<foo/>
ab
</item>
</root>
I want to first find the first occurrence of node "item" and then update the attribute, and then I want to update the first occurrence of node "foo" and then update the attribute etc.,
My Code is as below
myDoc.Load("Items2.xml");
myNode = myDoc.DocumentElement;
mySearchNode = myNode.SelectSingleNode("/root/item");
mySearchNode.Attributes["name"].Value = "Joel";
Console.WriteLine(mySearchNode.OuterXml);
mySearchChildNode = mySearchNode.SelectSingleNode("/item/foo");
Console.WriteLine(mySearchChildNode.OuterXml);
While, the first search and update of attribute works fine, the second one fails as mySearchNode.SelectSingleNode returns null.
Question - Is there something fundamentally that is wrong with this code? Why does not the SelectSingleNode work as expected in the second instance, as far as it is concerned, I am executing it on a XmlNode of type Element.
Kindly assist.
Many Thanks,
Upvotes: 0
Views: 3507
Reputation: 1100
From my understanding the main problem is, that SelectNode(s) called on a node (here: mySearchNode.SelectSingleNode) does not work as expected!!!
It does not search beginning with the given node, but always from root document. (see also the source code, where for a node the related XmlDocument node is used to initiate the XPathNavigator)
I did not expect this behaviour at all!
Better use XDocument that should work as expected.
Upvotes: 0
Reputation: 23848
You can do a SelectNodes and then loop through all the item nodes. For each item you should further do a SelectNodes on foo nodes. You should check for the node count and also whether the attribute name exists or not for both item and foo nodes. You can use this code:
/// <summary>
/// Changes the xml in sourceFileName and writes the changed xml to destFileName
/// </summary>
public static void ProcessNames(string sourceFileName, string destFileName)
{
XmlDocument xmlDoc = new XmlDocument();
XmlTextWriter xtw = null;
xmlDoc.Load(sourceFileName);
try
{
// Parse through all the item nodes and process them
XmlNodeList itemList = xmlDoc.SelectNodes("//root/item");
if (itemList.Count > 0)
{
foreach (XmlNode item in itemList)
{
// Change the name attribute, if it exists
if (item.Attributes["name"] != null)
{
string newItemName = item.Attributes["name"].Value + "Joel";
item.Attributes["name"].Value = newItemName;
}
// Parse through all the foo nodes and process them
XmlNodeList fooList = item.SelectNodes("foo");
if (fooList.Count > 0)
{
foreach (XmlNode foo in fooList)
{
// Change the name attribute, if it exists
if (foo.Attributes["name"] != null)
{
string newFooName = foo.Attributes["name"].Value + "Joel";
foo.Attributes["name"].Value = newFooName;
}
}
}
}
xtw = new XmlTextWriter(destFileName, Encoding.UTF8);
xmlDoc.WriteContentTo(xtw);
}
}
finally
{
xtw.Close();
}
}
Upvotes: 0
Reputation: 164341
Your second XPath query should be without the leading slash. / means "root of document". If you omit the slash, the query will be relative to your mySearchNode variable. You also should not include "item" again, your query is relative to the "item" node you selected. In code:
myDoc.Load("Items2.xml");
myNode = myDoc.DocumentElement;
mySearchNode = myNode.SelectSingleNode("/root/item");
mySearchNode.Attributes["name"].Value = "Joel";
Console.WriteLine(mySearchNode.OuterXml);
mySearchChildNode = mySearchNode.SelectSingleNode("foo");
Console.WriteLine(mySearchChildNode.OuterXml);
Upvotes: 5
Reputation: 63378
mySearchNode is the item
node, so if foo
is a child of item
your second xpath should simply be "foo"
Upvotes: 2