Interroger un XDocument pour les éléments par nom à n'importe quelle profondeur

143

J'ai un XDocumentobjet. Je veux interroger des éléments avec un nom particulier à n'importe quelle profondeur en utilisant LINQ. Lorsque j'utilise Descendants("element_name"), je n'obtiens que des éléments qui sont des enfants directs du niveau actuel. Ce que je recherche, c'est l'équivalent de "// nom_élément" dans XPath ... devrais-je simplement utiliser XPath, ou y a-t-il un moyen de le faire en utilisant les méthodes LINQ? Merci.

Riches
la source

Réponses:

213

Les descendants devraient fonctionner parfaitement. Voici un exemple:

using System;
using System.Xml.Linq;

class Test
{
    static void Main()
    {
        string xml = @"
<root>
  <child id='1'/>
  <child id='2'>
    <grandchild id='3' />
    <grandchild id='4' />
  </child>
</root>";
        XDocument doc = XDocument.Parse(xml);

        foreach (XElement element in doc.Descendants("grandchild"))
        {
            Console.WriteLine(element);
        }
    }
}

Résultats:

<grandchild id="3" />
<grandchild id="4" />

Jon Skeet
la source
1
Comment aborderiez-vous cela si un nom d'élément était dupliqué dans un document XML? Par exemple: si le xml contenait une collection de <Cars> avec des sous-éléments de <Part>, ainsi qu'une collection de <Planes> avec des sous-éléments de <Part>, et que vous voulez une liste de Parts for Cars uniquement.
pfeds
12
@pfeds: Ensuite, j'utiliserais doc.Descendants("Cars").Descendants("Part")(ou peut .Elements("Part")- être s'ils n'étaient que des enfants directs.
Jon Skeet
8
Six ans plus tard et toujours un exemple fantastique. En fait, c'est encore beaucoup plus utile que l'explication MSDN :-)
EvilDr
Et c'est toujours un mauvais exemple, Dr., car s'il n'y a pas de "Cars", le code ci-dessus aboutirait à un NPE. Peut-être le .? du nouveau C # le rendra enfin valide
Dror Harari
3
@DrorHarari Non, aucune exception n'est levée: Try out var foo = new XDocument().Descendants("Bar").Descendants("Baz"); Car Descendantsrenvoie un vide IEnumerable<XElement>et non null.
DareDude
54

Un exemple indiquant l'espace de noms:

String TheDocumentContent =
@"
<TheNamespace:root xmlns:TheNamespace = 'http://www.w3.org/2001/XMLSchema' >
   <TheNamespace:GrandParent>
      <TheNamespace:Parent>
         <TheNamespace:Child theName = 'Fred'  />
         <TheNamespace:Child theName = 'Gabi'  />
         <TheNamespace:Child theName = 'George'/>
         <TheNamespace:Child theName = 'Grace' />
         <TheNamespace:Child theName = 'Sam'   />
      </TheNamespace:Parent>
   </TheNamespace:GrandParent>
</TheNamespace:root>
";

XDocument TheDocument = XDocument.Parse( TheDocumentContent );

//Example 1:
var TheElements1 =
from
    AnyElement
in
    TheDocument.Descendants( "{http://www.w3.org/2001/XMLSchema}Child" )
select
    AnyElement;

ResultsTxt.AppendText( TheElements1.Count().ToString() );

//Example 2:
var TheElements2 =
from
    AnyElement
in
    TheDocument.Descendants( "{http://www.w3.org/2001/XMLSchema}Child" )
where
    AnyElement.Attribute( "theName" ).Value.StartsWith( "G" )
select
    AnyElement;

foreach ( XElement CurrentElement in TheElements2 )
{
    ResultsTxt.AppendText( "\r\n" + CurrentElement.Attribute( "theName" ).Value );
}
Jelgab
la source
2
Mais que se passe-t-il si mon XML source n'a pas d'espace de noms? Je suppose que je peux en ajouter un dans le code (je dois examiner cela), mais pourquoi est-ce nécessaire? Dans tous les cas, root.Descendants ("myTagName") ne trouve pas d'éléments enfouis à trois ou quatre niveaux dans mon code.
EoRaptor013
2
Merci! Nous utilisons la sérialisation des contrats de données. Cela crée un en-tête comme <MyClassEntries xmlns: i = " w3.org/2001/XMLSchema-instance " xmlns = " schemas.datacontract.org/2004/07/DataLayer.MyClass "> et j'ai été perplexe pourquoi je n'obtenais pas tous les descendants. J'avais besoin d'ajouter le préfixe { schemas.datacontract.org/2004/07/DataLayer.MyClass }.
Kim
38

Vous pouvez le faire de cette façon:

xml.Descendants().Where(p => p.Name.LocalName == "Name of the node to find")

xmlest unXDocument .

Sachez que la propriété Namerenvoie un objet qui a un LocalNameet un Namespace. C'est pourquoi vous devez utiliser Name.LocalNamesi vous souhaitez comparer par nom.

Francisco Goldenstein
la source
J'essaie d'obtenir tous les nœuds EmbeddedResource à partir du fichier de projet c #, et c'est la seule façon qui fonctionne. XDocument document = XDocument.Load (csprojPath); IEnumerable <XElement> embeddedResourceElements = document.Descendants ("EmbeddedResource"); Cela ne fonctionne pas et je ne comprends pas pourquoi.
Eugene Maksimov
22

Les descendants feront exactement ce dont vous avez besoin, mais assurez-vous que vous avez inclus un nom d'espace de noms avec le nom de l'élément. Si vous l'omettez, vous obtiendrez probablement une liste vide.

Nenad Dobrilovic
la source
11

Il y a deux façons d'accomplir cela,

  1. Linq vers xml
  2. XPath

Voici des exemples d'utilisation de ces approches,

List<XElement> result = doc.Root.Element("emails").Elements("emailAddress").ToList();

Si vous utilisez XPath, vous devez effectuer quelques manipulations avec IEnumerable:

IEnumerable<XElement> mails = ((IEnumerable)doc.XPathEvaluate("/emails/emailAddress")).Cast<XElement>();

Notez que

var res = doc.XPathEvaluate("/emails/emailAddress");

résultats soit un pointeur nul, soit aucun résultat.

Roland Roos
la source
1
juste pour mentionner que XPathEvaluatec'est dans l' System.Xml.XPathespace de noms.
Tahir Hassan
XPathEvaluate devrait faire l'affaire, mais votre requête ne prend que des nœuds à une profondeur particulière (un). Si vous vouliez sélectionner tous les éléments nommés "email" quel que soit l'endroit où ils se trouvent dans un document, vous utiliseriez le chemin "// email". Évidemment, de tels chemins sont plus chers, car l'arbre entier doit être parcouru quel que soit son nom, mais cela peut être très pratique - à condition de savoir ce que vous faites.
The Dag
8

J'utilise une XPathSelectElementsméthode d'extension qui fonctionne de la même manière que la XmlDocument.SelectNodesméthode:

using System;
using System.Xml.Linq;
using System.Xml.XPath; // for XPathSelectElements

namespace testconsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            XDocument xdoc = XDocument.Parse(
                @"<root>
                    <child>
                        <name>john</name>
                    </child>
                    <child>
                        <name>fred</name>
                    </child>
                    <child>
                        <name>mark</name>
                    </child>
                 </root>");

            foreach (var childElem in xdoc.XPathSelectElements("//child"))
            {
                string childName = childElem.Element("name").Value;
                Console.WriteLine(childName);
            }
        }
    }
}
Tahir Hassan
la source
1

Suite à la réponse de @Francisco Goldenstein, j'ai écrit une méthode d'extension

using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;

namespace Mediatel.Framework
{
    public static class XDocumentHelper
    {
        public static IEnumerable<XElement> DescendantElements(this XDocument xDocument, string nodeName)
        {
            return xDocument.Descendants().Where(p => p.Name.LocalName == nodeName);
        }
    }
}
Tiago Freitas Leal
la source
0

nous savons que ce qui précède est vrai. Jon n'a jamais tort; les souhaits de la vraie vie peuvent aller un peu plus loin

<ota:OTA_AirAvailRQ
    xmlns:ota="http://www.opentravel.org/OTA/2003/05" EchoToken="740" Target=" Test" TimeStamp="2012-07-19T14:42:55.198Z" Version="1.1">
    <ota:OriginDestinationInformation>
        <ota:DepartureDateTime>2012-07-20T00:00:00Z</ota:DepartureDateTime>
    </ota:OriginDestinationInformation>
</ota:OTA_AirAvailRQ>

Par exemple, généralement, le problème est, comment pouvons-nous obtenir EchoToken dans le document xml ci-dessus? Ou comment brouiller l'élément avec le nom attrbute.

1- Vous pouvez les trouver en accédant avec l'espace de noms et le nom comme ci-dessous

doc.Descendants().Where(p => p.Name.LocalName == "OTA_AirAvailRQ").Attributes("EchoToken").FirstOrDefault().Value

2- Vous pouvez le trouver par la valeur du contenu de l'attribut, comme celui-ci

Hamit YILDIRIM
la source
0

Ceci ma variante de la solution basée sur Linqet la méthode Descendants de la XDocumentclasse

using System;
using System.Linq;
using System.Xml.Linq;

class Test
{
    static void Main()
    {
        XDocument xml = XDocument.Parse(@"
        <root>
          <child id='1'/>
          <child id='2'>
            <subChild id='3'>
                <extChild id='5' />
                <extChild id='6' />
            </subChild>
            <subChild id='4'>
                <extChild id='7' />
            </subChild>
          </child>
        </root>");

        xml.Descendants().Where(p => p.Name.LocalName == "extChild")
                         .ToList()
                         .ForEach(e => Console.WriteLine(e));

        Console.ReadLine();
    }
}

Résultats:

Pour plus de détails sur la Desendantsméthode, jetez un œil ici.

Mselmi Ali
la source
-1

(Le code et les instructions sont pour C # et peuvent devoir être légèrement modifiés pour d'autres langues)

Cet exemple fonctionne parfaitement si vous souhaitez lire à partir d'un nœud parent qui a de nombreux enfants, par exemple, regardez le code XML suivant;

<?xml version="1.0" encoding="UTF-8"?> 
<emails>
    <emailAddress>[email protected]</emailAddress>
    <emailAddress>[email protected]</emailAddress>
    <emailAddress>rgreen@set_ig.ca</emailAddress> 
</emails>

Maintenant, avec ce code ci-dessous (en gardant à l'esprit que le fichier XML est stocké dans les ressources (voir les liens à la fin de l'extrait de code pour obtenir de l'aide sur les ressources). Vous pouvez obtenir chaque adresse e-mail dans la balise "emails".

XDocument doc = XDocument.Parse(Properties.Resources.EmailAddresses);

var emailAddresses = (from emails in doc.Descendants("emailAddress")
                      select emails.Value);

foreach (var email in emailAddresses)
{
    //Comment out if using WPF or Windows Form project
    Console.WriteLine(email.ToString());

   //Remove comment if using WPF or Windows Form project
   //MessageBox.Show(email.ToString());
}

Résultats

  1. [email protected]
  2. [email protected]
  3. rgreen@set_ig.ca

Remarque: pour l'application console et WPF ou Windows Forms, vous devez ajouter "using System.Xml.Linq;" En utilisant la directive en haut de votre projet, pour Console, vous devrez également ajouter une référence à cet espace de noms avant d'ajouter la directive Using. Aussi pour la console, il n'y aura pas de fichier de ressources par défaut sous le "dossier Propriétés", vous devez donc ajouter manuellement le fichier de ressources. Les articles MSDN ci-dessous expliquent cela en détail.

Ajout et modification de ressources

Comment: ajouter ou supprimer des ressources

Ravi Ramnarine
la source
1
Je ne veux pas être méchant ici, mais votre exemple ne montre pas les petits-enfants. emailAddress est un enfant des e-mails. Je me demande s'il existe un moyen d'utiliser Descendants sans utiliser d'espaces de noms?
SoftwareSavant