Спецификация XPath и пространства имен для документов XML с явным пространством имен по умолчанию

Я изо всех сил пытаюсь получить правильную комбинацию выражения XPath и спецификации пространства имен в соответствии с требованиями пакета XML(аргументnamespaces)для документа XML, который имеет явныйxmlnsпространство имен, определенное в верхнем элементе.

ОБНОВИТЬ

Благодаря 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"

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"))

Я думаю, что @HansHarhoff предлагает очень хорошее решение.

Для всех, кто еще ищет решение, по моему опыту, следующее работает более широко, поскольку один XML-документ может иметь несколько пространств имен.

doc <- xmlInternalTreeParse(xml_source)

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

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

У меня была похожая проблема, но в моем случае я не заботился о пространстве имен и хотел бы решение, которое игнорировало пространство имен.

Предположим, что у нас есть следующий 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)

Здесь я упростил код Раппстера, используя параметр simpify=TRUE. Теперь мы можем добавить имя / префикс пространства имен, как в коде Раппстера:

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

Теперь мы можем обратиться к этому пространству имен:

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

Более простое решение (игнорирование пространств имен)

Мы также можем быть более гибкими, сопоставляя любое пространство имен, выполняя:

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

Это решение было вдохновлено этим SO ответом.

Другие вопросы по тегам