【问题标题】:"Namespace 'x' is not defined" despite calling GetNamespacesInScope first尽管首先调用 GetNamespacesInScope,但“未定义命名空间‘x’”
【发布时间】:2012-09-12 14:51:02
【问题描述】:

我正在尝试使用 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

【问题讨论】:

  • 从不喜欢在 XML 中声明命名空间的方式,其中之一就是您提出的案例。无论如何,它的常见做法,可能不是标准,xml 所有命名空间都应在使用前声明,因此您正在使用的文档可能被视为“损坏”。

标签: c# .net xml xpath


【解决方案1】:

问题在于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']");

【讨论】:

  • 我实际上不知道确切的 .NET 实现,但对我来说,导航器的重点是您不必解析整个 xml 即可到达节点。尽管此解决方案有效,但它通过强制解析完整文档来“扼杀”导航器上的 XPath 查询请求的概念。
  • 效果很好,除非在不同的命名空间下有多个相同级别的元素具有相同的本地名称,但这种情况很少发生:)
【解决方案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);

【讨论】:

  • 这不是一个坏主意,我唯一担心的是我能得到的 xml 文档的类型相当多样化,而且我不一定事先知道命名空间会是什么。跨度>
  • 如果您不知道自己在请求什么,就无法编写 XPath 查询。如果您知道要定位的元素名称,您还应该知道它的命名空间。
猜你喜欢
  • 1970-01-01
  • 2012-07-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多