Warum ist XmlNamespaceManager erforderlich?

70

Ich bin mir ziemlich sicher , warum - zumindest im .Net Framework - bei der Ausführung von XPath-Abfragen ein verwendet werden XmlNamespaceManagermuss, um Namespaces (oder das eher klobige und ausführliche [local-name()=...XPath-Prädikat / Funktion / was auch immer) zu verarbeiten . Ich kann verstehen , warum Namespaces sind notwendig oder zumindest vorteilhaft, aber warum ist es so kompliziert?

Um ein einfaches XML-Dokument abzufragen (keine Namespaces) ...

<?xml version="1.0" encoding="ISO-8859-1"?>
<rootNode>
   <nodeName>Some Text Here</nodeName>
</rootNode>

... man kann so etwas verwenden doc.SelectSingleNode("//nodeName")(was passen würde <nodeName>Some Text Here</nodeName>)

Rätsel Nr. 1 : Mein erster Ärger - wenn ich das richtig verstehe - ist, dass ich lediglich einen Namespace-Verweis auf das übergeordnete / Root-Tag hinzufüge (unabhängig davon, ob es als Teil eines untergeordneten Knoten-Tags verwendet wird oder nicht).

<?xml version="1.0" encoding="ISO-8859-1"?>
<rootNode xmlns="http://example.com/xmlns/foo">
   <nodeName>Some Text Here</nodeName>
</rootNode>

... erfordert mehrere zusätzliche Codezeilen, um das gleiche Ergebnis zu erzielen:

Dim nsmgr As New XmlNamespaceManager(doc.NameTable)
nsmgr.AddNamespace("ab", "http://example.com/xmlns/foo")
Dim desiredNode As XmlNode = doc.SelectSingleNode("//ab:nodeName", nsmgr)

... sich im Wesentlichen ein nicht existierendes Präfix (" ab") ausdenken, um einen Knoten zu finden, der nicht einmal ein Präfix verwendet. Wie macht das Sinn? Was ist (konzeptionell) falsch doc.SelectSingleNode("//nodeName")?

Rätsel Nr. 2 : Angenommen, Sie haben ein XML-Dokument, das Präfixe verwendet:

<?xml version="1.0" encoding="ISO-8859-1"?>
<rootNode xmlns:cde="http://example.com/xmlns/foo" xmlns:feg="http://example.com/xmlns/bar">
   <cde:nodeName>Some Text Here</cde:nodeName>
   <feg:nodeName>Some Other Value</feg:nodeName>
   <feg:otherName>Yet Another Value</feg:otherName>
</rootNode>

... Wenn ich das richtig verstehe, müssten Sie beide Namespaces zum hinzufügen XmlNamespaceManager, um eine Abfrage für einen einzelnen Knoten durchzuführen ...

Dim nsmgr As New XmlNamespaceManager(doc.NameTable)
nsmgr.AddNamespace("cde", "http://example.com/xmlns/foo")
nsmgr.AddNamespace("feg", "http://example.com/xmlns/bar")
Dim desiredNode As XmlNode = doc.SelectSingleNode("//feg:nodeName", nsmgr)

... Warum brauche ich in diesem Fall (konzeptionell) einen Namespace-Manager?

****** ANONYMISIERT in Kommentare unten ****

Bearbeiten Hinzugefügt: Meine überarbeitete und verfeinerte Frage basiert auf der offensichtlichen Redundanz des XmlNamespaceManager in den meiner Meinung nach meisten Fällen und der Verwendung des Namespace-Managers zur Angabe einer Zuordnung des Präfixes zum URI:

Wenn die direkte Zuordnung des Namespace-Präfixes ("cde") zum Namespace-URI (" http://example.com/xmlns/foo ") im Quelldokument explizit angegeben ist:

...<rootNode xmlns:cde="http://example.com/xmlns/foo"...

Was ist die konzeptionelle Notwendigkeit für einen Programmierer, diese Zuordnung neu zu erstellen, bevor eine Abfrage durchgeführt wird?

Code Jockey
quelle
1
Als kurze Ergänzung gebe ich zu, dass es wahrscheinlich Situationen gibt, in denen so etwas wie ein XMLNamespaceManager die Dinge einfacher machen würde, aber ich glaube, dass es in den oben genannten Situationen die Dinge erheblich schwieriger macht, als sie sein müssen.
Code Jockey
1
Meine Hauptursache für Verwirrung ist, warum die Beziehung zwischen Präfix und Namespace sowohl im XML-Dokument als auch im Code, der die XPath-Abfrage implementiert, angegeben werden muss. Wenn der Stammknoten die Zuordnung bereits enthält, warum muss ich dann im Wesentlichen Hardcode-Informationen verarbeiten, die bereits beim Laden des Dokuments analysiert wurden? Wenn dem Dokument in Zukunft ein dritter Namespace hinzugefügt wird, muss ich dann meinen Code nicht ändern und neu kompilieren, um diese dritte Beziehung zu deklarieren?
Code Jockey
1
ANONYMISIERT von oben: Was ist falsch daran, einfach das Namespace-Präfix in die XPath-Abfrage einzufügen - doc.SelectSingleNode("//feg:nodeName")- und damit fertig zu sein? Kann es für das menschliche Gehirn Zweifel geben, was mit diesem Codefragment gemeint ist? [PARAGRAPH] Anders ausgedrückt , was trägt wirklich zum Verständnis der Situation durch die zusätzlichen Codezeilen und die Instanziierung eines XmlNamespaceManager bei, der nicht eindeutig vom XML-Quelldokument und / oder der XPath-Abfrage abgeleitet werden kann?
Code Jockey
1
Von oben ANONYMISIERT, Fortsetzung : Für die meisten XML-Dokumente und Situationen, in denen XML und XPath verwendet werden, ist es zumindest denkbar, wenn auch nicht ganz praktisch, die Namespace-Informationen einfach aus dem Dokument und der Abfrage abzurufen, anstatt vorheriges Wissen über das zu benötigen Namespaces oder manuelles Parsen des Dokuments, um die Argumente für AddNamespace()? Ich kann nicht anders, als zu glauben, dass mir etwas Offensichtliches fehlt, und wenn ja, bitte klären Sie mich auf!
Code Jockey
1
+1 für diese Frage. Ich habe gerade genau den gleichen Gedanken. Mein Stammknoten hat eine Reihe von xmlns:abc="..." xmlns:def="..."Attributen. Warum XPathNodeIteratorum alles in der Welt kann man nicht herausfinden, welcher Namespace mit einem untergeordneten Knoten wie <abc:SomeNode/>ohne verknüpft ist XmlNamespaceManager?
Jez

Antworten:

20

Der grundlegende Punkt (wie oben von Kev ausgeführt) ) ist, dass der Namespace-URI der wichtige Teil des Namespace ist und nicht das Namespace-Präfix, sondern eine "willkürliche Annehmlichkeit".

Ich kann mir zwei Gründe vorstellen, warum Sie einen Namespace-Manager benötigen, anstatt dass es etwas Magisches gibt, das mit dem Dokument funktioniert.

Grund 1

Wenn es erlaubt wäre, dem documentElement nur Namespace-Deklarationen hinzuzufügen, wie in Ihren Beispielen, wäre es für selectSingleNode in der Tat trivial, nur das zu verwenden, was definiert ist.

Sie können jedoch Namespace-Präfixe für jedes Element in einem Dokument definieren, und Namespace-Präfixe sind nicht eindeutig an einen bestimmten Namespace in einem Dokument gebunden. Betrachten Sie das folgende Beispiel

<w xmlns:a="mynamespace">
  <a:x>
    <y xmlns:a="myOthernamespace">
      <z xmlns="mynamespace">
      <b:z xmlns:b="mynamespace">
      <z xmlns="myOthernamespace">
      <b:z xmlns:b="myOthernamespace">
    </y>
  </a:x>
</w>

In diesem Beispiel, was würden Sie wollen //z, //a:zund//b:z auf Rückkehr? Wie würden Sie das ohne einen externen Namespace-Manager ausdrücken?

Grund 2

Sie können denselben XPath-Ausdruck für jedes gleichwertige Dokument wiederverwenden, ohne etwas über die verwendeten Namespace-Präfixe wissen zu müssen.

myXPathExpression = "//z:y"
doc1.selectSingleNode(myXPathExpression);
doc2.selectSingleNode(myXPathExpression);

doc1:

<x>
  <z:y xmlns:z="mynamespace" />
</x>

doc2:

<x xmlns"mynamespace">
  <y>
</x>

Um dieses letztere Ziel ohne einen Namespace-Manager zu erreichen, müssten Sie jedes Dokument überprüfen und für jedes einen benutzerdefinierten XPath-Ausdruck erstellen.

Paul Butcher
quelle
1
Obwohl die Stichprobe unter Grund 1 als AFAIK gültig erscheint, muss ich mich fragen, wie viele Fälle in der realen Welt so existieren, da sie wahnsinnig komplex erscheint. Die Verwendung von Einzelbuchstaben-Namespace und Knotennamen schränkt die Anzahl der Möglichkeiten etwas ein, obwohl ich einige Beispiele aus der Praxis für Abkürzungen mit 2, 3 und 4 Buchstaben gesehen habe, die als Namespace-Präfixe verwendet werden. Buchstabenpräfixe außerhalb von Theorie und Beispielen. Grundsätzlich bin ich wirklich auf der Suche nach einer Möglichkeit, dies mit einem Namespace-Manager auszudrücken .
Code Jockey
Um Ihre Frage aus Grund 1 zu beantworten : Es hängt davon ab, was ich aus den Daten herausfinden oder herausfiltern wollte - etwas, das mit solch komplexen UND GLEICHZEITIGEN unsinnigen Knotennamen und -beziehungen sehr schwer zu tun ist. Ihr Grund 1 bietet jedoch die bisher aufschlussreichste und klarste Antwort ... Was Grund 2 betrifft, bin ich mir nicht sicher, ob der bereitgestellte Code ausgeführt werden würde, da Ihre Quelle Namespaces verwendet, Sie jedoch keinen Namespace-Manager bereitstellen - bin Ich irre mich darin?
Code Jockey
In beiden Beispielen bitte ich Sie, über ein Leben ohne Namespace-Manager nachzudenken. Soweit ich sehen kann, ist die Frage, die ich in Grund 1 stelle , ohne Rückgriff auf einen Namespace-Manager nicht zu beantworten. Ich frage nicht, wie bestimmte Knoten extrahiert werden sollen, sondern welche Knoten diese Ausdrücke voraussichtlich zurückgeben würden.
Paul Butcher
Sie haben Recht - der Code in Grund 2 benötigt einen Namespace-Manager. Ich lasse den Namespace-Manager absichtlich weg, weil der Punkt Ihrer Frage (so wie ich es verstehe) ist, dass Sie glauben, wir können ohne ihn leben - dies zeigt eine Situation, in der wir nicht können.
Paul Butcher
1
Endgültige Antwort auf Ihre Frage in Grund 1 : //zsollte übereinstimmen<z xmlns="mynamespace"> und <z xmlns="myOthernamespace">, //a:zwürde eine leere Menge zurück und //b:zwürde passen <b:z xmlns:b="mynamespace">und <b:z xmlns:b="myOthernamespace">- die Logik dahinter ist , dass es keine Namespace - Manager war angegeben, und es gab keine " versuchen , zu erhalten Informationen aus dem Dokument selbst "Befehl, daher werden Namespaces wie alle anderen Attribute behandelt und werden :zu einem anderen gültigen Zeichen, wie -in meinem Kopf, wenn Sie Ihre Daten kennen oder es Ihnen egal ist, sollte das Abfragen eines Knotens nicht so schmerzhaft sein
Code Jockey
14

Der Grund ist einfach. Es ist keine Verbindung zwischen den Präfixen, die Sie in Ihrer XPath-Abfrage verwenden, und den deklarierten Präfixen im XML-Dokument erforderlich. Um ein Beispiel zu geben, sind die folgenden xmls semantisch äquivalent:

<aaa:root xmlns:aaa="http://someplace.org">
 <aaa:element>text</aaa:element>
</aaa:root>

vs.

  <bbb:root xmlns:bbb="http://someplace.org">
     <bbb:element>text</bbb:element>
  </bbb:root>

Die ccc:root/ccc:elementAbfrage " " stimmt mit beiden Instanzen überein, sofern im Namespace-Manager eine Zuordnung dafür vorhanden ist.

nsmgr.AddNamespace("ccc", "http://someplace.org")

Die .NET-Implementierung kümmert sich nicht um die in der XML verwendeten Literalpräfixe, sondern nur darum, dass für das Abfrageliteral ein Präfix definiert ist und der Namespace-Wert mit dem tatsächlichen Wert des Dokuments übereinstimmt. Dies ist erforderlich, um konstante Abfrageausdrücke zu haben, auch wenn die Präfixe zwischen den verwendeten Dokumenten variieren und dies die richtige Implementierung für den allgemeinen Fall ist.

Adrian Zanescu
quelle
12

Soweit ich das beurteilen kann, gibt es keinen guten Grund, warum Sie eine manuell definieren müssen XmlNamespaceManagerabc beurteilen , um an Knoten mit Präfix zu gelangen , wenn Sie ein Dokument wie dieses haben:

<itemContainer xmlns:abc="http://abc.com" xmlns:def="http://def.com">
    <abc:nodeA>...</abc:nodeA>
    <def:nodeB>...</def:nodeB>
    <abc:nodeC>...</abc:nodeC>
</itemContainer>

Microsoft konnte sich einfach nicht die Mühe machen, etwas zu schreiben, um zu erkennen, dass xmlns:abces bereits in einem übergeordneten Knoten angegeben wurde. Ich könnte mich irren, und wenn ja, würde ich Kommentare zu dieser Antwort begrüßen, damit ich sie aktualisieren kann.

Jedoch, Blog-Beitrag scheint jedoch meinen Verdacht zu bestätigen. Grundsätzlich heißt es, dass Sie eine manuell definieren XmlNamespaceManagerund manuell durchlaufen müssenxmlns: Attribut , wobei Sie jedes Attribut dem Namespace-Manager hinzufügen müssen. Keine Ahnung, warum Microsoft dies nicht automatisch tun konnte.

Hier ist eine Methode, die ich basierend auf diesem Blog-Beitrag erstellt habe, um automatisch eine XmlNamespaceManagerbasierend auf den xmlns:Attributen einer Quelle zu generierenXmlDocument :

/// <summary>
/// Creates an XmlNamespaceManager based on a source XmlDocument's name table, and prepopulates its namespaces with any 'xmlns:' attributes of the root node.
/// </summary>
/// <param name="sourceDocument">The source XML document to create the XmlNamespaceManager for.</param>
/// <returns>The created XmlNamespaceManager.</returns>
private XmlNamespaceManager createNsMgrForDocument(XmlDocument sourceDocument)
{
    XmlNamespaceManager nsMgr = new XmlNamespaceManager(sourceDocument.NameTable);

    foreach (XmlAttribute attr in sourceDocument.SelectSingleNode("/*").Attributes)
    {
        if (attr.Prefix == "xmlns")
        {
            nsMgr.AddNamespace(attr.LocalName, attr.Value);
        }
    }

    return nsMgr;
}

Und ich benutze es so:

XPathNavigator xNav = xmlDoc.CreateNavigator();
XPathNodeIterator xIter = xNav.Select("//abc:NodeC", createNsMgrForDocument(xmlDoc));
Jez
quelle
Willkommen, ich komme nach all der Zeit darauf zurück - es ist nicht nur Microsoft - ich glaube, es ist in der XML- oder XPATH-Spezifikation - und es passiert auf ähnliche Weise in anderen Nicht-MS-Sprachen, die ich verwendet habe - nicht sicher, ob es eine gibt das extrahiert Namespaces für Sie, aber wie gibt man dann an, welcher Bereich (weil Namespaces in jedem Bereich angegeben werden können) ... idunno - Ich würde einen Literalmodus lieben, in dem ein Literalzeichen :ähnlich einer Zahl, einem Buchstaben oder -und wird wird also prfx:NodeNamegenauso behandelt wie prfxNodeNameoder prfx-NodeName- eine einfache Kennung ... obwohl nicht standardkonform ... seufz
Code Jockey
4

Ich antworte auf Punkt 1:

Das Festlegen eines Standard-Namespace für ein XML-Dokument bedeutet weiterhin, dass die Knoten auch ohne Namespace-Präfix, dh:

<rootNode xmlns="http://someplace.org">
   <nodeName>Some Text Here</nodeName>
</rootNode>

befinden sich nicht mehr im "leeren" Namespace. Sie benötigen noch eine Möglichkeit, diese Knoten mit XPath zu referenzieren. Daher erstellen Sie ein Präfix, um auf sie zu verweisen, auch wenn es "erfunden" ist.

Um Punkt 2 zu beantworten:

<rootNode xmlns:cde="http://someplace.org" xmlns:feg="http://otherplace.net">
   <cde:nodeName>Some Text Here</cde:nodeName>
   <feg:nodeName>Some Other Value</feg:nodeName>
   <feg:otherName>Yet Another Value</feg:otherName>
</rootNode>

Intern im Instanzdokument werden die Knoten, die sich in einem Namespace befinden, mit ihrem Knotennamen und ihrem langen Namespace-Namen gespeichert. Dies wird (im W3C-Sprachgebrauch) als erweiterter Name bezeichnet .

Zum Beispiel <cde:nodeName>wird im Wesentlichen als gespeichert <http://someplace.org:nodeName>. Ein Namespace-Präfix ist eine willkürliche Annehmlichkeit für Menschen, sodass wir Folgendes nicht tun müssen, wenn wir XML eingeben oder lesen müssen:

<rootNode>
   <http://someplace.org:nodeName>Some Text Here</http://someplace.org:nodeName>
   <http://otherplace.net:nodeName>Some Other Value</http://otherplace.net:nodeName>
   <http://otherplace.net:otherName>Yet Another Value</http://otherplace.net:otherName>
</rootNode>

Wenn ein XML-Dokument durchsucht wird, wird es nicht nach dem benutzerfreundlichen Präfix durchsucht. Die Suche erfolgt nach dem Namespace-URI, sodass Sie XPath über eine mit übergebene Namespace-Tabelle über Ihre Namespaces informieren müssen XmlNamespaceManager.

Kev
quelle
Obwohl ich keinen konzeptionellen Grund sehe, jemanden zu verpflichten, einen nicht "leeren" Namespace zu bestätigen, wenn immer noch nur ein Namespace verwendet wird, warum sollte es notwendig sein, für eine Funktion mehr als ein Flag wiedoc.SelectSingleNode("//nodeName", NamespaceFlags.UseDocumentNamespace)
Code Jockey
- Das heißt, warum muss ein Programmierer ein separates Objekt instanziieren, den im Dokument verwendeten Namespace vorher kennen (oder Code analysieren und bestimmen) und dann ein völlig zufälliges und künstliches Namespace-Präfix angeben, das in die XPath-Abfrage eingefügt werden soll? Bitte vergib mir meinen Ton - ich bin einfach amüsiert.
Code Jockey
@code - Dies liegt daran, dass in komplexeren Dokumenten (z. B. RSS-Feeds ) häufig mehr als ein Namespace im Spiel ist. Ein spezielles Flag, das nur für diese bestimmte Bedingung geeignet ist (das Dokument befindet sich nur im Standard-Namespace gemäß Ihrem Beispiel), ist eine schlechte Entwurfswahl und erhöht die Komplexität des Framework-Codes. Warum also nicht alle Grundlagen abdecken und den Verbraucher des Codes bitten, XmlNamespaceManagerstattdessen einen zu übergeben ?
Kev
Ich glaube, Ihr bereitgestelltes Beispiel (RSS) bezieht sich auf mein Geheimnis Nr. 2 in der ursprünglichen Frage (mehr als ein Namespace). Die XPath-Abfrage und das RSS-Dokument selbst enthalten alle Informationen, die zum Abfragen eines Knotens erforderlich sind. Die einzige Situation, die ich mir vorstellen kann, XmlNamespaceManagerist eine, in der es mehrere Namespaces ("someplace.org" UND "otherplace.net") gibt, die dieselben Präfixe verwenden (beide verwenden xmlns:placeoder ähnlich, aber in unterschiedlichen Bereichen des Dokuments). Andernfalls liefern das Dokument und die Abfrage alle Informationen, die zur Erzielung des gewünschten Ergebnisses erforderlich sind.
Code Jockey
1
Ich schätze Ihre Geduld, aber dies scheint meine Frage immer noch nicht zu beantworten. Warum ist es notwendig, mehr zu verwenden, als //feg:nodeNameeinen bestimmten Knoten zu finden? Es sollte relativ trivial sein, intern zu konvertieren feg... ohne dass http://otherplace.netich diese Beziehung explizit angeben muss - sie befindet sich genau dort im Stammknoten! ( xmlns:feg="http://otherplace.net"). Zumindest denke ich, dass es eine Hilfsfunktion geben sollte wie XmlNamespaceManager.GetNSFromDocument(xdoc)... Wenn die Antwort einfach ist, dass sie diese Arbeit (noch) nicht für Sie erledigt haben, dann OK! Ist das der Fall?
Code Jockey
3

Sie müssen die URI / Präfix-Paare in der XmlNamespaceManager-Instanz registrieren, damit SelectSingleNode () weiß, auf welchen bestimmten "nodeName" -Knoten Sie sich beziehen - den von "http://someplace.org" oder den von "http:": //otherplace.net ".

Bitte beachten Sie, dass der konkrete Präfixname bei der XPath-Abfrage keine Rolle spielt. Ich glaube, das funktioniert auch:

Dim nsmgr As New XmlNamespaceManager(doc.NameTable)
nsmgr.AddNamespace("any", "http://someplace.org")
nsmgr.AddNamespace("thing", "http://otherplace.net")
Dim desiredNode As XmlNode = doc.SelectSingleNode("//thing:nodeName", nsmgr)

SelectSingleNode () benötigt lediglich eine Verbindung zwischen dem Präfix Ihres XPath-Ausdrucks und dem Namespace-URI.

Christian Schwarz
quelle
3

Dieser Thread hat mir geholfen, das Problem der Namespaces viel klarer zu verstehen. Vielen Dank. Als ich Jez 'Code sah , versuchte ich es, weil es nach einer besseren Lösung aussah, als ich programmiert hatte. Ich habe jedoch einige Mängel entdeckt. Wie geschrieben, sieht es nur im Stammknoten aus (Namespaces können jedoch überall aufgelistet werden.) Und es werden keine Standard-Namespaces verarbeitet. Ich habe versucht, diese Probleme durch Ändern seines Codes zu beheben, aber ohne Erfolg.

Hier ist meine Version dieser Funktion. Es verwendet reguläre Ausdrücke, um die Namespace-Zuordnungen in der gesamten Datei zu finden. arbeitet mit Standard-Namespaces und gibt ihnen das beliebige Präfix 'ns'; und behandelt mehrere Vorkommen desselben Namespace.

private XmlNamespaceManager CreateNamespaceManagerForDocument(XmlDocument document)
{
    var nsMgr = new XmlNamespaceManager(document.NameTable);

    // Find and remember each xmlns attribute, assigning the 'ns' prefix to default namespaces.
    var nameSpaces = new Dictionary<string, string>();
    foreach (Match match in new Regex(@"xmlns:?(.*?)=([\x22\x27])(.+?)\2").Matches(document.OuterXml))
        nameSpaces[match.Groups[1].Value + ":" + match.Groups[3].Value] = match.Groups[1].Value == "" ? "ns" : match.Groups[1].Value;

    // Go through the dictionary, and number non-unique prefixes before adding them to the namespace manager.
    var prefixCounts = new Dictionary<string, int>();
    foreach (var namespaceItem in nameSpaces)
    {
        var prefix = namespaceItem.Value;
        var namespaceURI = namespaceItem.Key.Split(':')[1];
        if (prefixCounts.ContainsKey(prefix)) 
            prefixCounts[prefix]++; 
        else 
            prefixCounts[prefix] = 0;
        nsMgr.AddNamespace(prefix + prefixCounts[prefix].ToString("#;;"), namespaceURI);
    }
    return nsMgr;
}
Phil R.
quelle