Pourquoi XmlNamespaceManager est-il nécessaire?

Je me suis demandé pourquoi – au moins dans le .Net Framework – il est nécessaire d’utiliser un XmlNamespaceManager pour gérer les espaces de noms (ou plutôt le [local-name()=... XPath prédicat / function / quoi) lors de l’exécution de requêtes XPath. Je comprends pourquoi les espaces de noms sont nécessaires ou du moins bénéfiques, mais pourquoi est-ce si complexe?

Afin d’interroger un document XML simple (pas d’espaces de noms) …

   Some Text Here  

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

Mystère n ° 1 : Mon premier désagrément – si je comprends bien – est simplement le fait d’append une référence d’espace de nom à la balise parent / racine (qu’elle soit utilisée dans une balise de nœud enfant ou non) comme ceci:

   Some Text Here  

… nécessite plusieurs lignes de code supplémentaires 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 en imaginant un préfixe inexistant (” ab “) pour trouver un nœud qui n’utilise même pas de préfixe. Comment ça peut vouloir dire quelque chose? Qu’est-ce qui ne va pas (conceptuellement) avec doc.SelectSingleNode("//nodeName") ?

Mystère # 2 : Donc, disons que vous avez un document XML qui utilise des préfixes:

   Some Text Here Some Other Value Yet Another Value  

… Si je comprends bien, vous devrez append les deux espaces de noms au XmlNamespaceManager , afin de faire une requête pour un seul noeud …

 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’espaces de noms?

** SUPPRIMÉ en commentaires ci-dessous **

Modifier: ma question révisée et affinée est basée sur la redondance apparente de XmlNamespaceManager dans ce que je pense être la majorité des cas et sur l’utilisation du gestionnaire d’espaces de noms pour spécifier un mappage de préfixe en URI:

Lorsque le mappage direct du préfixe d’espace de noms (“cde”) sur l’URI d’espace de noms (“http://someplace.org”) est explicitement indiqué dans le document source:

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

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

Le sharepoint base (comme souligné par Kev, ci-dessus ) est que l’URI de l’espace de nommage est la partie importante de l’espace de nommage, plutôt que le préfixe d’espace de nommage, le préfixe étant une “commodité arbitraire”.

Pour ce qui est de savoir pourquoi vous avez besoin d’un gestionnaire d’espaces de noms, plutôt que de trouver une solution magique à l’aide du document, je peux penser à deux raisons.

Raison 1

S’il était permis d’append uniquement des déclarations d’espace de noms au documentElement, comme dans vos exemples, il serait en effet sortingvial que selectSingleNode utilise simplement ce qui est défini.

Cependant, vous pouvez définir des préfixes d’espaces de noms sur n’importe quel élément d’un document, et les préfixes d’espace de noms ne sont liés de manière unique à aucun espace de noms donné dans un document. Considérons l’exemple suivant

           

Dans cet exemple, que voulez-vous que //z , //a:z et //b:z renvoient? Comment, sans une sorte de gestionnaire d’espace de noms externe, exprimeriez-vous cela?

Raison 2

Il vous permet de réutiliser la même expression XPath pour tout 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:

    

doc2:

    

Pour atteindre ce dernier objective sans gestionnaire d’espace de noms, vous devez inspecter chaque document, en créant une expression XPath personnalisée pour chacun.

La raison est simple. Il n’y a pas de connexion requirejse 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 fichiers XML suivants sont sémantiquement équivalents:

  text  

contre

   text  

La requête ” ccc:root/ccc:element ” correspondra aux deux instances à condition qu’il y ait un mappage dans le gestionnaire d’espace de noms pour cela.

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

L’implémentation .NET ne se soucie pas des préfixes littéraux utilisés dans le fichier XML uniquement, car il existe un préfixe défini pour le littéral de requête et que la valeur de l’espace de noms correspond à la valeur réelle du document. Cela est nécessaire pour avoir des expressions de requête constantes même si les préfixes varient entre les documents consommés et que c’est l’implémentation correcte pour le cas général.

Autant que je sache, il n’y a pas de bonne raison de définir manuellement un XmlNamespaceManager pour accéder aux noeuds abc -prefixed si vous avez un document comme celui-ci:

  ... ... ...  

Microsoft ne pouvait tout simplement pas prendre la peine d’écrire quelque chose pour détecter que xmlns:abc avait déjà été spécifié dans un nœud parent. Je peux me tromper et si oui, je serais heureux de recevoir des commentaires sur cette réponse pour que je puisse la mettre à jour.

Cependant, cet article de blog semble confirmer mes soupçons. En gros, il est dit que vous devez définir manuellement un XmlNamespaceManager et XmlNamespaceManager une XmlNamespaceManager manuelle dans les atsortingbuts xmlns: ajoutant chacun au gestionnaire d’espaces de noms. Je ne comprends pas pourquoi Microsoft ne pouvait pas le faire automatiquement.

Voici une méthode que j’ai créée sur la base de cet article pour générer automatiquement un XmlNamespaceManager basé sur les atsortingbuts xmlns: d’un document XmlDocument source:

 ///  /// Creates an XmlNamespaceManager based on a source XmlDocument's name table, and prepopulates its namespaces with any 'xmlns:' atsortingbutes of the root node. ///  /// The source XML document to create the XmlNamespaceManager for. /// The created XmlNamespaceManager. private XmlNamespaceManager createNsMgrForDocument(XmlDocument sourceDocument) { XmlNamespaceManager nsMgr = new XmlNamespaceManager(sourceDocument.NameTable); foreach (XmlAtsortingbute attr in sourceDocument.SelectSingleNode("/*").Atsortingbutes) { 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)); 

Je réponds au point 1:

Définir un espace de nom par défaut pour un document XML signifie toujours que les nœuds, même sans préfixe d’espace de noms, à savoir:

  Some Text Here  

ne sont plus dans l’espace de noms “vide”. Vous avez toujours besoin d’un moyen de référencer ces noeuds à l’aide de XPath, vous créez donc un préfixe pour les référencer, même s’il est “composé”.

Pour répondre au point 2:

  Some Text Here Some Other Value Yet Another Value  

En interne, dans le document d’instance, les nœuds qui résident dans un espace de noms sont stockés avec leur nom de nœud et leur nom d’espace de nom long. On appelle cela (en langage W3C) un nom étendu .

Par exemple, est essentiellement stocké sous . Un préfixe d’espace de nommage 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:

  Some Text Here Some Other Value Yet Another Value  

Lorsqu’un document XML est recherché, il ne fait pas l’object d’une recherche par le préfixe friendly, il est effectué par un URI d’espace de nommage.

Vous devez enregistrer les paires URI / prefix dans l’instance XmlNamespaceManager pour que SelectSingleNode () sache quel nœud “nodeName” particulier vous faites référence – celui de “http://someplace.org” ou celui de “http: //otherplace.net “.

Veuillez noter que le nom de préfixe concret importe peu lorsque vous exécutez 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 () a juste besoin d’une connexion entre le préfixe de votre expression XPath et l’URI d’espace de noms.

Ce fil de discussion m’a aidé à mieux comprendre la question des espaces de noms. Merci. Quand j’ai vu le code de Jez , je l’ai essayé parce que cela semblait être une meilleure solution que ce que j’avais programmé. J’ai découvert quelques défauts avec cela, cependant. Tel qu’écrit, il ne s’intéresse qu’au nœud racine (mais les espaces de noms peuvent être répertoriés n’importe où) et ne gère pas les espaces de noms par défaut. J’ai essayé de résoudre ces problèmes en modifiant son code, mais en vain.

Voici ma version de cette fonction. Il utilise des expressions régulières pour trouver les mappages d’espace de noms dans le fichier; fonctionne avec les espaces de noms par défaut, en leur donnant le préfixe arbitraire “ns”; et gère plusieurs occurrences du même espace de noms.

 private XmlNamespaceManager CreateNamespaceManagerForDocument(XmlDocument document) { var nsMgr = new XmlNamespaceManager(document.NameTable); // Find and remember each xmlns atsortingbute, assigning the 'ns' prefix to default namespaces. var nameSpaces = new Dictionary(); 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(); 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].ToSsortingng("#;;"), namespaceURI); } return nsMgr; }