SelectSingleNode使用XPath为已知良好的xml节点路径返回null

时间:2009-07-06 21:00:18

标签: c# xml xpath

考虑这个简单的XML文档。这里显示的序列化XML是来自复杂POCO对象的XmlSerializer的结果,该对象的模式我无法控制。

<My_RootNode xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="">
  <id root="2.16.840.1.113883.3.51.1.1.1" extension="someIdentifier" xmlns="urn:hl7-org:v3" /> 
  <creationTime xsi:nil="true" xmlns="urn:hl7-org:v3" />      
</My_RootNode>

目标是在id节点上提取扩展属性的值。在这种情况下,我们使用SelectSingleNode方法,并给出一个XPath表达式:

XmlNode idNode = myXmlDoc.SelectSingleNode("/My_RootNode/id");
//idNode is evaluated to null at this point in the debugger!
string msgID = idNode.Attributes.GetNamedItem("extension").Value;

问题是SelectSingleNode方法为给定的XPath表达式返回null。

问题:关于此XPath查询的正确性的任何想法,或者为什么此方法调用+ XPath表达式将返回空值?也许命名空间是问题的一部分?

9 个答案:

答案 0 :(得分:46)

我强烈怀疑问题与名称空间有关。尝试摆脱名称空间,你会没事的 - 但显然这对你的实际情况没有帮助,我认为文件是固定的。

我不记得如何在XPath表达式中指定命名空间,但我确定这是问题所在。

编辑:好的,我记得现在该怎么做。虽然它不是非常令人愉快 - 你需要为它创建一个XmlNamespaceManager。以下是一些适用于您的示例文档的示例代码:

using System;
using System.Xml;

public class Test
{
    static void Main()
    {
        XmlDocument doc = new XmlDocument();
        XmlNamespaceManager namespaces = new XmlNamespaceManager(doc.NameTable);
        namespaces.AddNamespace("ns", "urn:hl7-org:v3");
        doc.Load("test.xml");
        XmlNode idNode = doc.SelectSingleNode("/My_RootNode/ns:id", namespaces);
        string msgID = idNode.Attributes["extension"].Value;
        Console.WriteLine(msgID);
    }
}

答案 1 :(得分:12)

如果要完全忽略名称空间,可以使用:

static void Main(string[] args)
{
    string xml =
        "<My_RootNode xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"\">\n" +
        "    <id root=\"2.16.840.1.113883.3.51.1.1.1\" extension=\"someIdentifier\" xmlns=\"urn:hl7-org:v3\" />\n" +
        "    <creationTime xsi:nil=\"true\" xmlns=\"urn:hl7-org:v3\" />\n" +
        "</My_RootNode>";

    XmlDocument doc = new XmlDocument();
    doc.LoadXml(xml);

    XmlNode idNode = doc.SelectSingleNode("/*[local-name()='My_RootNode']/*[local-name()='id']");
}

答案 2 :(得分:8)

这应该适用于您的情况而不删除命名空间:

XmlNode idNode = myXmlDoc.GetElementsByTagName("id")[0];

答案 3 :(得分:7)

抱歉,您忘记了命名空间。你需要:

XmlNamespaceManager ns = new XmlNamespaceManager(myXmlDoc.NameTable);
ns.AddNamespace("hl7","urn:hl7-org:v3");
XmlNode idNode = myXmlDoc.SelectSingleNode("/My_RootNode/hl7:id", ns);

事实上,无论是在这里还是在Web服务中,从XPath操作或依赖于XPath的任何操作中获取null通常都表明XML命名空间存在问题。

答案 4 :(得分:2)

嗯......我遇到了同样的问题而且很头疼。由于我不太关心命名空间或xml架构,我只是从我的xml中删除了这些数据,它解决了我的所有问题。可能不是最好的答案?可能,但如果您不想处理所有这些并且您只关心数据(并且不会将xml用于其他任务),删除命名空间可能会解决您的问题。

XmlDocument vinDoc = new XmlDocument();
string vinInfo = "your xml string";
vinDoc.LoadXml(vinInfo);

vinDoc.InnerXml = vinDoc.InnerXml.Replace("xmlns=\"http://tempuri.org\/\", "");

答案 5 :(得分:1)

只是为了解决命名空间问题,在我的情况下,我一直在运行具有多个命名空间的文档,并且需要正确处理命名空间。我编写了下面的函数来获取命名空间管理器来处理文档中的任何命名空间:

private XmlNamespaceManager GetNameSpaceManager(XmlDocument xDoc)
    {
        XmlNamespaceManager nsm = new XmlNamespaceManager(xDoc.NameTable);
        XPathNavigator RootNode = xDoc.CreateNavigator();
        RootNode.MoveToFollowing(XPathNodeType.Element);
        IDictionary<string, string> NameSpaces = RootNode.GetNamespacesInScope(XmlNamespaceScope.All);

        foreach (KeyValuePair<string, string> kvp in NameSpaces)
        {
            nsm.AddNamespace(kvp.Key, kvp.Value);
        }

        return nsm;
    }

答案 6 :(得分:1)

要记住的规则是:如果您的文档指定g.MeasureString(text, font, origin, new StringFormat(StringFormatFlags.NoWrap)); ,则必须在致电namespaceXmlNamespaceManager时使用SelectNodes()。这是一件好事。

请参阅文章https://github.com/canoo/dolphin-platform。 Jon Skeet在他的答案中做得很好,展示了如何使用SelectSingleNode()。 (这个答案应该只是对答案的评论,但我没有足够的Rep Points来评论。)

答案 7 :(得分:0)

只使用// id而不是/ id。它在我的代码中工作正常

答案 8 :(得分:-1)

Roisgoen的回答对我有用,但为了使它更通用,你可以使用RegEx:

//Substitute "My_RootNode" for whatever your root node is
string strRegex = @"<My_RootNode(?<xmlns>\s+xmlns([\s]|[^>])*)>";
var myMatch = new Regex(strRegex, RegexOptions.None).Match(myXmlDoc.InnerXml);
if (myMatch.Success)
{
    var grp = myMatch.Groups["xmlns"];
    if (grp.Success)
    {
        myXmlDoc.InnerXml = myXmlDoc.InnerXml.Replace(grp.Value, "");
    }
}

我完全承认这不是一个最佳实践答案,但它是一个简单的解决方案,有时这就是我们所需要的。