Fragen Sie ein XDocument nach Elementen in einer beliebigen Tiefe nach Namen ab


143

Ich habe ein XDocumentObjekt. Ich möchte mit LINQ in jeder Tiefe nach Elementen mit einem bestimmten Namen suchen. Wenn ich benutze Descendants("element_name"), bekomme ich nur Elemente, die direkte Kinder der aktuellen Ebene sind. Was ich suche, ist das Äquivalent von "// element_name" in XPath ... sollte ich nur verwenden XPath, oder gibt es eine Möglichkeit, dies mit LINQ-Methoden zu tun? Vielen Dank.

Antworten:


213

Nachkommen sollten absolut gut funktionieren. Hier ist ein Beispiel:

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);
        }
    }
}

Ergebnisse:

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


1
Wie würden Sie dies angehen, wenn ein Elementname in einem XML-Dokument dupliziert würde? Beispiel: Wenn die XML-Datei eine Sammlung von <Cars> mit Unterelementen von <Part> sowie eine Sammlung von <Planes> mit Unterelementen von <Part> enthält und Sie eine Liste von Teilen nur für Autos wünschen.
Pfeds

12
@pfeds: Dann würde ich verwenden doc.Descendants("Cars").Descendants("Part")(oder möglicherweise, .Elements("Part")wenn sie nur direkte Kinder waren.
Jon Skeet

8
Sechs Jahre später und immer noch ein fantastisches Beispiel. Tatsächlich ist dies immer noch weitaus hilfreicher als die MSDN-Erklärung :-)
EvilDr

Und es ist immer noch ein böses Beispiel, Dr., denn wenn es keine "Autos" gibt, würde der obige Code zu einer NPE führen. Vielleicht der .? von der neuen C # wird es endlich gültig machen
Dror Harari

3
@DrorHarari Nein, es wird keine Ausnahme ausgelöst: Try out var foo = new XDocument().Descendants("Bar").Descendants("Baz"); Weil Descendantsein leeres zurückgibt IEnumerable<XElement>und nicht null.
DareDude

54

Ein Beispiel für den Namespace:

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 );
}

2
Aber was ist, wenn meine Quell-XML keinen Namespace hat? Ich nehme an, ich kann einen im Code hinzufügen (muss das prüfen), aber warum ist das notwendig? In jedem Fall findet root.Descendants ("myTagName") keine Elemente, die drei oder vier Ebenen tief in meinem Code vergraben sind.
EoRaptor013

2
Vielen Dank! Wir verwenden die Serialisierung von Datenverträgen. Dadurch wird ein Header wie <MyClassEntries xmlns: i = " w3.org/2001/XMLSchema-instance " xmlns = " schemas.datacontract.org/2004/07/DataLayer.MyClass "> erstellt, und ich war ratlos, warum ich nicht bekam Nachkommen. Ich musste das Präfix { schemas.datacontract.org/2004/07/DataLayer.MyClass } hinzufügen .
Kim

38

Sie können es so machen:

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

wo xmlist einXDocument .

Beachten Sie, dass die Eigenschaft Nameein Objekt mit a LocalNameund a zurückgibt Namespace. Deshalb müssen Sie verwenden, Name.LocalNamewenn Sie nach Namen vergleichen möchten.


Ich versuche, alle EmbeddedResource-Knoten aus der c # -Projektdatei abzurufen, und dies funktioniert nur so. XDocument document = XDocument.Load (csprojPath); IEnumerable <XElement> embeddedResourceElements = document.Descendants ("EmbeddedResource"); Funktioniert nicht und ich verstehe nicht warum.
Eugene Maksimov

22

Nachkommen tun genau das, was Sie brauchen, aber stellen Sie sicher, dass Sie einen Namespace-Namen zusammen mit dem Namen des Elements angegeben haben. Wenn Sie es weglassen, erhalten Sie wahrscheinlich eine leere Liste.


11

Es gibt zwei Möglichkeiten, dies zu erreichen:

  1. Linq-to-xml
  2. XPath

Das Folgende sind Beispiele für die Verwendung dieser Ansätze:

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

Wenn Sie XPath verwenden, müssen Sie einige Manipulationen mit IEnumerable durchführen:

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

Beachten Sie, dass

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

ergibt entweder einen Nullzeiger oder keine Ergebnisse.


1
nur um zu erwähnen, dass XPathEvaluatedas im System.Xml.XPathNamespace ist.
Tahir Hassan

XPathEvaluate sollte den Trick ausführen, aber Ihre Abfrage akzeptiert nur Knoten in einer bestimmten Tiefe (eins). Wenn Sie alle Elemente mit dem Namen "E-Mail" auswählen möchten, unabhängig davon, wo sie in einem Dokument vorkommen, verwenden Sie den Pfad "// E-Mail". Offensichtlich sind solche Wege teurer, da der gesamte Baum unabhängig vom Namen begangen werden muss, aber es kann sehr praktisch sein - vorausgesetzt, Sie wissen, was Sie tun.
Der Tag

8

Ich verwende eine XPathSelectElementsErweiterungsmethode, die genauso funktioniert wie die XmlDocument.SelectNodesMethode:

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);
            }
        }
    }
}

1

Nach der Antwort von @Francisco Goldenstein habe ich eine Erweiterungsmethode geschrieben

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);
        }
    }
}

0

Wir wissen, dass das oben Genannte wahr ist. Jon liegt niemals falsch; reale Wünsche können ein wenig weiter gehen

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

Beispiel: Normalerweise besteht das Problem darin, wie wir EchoToken im obigen XML-Dokument erhalten können. Oder wie man das Element mit dem Namen attrbute verwischt.

1- Sie können sie finden, indem Sie mit dem Namespace und dem Namen wie unten darauf zugreifen

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

2- Sie finden es am Attributinhaltswert wie diesem


0

Dies ist meine Variante der Lösung basierend auf Linqund Descendants Methode der XDocumentKlasse

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();
    }
}

Ergebnisse:

Weitere Details zur DesendantsMethode finden Sie hier.


-1

(Code und Anweisungen gelten für C # und müssen möglicherweise für andere Sprachen geringfügig geändert werden.)

Dieses Beispiel funktioniert perfekt, wenn Sie von einem übergeordneten Knoten mit vielen untergeordneten Knoten lesen möchten. Sehen Sie sich beispielsweise das folgende XML an.

<?xml version="1.0" encoding="UTF-8"?> 
<emails>
    <emailAddress>jdoe@set.ca</emailAddress>
    <emailAddress>jsmith@hit.ca</emailAddress>
    <emailAddress>rgreen@set_ig.ca</emailAddress> 
</emails>

Mit diesem Code unten (unter Berücksichtigung der Tatsache, dass die XML-Datei in Ressourcen gespeichert ist (Hilfe zu Ressourcen finden Sie unter den Links am Ende des Snippets)) können Sie jede E-Mail-Adresse innerhalb des Tags "E-Mails" abrufen.

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());
}

Ergebnisse

  1. jdoe@set.ca
  2. jsmith@hit.ca
  3. rgreen@set_ig.ca

Hinweis: Für Konsolenanwendungen und WPF- oder Windows Forms müssen Sie "using System.Xml.Linq;" hinzufügen. Wenn Sie die Direktive oben in Ihrem Projekt verwenden, müssen Sie für Console auch einen Verweis auf diesen Namespace hinzufügen, bevor Sie die Direktive Using hinzufügen. Auch für die Konsole befindet sich standardmäßig keine Ressourcendatei unter dem Ordner "Eigenschaften", sodass Sie die Ressourcendatei manuell hinzufügen müssen. In den folgenden MSDN-Artikeln wird dies ausführlich erläutert.

Hinzufügen und Bearbeiten von Ressourcen

Gewusst wie: Hinzufügen oder Entfernen von Ressourcen


1
Ich möchte hier nicht gemein sein, aber Ihr Beispiel zeigt keine Enkelkinder. emailAddress ist ein Kind von E-Mails. Ich frage mich, ob es eine Möglichkeit gibt, Nachkommen ohne Verwendung von Namespaces zu verwenden.
SoftwareSavant
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.