我遇到了一个问题,我的代码解析xml很好但是一旦我在第二个节点中添加它就开始加载不正确的数据。真正的代码涵盖了许多类和项目,但是对于样本我已经汇总了导致问题的基础知识
当代码运行时,我希望输出是第二个Task节点的内容,而是输出第一个节点的内容。尽管如何检查设置对象,其内部xml是第二个Task节点的内部xml,它仍然会从第一次出现的EmailAddresses节点开始拉出。拨打SelectSingleNode("//EmailAddresses")
是问题发生的地方。
我有两种解决此问题的方法
Clone()
解决方案1适用于这种情况,但我相信这会导致我的项目中的其他代码停止工作。
对于我而言,解决方案2看起来更像是一个真正的解决方案。
我的问题是我实际上是在正确地执行此操作并且.NET(所有版本)中存在错误,或者我只是将XML拉错了?
c#代码
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);
}
}
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>
答案 0 :(得分:5)
来自http://www.w3.org/TR/xpath/#path-abbrev
//
是简称/descendant-or-self::node()/
。对于 例如,//para
是简称/descendant-or-self::node()/child::para
因此将选择任何para元素 该文件(甚至是一个para元素 是一个将被选中的文档元素 自文档元素以来//para
node是根节点的子节点;;
还有:
的缩写
.
的位置步骤是简称self::node()
。这是特别的 与//
结合使用。对于 例如,位置路径.//para
是self::node()/descendant-or-self::node()/child::para
所以将选择所有para后代 上下文节点的元素。
而不是:
var settings = taskNode.SelectSingleNode("Settings");
var emails = settings.SelectSingleNode("//EmailAddresses");
使用:
var emails = taskNode.SelectSingleNode("Settings/EmailAddresses");
答案 1 :(得分:3)
//
XPath expression没有按照您的想法行事。它selects nodes in the document from the current node that match the selection no matter where they are
。
换句话说,它不受当前范围的限制,它实际上会爬回文档树并从根元素开始匹配。
要选择当前范围中的第一个<EmailAddresses>
元素,您只需要:
var emails = settings.SelectSingleNode("EmailAddresses");