Запрос 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>, и вам нужен только список деталей для автомобилей.
Детям
12
@pfeds: Тогда я бы использовал doc.Descendants("Cars").Descendants("Part")(или, возможно, .Elements("Part")если бы они были только прямыми детьми.
Джон Скит
8
Шесть лет спустя и все еще фантастический пример. На самом деле, это все еще гораздо
полезнее,
И это по-прежнему злой пример, доктор, так как если бы не было «Автомобилей», приведенный выше код привел бы к NPE. Может быть.? из нового C #, наконец, сделает его действительным
Дрор Харари
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 );
}
Jelgab
источник
2
Но что если мой исходный xml не имеет пространства имен? Я полагаю, я могу добавить один в коде (нужно посмотреть на это), но зачем это нужно? В любом случае root.Descendants ("myTagName") не находит элементы, скрытые на глубине трех или четырех уровней в моем коде.
EoRaptor013
2
Спасибо! Мы используем сериализацию Datacontract. Это создает заголовок наподобие <MyClassEntries xmlns: i = " w3.org/2001/XMLSchema-instance " 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возвращает объект, который имеет LocalNameи Namespace. Вот почему вы должны использовать, Name.LocalNameесли вы хотите сравнить по имени.

Франсиско Гольденштейн
источник
Я пытаюсь получить все узлы EmbeddedResource из файла проекта c #, и это единственный способ, который работает. XDocument document = XDocument.Load (csprojPath); IEnumerable <XElement> embeddedResourceElements = document.Descendants ("EmbeddedResource"); Это не работает, и я не понимаю, почему.
Евгений Максимов
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");

результаты либо нулевой указатель, либо нет результатов.

Roland Roos
источник
1
просто упомянуть, что XPathEvaluateнаходится в System.Xml.XPathпространстве имен.
Тахир Хасан
XPathEvaluate должен сделать свое дело, но ваш запрос принимает узлы только на определенной глубине (один). Если вы хотите выбрать все элементы с именем «электронная почта» независимо от того, где в документе они находятся, вы должны использовать путь «// электронная почта». Очевидно, что такие пути более дороги, так как нужно пройтись по всему дереву независимо от имени, но это может быть довольно удобно - при условии, что вы знаете, что делаете.
The 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-файл хранится в ресурсах (см. Ссылки в конце фрагмента для получения справки о ресурсах). Каждый адрес электронной почты можно получить в теге «emails».

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 Forms необходимо добавить «using System.Xml.Linq;» Используя директиву в верхней части вашего проекта, для консоли вам также нужно будет добавить ссылку на это пространство имен перед добавлением директивы Using. Также для консоли не будет файла ресурсов по умолчанию в «Папке свойств», поэтому вы должны вручную добавить файл ресурсов. Статьи MSDN ниже, объясняют это подробно.

Добавление и редактирование ресурсов

Как: добавить или удалить ресурсы

Рави Рамнарин
источник
1
Не хочу быть здесь злым, но ваш пример не показывает внуков. emailAddress - дочерний элемент электронных писем. Мне интересно, есть ли способ использовать потомков без использования пространств имен?
SoftwareSavant