“尽管先调用GetNamespacesInScope,但未定义”命名空间'x'

时间:2012-09-12 14:51:02

标签: c# .net xml xpath

我正在尝试使用xPathNavigator在c#.NET中解析有效的XML文档。 XML文档的开头如下所示:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <soapenv:Body>
        <ns1:myResponse xmlns:ns1="ExampleNS" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
            <myReturn href="#2" />

正如您所看到的,在根元素上定义了一些名称空间,但稍后会定义ns1。当我尝试评估xPath查询/soapenv:Envelope/soapenv:Body/ns1:myResponse/myReturn时,我得到XPathException

Namespace prefix 'ns1' is not defined.

我正在做的是将XML文档作为字符串,将其加载到XmlDocument对象中。然后我创建一个导航器,移动到根元素并调用GetNamespacesInScope(XmlNamespaceScope.All)。我遍历此集合,其中包含根目录中定义的xmlsoapenvxsdxsi的命名空间,并将它们添加到命名空间管理器中。然后我创建xPathNavigator并调用Evaluate,然后获取异常。

代码就是这样:

string response = GetXMLString();
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(response);

var xmlDocumentNavigator = xmlDocument.CreateNavigator();
xmlDocumentNavigator.MoveToFollowing(XPathNodeType.Element);
var xnm = new XmlNamespaceManager(xmlDocument.NameTable);
var namespacesInScope = xmlDocumentNavigator.GetNamespacesInScope(XmlNamespaceScope.All);
if (namespacesInScope != null)
{
    foreach (var prefix in namespacesInScope.Keys)
    {
        xnm.AddNamespace(prefix, namespacesInScope[prefix]);
    }
}

var xPathDocument = new XPathDocument(new StringReader(response));
var xPathNavigator = xPathDocument.CreateNavigator();

xPathNavigator.Evaluate("/soapenv:Envelope/soapenv:Body/ns1:myResponse/myReturn", xnm);

为什么GetNamespacesInScope方法没有接听ns1

2 个答案:

答案 0 :(得分:3)

问题是GetNamespacesInScope只返回导航器当前节点范围内的命名空间(我猜方法建议:)。

获取所有命名空间的一个方法是遍历所有元素,获取命名空间,然后重新组合唯一的命名空间。

字典合并代码courtesy of JonSkeet

 XPathDocument xpd = new XPathDocument(new StringReader(s));
 XPathNavigator xpn = xpd.CreateNavigator();
 List<IDictionary<string, string>> xmlnsList = new List<IDictionary<string,string>>();
 while (xpn.MoveToFollowing(XPathNodeType.Element))
 {
     xmlnsList.Add(xpn.GetNamespacesInScope(XmlNamespaceScope.All));
 }
 var result = xmlnsList.SelectMany(dict => dict)
              .ToLookup(pair => pair.Key, pair => pair.Value)
              .ToDictionary(group => group.Key, group => group.First());
 if (result != null)
 {
     foreach (var prefix in result.Keys)
     {
         xnm.AddNamespace(prefix, result[prefix]);
     }
 }

修改

根据Paciv的评论,如果您事先不知道命名空间在xml文档中是什么,另一种方法是使用命名空间无关的Xpath,并避免嗅探命名空间的问题(并消除对名称空间的需求)管理者完全)。

在您的示例中,这将是:

xPathNavigator.Evaluate("/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='myResponse']/*[local-name()='myReturn']");

答案 1 :(得分:2)

如果只是添加事先知道的命名空间,您需要执行XPath查询呢?

你可以写

xnm.AddNamespace("MyNS", "ExampleNS");

然后

xPathNavigator
    .Evaluate("/soapenv:Envelope/soapenv:Body/MyNS:myResponse/myReturn", xnm);

只要前缀设计的命名空间相同,前缀是什么并不重要。

顺便说一下,你应该为SOAP命名空间做同样的事情,因为你的XPath查询基于前缀,并且可以使用针对相同命名空间的不同前缀生成完全相同的XML。

我会写一些更强大的东西,适用于不同的SOAP生成器,如

string response = GetXMLString();
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(response);
XmlNamespaceManager xnm = new XmlNamespaceManager(xmlDocument.NameTable);
xnm.AddNamespace("soapenv", "http://schemas.xmlsoap.org/soap/envelope/");
xnm.AddNamespace("ns1", "ExampleNS");

xmlDocument.CreateNavigator()
    .Evaluate("/soapenv:Envelope/soapenv:Body/ns1:myResponse/myReturn", xnm);