Frage Müssen XML-Namespaces im Stammelement deklariert werden, damit sie von einer XPath-Abfrage abgeglichen werden können?


Ich kann nicht herausfinden, ob XPath selbst dafür verantwortlich ist oder ob es die speziellen XPath-Implementierungen sind, die dies so schwierig machen. Die SO Frage - Wie ändere ich ein XML-Element in einem Namespace mit der Datei MSDeploy Parameters.xml? - war meine Inspiration.

Was nicht funktioniert

Hier ist das grundlegende Beispiel, das nicht funktioniert.

XML:

<spring>
    <objects xmlns="http://www.springframework.net">
        <object id="CultureResolver" type="Spring.Globalization.Resolvers.SessionCultureResolver, Spring.Web">
             <!--configure for server--> 
            <property name="DefaultCulture" value="en" />
        </object>
    </objects>
</spring>

XPath:

//spring/objects/object[@id='CultureResolver']/@type

Die XPath-Abfrage gibt nichts zurück anstelle von:

Spring.Globalization.Resolvers.SessionCultureResolver, Spring.Web

Was ich erwarte zu arbeiten

Ich würde vielleicht naiv erwarten, dass das Folgende funktioniert.

Geändertes XML:

<spring>
    <spring:objects xmlns:spring="http://www.springframework.net">
        <spring:object id="CultureResolver" type="Spring.Globalization.Resolvers.SessionCultureResolver, Spring.Web">
             <!--configure for server--> 
            <spring:property name="DefaultCulture" value="en" />
        </spring:object>
    </spring:objects>
</spring>

Geänderte XPath-Abfrage:

//spring/spring:objects/spring:object[@id='CultureResolver']/@type

Diese Abfrage löst einen Fehler aus der Online-Tester Ich benutzte:

ERROR - Failed to evaluate XPath expression: org.apache.xpath.domapi.XPathStylesheetDOM3Exception: Prefix must resolve to a namespace: spring

Was funktioniert?

Geändertes XML:

<spring xmlns="" xmlns:spring="http://www.springframework.net">
    <spring:objects>
        <spring:object id="CultureResolver" type="Spring.Globalization.Resolvers.SessionCultureResolver, Spring.Web">
             <!--configure for server--> 
            <spring:property name="DefaultCulture" value="en" />
        </spring:object>
    </spring:objects>
</spring>

Modifizierte XPath-Abfrage (wie unter Was ich erwarte zu arbeiten):

//spring/spring:objects/spring:object[@id='CultureResolver']/@type

Um der Verwirrung etwas hinzuzufügen, habe ich festgestellt, dass die folgende XPath-Abfrage für das ursprüngliche XML-Beispiel (in der Online-Test-XPath-Engine) funktioniert:

//spring/*[local-name() = 'objects' and namespace-uri() = 'http://www.springframework.net']/*[@id='CultureResolver' and local-name() = 'object' and namespace-uri() = 'http://www.springframework.net']/@type

Warum?

Ist das aufgrund des Zusammenspiels von Namespaces und Präfixen verwirrend? Es scheint, als deklariere ein Namespace ohne Präfix nicht nur das relevante Element in diesem Namespace, sondern auch alle seine untergeordneten Elemente und beschreibe es daher als "Standard - Namespace" (wie in diese Antwort zu einer verwandten Frage). Während das Deklarieren eines Namespace mit einem Präfix nicht einmal das relevante Element in diesem Namespace enthält!

Gibt es einen Grund, warum Namespaces brauchen unabhängig von bestimmten XPath-Implementierungen in das Stammelement des XML-Dokuments aufgenommen werden?

Meine XPath-Engines

Das Problem, das ich lösen wollte, beinhaltete jede XPath-Engine, die von Microsoft Web Deploy (MSDeploy) verwendet wird.

Ich habe auch benutzt dieser Online-XPath-Tester.


5
2018-01-23 20:41


Ursprung


Antworten:


Eine interessante und gut gestellte Frage! Soweit ich sehen kann, liegt die Schwierigkeit darin, wie Ihre XPath-Engine Namespace-Deklarationen im Eingabedokument verarbeitet.

Die kurze Antwort

Nein, dieses Verhalten hat nichts mit XPath im Allgemeinen oder mit der XPath-Spezifikation zu tun. Es liegt an einzelnen Implementierungen.


Was die Spezifikationen sagen

Was die XML- und XPath-Spezifikationen betrifft, können Namespaces für jedes Element deklariert werden, und am äußersten (oder "root") Element gibt es nichts Spezielles. Namespace-Deklarationen auf dem Root-Element sind wie jede andere Deklaration.

Natürlich gibt es noch Regeln. Zum Beispiel muss ein Präfix einem Namespace-URI für das Element, in dessen QName es verwendet wird, oder für einen Vorgänger dieses Elements (oder dieses Attributs) zugeordnet werden. Das folgende ist also kein wohlgeformtes XML:

<prefix:root>
    <child xmlns:prefix="www.example.com"/>
</prefix:root>

Und die zweite wichtige Regel: A Standard-Namespace kann nur auf das Element angewendet werden, auf dem es deklariert ist, und auf alle untergeordneten Elemente. Im folgenden Dokument, der root Element befindet sich überhaupt nicht im Namespace:

<root>
   <child xmlns="www.example.com">
      <grandchild/>
   </child>
</root>

Die Spezifikationen, über die ich spreche, sind die XML, XML-Namespaces und Xpath Spezifikationen.

Was passiert in Ihrer XPath-Implementierung?

Wenn nun ein XPath-Ausdruck für ein XML-Dokument ausgewertet wird, müssen alle Namespace-Deklarationen, die in diesem Eingabedokument vorhanden sind, explizit für die XPath-Engine verfügbar gemacht (deklariert oder "registriert") werden.

Einige Implementierungen von XPath vereinfachen dies einfach alle Namespacedeklarationen neu deklarieren, die für ein Element oder Attribut gelten eines XML-Dokuments, das als Eingabe für eine Xpath-Engine dient (siehe auch Dies).

In Ihrem Fall werden nur die am äußersten Element gemachten Angaben berücksichtigt. Deshalb ist dein letztes XML-Dokument:

<spring xmlns="" xmlns:spring="http://www.springframework.net">
    <spring:objects>
        <spring:object id="CultureResolver" type="Spring.Globalization.Resolvers.SessionCultureResolver, Spring.Web">
             <!--configure for server--> 
            <spring:property name="DefaultCulture" value="en" />
        </spring:object>
    </spring:objects>
</spring>

funktioniert - weil die Namespace-Deklaration für das Root-Element erfolgt und Sie den XPath-Ausdruck vom Root-Element ausführen. Sie könnten die Deklaration eines Standardnamespace jedoch weglassen, da dies keine Auswirkungen hat.


Zum Schluss, um Ihre letzte Frage zu beantworten:

Gibt es einen Grund, warum Namespaces unabhängig von bestimmten XPath-Implementierungen im Stammelement des XML-Dokuments enthalten sein müssen?

Nein, es gibt keinen Grund, warum Namespace-Deklarationen auf dem Root-Element stehen sollten, außer

  • dass sie etwas leichter zu finden sind, wenn sie auf dem Wurzelelement deklariert werden, meiner Meinung nach (sehr subjektiv)
  • wenn Sie einen Standard-Namespace für das gesamte Dokument deklarieren möchten. Die Angabe auf dem Wurzelelement ist die einzige Möglichkeit, diese auch auf das Wurzelelement anzuwenden
  • wenn das Wurzelelement selbst einen qualifizierten Namen hat, d.h. vorangestellt. Dann müssen Sie dieses Präfix und den Namespace-URI für das Root-Element deklarieren.

Wenn Ihre Implementierung von XPath Namespace-Deklarationen, die sich im Gültigkeitsbereich befinden, automatisch neu deklariert, können Sie natürlich davon profitieren, aber es wird auch manchmal verwirrend sein, wie Sie bemerkt haben.


6
2018-01-23 22:02



Nein, die Namespace-Definition für das Dokument und XPath sind getrennt. Einige Implementierungen registrieren automatisch die Space-Definitionen des aktuellen Kontexts standardmäßig. Ich halte dies für einen Fehler, weil es den XPath mehrdeutig macht.

Beginnen wir mit einem einfachen Beispiel:

<foo:element xmlns:foo="urn:foo"/>

Das definiert einen Alias ​​/ ein Präfix foo für den Namespace urn:foo. Der XML-Parser löst das auf und erkennt den Knoten element gehört zum Namespace urn:foo. Aus Gründen der Fehlersuche könnte der Knotenname als geschrieben werden {urn:foo}element.

Wenn Sie das Präfix ändern oder es sogar entfernen, wird dies immer auf die gleiche Weise gelöst. Betrachten Sie die folgenden Beispiele:

<foo:element xmlns:foo="urn:foo"/>
<bar:element xmlns:bar="urn:foo"/>
<element xmlns="urn:foo"/>

Das Präfix / Alias ​​ist nur für den Knoten und seine Nachkommen gültig. Jeder Nachkomme kann seine eigene Definition haben, die möglicherweise die seines Vorgängers überschreiben kann.

Für XPath definieren Sie Ihre eigenen Aliase. Sie schreiben einen Namespace-Resolver oder registrieren sie in der XPath-Engine. Das hängt wirklich von der Implementierung ab.

Hier ist ein kleines PHP-Beispiel:

$dom = new DOMDocument();
$dom->loadXml('<foo:element xmlns:foo="urn:foo"/>');

$xpath = new DOMXPath($dom);
$xpath->registerNamespace('alias', 'urn:foo');

var_dump($xpath->evaluate('name(/alias:element)'));

Ausgabe:

string(11) "foo:element"

Sie können sehen, dass die Namespacedefinitionen für den XPath separat und unabhängig von den im XML-Dokument definierten Präfixen sind.

In Javascript wird XPath mit verwendet Document.evaluate(). Das dritte Argument ist der Namespace-Resolver.

var resolver = {
  namespaces : {
   'alias' : 'urn:foo'
  },
  lookupNamespaceURI : function(prefix) {
    if (prefix == '') {
      return null;
    }
    return this.namespaces[prefix] || null;
  }
};

console.log(
    document.evaluate(
       'name(/alias:element)'
    ),
    document,
    resolver,
    XPathResult.ANY_TYPE,
    null
  ).stringValue
);

Zurück zu deiner Frage. Sie müssen herausfinden, wie Sie die Aliase / Präfixe für Ihre Namespaces registrieren / definieren. Danach können Sie sie in Ihren XPath-Ausdrücken verwenden. Wenn Sie den Alias ​​definieren spring für den Namespace http://www.springframework.net" Der folgende XPath-Ausdruck sollte funktionieren:

//spring/spring:objects/spring:object[@id='CultureResolver']/@type


2
2018-01-27 20:44



In "Was nicht funktioniert" ist das Problem, dass <object> und seine Nachkommen sind in der http://www.springframework.net Namensraum, aber der XPath-Ausdruck fragt nach einem <object> das ist in keinem Namensraum.

Es ist nicht sofort klar, warum "Was ich erwarte zu arbeiten" nicht funktionieren sollte, weil <objects> und <object> sind beide explizit in der http://www.springframework.net Namespace und der XPath-Ausdruck qualifiziert die Elementnamen korrekt (vorausgesetzt, dass der Code, der den spring Präfix hat Zugriff auf die Namensraumbindungen).

In "Was funktioniert", wieder, <objects> und <object> sind beide explizit in der http://www.springframework.net Namespace und der XPath-Ausdruck qualifiziert die Elementnamen korrekt.

Der Unterschied zu "Was ich erwarte zu arbeiten" ist der Standard-Namespace, der für <spring>, ist explizit an keinen Namensraum gebunden; so kann ich nur vermuten, dass mit "Was ich erwarte zu arbeiten" der Standard - Namespace (der für <spring>) ist an einen Namespace gebunden, den Sie nicht kennen. Ich schlage vor, Sie überprüfen, ob //spring funktioniert - ich denke, das wird schon ein Problem offenbaren.


1
2018-01-23 21:54