121 votes

comment utiliser XPath avec XDocument ?

Il existe une question similaire, mais il semble que la solution n'ait pas fonctionné dans mon cas : Bizarreries avec XDocument, XPath et les espaces de noms

Voici le XML avec lequel je travaille :

<?xml version="1.0" encoding="utf-8"?>
<Report Id="ID1" Type="Demo Report" Created="2011-01-01T01:01:01+11:00" Culture="en" xmlns="http://demo.com/2011/demo-schema">
    <ReportInfo>
        <Name>Demo Report</Name>
        <CreatedBy>Unit Test</CreatedBy>
    </ReportInfo>
</Report>

Et ci-dessous le code qui devrait fonctionner mais qui ne fonctionne pas...

XDocument xdoc = XDocument.Load(@"C:\SampleXML.xml");
XmlNamespaceManager xnm = new XmlNamespaceManager(new NameTable()); 
xnm.AddNamespace(String.Empty, "http://demo.com/2011/demo-schema");
Console.WriteLine(xdoc.XPathSelectElement("/Report/ReportInfo/Name", xnm) == null);

Quelqu'un a-t-il une idée ? Merci.

2 votes

Voir l'autre réponse ci-dessous, cela ne fonctionne pas car l'implémentation de XPath 1.0 ne peut pas gérer un préfixe vide.

2 votes

Comme d'autres l'ont dit ici, n'utilisez pas un préfixe vide lorsque vous ajoutez un espace de nom au [XmlNamespaceManager]. J'ajoute juste ce commentaire au cas où quelqu'un voudrait voir un petit exemple de code avec un document qui a plusieurs attributs [xmlns], avec et sans suffixe. Voir ici : stackoverflow.com/a/38272604/5838538

178voto

Alex Aza Points 29204

Si vous avez XDocument, il est plus facile d'utiliser LINQ-to-XML :

var document = XDocument.Load(fileName);
var name = document.Descendants(XName.Get("Name", @"http://demo.com/2011/demo-schema")).First().Value;

Si vous êtes sûr que XPath est la seule solution dont vous avez besoin :

using System.Xml.XPath;

var document = XDocument.Load(fileName);
var namespaceManager = new XmlNamespaceManager(new NameTable());
namespaceManager.AddNamespace("empty", "http://demo.com/2011/demo-schema");
var name = document.XPathSelectElement("/empty:Report/empty:ReportInfo/empty:Name", namespaceManager).Value;

17 votes

Je dirais qu'il est difficile de dire que linq est plus facile que xpath dans la plupart des cas. Par exemple, dans ce cas, l'équivalent LINQ n'est pas vraiment équivalent, car il obtiendrait également des nœuds "Name" sous d'autres nœuds (qui n'existent pas actuellement mais pourraient être ajoutés par des modifications ultérieures du format du fichier). Cependant, votre solution est certainement la bonne.

12 votes

NOTE : l'utilisation de System.Xml.XPath ; est assez importante car le XPathSelectElement est une méthode d'extension. Ne faites pas comme moi et ignorez cette partie ;)

7 votes

XPath est toujours utile dans la mesure où il vous permet de contextualiser vos relations parents-enfants. Par exemple, si vous voulez obtenir /Banana/Banana/Banana au lieu d'obtenir toutes les Banana

9voto

Richard Schneider Points 16054

XPath 1.0, qui est ce que MS met en œuvre, n'a pas l'idée d'un espace de noms par défaut. Essayez donc ceci :

XDocument xdoc = XDocument.Load(@"C:\SampleXML.xml");
XmlNamespaceManager xnm = new XmlNamespaceManager(new NameTable()); 
xnm.AddNamespace("x", "http://demo.com/2011/demo-schema");
Console.WriteLine(xdoc.XPathSelectElement("/x:Report/x:ReportInfo/x:Name", xnm) == null);

9 votes

Votre réponse implique que XPath 2.0, contrairement à XPath 1.0, "*a" l'idée" d'un espace de noms par défaut. Je ne suis pas au courant d'une telle nouvelle fonctionnalité XPath (nous parlons ici de XPath, pas de XSLT ou XQuery). Par conséquent, pourriez-vous, s'il vous plaît, mentionner explicitement dans votre réponse ce que vous sous-entendez ?

2 votes

Je pense que ce qu'il veut dire ici, c'est que si vous avez un document qui définit un espace de noms, votre xpath doit inclure des éléments qualifiés, c'est-à-dire que vous ne pouvez pas faire xnm.AddNamespace(string.Empty, " demo.com/2011/demo-schema " ) ; et ensuite xdoc.XPathSelectElement("/Report/ReportInfo/Name", xnm) - le résultat est toujours nul.

7voto

Bernhard Points 157

Vous pouvez utiliser l'exemple de Microsoft - pour vous sans espace de nom :

using System.Xml.Linq;
using System.Xml.XPath;
var e = xdoc.XPathSelectElement("./Report/ReportInfo/Name");     

devrait le faire

0 votes

Cet exemple ne fonctionne que parce que le document n'a pas d'espace de nom par défaut. mais le document du PO contient un espace de nom par défaut "xmlns=..." et faire de même avec xpath n'est pas supporté. vous devez toujours spécifier un suffixe qui n'est pas vide.

0voto

kux Points 116

Pour travailler sans le suffixe d'espace de nom par défaut, je développe automatiquement le chemin.

Utilisation : SelectElement(xdoc.Root, "/Report/ReportInfo/Name");

private static XElement SelectElement(XElement startElement, string xpathExpression, XmlNamespaceManager namespaceManager = null) {
    // XPath 1.0 does not have support for default namespace, so we have to expand our path.
    if (namespaceManager == null) {
        var reader = startElement.CreateReader();
        namespaceManager = new XmlNamespaceManager(reader.NameTable);
    }
    var defaultNamespace = startElement.GetDefaultNamespace();
    var defaultPrefix = namespaceManager.LookupPrefix(defaultNamespace.NamespaceName);
    if (string.IsNullOrEmpty(defaultPrefix)) {
        defaultPrefix = "ᆞ";
        namespaceManager.AddNamespace(defaultPrefix, defaultNamespace.NamespaceName);
    }
    xpathExpression = AddPrefix(xpathExpression, defaultPrefix);
    var selected = startElement.XPathSelectElement(xpathExpression, namespaceManager);
    return selected;
}

private static string AddPrefix(string xpathExpression, string prefix) {
    // Implementation notes:
    // * not perfect, but it works for our use case.
    // * supports: "Name~~" "~~/Name~~" "~~@Name~~" "~~[Name~~" "~~[@Name~~"
    // * does not work in complex expressions like //*[local-name()="HelloWorldResult" and namespace-uri()='http://tempuri.org/']/text()
    // * does not exclude strings like 'string' or function like func()
    var s = Regex.Replace(xpathExpression, @"(?<a>/|\[@|@|\[|^)(?<name>\w(\w|[-])*)", "${a}${prefix}:${name}".Replace("${prefix}", prefix));
    return s;
}

Si quelqu'un a une meilleure solution pour trouver les noms des éléments et des attributs, n'hésitez pas à modifier cet article.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X