web-dev-qa-db-fra.com

Comment utiliser XPath avec un espace de noms par défaut sans préfixe?

Qu'est-ce que XPath (dans l'API C # de XDocument.XPathSelectElements (xpath, nsman) si c'est important) pour interroger tous les MyNodes de ce document?

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <MyNode xmlns="lcmp" attr="true">
    <subnode />
  </MyNode>
</configuration>
  • J'ai essayé /configuration/MyNode qui est faux parce qu'il ignore l'espace de noms.
  • J'ai essayé /configuration/lcmp:MyNode qui est faux parce que lcmp est l'URI, pas le préfixe.
  • J'ai essayé /configuration/{lcmp}MyNode qui a échoué parce que Additional information: '/configuration/{lcmp}MyNode' has an invalid token.

EDIT: Je ne peux pas utiliser mgr.AddNamespace("df", "lcmp"); comme l'ont suggéré certains des répondants. Cela nécessite que le programme d'analyse XML connaisse tous les espaces de noms que je prévois d'utiliser à l'avance. Comme cela est censé être applicable à n’importe quel fichier source, je ne sais pas pour quels espaces de noms ajouter manuellement des préfixes. Il semble que {my uri} soit la syntaxe XPath, mais Microsoft ne s'est pas donné la peine d'implémenter cela ... vrai?

34
Scott Stafford

L'élément configuration est dans l'espace de nom non nommé et le MyNode est lié à l'espace de nom lcmp sans préfixe d'espace de nom.

Cette instruction XPATH vous permettra d'adresser l'élément MyNode sans avoir déclaré l'espace de noms lcmp ou d'utiliser un préfixe d'espace de nom dans votre XPATH:

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

Elle correspond à tout élément enfant de configuration et utilise ensuite un fichier de prédicat avec namespace-uri() et local-name() pour le limiter à l'élément MyNode.

Si vous ne savez pas quel espace de nom-uri sera utilisé pour les éléments, vous pouvez alors rendre le XPATH plus générique et le faire correspondre à la fonction local-name():

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

Cependant, vous courez le risque de faire correspondre différents éléments dans différents vocabulaires (liés à différents namespace-uri) qui utilisent le même nom.

37
Mads Hansen

Vous devez utiliser un XmlNamespaceManager comme suit:

   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 n'est pas (délibérément) conçu pour le cas où vous souhaitez utiliser la même expression XPath pour certains espaces de noms inconnus qui résident uniquement dans le document XML. Vous devez connaître l’espace de noms à l’avance, le déclarer au processeur XPath et utiliser le nom dans votre expression. Les réponses de Martin et Dan montrent comment faire cela en C #. 

La raison de cette difficulté est mieux exprimée dans les espaces de nom XML spec:

Nous envisageons des applications du langage XML (Extensible Markup Language), dans lesquelles un seul document XML peut contenir des éléments et des attributs (appelés ici "vocabulaire de balisage") définis et utilisés par plusieurs modules logiciels. La modularité en est une des motivations: s'il existe un tel vocabulaire de balisage bien compris et pour lequel un logiciel utile est disponible, il est préférable de le réutiliser plutôt que de le réinventer.

Ces documents, contenant plusieurs vocabulaires de balisage, posent des problèmes de reconnaissance et de collision. Les modules logiciels doivent être capables de reconnaître les éléments et les attributs qu’ils sont conçus pour traiter, même en cas de "collision" lorsque le balisage destiné à un autre progiciel utilise le même nom d’élément ou d’attribut.

Ces considérations nécessitent que les noms de document soient construits de manière à éviter les conflits entre les noms de vocabulaires de balisage différents. Cette spécification décrit un mécanisme, les espaces de noms XML, qui accomplit cela en attribuant des noms étendus à des éléments et des attributs. 

C'est-à-dire que les espaces de noms sont censés être utilisés pour vous assurer que vous savez de quoi parle votre document: cet élément <head> parle-t-il du préambule d'un document XHTML ou de la tête d'un corps dans un document AnatomyML? Vous n'êtes jamais "supposé" être agnostique à propos de l'espace de noms et c'est à peu près la première chose que vous devez définir dans n'importe quel vocabulaire XML. 

Il devrait être possible de faire ce que vous voulez, mais je ne pense pas que cela puisse être fait avec une seule expression XPath. Tout d’abord, vous devez fouiller dans le document et extraire tous les URI de l’espace de nommage, puis les ajouter au gestionnaire d’espace de nommage, puis exécuter l’expression XPath souhaitée (et connaître la répartition des espaces de nommage dans le document). point, ou vous avez beaucoup d'expressions à exécuter). Je pense que vous êtes probablement mieux en utilisant autre chose que XPath (par exemple, une API de type DOM ou SAX) pour trouver les URI d'espace de nom, mais vous pouvez aussi explorer l'axe d'espace de nom XPath (dans XPath 1.0), utilisez le namespace-uri-from-QName function (dans XPath 2.0) ou utilisez des expressions telles que "configuration/*[local-name() = 'MyNode']" d'Oleg. Quoi qu'il en soit, je pense que votre meilleur pari est d'essayer d'éviter d'écrire XPath, agnostique d'espace de noms! Pourquoi ne connais-tu pas ton espace de noms à l'avance? Comment allez-vous éviter de faire correspondre les choses que vous n'avez pas l'intention de faire correspondre?

Edit - vous connaissez le namespaceURI?

Il se trouve donc que votre question nous a tous déroutés. Apparemment, vous connaissez l'URI de l'espace de noms, mais vous ne connaissez pas le préfixe d'espace de nom utilisé dans le document XML. En effet, dans ce cas, aucun préfixe d'espace de nom n'est utilisé et l'URI devient l'espace de nommage par défaut où il est défini. La chose clé à savoir est que le préfixe choisi (ou l’absence de préfixe) n’est pas pertinent pour votre expression XPath (et l’analyse XML en général). L'attribut prefix/xmlns n'est qu'un moyen d'associer un nœud à un URI d'espace de nom lorsque le document est exprimé sous forme de texte. Vous voudrez peut-être jeter un oeil à cette réponse où j'essaie de clarifier les préfixes d'espace de nom. 

Vous devez essayer de penser au document XML de la même manière que l'analyseur: chaque nœud a un URI d'espace de nom et un nom local. Les règles de préfixe/héritage d’espace de nom permettent simplement d’enregistrer de nombreuses saisies de l’URI. Une façon d’écrire ceci est en notation Clark: c’est-à-dire que vous écrivez { http://www.example.com/namespace/example } LocalNodeName, mais cette notation n’est généralement utilisée que pour la documentation - XPath ne sait rien de cette notation.Au lieu de cela, XPath utilise ses propres préfixes d'espace de nom. Quelque chose comme /ns1:root/ns2:node. Mais ceux-ci sont complètement séparés de rien et n'ont rien à voir avec les préfixes pouvant être utilisés dans le document XML d'origine. Toute implémentation XPath disposera d’un moyen de mapper ses propres préfixes avec des URI d’espace de noms. Pour l'implémentation C # pour laquelle vous utilisez XmlNamespaceManager, vous fournissez un hachage en Perl, xmllint accepte les arguments de ligne de commande ... Il suffit donc de créer un préfixe arbitraire pour l'URI d'espace de nom que vous connaissez et d'utiliser ce préfixe dans l'expression XPath . Peu importe le préfixe que vous utilisez, en XML, vous ne vous souciez que de la combinaison de l'URI et du nom local.

L'autre chose à retenir (c'est souvent une surprise) est que XPath ne gère pas l'héritage des espaces de noms. Vous devez ajouter un préfixe pour chaque élément possédant un espace de noms, que celui-ci provienne d'un héritage, d'un attribut xmlns ou d'un préfixe d'espace de nom. En outre, bien que vous deviez toujours penser en termes d'URI et de noms locaux, il existe également des moyens d'accéder au préfixe à partir d'un document XML. Il est rare d'avoir à utiliser ceux-ci. 

The other thing to remember (it's often a surprise) is that XPath doesn't do namespace inheritance. You need to add a prefix for every that has a namespace, irrespective of whether the namespace comes from inheritance, an xmlns attribute, or a namespace prefix. Also, although you should always think in terms of URIs and localNames, there are also ways to access the prefix from an XML document. It's rare to have to use these.

6
Andrew Walker

Voici un exemple de mise à disposition de l'espace de nom pour l'expression XPath dans la méthode d'extension XPathSelectElements:

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

J'aime @ mads-hansen, sa réponse, si bien que j'ai écrit ces membres de la classe utilitaire à usage général:

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