Truy vấn một XDocument cho các thành phần theo tên ở bất kỳ độ sâu nào


143

Tôi có một XDocumentđối tượng. Tôi muốn truy vấn các phần tử có tên cụ thể ở bất kỳ độ sâu nào bằng LINQ. Khi tôi sử dụng Descendants("element_name"), tôi chỉ nhận được các yếu tố là con trực tiếp của cấp độ hiện tại. Những gì tôi đang tìm kiếm tương đương với "// Element_name" trong XPath ... tôi chỉ nên sử dụng XPath, hay có cách nào để làm điều đó bằng các phương thức LINQ? Cảm ơn.

Câu trả lời:


213

Con cháu nên làm việc hoàn toàn tốt. Đây là một ví dụ:

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

Các kết quả:

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


1
Làm thế nào bạn giải quyết vấn đề này nếu một tên thành phần được sao chép trong tài liệu xml? Ví dụ: Nếu xml chứa bộ sưu tập <Ô tô> có các thành phần phụ của <Phần> và cũng là bộ sưu tập <Máy bay> với các thành phần phụ của <Phần> và bạn chỉ muốn một danh sách Phụ tùng cho Ô tô.
pfbed

12
@pfeds: Sau đó, tôi sẽ sử dụng doc.Descendants("Cars").Descendants("Part")(hoặc có thể .Elements("Part")nếu chúng chỉ là trẻ em trực tiếp.
Jon Skeet

8
Sáu năm và vẫn là một ví dụ tuyệt vời. Trên thực tế, điều này vẫn hữu ích hơn nhiều so với lời giải thích của MSDN :-)
EvilDr

Và nó vẫn là một ví dụ xấu xa, Tiến sĩ, vì nếu không có "Ô tô", đoạn mã trên sẽ dẫn đến một NPE. Có thể là .? từ C # mới cuối cùng sẽ làm cho nó hợp lệ
Dror Harari

3
@DrorHarari Không, không có ngoại lệ được ném: Hãy thử var foo = new XDocument().Descendants("Bar").Descendants("Baz");Descendantstrả về một sản phẩm nào trống IEnumerable<XElement>và không null.
DareDude

54

Một ví dụ chỉ ra không gian tên:

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
Nhưng, nếu xml nguồn của tôi không có không gian tên thì sao? Tôi cho rằng tôi có thể thêm một mã trong mã (phải xem xét điều đó), nhưng tại sao điều đó lại cần thiết? Trong mọi trường hợp, root.Descendants ("myTagName") không tìm thấy các phần tử chôn sâu ba hoặc bốn cấp trong mã của tôi.
EoRaptor013

2
Cảm ơn! Chúng tôi đang sử dụng tuần tự hóa datacontract. Điều này tạo ra một tiêu đề như <MyClassEntries xmlns: i = " w3.org/2001/XMLSchema-instance " xmlns = " schemas.datacontract.org/2004/07/DataLayer.MyClass "> và tôi đã bối rối tại sao tôi lại nhận được bất kỳ hậu duệ. Tôi cần thêm tiền tố { schemas.datacontract.org/2004/07/DataLayer.MyClass }.
Kim

38

Bạn có thể làm theo cách này:

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

nơi xmllà một XDocument.

Xin lưu ý rằng thuộc tính Nametrả về một đối tượng có a LocalNamevà a Namespace. Đó là lý do tại sao bạn phải sử dụng Name.LocalNamenếu bạn muốn so sánh theo tên.


Tôi đang cố gắng để có được tất cả nút EmbeddedResource từ tệp dự án c # và đây chỉ là cách hoạt động. Tài liệu XDocument = XDocument.Load (csprojPath); IEnumerable <XEuity> embedResourceElements = document.Descendants ("EmbeddedResource"); Không hoạt động và tôi không hiểu tại sao.
Eugene Maksimov

22

Hậu duệ sẽ làm chính xác những gì bạn cần, nhưng hãy chắc chắn rằng bạn đã bao gồm một tên không gian tên cùng với tên của thành phần. Nếu bạn bỏ qua nó, bạn có thể sẽ nhận được một danh sách trống.


11

Có hai cách để thực hiện điều này,

  1. Linq-xml
  2. XPath

Sau đây là các mẫu sử dụng các phương pháp này,

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

Nếu bạn sử dụng XPath, bạn cần thực hiện một số thao tác với IEnumerable:

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

Lưu ý rằng

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

kết quả là một con trỏ null hoặc không có kết quả.


1
chỉ cần đề cập đến đó XPathEvaluatelà trong System.Xml.XPathkhông gian tên.
Tahir Hassan

XPathEvaliated nên thực hiện thủ thuật, nhưng truy vấn của bạn chỉ lấy các nút ở độ sâu cụ thể (một). Nếu bạn muốn chọn tất cả các thành phần có tên "email" bất kể chúng xuất hiện ở đâu trong tài liệu, bạn sẽ sử dụng đường dẫn "// email". Rõ ràng những con đường như vậy đắt hơn, vì toàn bộ cây phải được đi bất kể tên là gì, nhưng nó có thể khá thuận tiện - miễn là bạn biết bạn đang làm gì.
Dag

8

Tôi đang sử dụng XPathSelectElementsphương thức mở rộng hoạt động theo cùng một XmlDocument.SelectNodesphương thức:

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

Theo câu trả lời của @Francisco Goldenstein, tôi đã viết một phương pháp mở rộng

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

chúng tôi biết những điều trên là đúng Jon không bao giờ sai; mong muốn cuộc sống thực có thể đi xa hơn một chút

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

Ví dụ: Thông thường vấn đề là, làm thế nào chúng ta có thể nhận EchoToken trong tài liệu xml ở trên? Hoặc làm thế nào để làm mờ phần tử với tên attrbute.

1- Bạn có thể tìm thấy chúng bằng cách truy cập với không gian tên và tên như bên dưới

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

2- Bạn có thể tìm thấy nó theo giá trị nội dung thuộc tính, như giá trị này


0

Đây là biến thể của giải pháp dựa trên Linqvà phương pháp Hậu duệ của XDocumentlớp

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

Các kết quả:

Để biết thêm chi tiết về Desendantsphương pháp hãy xem ở đây.


-1

(Mã và hướng dẫn dành cho C # và có thể cần được thay đổi một chút cho các ngôn ngữ khác)

Ví dụ này hoạt động hoàn hảo nếu bạn muốn đọc từ Node cha có nhiều con, ví dụ, hãy xem XML sau đây;

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

Bây giờ với mã này bên dưới (lưu ý rằng Tệp XML được lưu trữ trong tài nguyên (Xem các liên kết ở cuối đoạn trích để được trợ giúp về tài nguyên) Bạn có thể lấy từng địa chỉ email trong thẻ "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());
}

Các kết quả

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

Lưu ý: Đối với Ứng dụng Console và WPF hoặc Windows Forms, bạn phải thêm "bằng cách sử dụng System.Xml.Linq;" Sử dụng chỉ thị ở đầu dự án của bạn, đối với Bảng điều khiển, bạn cũng sẽ cần thêm một tham chiếu đến không gian tên này trước khi thêm lệnh Sử dụng. Ngoài ra, đối với Console sẽ không có tệp Tài nguyên theo mặc định trong "Thư mục thuộc tính", do đó bạn phải thêm tệp Tài nguyên theo cách thủ công. Các bài viết MSDN dưới đây, giải thích chi tiết này.

Thêm và chỉnh sửa tài nguyên

Cách: Thêm hoặc xóa tài nguyên


1
Đừng muốn có ý nghĩa ở đây, nhưng ví dụ của bạn không cho thấy cháu. emailAddress là một đứa con của email. Tôi tự hỏi liệu có cách nào để sử dụng Hậu duệ mà không sử dụng không gian tên?
Phần
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.