web-dev-qa-db-fra.com

Comment XPath gère-t-il les espaces de noms XML?

Comment XPath gère-t-il les espaces de noms XML?

Si j'utilise

/IntuitResponse/QueryResponse/Bill/Id

pour analyser le document XML ci-dessous, je récupère 0 noeuds.

<?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>

Cependant, je ne spécifie pas l'espace de noms dans XPath (c'est-à-dire http://schema.intuit.com/finance/v3 n'est pas un préfixe de chaque jeton du chemin). Comment XPath peut-il savoir quel Id je veux si je ne le dis pas explicitement? Je suppose que dans ce cas (puisqu'il n'y a qu'un seul espace de noms) XPath pourrait s'en tirer en ignorant complètement le xmlns. Mais s'il y a plusieurs espaces de noms, les choses pourraient mal tourner.

26
Adam

Définition des espaces de noms dans XPath (conseillé)

XPath lui-même n'a aucun moyen de lier un préfixe d'espace de noms à un espace de noms. Ces installations sont fournies par la bibliothèque d'hébergement.

Il est recommandé d'utiliser ces fonctionnalités et de définir des préfixes d'espace de noms qui peuvent ensuite être utilisés pour qualifier les noms d'élément et d'attribut XML si nécessaire.


Voici quelques-uns des divers mécanismes que les hôtes XPath fournissent pour spécifier les liaisons de préfixe d'espace de noms aux URI d'espace de noms:

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:

Voir Implémentation d'un résolveur d'espace de noms défini par l'utilisateur :

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:

Adapté de @ Tomalak's answer using 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");

Voir aussi @ QMS canonique d'IMSO sur PHP espaces de noms SimpleXML .

C #:

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)

Rubis (Nokogiri):

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

Notez que Nokogiri prend en charge la suppression des espaces de noms,

doc.remove_namespaces!

mais voyez les avertissements ci-dessous décourageant la défaite des espaces de noms XML.


Une fois que vous avez déclaré un préfixe d'espace de noms, votre XPath peut être écrit pour l'utiliser:

/i:IntuitResponse/i:QueryResponse

Vaincre les espaces de noms dans XPath (non recommandé)

Une alternative est d'écrire des prédicats qui testent contre local-name():

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

Ou, dans XPath 2.0:

/*:IntuitResponse/*:QueryResponse/@startPosition

Le contournement des espaces de noms de cette manière fonctionne mais n'est pas recommandé car il

  • Sous-spécifie le nom complet de l'élément/attribut.
  • Ne parvient pas à différencier les noms d'élément/attribut dans différents espaces de noms (le but même des espaces de noms). Notez que ce problème pourrait être résolu en ajoutant un prédicat supplémentaire pour vérifier explicitement l'URI de l'espace de noms1:

    /*[    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
    

    1Merci à Daniel Haley pour la note namespace-uri().

  • Est excessivement verbeux.

36
kjhughes