在XDocument的任何深度按名称查询元素


143

我有一个XDocument对象。我想使用LINQ在任何深度查询具有特定名称的元素。使用时Descendants("element_name"),我只会得到当前级别的直接子级元素。我要寻找的是XPath中的“ // element_name”等价...我应该只使用XPath,还是可以使用LINQ方法来做到这一点?谢谢。

Answers:


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

12
@pfeds:然后我会使用doc.Descendants("Cars").Descendants("Part")(或可能.Elements("Part"),如果他们只是直接的儿童。
乔恩斯基特

8
六年过去了,仍然是一个了不起的例子。实际上,这仍然比MSDN解释有用得多:-)
EvilDr

博士,这仍然是一个邪恶的例子,因为如果没有“汽车”,则上面的代码将导致NPE。也许。?新C#中的代码最终将使其有效
Dror Harari 2015年

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

2
谢谢!我们正在使用数据合同序列化。这会创建一个类似于<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")

哪里xmlXDocument

请注意,该属性Name返回的对象具有LocalNameNamespace。这就是为什么Name.LocalName要按名称比较时必须使用的原因。


我正在尝试从c#项目文件中获取所有EmbeddedResource节点,这是唯一可行的方法。XDocument文档= XDocument.Load(csprojPath); IEnumerable <XElement> EmbeddedResourceElements = document.Descendants(“ EmbeddedResource”); 是行不通的,我也不明白为什么。
尤金·马克西莫夫


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
只需提及XPathEvaluateSystem.Xml.XPath名称空间中。
塔希尔·哈桑

XPathEvaluate应该可以解决问题,但是您的查询仅采用特定深度(一个)的节点。如果要选择所有名为“ email”的元素,而不管它们出现在文档中的何处,则可以使用路径“ // 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>

例如,通常问题是,如何在上述xml文档中获取EchoToken?或如何使用Attrbute名称模糊元素。

1-您可以通过使用以下名称空间和名称进行访问来找到它们

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

2-您可以通过属性内容值找到它,就像这样


0

这是我基于类的LinqDescendants方法的解决方案的变体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窗体,必须添加“ using System.Xml.Linq;”。在项目顶部的using指令,对于Console,在添加using指令之前,还需要添加对此命名空间的引用。同样对于控制台,默认情况下,“属性文件夹”下将没有资源文件,因此您必须手动添加资源文件。下面的MSDN文章对此进行了详细说明。

添加和编辑资源

如何:添加或删除资源


1
不想在这里刻薄,但您的示例并未显示孙辈。emailAddress是电子邮件的子代。我想知道是否有一种无需使用名称空间即可使用后代的方法?
SoftwareSavant
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.