开发者

Is there a bug in my XML code or in .NET?

I just ran into an issue where my code was parsing xml fine but once I added in a second node it started to load incorrect data. The real code spans a number of classes and projects but for the sample I've put together the basics of what's causing the issue

When the code runs I'd expect the output to be the contents of the second Task node, but instead the contents of the first node is output. It keeps pulling from the first occurrence of the EmailAddresses node despite how when you check the settings object it开发者_开发问答s inner xml is that of the second Task node. The call to SelectSingleNode("//EmailAddresses") is where the issue happens.

I have two ways around this issue

  1. Remove the leading slashes from the EmailAddresses XPath expression
  2. Call Clone() after getting the Task or Settings node

Solution 1 works in this case but I believe this will cause other code in my project to stop working.

Solution 2 looks more like a hack to me than a real solution.

MY question is am I in fact doing this correctly and there's a bug in .NET (all versions) or am I just pulling the XML wrong?

The c# code

var doc = new XmlDocument();
doc.Load(@"D:\temp\Sample.xml");

var tasks = doc.SelectSingleNode("Server/Tasks");

foreach (XmlNode threadNode in tasks.ChildNodes)
{
    if (threadNode.Name.ToLower() != "thread")
    {
        continue;
    }

    foreach (XmlNode taskNode in threadNode.ChildNodes)
    {
        if (taskNode.Name.ToLower() != "task" || taskNode.Attributes["name"].Value != "task 1")
        {
            continue;
        }

        var settings = taskNode.SelectSingleNode("Settings");

        var emails = settings.SelectSingleNode("//EmailAddresses");

        Console.WriteLine(emails.InnerText);
    }
}

The XML

<?xml version="1.0"?>
<Server>
    <Tasks>
        <Thread>
            <Task name="task 2">
                <Settings>
                    <EmailAddresses>task 2 data</EmailAddresses>
                </Settings>
            </Task>
        </Thread>
        <Thread>
            <Task name="task 1">
                <Settings>
                    <EmailAddresses>task 1 data</EmailAddresses>
                </Settings>
            </Task>
        </Thread>
    </Tasks>
</Server>


From http://www.w3.org/TR/xpath/#path-abbrev

// is short for /descendant-or-self::node()/. For example, //para is short for /descendant-or-self::node()/child::para and so will select any para element in the document (even a para element that is a document element will be selected by //para since the document element node is a child of the root node);

And also:

A location step of . is short for self::node(). This is particularly useful in conjunction with //. For example, the location path .//para is short for

self::node()/descendant-or-self::node()/child::para

and so will select all para descendant elements of the context node.

Instead of:

var settings = taskNode.SelectSingleNode("Settings");

var emails = settings.SelectSingleNode("//EmailAddresses");

Use:

var emails = taskNode.SelectSingleNode("Settings/EmailAddresses");


The // XPath expression does not do what you think it does. It selects nodes in the document from the current node that match the selection no matter where they are.

In other words, it's not limited by the current scope, it actually crawls back up the document tree and starts matching from the root element.

To select the first <EmailAddresses> element in your current scope, you only need:

var emails = settings.SelectSingleNode("EmailAddresses");
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜