Comment utiliser XPath avec un namespace par défaut sans préfixe?

Qu'est-ce que le XPath (en C# API vers 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 ce qui est faux parce qu'il ignore l'espace de noms.
  • j'ai essayé /configuration/lcmp:MyNode ce qui est faux car 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 certains de la answerers l'ont suggéré. Cela exige que le programme D'analyse XML connaisse à l'avance tous les espaces de noms que j'ai l'intention d'utiliser. Puisque ceci est censé être applicable à n'importe quel fichier source, Je ne sais pas pour quels namespaces ajouter manuellement des préfixes. Il semble que {my uri} soit la syntaxe XPath, mais Microsoft n'a pas pris la peine de l'implémenter... vrai?

32
demandé sur Mads Hansen 2010-03-26 19:15:24

6 réponses

l'élément configuration se trouve dans l'espace de noms sans nom, et le MyNode est lié à l'espace de noms lcmp sans préfixe d'espace de noms.

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

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

il correspond à tout élément qui est un enfant de configuration et utilise ensuite inscrire , namespace-uri() , et , local-name() , et MyNode pour limiter la déclaration à l'élément MyNode .

si vous ne savez pas quel namespace-uri sera utilisé pour les éléments, alors vous pouvez faire le XPATH plus générique et juste correspondre sur le local-name() :

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

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

35
répondu Mads Hansen 2010-03-27 16:30:58

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
répondu Martin Honnen 2010-03-26 18:05:20

XPath n'est (délibérément) pas conçu pour le cas où vous souhaitez utiliser la même expression XPath pour certains espaces de noms inconnus qui ne vivent que dans le document XML. Vous devez connaître l'espace de noms à l'avance, déclarer l'espace de noms pour le processeur XPath, et utiliser le nom de votre expression. Les réponses de Martin et Dan montrent comment faire cela dans C#.

la raison de cette difficulté est mieux exprimée dans les espaces de noms XML 151990920" spec:

nous envisageons des applications de langage de balisage Extensible (XML) où un document XML unique peut contenir des éléments et des attributs (ici appelés" vocabulaire de balisage") qui sont définis et utilisés par plusieurs modules logiciels. La modularité est une motivation à cet égard: s'il existe un tel vocabulaire de balisage qui est bien compris et pour lequel il existe un logiciel utile, il est préférable de réutiliser ce balisage plutôt que de le réinventer.

ces documents, qui contiennent 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 "collisions" se produisant lorsque le markup destiné à un autre paquet logiciel utilise le même nom d'élément ou d'attribut.

ces considérations exigent que les constructions de documents doivent avoir des noms construits afin d'éviter les conflits entre les noms de différents vocabulaires de balisage. Cette spécification décrit un mécanisme, les namespaces XML, qui accomplit ceci en assignant des noms élargis aux éléments et aux attributs.

C'est-à-dire que les espaces de noms sont censés être utilisés pour s'assurer que vous savez de quoi parle votre document: est-ce que cet élément <head> parle du préambule d'un document XHTML ou de quelque chose de tête dans un document AnatomyML? Vous n'êtes jamais "supposé" être agnostique à propos de l'Espace-nom et c'est à peu près la première chose que vous devriez 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 dans une seule expression XPath. Tout d'abord, vous devez fouiller dans le document et extraire tous les namespaceURIs, puis les ajouter au gestionnaire de namespace et ensuite exécuter l'expression XPath réelle que vous voulez (et vous devez savoir quelque chose sur la distribution de les espaces de noms dans le document à ce point, ou vous avez beaucoup d'expressions à exécuter). Je pense que vous êtes probablement mieux en utilisant quelque chose d'autre que XPath (par exemple un DOM ou une API de type SAX) pour trouver l'espaceur de noms, mais vous pouvez aussi explorer L'axe de l'espace de noms XPath (dans XPath 1.0), utiliser la fonction namespace-uri-from-QName (dans XPath 2.0) ou utiliser des expressions comme "configuration/*[local-name() = 'MyNode']" D'Oleg . Quoi qu'il en soit, je pense que votre meilleur pari est d'essayer d'éviter d'écrire agnostic XPath espace de noms! Pourquoi ne savez-vous pas votre espace de noms à l'avance? Comment allez-vous éviter une correspondance choses que vous n'avez pas l'intention de match?

Edit - vous connaissez le namespaceURI?

il s'avère que votre question nous a tous confondus. Apparemment, vous connaissez L'URI de l'espace de noms, mais vous ne connaissez pas le préfixe d'espace de noms utilisé dans le document XML. En effet, dans ce cas, aucun préfixe d'espace de noms n'est utilisé et L'URI devient l'espace de noms par défaut où il est défini. Clé ce qu'il faut savoir, c'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'une façon d'associer un noeud à un URI d'espace de noms lorsque le document est exprimé sous forme de texte. Vous pouvez jeter un oeil à cette réponse , où j'essaie de clarifier les préfixes d'espace de noms.

vous devriez essayer de penser au document XML de la même manière que l'analyseur le pense - chaque noeud a un URI d'espace de nom et un nom local. Le préfixe de l'espace de noms / les règles de l'héritage ne font que sauver en tapant l'URI de nombreuses fois. Une façon d'écrire ceci est en notation Clark: c'est-à-dire, 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.

à la place, XPath utilise ses propres préfixes d'espace de noms.Quelque chose comme /ns1:root/ns2:node . Mais ce sont complètement séparé des préfixes qui peuvent être utilisés dans le document XML original et rien à voir avec eux. Toute implémentation de XPath aura un moyen de cartographier ses propres préfixes avec URIs d'espace de noms. Pour l'implémentation de C# vous utilisez un XmlNamespaceManager , dans Perl vous fournissez un hachage, xmllint prend les arguments en ligne de commande... Donc tout ce que vous avez à faire est de créer un préfixe arbitraire pour L'URI de l'espace de noms que vous connaissez, et d'utiliser ce préfixe dans L'expression XPath. Peu importe le préfixe que vous utilisez, dans XML vous il suffit de se soucier de la combinaison de L'URI et le nom local.

l'autre chose à se rappeler (c'est souvent une surprise) est que XPath ne fait pas d'héritage namespace. Vous avez besoin d'ajouter un préfixe pour chaque qui a un espace de noms, indépendamment du fait que l'espace de noms vient de l'héritage, un attribut xmlns, ou un préfixe d'espace de noms. En outre, bien que vous devez toujours penser en termes D'URIs et de noms locaux, il existe également des moyens d'accéder au préfixe à partir d'un document XML. C'est rare d'avoir à les utiliser.

6
répondu Andrew Walker 2017-05-23 12:10:50

voici un exemple de comment rendre l'espace de noms disponible pour L'expression XPath XPathSelectElements extension de la méthode:

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
répondu Dan Blanchard 2010-03-26 19:25:54

exemple avec Xpath 2.0 + une bibliothèque:

using Wmhelp.XPath2;

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

voir:

XPath et XSLT 2.0 pour .NET?

1
répondu Akli 2017-05-23 10:30:49

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

    /// <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
répondu rasx 2015-11-18 23:13:56