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

Posted

技术标签:

【中文标题】在 XDocument 中按名称查询任意深度的元素【英文标题】:Query an XDocument for elements by name at any depth 【发布时间】:2009-02-19 16:46:09 【问题描述】:

我有一个XDocument 对象。我想使用 LINQ 在任意深度查询具有特定名称的元素。

当我使用Descendants("element_name") 时,我只得到当前关卡的直接子元素。我正在 XPath 中寻找“//element_name”的等价物...我应该只使用XPath,还是有办法使用 LINQ 方法?

【问题讨论】:

【参考方案1】:

后代应该工作得很好。这是一个例子:

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

结果:

&lt;grandchild id="3" /&gt;&lt;grandchild id="4" /&gt;

【讨论】:

如果元素名称在 xml 文档中重复,您将如何解决这个问题?例如:如果 xml 包含一个带有 子元素的 集合,以及一个带有 子元素的 集合,并且您只需要 Cars 的零件列表。跨度> @pfeds:那么我会使用doc.Descendants("Cars").Descendants("Part")(如果他们只是直系子女,我可能会使用.Elements("Part") 六年过去了,仍然是一个很好的例子。事实上,这仍然比 MSDN 的解释更有帮助:-) 这仍然是一个邪恶的例子,博士,因为如果没有“汽车”,上面的代码将导致 NPE。也许.?从新的 C# 将最终使其有效 @DrorHarari 不,不会抛出异常:试试var foo = new XDocument().Descendants("Bar").Descendants("Baz"); 因为Descendants 返回一个空的IEnumerable&lt;XElement&gt; 而不是null【参考方案2】:

一个表示命名空间的例子:

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/XMLSchemaChild" )
select
    AnyElement;

ResultsTxt.AppendText( TheElements1.Count().ToString() );

//Example 2:
var TheElements2 =
from
    AnyElement
in
    TheDocument.Descendants( "http://www.w3.org/2001/XMLSchemaChild" )
where
    AnyElement.Attribute( "theName" ).Value.StartsWith( "G" )
select
    AnyElement;

foreach ( XElement CurrentElement in TheElements2 )

    ResultsTxt.AppendText( "\r\n" + CurrentElement.Attribute( "theName" ).Value );

【讨论】:

但是,如果我的源 xml 没有命名空间怎么办?我想我可以在代码中添加一个(必须研究一下),但为什么有必要呢?无论如何,root.Descendants("myTagName") 在我的代码中找不到隐藏三到四层的元素。 谢谢!我们正在使用数据合同序列化。这会创建一个像 w3.org/2001/XMLSchema-instance" xmlns="schemas.datacontract.org/2004/07/DataLayer.MyClass"> 这样的标题,我很困惑为什么我没有得到任何后代。我需要添加 schemas.datacontract.org/2004/07/DataLayer.MyClass 前缀。 经过数小时的搜索和试验,这是唯一有帮助的答案。男人不能感谢你。感谢将命名空间添加到后代中。【参考方案3】:

你可以这样做:

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 embeddedResourceElements = document.Descendants("EmbeddedResource");不起作用,我不明白为什么。【参考方案4】:

后代将完全满足您的需求,但请确保您已将命名空间名称与元素名称一起包含在内。如果省略它,您可能会得到一个空列表。

【讨论】:

【参考方案5】:

有两种方法可以做到这一点,

    LINQ to XML 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");

结果要么是空指针,要么没有结果。

【讨论】:

只是提到XPathEvaluateSystem.Xml.XPath 命名空间中。 XPathEvaluate 应该可以解决问题,但您的查询只需要特定深度(一个)的节点。如果您想选择所有名为“email”的元素,无论它们出现在文档中的哪个位置,您都可以使用路径“//email”。显然这样的路径更昂贵,因为无论名称是什么都必须走整棵树,但它可以非常方便 - 只要您知道自己在做什么。【参考方案6】:

我正在使用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);
            
        
    

【讨论】:

【参考方案7】:

按照@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);
        
    

【讨论】:

【参考方案8】:

这是我基于 LINQ 和 XDocument 类的 Descendants 方法的解决方案的变体

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

Results:

For more details on the Desendants method take a look here.

【讨论】:

【参考方案9】:

我们知道以上是真的。乔恩永远不会错;现实生活中的愿望可以走得更远。

<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?或者如何用name属性模糊元素。

    您可以通过使用命名空间和如下名称访问它们来找到它们

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

    您可以通过属性内容值找到它,like this one。

【讨论】:

【参考方案10】:

(代码和说明适用于 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 文件存储在资源中(有关资源的帮助,请参阅 sn-p 末尾的链接)您可以获取“电子邮件”标签中的每个电子邮件地址。

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

结果

    jdoe@set.ca jsmith@hit.ca rgreen@set_ig.ca

注意:对于控制台应用程序和 WPF 或 Windows 窗体,您必须添加“使用 System.Xml.Linq;” Using 指令位于项目的顶部,对于 Console,您还需要在添加 Using 指令之前添加对此命名空间的引用。同样对于控制台,“属性文件夹”下默认没有资源文件,因此您必须手动添加资源文件。下面的 MSDN 文章,详细解释了这一点。

Adding and Editing Resources

How to: Add or Remove Resources

【讨论】:

不想在这里刻薄,但你的例子没有显示孙子。 emailAddress 是电子邮件的子级。我想知道是否有一种方法可以在不使用命名空间的情况下使用后代?

以上是关于在 XDocument 中按名称查询任意深度的元素的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法将默认命名空间设置为从 XDocument 查询?

XDocument 读取具有名称空间的根元素的 XML 文件

如何使用 LINQ 在 XML 中按名称获取元素

使用 XDocument 按属性查找元素

在 UIAutomation 中从 UIAElementArray 中按名称获取元素时遇到问题

如何从元素中具有相同名称的 xml 文件中获取特定值?