ค้นหา XDocument สำหรับองค์ประกอบตามชื่อที่ความลึกใด ๆ


143

ฉันมีXDocumentวัตถุ ฉันต้องการสอบถามองค์ประกอบที่มีชื่อเฉพาะที่ระดับความลึกใด ๆ โดยใช้ LINQ เมื่อฉันใช้Descendants("element_name")ฉันจะได้รับองค์ประกอบที่เป็นลูกโดยตรงของระดับปัจจุบัน สิ่งที่ฉันกำลังมองหาคือสิ่งที่เทียบเท่ากับ "// element_name" ใน XPath ... ฉันควรใช้XPathหรือมีวิธีใช้ LINQ หรือไม่? ขอบคุณ

คำตอบ:


213

ลูกหลานควรทำงานได้ดีอย่างแน่นอน นี่คือตัวอย่าง:

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

ผล:

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


1
คุณจะแก้ไขปัญหานี้อย่างไรถ้าชื่อองค์ประกอบซ้ำกันภายในเอกสาร xml ตัวอย่างเช่น: หาก xml มีชุด <Cars> พร้อมองค์ประกอบย่อยของ <Part> และยังมีชุด <Planes> พร้อมองค์ประกอบย่อยของ <Part> และคุณต้องการรายการอะไหล่สำหรับรถยนต์เท่านั้น
pfeds

12
@pfeds: จากนั้นฉันจะใช้doc.Descendants("Cars").Descendants("Part")(หรืออาจเป็นได้.Elements("Part")ว่าพวกเขาเป็นลูกโดยตรงเท่านั้น
Jon Skeet

8
เมื่อหกปีที่แล้วและยังคงเป็นตัวอย่างที่ยอดเยี่ยม ในความเป็นจริงนี้ยังห่างไกลที่เป็นประโยชน์มากขึ้นกว่าคำอธิบาย MSDN :-)
EvilDr

และมันก็ยังเป็นตัวอย่างที่ชั่วร้ายดร. เนื่องจากหากไม่มี "รถยนต์" รหัสด้านบนจะส่งผลให้เกิด NPE บางที. จาก C # ใหม่จะทำให้ใช้ได้ในที่สุด
Dror Harari

3
@DrorHarari Nope ไม่มียกเว้นจะโยน: ลองvar foo = new XDocument().Descendants("Bar").Descendants("Baz"); เพราะDescendantsผลตอบแทนที่ว่างเปล่าและไม่ได้IEnumerable<XElement> null
DareDude

54

ตัวอย่างที่ระบุเนมสเปซ:

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
แต่จะเกิดอะไรขึ้นถ้าซอร์ส xml ของฉันไม่มีเนมสเปซ ฉันคิดว่าฉันสามารถเพิ่มหนึ่งในรหัส (ต้องดูที่) แต่ทำไมจึงจำเป็น ไม่ว่าในกรณีใด ๆ root.Descendants ("myTagName") จะไม่พบองค์ประกอบที่ฝังลึกสามหรือสี่ระดับในรหัสของฉัน
EoRaptor013

2
ขอบคุณ! เรากำลังใช้การจัดลำดับข้อมูลดาต้าontract สิ่งนี้สร้างส่วนหัวเช่น <MyClassEntries xmlns: i = " w3.org/2001/XMLSchema-instance " xmlns = " schemas.datacontract.org/2004/07/DataLayer.MyClass "> และฉันถูกนิ่งงันว่าทำไมฉันจึงไม่ได้รับ ลูกหลานใด ๆ ฉันต้องการเพิ่มคำนำหน้า{ schemas.datacontract.org/2004/07/DataLayer.MyClass }
Kim

38

คุณสามารถทำได้ด้วยวิธีนี้:

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

ที่เป็นxmlXDocument

โปรดทราบว่าสถานที่ให้Nameผลตอบแทนที่วัตถุที่มีหนึ่งและLocalName Namespaceนั่นเป็นเหตุผลที่คุณต้องใช้Name.LocalNameถ้าคุณต้องการเปรียบเทียบตามชื่อ


ฉันพยายามรับโหนด EmbeddedResource ทั้งหมดจากไฟล์ c # project และนี่เป็นวิธีเดียวที่ใช้งานได้ XDocument document = XDocument.Load (csprojPath); IENumerable <XElement> embeddedResourceElements = document.Descendants ("EmbeddedResource"); ใช้งานไม่ได้และฉันไม่เข้าใจว่าทำไม
Eugene Maksimov

22

ลูกหลานจะทำสิ่งที่คุณต้องการอย่างแน่นอน แต่ให้แน่ใจว่าคุณได้รวมชื่อเนมสเปซพร้อมกับชื่อองค์ประกอบ หากคุณข้ามมันไปคุณจะได้รายการว่างเปล่า


11

มีสองวิธีในการบรรลุเป้าหมายนี้

  1. Linq เพื่อ XML
  2. XPath

ต่อไปนี้เป็นตัวอย่างของการใช้วิธีการเหล่านี้

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

หากคุณใช้ XPath คุณต้องทำการจัดการบางอย่างด้วย IEnumerable:

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

สังเกตได้ว่า

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

ส่งผลให้ทั้งตัวชี้โมฆะหรือไม่มีผลลัพธ์


1
เพียงพูดถึงที่XPathEvaluateอยู่ในSystem.Xml.XPathnamespace
Tahir Hassan

XPathEvaluate ควรทำเคล็ดลับ แต่แบบสอบถามของคุณใช้โหนดที่ระดับความลึกเฉพาะ (หนึ่ง) หากคุณต้องการเลือกองค์ประกอบทั้งหมดที่ชื่อ "อีเมล" โดยไม่คำนึงถึงว่าอยู่ที่ไหนในเอกสารพวกเขาจะใช้เส้นทาง "// อีเมล" เห็นได้ชัดว่าเส้นทางดังกล่าวมีราคาแพงกว่าเนื่องจากต้นไม้ทั้งต้นต้องเดินตามชื่อ แต่มันก็ค่อนข้างสะดวก - หากคุณรู้ว่าคุณกำลังทำอะไรอยู่
Dag

8

ฉันใช้XPathSelectElementsวิธีการขยายซึ่งทำงานในวิธีเดียวกันกับXmlDocument.SelectNodesวิธีการ:

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

@Francisco Goldenstein ต่อไปนี้ฉันเขียนวิธีการขยาย

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

เรารู้ว่าข้างต้นเป็นจริง จอนไม่เคยผิด ความปรารถนาในชีวิตจริงสามารถเพิ่มขึ้นอีกเล็กน้อย

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

ตัวอย่างเช่นโดยทั่วไปปัญหาคือเราจะรับ EchoToken ในเอกสาร xml ด้านบนได้อย่างไร หรือวิธีการเบลอองค์ประกอบที่มีชื่อ attrbute

1- คุณสามารถค้นหาได้โดยการเข้าถึงด้วยเนมสเปซและชื่อดังนี้

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

2- คุณสามารถค้นหาได้ด้วยค่าเนื้อหาของคุณลักษณะเช่นนี้


0

สิ่งนี้แตกต่างของฉันของการแก้ปัญหาขึ้นอยู่กับLinqวิธีการและลูกหลานของXDocumentชั้นเรียน

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

ผล:

สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับDesendantsวิธีการดูที่นี่


-1

(รหัสและคำแนะนำสำหรับ C # และอาจจำเป็นต้องเปลี่ยนแปลงเล็กน้อยสำหรับภาษาอื่น ๆ )

ตัวอย่างนี้ทำงานได้สมบูรณ์ถ้าคุณต้องการอ่านจากโหนดผู้ปกครองที่มีลูกหลายคนตัวอย่างเช่นดูที่ 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>

ขณะนี้มีรหัสนี้ด้านล่าง (โปรดจำไว้ว่าไฟล์ XML ถูกเก็บไว้ในแหล่งข้อมูล (ดูลิงค์ที่ส่วนท้ายของตัวอย่างเพื่อขอความช่วยเหลือเกี่ยวกับทรัพยากร) คุณสามารถรับที่อยู่อีเมลแต่ละรายการได้ภายในแท็ก "อีเมล"

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

ผล

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

หมายเหตุ: สำหรับ Console Application และ WPF หรือ Windows Forms คุณต้องเพิ่ม "using System.Xml.Linq;" การใช้คำสั่งที่ด้านบนของโครงการของคุณสำหรับคอนโซลคุณจะต้องเพิ่มการอ้างอิงถึงเนมสเปซนี้ก่อนที่จะเพิ่มการใช้คำสั่ง นอกจากนี้สำหรับคอนโซลจะไม่มีไฟล์ทรัพยากรเป็นค่าเริ่มต้นภายใต้ "โฟลเดอร์คุณสมบัติ" ดังนั้นคุณต้องเพิ่มไฟล์ทรัพยากรด้วยตนเอง บทความ MSDN ด้านล่างอธิบายรายละเอียด

การเพิ่มและแก้ไขทรัพยากร

วิธีการ: เพิ่มหรือลบทรัพยากร


1
ไม่ต้องการที่จะหมายถึงที่นี่ แต่ตัวอย่างของคุณไม่แสดงหลาน emailAddress เป็นลูกของอีเมล ฉันสงสัยว่ามีวิธีการใช้ลูกหลานโดยไม่ต้องใช้เนมสเปซหรือไม่?
SoftwareSavant
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.