Interroga un documento X per gli elementi per nome a qualsiasi profondità


143

Ho un XDocumentoggetto Voglio cercare elementi con un nome particolare a qualsiasi profondità usando LINQ. Quando uso Descendants("element_name"), ottengo solo elementi che sono figli diretti del livello corrente. Quello che sto cercando è l'equivalente di "// element_name" in XPath ... dovrei semplicemente usare XPath, o c'è un modo per farlo usando i metodi LINQ? Grazie.

Risposte:


213

I discendenti dovrebbero funzionare perfettamente. Ecco un esempio:

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

risultati:

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


1
Come affronteresti questo problema se un nome di elemento fosse duplicato in un documento XML? Ad esempio: se l'xml conteneva una raccolta di <Auto> con elementi secondari di <Parte>, e anche una raccolta di <Piani> con elementi secondari di <Parte>, e si desidera solo un elenco di Parti per automobili.
pfeds il

12
@pfeds: Quindi userei doc.Descendants("Cars").Descendants("Part")(o forse .Elements("Part")se fossero solo bambini diretti.
Jon Skeet,

8
Sei anni dopo e ancora un esempio fantastico. In realtà, questo è ancora molto più utile della spiegazione MSDN :-)
EvilDr

Ed è ancora un esempio malvagio, Dr., poiché se non ci sono "Auto", il codice sopra riportato si tradurrebbe in un NPE. Forse il .? dal nuovo C # lo renderà finalmente valido
Dror Harari il

3
@DrorHarari No, non viene generata alcuna eccezione: prova var foo = new XDocument().Descendants("Bar").Descendants("Baz"); perché Descendantsrestituisce un valore vuoto IEnumerable<XElement>e non null.
DareDude,

54

Un esempio che indica lo spazio dei nomi:

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
Ma cosa succede se il mio XML di origine non ha uno spazio dei nomi? Suppongo di poterne aggiungere uno nel codice (bisogna esaminarlo), ma perché è necessario? In ogni caso, root.Descendants ("myTagName") non trova elementi sepolti a tre o quattro livelli nel mio codice.
EoRaptor013,

2
Grazie! Stiamo utilizzando la serializzazione di datacontract. Questo crea un'intestazione come <MyClassEntries xmlns: i = " w3.org/2001/XMLSchema-instance " xmlns = " schemas.datacontract.org/2004/07/DataLayer.MyClass "> e sono rimasto sconcertato perché non stavo ottenendo eventuali discendenti. Ho dovuto aggiungere il prefisso { schemas.datacontract.org/2004/07/DataLayer.MyClass }.
Kim,

38

Puoi farlo in questo modo:

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

dove xmlè un XDocument.

Tenere presente che la proprietà Namerestituisce un oggetto con a LocalNamee a Namespace. Ecco perché devi usare Name.LocalNamese vuoi confrontare per nome.


Sto cercando di ottenere tutto il nodo EmbeddedResource dal file di progetto c #, e questo è l'unico modo che funziona. XDocument document = XDocument.Load (csprojPath); IEnumerable <XElement> embeddedResourceElements = document.Descendants ("EmbeddedResource"); Non funziona e non capisco perché.
Eugene Maksimov,

22

I discendenti faranno esattamente ciò di cui hai bisogno, ma assicurati di aver incluso un nome dello spazio dei nomi insieme al nome dell'elemento. Se lo ometti, probabilmente otterrai un elenco vuoto.


11

Ci sono due modi per raggiungere questo obiettivo,

  1. LINQ to XML
  2. XPath

I seguenti sono esempi di utilizzo di questi approcci,

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

Se usi XPath, devi eseguire alcune manipolazioni con IEnumerable:

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

Nota che

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

risulta un puntatore nullo o nessun risultato.


1
solo per citare che si XPathEvaluatetrova nello System.Xml.XPathspazio dei nomi.
Tahir Hassan,

XPathEvaluate dovrebbe fare il trucco, ma la tua query prende nodi solo a una profondità particolare (una). Se si desidera selezionare tutti gli elementi denominati "e-mail" indipendentemente da dove si trovano in un documento, è necessario utilizzare il percorso "// e-mail". Ovviamente tali percorsi sono più costosi, dal momento che l'intero albero deve essere percorso qualunque sia il nome, ma può essere abbastanza conveniente - purché tu sappia cosa stai facendo.
Il Dag

8

Sto usando il XPathSelectElementsmetodo di estensione che funziona allo stesso modo del XmlDocument.SelectNodesmetodo:

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

Dopo la risposta di @Francisco Goldenstein, ho scritto un metodo di estensione

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

sappiamo che quanto sopra è vero. Jon non sbaglia mai; i desideri della vita reale possono andare un po 'oltre

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

Ad esempio, di solito il problema è, come possiamo ottenere EchoToken nel documento XML sopra? O come sfocare l'elemento con il nome attrbute.

1- Li puoi trovare accedendo con lo spazio dei nomi e il nome come sotto

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

2- Puoi trovarlo dal valore del contenuto dell'attributo, come questo


0

Questa è la mia variante della soluzione basata sul Linqmetodo Descendants della 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();
    }
}

risultati:

Per maggiori dettagli sul Desendantsmetodo, dai un'occhiata qui.


-1

(Codice e istruzioni sono per C # e potrebbe essere necessario modificarli leggermente per altre lingue)

Questo esempio funziona perfettamente se vuoi leggere da un nodo genitore che ha molti figli, ad esempio guarda il seguente XML;

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

Ora con questo codice di seguito (tenendo presente che il file XML è archiviato nelle risorse (vedere i collegamenti alla fine dello snippet per assistenza sulle risorse) È possibile ottenere ciascun indirizzo e-mail all'interno del tag "e-mail".

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

risultati

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

Nota: per l'applicazione console e WPF o Windows Form è necessario aggiungere "using System.Xml.Linq;" Usando la direttiva nella parte superiore del progetto, per Console dovrai anche aggiungere un riferimento a questo spazio dei nomi prima di aggiungere la direttiva Using. Anche per Console non ci sarà alcun file di risorse per impostazione predefinita nella "cartella Proprietà", quindi è necessario aggiungere manualmente il file di risorse. Gli articoli MSDN qui sotto, spiegano questo in dettaglio.

Aggiunta e modifica di risorse

Procedura: aggiungere o rimuovere risorse


1
Non voglio essere cattivo qui, ma il tuo esempio non mostra i nipoti. emailAddress è figlio di email. Mi chiedo se c'è un modo per usare i discendenti senza usare gli spazi dei nomi?
SoftwareSavant
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.