webentwicklung-frage-antwort-db.com.de

Wie verwende ich XPath mit einem Standardnamespace ohne Präfix?

Was ist der XPath (in der C # -API für XDocument.XPathSelectElements (xpath, nsman), wenn es darauf ankommt), um alle MyNodes aus diesem Dokument abzufragen?

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <MyNode xmlns="lcmp" attr="true">
    <subnode />
  </MyNode>
</configuration>
  • Ich habe /configuration/MyNode ausprobiert, was falsch ist, da der Namespace ignoriert wird.
  • Ich habe versucht, /configuration/lcmp:MyNode zu verwenden, was falsch ist, da lcmp die URI und nicht das Präfix ist.
  • Ich habe versucht, /configuration/{lcmp}MyNode, was fehlgeschlagen ist, weil Additional information: '/configuration/{lcmp}MyNode' has an invalid token.

EDIT: Ich kann mgr.AddNamespace("df", "lcmp"); nicht verwenden, wie einige der Antwortenden vorgeschlagen haben. Dazu muss das XML-Parsing-Programm alle Namespaces, die ich verwenden möchte, im Voraus kennen. Da dies für jede Quelldatei gelten soll, weiß ich nicht, für welche Namespaces Präfixe manuell hinzugefügt werden müssen. Anscheinend ist {my uri} die XPath-Syntax, aber Microsoft hat sich nicht darum gekümmert, das zu implementieren ... stimmt das?

34
Scott Stafford

Das configuration-Element befindet sich im unbenannten Namespace, und MyNode ist ohne ein Namespacepräfix an den lcmp-Namespace gebunden.

Mit dieser AnweisungXPATHkönnen Sie das Element MyNode adressieren, ohne den Namespace lcmp deklariert zu haben oder ein Namensraum-Präfix in Ihrem XPATH zu verwenden:

/configuration/*[namespace-uri()='lcmp' and local-name()='MyNode']

Es passt zu jedem Element, das ein Kind von configuration ist, und verwendet dann einen Prädikatdateisierer mit den Funktionen namespace-uri() und local-name() , um es auf das Element MyNode zu beschränken.

Wenn Sie nicht wissen, welche Namespace-Uris für die Elemente verwendet werden, können Sie denXPATHgenerischer machen und nur auf die local-name() passen:

/configuration/*[local-name()='MyNode']

Es besteht jedoch die Gefahr, dass verschiedene Elemente in verschiedenen Vokabularen (an verschiedene Namespace-Uris gebunden) abgeglichen werden, die den gleichen Namen verwenden.

37
Mads Hansen

Sie müssen einen XmlNamespaceManager wie folgt verwenden:

   XDocument doc = XDocument.Load(@"..\..\XMLFile1.xml");
   XmlNamespaceManager mgr = new XmlNamespaceManager(new NameTable());
   mgr.AddNamespace("df", "lcmp");
   foreach (XElement myNode in doc.XPathSelectElements("configuration/df:MyNode", mgr))
   {
       Console.WriteLine(myNode.Attribute("attr").Value);
   }
12
Martin Honnen

XPath ist (absichtlich) nicht für den Fall konzipiert, dass Sie denselben XPath-Ausdruck für einige unbekannte Namespaces verwenden möchten, die nur im XML-Dokument enthalten sind. Es wird von Ihnen erwartet, dass Sie den Namespace vorab kennen, den Namespace dem XPath-Prozessor mitteilen und den Namen in Ihrem Ausdruck verwenden. Die Antworten von Martin und Dan zeigen, wie das in C # geht. 

Der Grund für diese Schwierigkeit lässt sich am besten in den XML-Namespaces spec ausdrücken:

Wir stellen uns Anwendungen von Extensible Markup Language (XML) vor, bei denen ein einzelnes XML-Dokument Elemente und Attribute enthalten kann (hier als "Markup-Vokabular" bezeichnet), die für mehrere Softwaremodule definiert sind und von diesen verwendet werden. Eine Motivation dafür ist die Modularität: Wenn ein solches Markup-Vokabular existiert, das gut verstanden ist und für das nützliche Software verfügbar ist, ist es besser, dieses Markup erneut zu verwenden, anstatt es neu zu erfinden.

Solche Dokumente, die mehrere Auszeichnungsvokabulare enthalten, stellen das Erkennungs- und Kollisionsproblem dar. Softwaremodule müssen in der Lage sein, die Elemente und Attribute zu erkennen, die sie verarbeiten sollen, auch wenn Kollisionen auftreten, wenn Markup für ein anderes Softwarepaket denselben Elementnamen oder Attributnamen verwendet.

Diese Überlegungen erfordern, dass Dokumentenkonstrukte Namen haben sollten, die so konstruiert sind, dass Konflikte zwischen Namen aus verschiedenen Markup-Vokabularen vermieden werden. Diese Spezifikation beschreibt einen Mechanismus, XML-Namespaces, der dies durch Zuweisen erweiterter Namen zu Elementen und Attributen bewerkstelligt. 

Das heißt, Namespaces sollten verwendet werden, um sicherzustellen, dass Sie wissen, worüber Ihr Dokument spricht: Spricht dieses <head>-Element über die Präambel zu einem XHTML-Dokument oder dem Kopf eines anderen Benutzers in einem AnatomyML-Dokument? Es wird nie "vermutet", dass Sie mit dem Namespace "agnostisch" umgehen. Es ist so ziemlich das erste, was Sie in einem XML-Vokabular definieren müssen. 

Es sollte möglich sein, das zu tun, was Sie möchten, aber ich glaube nicht, dass dies in einem einzigen XPath-Ausdruck möglich ist. Zunächst müssen Sie im Dokument stöbern und alle NamespaceURIs extrahieren, diese dann dem Namespace-Manager hinzufügen und dann den eigentlichen XPath-Ausdruck ausführen, den Sie möchten (und Sie müssen etwas über die Verteilung der Namespaces im Dokument an dieser Stelle wissen.) oder Sie haben viele Ausdrücke, die Sie ausführen müssen. Ich denke, Sie verwenden wahrscheinlich etwas anderes als XPath (z. B. eine DOM- oder SAX-ähnliche API), um die NamespaceURIs zu finden. Sie können jedoch auch die XPath-Namespace-Achse (in XPath 1.0) erkunden und die Funktion namespace-uri-from-QName verwenden (in XPath 2.0) oder verwenden Sie Ausdrücke wie Olegs "configuration/*[local-name() = 'MyNode']". Jedenfalls denke ich, dass Sie am besten versuchen sollten, den Namespace-agnostischen XPath nicht zu schreiben! Warum kennen Sie Ihren Namensraum nicht vorab? Wie vermeiden Sie das Übereinstimmen von Dingen, die Sie nicht zusammenbringen möchten?

Bearbeiten - Sie kennen den NamespaceURI?

Es stellt sich also heraus, dass Ihre Frage uns alle verwirrt hat. Anscheinend kennen Sie den Namespace-URI, aber Sie kennen nicht das Namespace-Präfix, das im XML-Dokument verwendet wird. In diesem Fall wird kein Namespace-Präfix verwendet, und der URI wird zum Standard-Namespace, in dem er definiert ist. Der Schlüssel ist, dass das gewählte Präfix (oder das Fehlen eines Präfixes) für Ihren XPath-Ausdruck (und das XML-Parsing im Allgemeinen) keine Rolle spielt. Das Präfix/xmlns-Attribut ist nur eine Möglichkeit, einem Knoten einen Namespace-URI zuzuordnen, wenn das Dokument als Text ausgedrückt wird. Vielleicht möchten Sie einen Blick auf diese Antwort werfen, wo ich Namespace-Präfixe versuche und kläre. 

Sie sollten versuchen, das XML-Dokument so zu betrachten, wie der Parser es denkt - jeder Knoten hat einen Namespace-URI und einen lokalen Namen. Die Namespace-Präfix-/Vererbungsregeln erspart Ihnen oft, den URI häufig einzugeben. Eine Möglichkeit, dies aufzuschreiben, ist in der Clark-Notation: Sie schreiben also { http://www.example.com/namespace/example } LocalNodeName, aber diese Notation wird normalerweise nur zur Dokumentation verwendet - XPath weiß nichts über diese Notation.

Stattdessen verwendet XPath eigene Namespace-Präfixe. Etwas wie /ns1:root/ns2:node. Diese sind jedoch völlig unabhängig von Präfixen, die im ursprünglichen XML-Dokument verwendet werden können, und haben nichts damit zu tun. Jede XPath-Implementierung hat eine Möglichkeit, ihre eigenen Präfixe mit Namespace-URIs abzubilden. Für die C # -Implementierung verwenden Sie eine XmlNamespaceManager. In Perl geben Sie einen Hashwert an, xmllint verwendet Befehlszeilenargumente. Sie müssen also nur ein beliebiges Präfix für den bekannten Namespace-URI erstellen und dieses Präfix im XPath-Ausdruck verwenden . Es spielt keine Rolle, welches Präfix Sie verwenden, in XML interessieren Sie sich nur für die Kombination aus URI und localName. 

Die andere Sache, die Sie sich merken sollten (es ist oft eine Überraschung) ist, dass XPath keine Namensraum-Vererbung vornimmt. Sie müssen ein Präfix für jeden hinzufügen, der über einen Namespace verfügt, unabhängig davon, ob der Namespace von der Vererbung, einem xmlns-Attribut oder einem Namespacepräfix stammt. Obwohl Sie immer in Bezug auf URIs und localNames denken sollten, gibt es auch Möglichkeiten, auf das Präfix eines XML-Dokuments zuzugreifen. Es ist selten, diese verwenden zu müssen. 

6
Andrew Walker

Hier ein Beispiel, wie der Namespace für den XPath-Ausdruck in der XPathSelectElements-Erweiterungsmethode Verfügbar gemacht wird:

using System;
using System.Xml.Linq;
using System.Xml.XPath;
using System.Xml;
namespace XPathExpt
{
 class Program
 {
   static void Main(string[] args)
   {
     XElement cfg = XElement.Parse(
       @"<configuration>
          <MyNode xmlns=""lcmp"" attr=""true"">
            <subnode />
          </MyNode>
         </configuration>");
     XmlNameTable nameTable = new NameTable();
     var nsMgr = new XmlNamespaceManager(nameTable);
     // Tell the namespace manager about the namespace
     // of interest (lcmp), and give it a prefix (pfx) that we'll
     // use to refer to it in XPath expressions. 
     // Note that the prefix choice is pretty arbitrary at 
     // this point.
     nsMgr.AddNamespace("pfx", "lcmp");
     foreach (var el in cfg.XPathSelectElements("//pfx:MyNode", nsMgr))
     {
         Console.WriteLine("Found element named {0}", el.Name);
     }
   }
 }
}
4
Dan Blanchard

Beispiel mit Xpath 2.0 + einer Bibliothek:

using Wmhelp.XPath2;

doc.XPath2SelectElements("/*:configuration/*:MyNode");

Sehen :

XPath und XSLT 2.0 für .NET?

1
Akli

Ich mag @ mads-hansen, seine Antwort, so gut, dass ich diese Allzweck-Klassiker geschrieben habe:

    /// <summary>
    /// Gets the <see cref="XNode" /> into a <c>local-name()</c>, XPath-predicate query.
    /// </summary>
    /// <param name="childElementName">Name of the child element.</param>
    /// <returns></returns>
    public static string GetLocalNameXPathQuery(string childElementName)
    {
        return GetLocalNameXPathQuery(namespacePrefixOrUri: null, childElementName: childElementName, childAttributeName: null);
    }

    /// <summary>
    /// Gets the <see cref="XNode" /> into a <c>local-name()</c>, XPath-predicate query.
    /// </summary>
    /// <param name="namespacePrefixOrUri">The namespace prefix or URI.</param>
    /// <param name="childElementName">Name of the child element.</param>
    /// <returns></returns>
    public static string GetLocalNameXPathQuery(string namespacePrefixOrUri, string childElementName)
    {
        return GetLocalNameXPathQuery(namespacePrefixOrUri, childElementName, childAttributeName: null);
    }

    /// <summary>
    /// Gets the <see cref="XNode" /> into a <c>local-name()</c>, XPath-predicate query.
    /// </summary>
    /// <param name="namespacePrefixOrUri">The namespace prefix or URI.</param>
    /// <param name="childElementName">Name of the child element.</param>
    /// <param name="childAttributeName">Name of the child attribute.</param>
    /// <returns></returns>
    /// <remarks>
    /// This routine is useful when namespace-resolving is not desirable or available.
    /// </remarks>
    public static string GetLocalNameXPathQuery(string namespacePrefixOrUri, string childElementName, string childAttributeName)
    {
        if (string.IsNullOrEmpty(childElementName)) return null;

        if (string.IsNullOrEmpty(childAttributeName))
        {
            return string.IsNullOrEmpty(namespacePrefixOrUri) ?
                string.Format("./*[local-name()='{0}']", childElementName)
                :
                string.Format("./*[namespace-uri()='{0}' and local-name()='{1}']", namespacePrefixOrUri, childElementName);
        }
        else
        {
            return string.IsNullOrEmpty(namespacePrefixOrUri) ?
                string.Format("./*[local-name()='{0}']/@{1}", childElementName, childAttributeName)
                :
                string.Format("./*[namespace-uri()='{0}' and local-name()='{1}']/@{2}", namespacePrefixOrUri, childElementName, childAttributeName);
        }
    }
0
rasx