Permintaan dokumen XD untuk elemen dengan nama pada kedalaman apa pun

143

Saya punya XDocumentobjek. Saya ingin menanyakan elemen dengan nama tertentu di kedalaman apa pun menggunakan LINQ. Ketika saya gunakan Descendants("element_name"), saya hanya mendapatkan elemen yang merupakan anak-anak langsung dari level saat ini. Apa yang saya cari sama dengan "// element_name" di XPath ... haruskah saya gunakan saja XPath, atau adakah cara untuk melakukannya menggunakan metode LINQ? Terima kasih.

Kaya
sumber

Jawaban:

213

Keturunan harus bekerja dengan sangat baik. Ini sebuah contoh:

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

Hasil:

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

Jon Skeet
sumber
1
Bagaimana Anda mengatasi hal ini jika nama elemen digandakan dalam dokumen xml? Misalnya: Jika xml berisi koleksi <Cars> dengan sub elemen <Part>, dan juga koleksi <Plan> dengan sub elemen <Part>, dan Anda menginginkan daftar Parts untuk Mobil saja.
pfeds
12
@pfeds: Maka saya akan menggunakan doc.Descendants("Cars").Descendants("Part")(atau mungkin .Elements("Part")jika mereka hanya anak-anak langsung.
Jon Skeet
8
Enam tahun berlalu dan masih merupakan contoh yang fantastis. Bahkan, ini masih jauh lebih bermanfaat daripada penjelasan MSDN :-)
EvilDr
Dan itu masih merupakan contoh jahat, Dr., karena jika tidak ada "Mobil", kode di atas akan menghasilkan NPE. Mungkin itu? dari C # baru akhirnya membuatnya valid
Dror Harari
3
@DrorHarari Tidak, tidak ada pengecualian yang dilemparkan: Coba var foo = new XDocument().Descendants("Bar").Descendants("Baz"); karena Descendantsmengembalikan yang kosong IEnumerable<XElement>dan tidak null.
DareDude
54

Contoh yang menunjukkan 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 );
}
Jelgab
sumber
2
Tetapi, bagaimana jika xml sumber saya tidak memiliki namespace? Saya kira saya dapat menambahkan satu dalam kode (harus melihat ke dalamnya), tetapi mengapa itu perlu? Bagaimanapun, root.Descendants ("myTagName") tidak menemukan elemen terkubur dalam tiga atau empat level dalam kode saya.
EoRaptor013
2
Terima kasih! Kami menggunakan serialisasi datacontract. Ini menciptakan tajuk seperti <MyClassEntries xmlns: i = " w3.org/2001/XMLSchema-instance " xmlns = " schemas.datacontract.org/2004/07/DataLayer.MyClass "> dan saya bingung mengapa saya tidak mendapatkan ada keturunan. Saya perlu menambahkan awalan { schemas.datacontract.org/2004/07/DataLayer.MyClass }.
Kim
38

Anda bisa melakukannya dengan cara ini:

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

dimana xmla XDocument.

Ketahuilah bahwa properti Namemengembalikan objek yang memiliki a LocalNamedan a Namespace. Itu sebabnya Anda harus menggunakan Name.LocalNamejika Anda ingin membandingkan dengan nama.

Francisco Goldenstein
sumber
Saya mencoba untuk mendapatkan semua simpul EmbeddedResource dari file c # project, dan ini hanya cara yang berfungsi. Dokumen XDocument = XDocument.Load (csprojPath); IEnumerable <XElement> embeddedResourceElements = document.Descendants ("EmbeddedResource"); Tidak berhasil dan saya tidak mengerti mengapa.
Eugene Maksimov
22

Keturunan akan melakukan persis apa yang Anda butuhkan, tetapi pastikan bahwa Anda telah memasukkan nama namespace bersama dengan nama elemen. Jika Anda menghilangkannya, Anda mungkin akan mendapatkan daftar kosong.

Nenad Dobrilovic
sumber
11

Ada dua cara untuk mencapai ini,

  1. Linq-to-xml
  2. XPath

Berikut ini adalah contoh penggunaan pendekatan ini,

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

Jika Anda menggunakan XPath, Anda perlu melakukan manipulasi dengan IEnumerable:

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

Catat itu

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

menghasilkan pointer nol, atau tanpa hasil.

roland roos
sumber
1
hanya untuk menyebutkan bahwa XPathEvaluateada di System.Xml.XPathnamespace.
Tahir Hassan
XPathEvaluate harus melakukan trik, tetapi permintaan Anda hanya membutuhkan node pada kedalaman tertentu (satu). Jika Anda ingin memilih semua elemen bernama "email" di mana pun dalam dokumen itu terjadi, Anda akan menggunakan jalur "// email". Jelas jalan seperti itu lebih mahal, karena seluruh pohon harus berjalan apa pun namanya, tetapi itu bisa sangat nyaman - asalkan Anda tahu apa yang Anda lakukan.
The Dag
8

Saya menggunakan XPathSelectElementsmetode ekstensi yang bekerja dengan cara yang sama dengan XmlDocument.SelectNodesmetode:

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

Mengikuti jawaban @Francisco Goldenstein, saya menulis metode ekstensi

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

kita tahu hal di atas benar. Jon tidak pernah salah; harapan kehidupan nyata bisa sedikit lebih jauh

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

Sebagai contoh, Biasanya masalahnya adalah, bagaimana kita bisa mendapatkan EchoToken dalam dokumen xml di atas? Atau cara mengaburkan elemen dengan nama attrbute.

1- Anda dapat menemukannya dengan mengakses dengan namespace dan nama seperti di bawah ini

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

2- Anda dapat menemukannya dengan nilai konten atribut, seperti ini

Hamit YILDIRIM
sumber
0

Ini varian saya dari solusi berdasarkan Linqdan metode Keturunan XDocumentkelas

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

Hasil:

Untuk detail lebih lanjut tentang Desendantsmetode ini lihat di sini.

Mselmi Ali
sumber
-1

(Kode dan Instruksi adalah untuk C # dan mungkin perlu sedikit diubah untuk bahasa lain)

Contoh ini berfungsi sempurna jika Anda ingin membaca dari Parent Node yang memiliki banyak anak, misalnya lihat XML berikut;

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

Sekarang dengan kode di bawah ini (dengan mengingat bahwa File XML disimpan dalam sumber daya (Lihat tautan di akhir cuplikan untuk bantuan tentang sumber daya) Anda dapat memperoleh setiap alamat email dalam tag "email".

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

Hasil

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

Catatan: Untuk Aplikasi Konsol dan WPF atau Formulir Windows Anda harus menambahkan "using System.Xml.Linq;" Menggunakan arahan di bagian atas proyek Anda, untuk Konsol Anda juga perlu menambahkan referensi ke namespace ini sebelum menambahkan arahan Menggunakan. Juga untuk Konsol tidak akan ada file Resource secara default di bawah "Properties folder" sehingga Anda harus menambahkan file Resource secara manual. Artikel MSDN di bawah ini, jelaskan ini secara terperinci.

Menambah dan Mengedit Sumber Daya

Cara: Tambah atau Hapus Sumber Daya

Ravi Ramnarine
sumber
1
Tidak ingin menjadi kejam di sini, tetapi teladan Anda tidak menunjukkan cucu. emailAddress adalah anak dari email. Saya bertanya-tanya apakah ada cara untuk menggunakan Keturunan tanpa menggunakan ruang nama?
SoftwareSavant