Запитайте 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")якби це були лише прямі діти.
Джон Скіт

8
Шість років і досі фантастичний приклад. Насправді це все ще набагато корисніше, ніж пояснення MSDN :-)
EvilDr

І це все-таки злий приклад, доктор, оскільки, якщо немає "Автомобілів", вищезазначений код призведе до створення NPE. Можливо.? з нового C # нарешті зробить його дійсним
Dror Harari

3
@DrorHarari Ні, виняток не кидається: Спробуйте, 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
Дякую! Ми використовуємо серіалізацію контракту даних. Це створює заголовок на кшталт <MyClassEntries xmlns: i = " w3.org/2001/XMLSchema-instanca " xmlns = " schemas.datacontract.org/2004/07/DataLayer.MyClass ">, і я був спотиканий, чому я не отримую будь-які нащадки. Мені потрібно було додати префікс { schemas.datacontract.org/2004/07/DataLayer.MyClass }.
Кім

38

Ви можете це зробити так:

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

де xmlє XDocument.

Майте на увазі, що властивість Nameповертає об'єкт, у якого є a LocalNameі a Namespace. Ось чому ви повинні використовувати, Name.LocalNameякщо ви хочете порівняти за назвою.


Я намагаюся отримати весь вузол EmbeddedResource з файлу проекту c #, і це працює тільки так. XDocument document = XDocument.Load (csprojPath); IEnumerable <XElement> embeddedResourceElements = document.Descendants ("EmbeddedResource"); Це не твори, і я не розумію, чому.
Євген Максимов

22

Нащадки виконають саме те, що вам потрібно, але будьте впевнені, що ви включили ім'я простору імен разом з іменем елемента. Якщо ви його опустите, ви, ймовірно, отримаєте порожній список.


11

Є два способи досягти цього,

  1. Linq-to-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.XPathпросторі імен.
Тахір Хассан

XPathEvaluate повинен зробити трюк, але ваш запит бере лише вузли на певній глибині (одній). Якщо ви хочете вибрати всі елементи з назвою "електронна пошта" незалежно від того, де в документі вони зустрічаються, ви б використали шлях "// email". Очевидно, такі стежки коштують дорожче, оскільки по всьому дереву треба ходити, як би це не було, але це може бути досить зручно - за умови, що ви знаєте, що робите.
Даг

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 зберігається у ресурсах (див. Посилання в кінці фрагмента довідки про ресурси). Ви можете отримати кожну адресу електронної пошти у тезі "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());
}

Результати

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

Примітка. Для додатків консолі та форм WPF або Windows необхідно додати "за допомогою System.Xml.Linq;" Використовуючи директиву у верхній частині вашого проекту, для Console вам також потрібно буде додати посилання на цей простір імен, перш ніж додати директиву Використання. Також для консолі за замовчуванням у папці "Властивості" не буде файлу ресурсу, тому вам доведеться вручну додати файл ресурсу. Статті MSDN нижче, пояснюють це докладно.

Додавання та редагування ресурсів

Як: Додавання та видалення ресурсів


1
Не хочу бути тут злим, але ваш приклад не показує онуків. emailAddress - це дочірнє повідомлення електронної пошти. Мені цікаво, чи існує спосіб використання нащадків без використання просторів імен?
SoftwareSavant
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.