Как XPath обрабатывает пространства имен XML?

Как XPath обрабатывает пространства имен XML?

Если я использую

/IntuitResponse/QueryResponse/Bill/Id

чтобы проанализировать XML-документ ниже, я получаю 0 узлов назад.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<IntuitResponse xmlns="http://schema.intuit.com/finance/v3" 
                time="2016-10-14T10:48:39.109-07:00">
    <QueryResponse startPosition="1" maxResults="79" totalCount="79">
        <Bill domain="QBO" sparse="false">
            <Id>=1</Id>
        </Bill>
    </QueryResponse>
</IntuitResponse>

Однако я не указываю, что пространство имен в XPath (т.е. http://schema.intuit.com/finance/v3 не является префиксом каждого токена пути). Как XPath может узнать, какой Id я хочу, если я не говорю это явно? Я предполагаю, что в этом случае (поскольку существует только одно пространство имен) XPath может уйти, полностью игнорируя xmlns. Но если есть несколько пространств имен, все может стать уродливым.

Ответ 1

Определение пространств имен в XPath (рекомендуется)

У самого XPath нет способа связать префикс пространства имен с пространством имен. Такие возможности предоставляются библиотекой хостинга.

Рекомендуется использовать эти средства и определить префиксы пространства имен, которые затем можно будет использовать для квалификации имен элементов и атрибутов XML при необходимости.


Вот некоторые из различных механизмов, которые предоставляют хосты XPath для указания привязок префиксов пространства имен к URI пространства имен:

XSLT:

<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:i="http://schema.intuit.com/finance/v3">
   ...

Perl (LibXML):

my $xc = XML::LibXML::XPathContext->new($doc);
$xc->registerNs('i', 'http://schema.intuit.com/finance/v3');
my @nodes = $xc->findnodes('/i:IntuitResponse/i:QueryResponse');

Python (lxml):

from lxml import etree
f = StringIO('<IntuitResponse>...</IntuitResponse>')
doc = etree.parse(f)
r = doc.xpath('/i:IntuitResponse/i:QueryResponse', 
              namespaces={'i':'http://schema.intuit.com/finance/v3'})

Python (ElementTree):

namespaces = {'i': 'http://schema.intuit.com/finance/v3'}
root.findall('/i:IntuitResponse/i:QueryResponse', namespaces)

Java (SAX):

NamespaceSupport support = new NamespaceSupport();
support.pushContext();
support.declarePrefix("i", "http://schema.intuit.com/finance/v3");

Java (XPath):

xpath.setNamespaceContext(new NamespaceContext() {
    public String getNamespaceURI(String prefix) {
      switch (prefix) {
        case "i": return "http://schema.intuit.com/finance/v3";
        // ...
       }
    });

XMLStarlet:

-N i="http://schema.intuit.com/finance/v3"

JavaScript:

Посмотрите Реализацию Определителя, определяемого пользователем пространства имен:

function nsResolver(prefix) {
  var ns = {
    'i' : 'http://schema.intuit.com/finance/v3'
  };
  return ns[prefix] || null;
}
document.evaluate( '/i:IntuitResponse/i:QueryResponse', 
                   document, nsResolver, XPathResult.ANY_TYPE, 
                   null );

PhP:

Адаптировано из ответа @Tomalak с использованием DOMDocument:

$result = new DOMDocument();
$result->loadXML($xml);

$xpath = new DOMXpath($result);
$xpath->registerNamespace("i", "http://schema.intuit.com/finance/v3");

$result = $xpath->query("/i:IntuitResponse/i:QueryResponse");

Смотрите также @IMSoP canonical Q/A в пространствах имен PHP SimpleXML.

С#:

XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("i", "http://schema.intuit.com/finance/v3");
XmlNodeList nodes = el.SelectNodes(@"/i:IntuitResponse/i:QueryResponse", nsmgr);

VBA:

xmlNS = "xmlns:i='http://schema.intuit.com/finance/v3'"
doc.setProperty "SelectionNamespaces", xmlNS  
Set queryResponseElement =doc.SelectSingleNode("/i:IntuitResponse/i:QueryResponse")

VB.NET:

xmlDoc = New XmlDocument()
xmlDoc.Load("file.xml")
nsmgr = New XmlNamespaceManager(New XmlNameTable())
nsmgr.AddNamespace("i", "http://schema.intuit.com/finance/v3");
nodes = xmlDoc.DocumentElement.SelectNodes("/i:IntuitResponse/i:QueryResponse",
                                           nsmgr)

Рубин (Нокогири):

puts doc.xpath('/i:IntuitResponse/i:QueryResponse',
                'i' => "http://schema.intuit.com/finance/v3")

Обратите внимание, что Nokogiri поддерживает удаление пространств имен,

doc.remove_namespaces!

но посмотрите нижеприведенные предупреждения, препятствующие победе пространств имен XML.


После того, как вы объявили префикс пространства имен, ваш XPath может быть написан для его использования:

/i:IntuitResponse/i:QueryResponse

Победить пространства имен в XPath (не рекомендуется)

Альтернативой является написание предикатов, которые проверяют local-name():

/*[local-name()='IntuitResponse']/*[local-name()='QueryResponse']/@startPosition

Или в XPath 2.0:

/*:IntuitResponse/*:QueryResponse/@startPosition

Обход пространств имен этим способом работает, но не рекомендуется, потому что это

  • Недостаточно указывает полное имя элемента/атрибута.
  • Не в состоянии различать имена элементов/атрибутов в разных пространствах имен (само назначение пространств имен). Обратите внимание, что эту проблему можно решить, добавив дополнительный предикат для явной проверки URI пространства имен 1:

    /*[    namespace-uri()='http://schema.intuit.com/finance/v3' 
       and local-name()='IntuitResponse']
    /*[    namespace-uri()='http://schema.intuit.com/finance/v3' 
       and local-name()='QueryResponse']
    /@startPosition
    

    1 Спасибо Дэниелу Хейли за примечание namespace-uri().

  • Это слишком многословно.