Pourquoi XmlNamespaceManager est-il nécessaire?

je suis venu un peu sec comme pourquoi -- au moins dans le cadre .Net -- il est nécessaire d'utiliser un XmlNamespaceManager afin de gérer les espaces de noms (ou le plutôt clunky et verbeux [local-name()=... prédicat de XPath/fonction/n'importe quoi) lors de l'exécution des requêtes XPath. J' faire comprendre pourquoi les espaces de noms sont nécessaires ou du moins bénéfique, mais pourquoi est-il si complexe?

pour interroger un document XML simple (pas d'espaces de noms)...

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

...on peut utiliser quelque chose comme doc.SelectSingleNode("//nodeName") (qui correspondrait à <nodeName>Some Text Here</nodeName> )

mystère #1 : mon premier ennui -- si je comprends bien -- est que le simple ajout d'une référence à l'espace de nom à la balise parent/root (qu'elle soit utilisée comme partie d'une balise de noeud d'enfant ou non) comme ceci:

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

...nécessite plusieurs lignes supplémentaires de code pour obtenir le même résultat:

Dim nsmgr As New XmlNamespaceManager(doc.NameTable)
nsmgr.AddNamespace("ab", "http://s+omeplace.org")
Dim desiredNode As XmlNode = doc.SelectSingleNode("//ab:nodeName", nsmgr)

...essentiellement imaginer un préfixe inexistant (" ab ") pour trouver un noeud qui n'utilise même pas de préfixe. comment cela a-t-il un sens? Ce qui est mauvais (conceptuellement) avec doc.SelectSingleNode("//nodeName") ?

mystère #2 : alors, dites que vous avez un document XML qui utilise des préfixes:

<?xml version="1.0" encoding="ISO-8859-1"?>
<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>

... Si je comprends bien correctement, vous devez ajouter les deux espaces de la XmlNamespaceManager , afin de faire une requête pour un seul nœud...

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

... Pourquoi, dans ce cas, ai-je besoin (conceptuellement) d'un gestionnaire d'espace de noms?

**repris dans les commentaires ci-dessous* *

Modifier Ajouté: Ma question révisée et affinée est basée sur la redondance apparente de la XmlNamespaceManager dans ce que je crois être la majorité des cas et l'utilisation du namespace manager pour spécifier un mapping du préfixe vers URI:

lorsque la cartographie directe du préfixe de l'espace de noms ("cde") vers L'URI de l'espace de noms ("http://someplace.org") est explicitement indiqué dans le document source:

...<rootNode xmlns:cde="http://someplace.org"...

Quel est le besoin conceptuel d'un programmeur pour recréer cette cartographie avant de faire une requête?

62
demandé sur Code Jockey 2011-08-24 19:28:43

6 réponses

le point de base (comme souligné par Kev, au-dessus de ), est que L'URI d'espace de noms est la partie importante de l'espace de noms, plutôt que le préfixe d'espace de noms, le préfixe est une "commodité arbitraire"

en ce qui concerne la raison pour laquelle vous avez besoin d'un gestionnaire d'espace de noms, plutôt qu'il y ait quelque magie qui fonctionne en utilisant le document, je peux penser à deux raisons.

raison 1

S'il était permis d'ajouter déclarations d'espace de noms pour le documentElement, comme dans votre exemple, il serait en effet trivial pour selectSingleNode à l'utiliser tout ce qui est défini.

cependant, vous pouvez définir les préfixes namespace sur n'importe quel élément d'un document, et les préfixes namespace ne sont pas uniquement liés à n'importe quel namespace donné dans un document. Prenons l'exemple suivant:

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

dans cet exemple, que voulez-vous //z , //a:z et //b:z pour de retour? Comment, sans une sorte d'espace de noms externe gestionnaire, voulez-vous exprimer?

Raison 2

il vous permet de réutiliser la même expression XPath pour n'importe quel document équivalent, sans avoir besoin de savoir quoi que ce soit sur les préfixes d'espace de noms utilisés.

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

doc1:

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

doc2:

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

afin d'atteindre ce dernier objectif, sans namespace manager, vous devriez inspecter chaque document, en construisant une expression XPath personnalisée pour chacun d'eux.

17
répondu Paul Butcher 2017-05-23 12:25:21

la raison est simple. Il n'y a pas de connexion requise entre les préfixes que vous utilisez dans votre requête XPath et les préfixes déclarés dans le document xml. Pour donner un exemple, les XML suivants sont sémantiquement équivalents:

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

la requête " ccc:root/ccc:element " correspondra aux deux instances à condition qu'il y ait un mappage dans le namespace manager pour cela.

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

.NET de la mise en œuvre ne se soucie pas des préfixes littéraux utilisés dans le xml seulement qu'il y ait un préfixe défini pour la requête littérale et que la valeur de l'espace de noms corresponde à la valeur réelle du doc. Ceci est requis pour avoir des expressions de requête constantes même si les préfixes varient entre les documents consommés et c'est l'implémentation correcte pour le cas général.

12
répondu Adrian Zanescu 2013-02-15 11:59:03

autant que je puisse dire, il n'y a aucune bonne raison que vous ayez besoin de définir manuellement un XmlNamespaceManager pour obtenir abc -noeuds préfixés si vous avez un document comme celui-ci:

<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 ne pouvait tout simplement pas se donner la peine d'écrire quelque chose pour détecter que xmlns:abc avait déjà été spécifié dans un noeud parent. Je pourrais me tromper, et si c'est le cas, j'aimerais recevoir des commentaires sur cette réponse afin de pouvoir la mettre à jour.

cependant, ce blog semble confirmer mes soupçons. Il dit essentiellement que vous devez définir manuellement un XmlNamespaceManager et itérer manuellement à travers les attributs xmlns: , en ajoutant chacun d'eux au gestionnaire d'espace de noms. Je ne sais pas pourquoi Microsoft ne pouvait pas faire cela automatiquement.

Voici une méthode que j'ai créée à partir de ce billet de blog pour générer automatiquement un XmlNamespaceManager basé sur les attributs xmlns: d'une source XmlDocument :

/// <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;
}

et je l'utilise comme ça:

XPathNavigator xNav = xmlDoc.CreateNavigator();
XPathNodeIterator xIter = xNav.Select("//abc:NodeC", createNsMgrForDocument(xmlDoc));
11
répondu Jez 2011-10-13 13:26:38

je réponds au point 1:

définir un espace de noms par défaut pour un document XML signifie que les noeuds, même sans préfixe d'espace de noms, i.e.:

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

ne sont plus dans l'espace de noms" empty". Vous avez encore besoin d'un moyen de référencer ces noeuds en utilisant XPath, donc vous créez un préfixe pour les référencer, même s'il est "inventé".

pour répondre au point 2:

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

document d'instance, les noeuds qui résident dans un espace de noms sont stockés avec leur nom de noeud et leur nom d'espace de noms long, il est appelé (dans le jargon W3C) un nom étendu .

par exemple <cde:nodeName> est essentiellement stocké sous la forme <http://someplace.org:nodeName> . Un préfixe d'espace de noms est une commodité arbitraire pour les humains, de sorte que lorsque nous tapons XML ou que nous devons le lire, nous n'avons pas à le faire:

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

Lorsqu'un document XML fait l'objet d'une recherche, il n'est pas recherché par le préfixe Amical, la recherche est faite par URI d'espace de noms donc vous devez informer XPath de vos espaces de noms via une table d'espace de noms passée en utilisant XmlNamespaceManager .

4
répondu Kev 2011-08-24 16:33:13

vous devez enregistrer les paires URI/prefix de L'instance XmlNamespaceManager pour faire savoir à SelectSingleNode () à quel noeud particulier "nodeName" vous faites référence - celui de "http://someplace.org" or the one from "http://otherplace.net".

veuillez noter que le nom du préfixe concret n'a pas d'importance lorsque vous faites la requête XPath. Je crois que cela fonctionne aussi:

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() juste besoin de une connexion entre le préfixe de votre expression XPath et L'URI de l'espace de noms.

3
répondu Christian Schwarz 2011-08-26 13:15:18

ce fil m'a aidé à comprendre beaucoup plus clairement la question des espaces de noms. Grâce. Quand j'ai vu code de Jez , je l'ai essayé parce qu'il ressemblait à une meilleure solution que j'avais programmé. J'ai découvert quelques défauts avec, cependant. Comme écrit, il ne regarde que dans le noeud racine (mais les espaces de noms peuvent être listés n'importe où.), et il ne gère pas les espaces de noms par défaut. J'ai essayé d'aborder ces questions en modifiant son code, mais en vain.

Voici ma version de cette fonction. Il utilise des expressions régulières pour trouver les mappages de namespace dans tout le fichier; fonctionne avec des namespaces par défaut, leur donnant le préfixe arbitraire 'ns'; et gère plusieurs occurrences du même 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])(.+?)").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;
}
3
répondu Phil R 2017-05-23 12:17:21