Zapytaj XDocument o elementy według nazwy na dowolnej głębokości

143

Mam XDocumentprzedmiot. Chcę wyszukiwać elementy o określonej nazwie na dowolnej głębokości przy użyciu LINQ. Kiedy używam Descendants("element_name"), otrzymuję tylko elementy, które są bezpośrednimi elementami podrzędnymi bieżącego poziomu. To, czego szukam, to odpowiednik „// nazwa_elementu” w XPath ... powinienem po prostu użyć XPath, czy jest sposób, aby to zrobić za pomocą metod LINQ? Dzięki.

Bogaty
źródło

Odpowiedzi:

213

Potomkowie powinni działać absolutnie dobrze. Oto przykład:

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

Wyniki:

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

Jon Skeet
źródło
1
Jak sobie z tym poradzisz, jeśli nazwa elementu została zduplikowana w dokumencie xml? Na przykład: jeśli plik xml zawiera kolekcję <Samochody> z podelementami <Part>, a także kolekcję <Planes> z podelementami <Part>, a chcesz wyświetlić listę części tylko do samochodów.
pfeds
12
@pfeds: Wtedy użyłbym doc.Descendants("Cars").Descendants("Part")(lub prawdopodobnie .Elements("Part")gdyby były tylko bezpośrednimi dziećmi.
Jon Skeet
8
Sześć lat później i wciąż fantastyczny przykład. W rzeczywistości jest to nadal znacznie bardziej pomocne niż wyjaśnienie MSDN :-)
EvilDr
I wciąż jest to zły przykład, dr., Ponieważ gdyby nie było „samochodów”, powyższy kod spowodowałby NPE. Może .? z nowego C # ostatecznie sprawi, że będzie on ważny
Dror Harari
3
@DrorHarari Nie, żaden wyjątek nie jest zgłaszany: Wypróbuj, var foo = new XDocument().Descendants("Bar").Descendants("Baz"); ponieważ Descendantszwraca wartość pustą IEnumerable<XElement>i nie null.
DareDude
54

Przykład wskazujący przestrzeń nazw:

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
źródło
2
Ale co, jeśli mój źródłowy plik XML nie ma przestrzeni nazw? Przypuszczam, że mogę dodać jeden w kodzie (muszę się temu przyjrzeć), ale dlaczego jest to konieczne? W każdym razie root.Descendants ("myTagName") nie znajduje elementów zakopanych na trzech lub czterech poziomach w moim kodzie.
EoRaptor013
2
Dzięki! Używamy serializacji datakonu. Spowoduje to utworzenie nagłówka, takiego jak <MyClassEntries xmlns: i = " w3.org/2001/XMLSchema-instance " xmlns = " schemas.datacontract.org/2004/07/DataLayer.MyClass ">. Zaskoczyło mnie, dlaczego nie otrzymuję żadnych potomków. Musiałem dodać prefiks { schemas.datacontract.org/2004/07/DataLayer.MyClass }.
Kim
38

Możesz to zrobić w ten sposób:

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

gdzie xmljest XDocument.

Należy pamiętać, że właściwość Namezwraca obiekt, który ma a LocalNamei Namespace. Dlatego musisz użyć, Name.LocalNamejeśli chcesz porównać według nazwy.

Francisco Goldenstein
źródło
Próbuję uzyskać cały węzeł EmbeddedResource z pliku projektu C #, a to tylko sposób, który działa. Dokument XDocument = XDocument.Load (csprojPath); IEnumerable <XElement> embeddedResourceElements = document.Descendants ("EmbeddedResource"); Nie działa i nie rozumiem dlaczego.
Eugene Maksimov
22

Potomkowie zrobią dokładnie to, czego potrzebujesz, ale upewnij się, że dołączyłeś nazwę przestrzeni nazw wraz z nazwą elementu. Jeśli go pominiesz, prawdopodobnie otrzymasz pustą listę.

Nenad Dobrilovic
źródło
11

Można to osiągnąć na dwa sposoby:

  1. Linq-to-xml
  2. XPath

Poniżej znajdują się przykłady stosowania tych podejść,

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

Jeśli używasz XPath, musisz dokonać pewnych manipulacji z IEnumerable:

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

Zwróć na to uwagę

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

daje pusty wskaźnik lub brak wyników.

roland roos
źródło
1
żeby wspomnieć, że XPathEvaluatejest w System.Xml.XPathprzestrzeni nazw.
Tahir Hassan
XPathEvaluate powinno załatwić sprawę, ale zapytanie bierze tylko węzły na określonej głębokości (jeden). Jeśli chcesz zaznaczyć wszystkie elementy o nazwie „email”, niezależnie od tego, gdzie w dokumencie się one pojawiają, użyj ścieżki „// email”. Oczywiście takie ścieżki są droższe, ponieważ trzeba przejść całe drzewo, bez względu na to, jak się nazywa, ale może to być całkiem wygodne - pod warunkiem, że wiesz, co robisz.
The Dag
8

Używam XPathSelectElementsmetody rozszerzenia, która działa w taki sam sposób, jak XmlDocument.SelectNodesmetoda:

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
źródło
1

Po odpowiedzi @Francisco Goldenstein napisałem metodę rozszerzenia

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
źródło
0

wiemy, że powyższe jest prawdą. Jon nigdy się nie myli; pragnienia z życia wzięte mogą pójść trochę dalej

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

Na przykład, zwykle problem polega na tym, w jaki sposób możemy uzyskać EchoToken w powyższym dokumencie xml? Albo jak rozmyć element z nazwą attrbute.

1- Możesz je znaleźć, korzystając z przestrzeni nazw i nazwy, jak poniżej

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

2- Możesz go znaleźć po wartości zawartości atrybutu, takiej jak ta

Hamit YILDIRIM
źródło
0

To mój wariant rozwiązania oparty na Linqmetodzie Descendants XDocumentklasy

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

Wyniki:

Więcej informacji na temat Desendantsmetody można znaleźć tutaj.

Mselmi Ali
źródło
-1

(Kod i instrukcje są przeznaczone dla języka C # i mogą wymagać nieznacznej zmiany w przypadku innych języków)

Ten przykład działa doskonale, jeśli chcesz czytać z węzła nadrzędnego, który ma wiele dzieci, na przykład spójrz na następujący kod XML;

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

Teraz z poniższym kodem (pamiętaj, że plik XML jest przechowywany w zasobach (zobacz linki na końcu fragmentu, aby uzyskać pomoc dotyczącą zasobów). Możesz uzyskać każdy adres e-mail w tagu „e-maile”.

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

Wyniki

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

Uwaga: w przypadku aplikacji konsoli i formularzy WPF lub Windows należy dodać „using System.Xml.Linq;” Używając dyrektywy u góry projektu, w przypadku konsoli musisz również dodać odwołanie do tej przestrzeni nazw przed dodaniem dyrektywy Using. Również w przypadku konsoli nie będzie domyślnie żadnego pliku zasobów w folderze „Właściwości”, więc musisz ręcznie dodać plik zasobów. Poniższe artykuły MSDN wyjaśniają to szczegółowo.

Dodawanie i edycja zasobów

Instrukcje: dodawanie lub usuwanie zasobów

Ravi Ramnarine
źródło
1
Nie chcę być tutaj złośliwy, ale twój przykład nie pokazuje wnuków. emailAddress jest dzieckiem e-maili. Zastanawiam się, czy istnieje sposób używania potomków bez używania przestrzeni nazw?
SoftwareSavant