XPath和命名空间规范适用于具有显式默认命名空间的XML文档

5
我正在努力寻找正确的XPath表达式和命名空间规范的组合,如 XML 包(参数 namespaces)所需,适用于具有在顶级元素中定义了显式 xmlns 命名空间的 XML 文档。

更新

感谢 har07 的帮助,我终于把它整理好了。
一旦查询命名空间,ns 的第一个条目尚未命名,这就是问题所在:
nsDefs <- xmlNamespaceDefinitions(doc)
ns <- structure(sapply(nsDefs, function(x) x$uri), names = names(nsDefs))

> ns
                                             omegahat                          r 
    "http://something.org"  "http://www.omegahat.org" "http://www.r-project.org" 

所以我们只需指定一个作为前缀的名称(这可以是任何有效的R名称):
names(ns)[1] <- "xmlns"

现在我们只需要在XPath表达式中任何地方使用默认的命名空间前缀即可:
getNodeSet(doc, "/xmlns:doc//xmlns:b[@omegahat:status='foo']", ns)

对于那些希望基于 name()namespace-uri()(等等)的替代方案的人可能会发现这篇文章有所帮助。


仅供参考:在我们找到解决方案之前,这是试错代码:

考虑来自?xmlParse的示例:

require("XML")

doc <- xmlParse(system.file("exampleData", "tagnames.xml", package = "XML"))

> doc
<?xml version="1.0"?>
<doc>
  <!-- A comment -->
  <a xmlns:omegahat="http://www.omegahat.org" xmlns:r="http://www.r-project.org">
    <b>
      <c>
        <b/>
      </c>
    </b>
    <b omegahat:status="foo">
      <r:d>
        <a status="xyz"/>
        <a/>
        <a status="1"/>
      </r:d>
    </b>
  </a>
</doc>
nsDefs <- xmlNamespaceDefinitions(getNodeSet(doc, "/doc/a")[[1]])
ns <- structure(sapply(nsDefs, function(x) x$uri), names = names(nsDefs))
getNodeSet(doc, "/doc//b[@omegahat:status='foo']", ns)[[1]]

然而,在我的文档中,命名空间已经在<doc>标签中定义了,因此我相应地调整了示例XML代码:

xml_source <- c(
  "<?xml version=\"1.0\"?>",
  "<doc xmlns:omegahat=\"http://www.omegahat.org\" xmlns:r=\"http://www.r-project.org\">",
  "<!-- A comment -->",
  "<a>",
  "<b>",
  "<c>",
  "<b/>",
  "</c>",
  "</b>",
  "<b omegahat:status=\"foo\">",
  "<r:d>",
  "<a status=\"xyz\"/>",
  "<a/>",
  "<a status=\"1\"/>",
  "</r:d>",
  "</b>",
  "</a>",
  "</doc>"
)
write(xml_source, file="exampleData_2.xml")  
doc <- xmlParse("exampleData_2.xml")
nsDefs <- xmlNamespaceDefinitions(doc)
ns <- structure(sapply(nsDefs, function(x) x$uri), names = names(nsDefs))    
getNodeSet(doc, "/doc", namespaces = ns)
getNodeSet(doc, "/doc//b[@omegahat:status='foo']", namespaces = ns)[[1]]  

一切仍然正常。更重要的是,我的XML代码还具有默认命名空间的明确定义(xmlns):

xml_source <- c(
  "<?xml version=\"1.0\"?>",
  "<doc xmlns=\"http://something.org\" xmlns:omegahat=\"http://www.omegahat.org\" xmlns:r=\"http://www.r-project.org\">",
  "<!-- A comment -->",
  "<a>",
  "<b>",
  "<c>",
  "<b/>",
  "</c>",
  "</b>",
  "<b omegahat:status=\"foo\">",
  "<r:d>",
  "<a status=\"xyz\"/>",
  "<a/>",
  "<a status=\"1\"/>",
  "</r:d>",
  "</b>",
  "</a>",
  "</doc>"  
)
write(xml_source, file="exampleData_3.xml")  
doc <- xmlParse("exampleData_3.xml")
nsDefs <- xmlNamespaceDefinitions(doc)
ns <- structure(sapply(nsDefs, function(x) x$uri), names = names(nsDefs))

曾经有效的方法现在已经失效:

> getNodeSet(doc, "/doc", namespaces = ns)
list()
attr(,"class")
[1] "XMLNodeSet"
Warning message:
using http://something.org as prefix for default namespace http://something.org 

> getNodeSet(doc, "/xmlns:doc", namespaces = ns)
XPath error : Undefined namespace prefix
XPath error : Invalid expression
Error in xpathApply.XMLInternalDocument(doc, path, fun, ..., namespaces = namespaces,  : 
  error evaluating xpath expression /xmlns:doc
In addition: Warning message:
using http://something.org as prefix for default namespace http://something.org 
getNodeSet(doc, "/xmlns:doc", 
  namespaces = matchNamespaces(doc, namespaces="xmlns", nsDefs = nsDefs)
)

这似乎让我更接近了:
> getNodeSet(doc, "/xmlns:doc",
+ namespaces = matchNamespaces(doc, namespaces="xmlns", nsDefs = nsDefs)
+ )[[1]]
<doc xmlns="http://something.org" xmlns:omegahat="http://www.omegahat.org" xmlns:r="http://www.r-project.org">
  <!-- A comment -->
  <a>
    <b>
      <c>
        <b/>
      </c>
    </b>
    <b omegahat:status="foo">
      <r:d>
        <a status="xyz"/>
        <a/>
        <a status="1"/>
      </r:d>
    </b>
  </a>
</doc> 

attr(,"class")
[1] "XMLNodeSet"

但是,我现在不知道如何继续以便获取子节点:

> getNodeSet(doc, "/xmlns:doc//b[@omegahat:status='foo']", ns)[[1]]
XPath error : Undefined namespace prefix
XPath error : Invalid expression
Error in xpathApply.XMLInternalDocument(doc, path, fun, ..., namespaces = namespaces,  : 
  error evaluating xpath expression /xmlns:doc//b[@omegahat:status='foo']
In addition: Warning message:
using http://something.org as prefix for default namespace http://something.org 

> getNodeSet(doc, "/xmlns:doc//b[@omegahat:status='foo']",
+ namespaces = c(
+ matchNamespaces(doc, namespaces="xmlns", nsDefs = nsDefs),
+ matchNamespaces(doc, namespaces="omegahat", nsDefs = nsDefs)
+ )
+ )
list()
attr(,"class")
[1] "XMLNodeSet"

作为说明,ns 是 R 中的自然样条函数。这将覆盖该基本函数。 - Ashe
3个回答

3

没有前缀的命名空间定义(xmlns="...")是默认命名空间。在具有默认命名空间的XML文档中,声明默认命名空间的元素及其所有后代都被视为在该先前提到的默认命名空间中,且不带前缀和不带不同默认命名空间声明。

因此,在您的情况下,您需要在XPath中所有元素的开头使用为默认命名空间注册的前缀,例如:

/xmlns:doc//xmlns:b[@omegahat:status='foo']

更新:

实际上我不是r的用户,但是在网上查找一些参考资料,类似这样的东西可能会起作用:

getNodeSet(doc, "/ns:doc//ns:b[@omegahat:status='foo']", c(ns="http://something.org"))

啊哈!感谢您提供的信息!然而,我仍然在努力将所有内容整合起来:getNodeSet(doc, "/xmlns:doc//xmlns:b[@omegahat:status='foo'")[[1]] 以及 getNodeSet(doc, "/xmlns:doc//xmlns:b[@omegahat:status='foo'", namespaces = ns)[[1]] 都返回错误。我还缺少什么? - Rappster
好的,现在明白了:将ns中第一个条目的名称更改为xmlns(或任何其他前缀)就可以解决问题了:names(ns)[1] <- "xmlns",然后使用getNodeSet(doc, "/xmlns:doc//xmlns:b[@omegahat:status='foo']", ns)。谢谢! - Rappster
抱歉,我来晚了。 - Rappster

1
我认为@HansHarhoff提供了一个非常好的解决方案。
对于任何仍在寻找解决方案的人,根据我的经验,我认为以下方法更普遍适用,因为单个XML文档可以有多个命名空间。
doc <- xmlInternalTreeParse(xml_source)

ns <- getDefaultNamespace(doc)[[1]]$uri
names(ns)[1] <- "xmlns"

getNodeSet(doc, "//xmlns:Parameter", namespaces = ns)

0

我有一个类似的问题,但在我的情况下,我不关心命名空间,想要一个忽略命名空间的解决方案。

假设我们有以下 XML 存储在变量 myxml 中:

<root xmlns="uri:someuri.com:schema">
<Parameter>Test
</Parameter>
</root>

在R中,我们想要读取这个,所以我们运行:
myxml <- '
<root xmlns="uri:someuri.com:schema">
  <Parameter>Test
</Parameter>
</root>
'
myxmltop <- xmlParse(myxml)
ns <- xmlNamespaceDefinitions(myxmltop, simplify =  TRUE)

在这里,我使用simplify=TRUE参数简化了Rappster的代码。 现在我们可以像Rappster的代码一样添加命名空间的名称/前缀:

names(ns)[1] <- "xmlns"

现在我们可以通过以下方式引用该命名空间:

getNodeSet(myxmltop, "//xmlns:Parameter", namespaces =ns)

更简单的解决方案(忽略命名空间)

我们也可以通过以下方式匹配任何命名空间,从而变得更加灵活:

myxmltop <- xmlParse(myxml)
getNodeSet(myxmltop, "//*[local-name() = 'Parameter']")

这个解决方案的灵感来自于这个Stack Overflow回答


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接