Спецификация 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 ответом.