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

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

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

284202 次浏览

子孙后代应该能够很好地工作,这里有一个例子:

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

后代将完全满足您的需要,但是请确保已经包含了名称空间名称和元素名称。如果省略它,可能会得到一个空列表。

指示命名空间的示例:

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

(代码和说明是针对 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 文件存储在资源中(请参阅代码片段末尾的链接以获得有关资源的帮助) ,您可以在“ email”标记中获得每个电子邮件地址。

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 表格或视窗表格必须加入「使用系统」。Xml.在项目顶部使用指令,对于 Console,在添加 Use 指令之前,还需要添加对此命名空间的引用。同样,对于控制台,默认情况下在“属性文件夹”下没有资源文件,因此您必须手动添加资源文件。下面的 MSDN 文章详细解释了这一点。

添加和编辑资源

如何: 添加或删除资源

有两种方法可以实现,

  1. 从 LINQ 到 XML
  2. XPath

以下是使用这些方法的示例,

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

如果使用 XPath,则需要对 IEnumable 进行一些操作:

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

请注意

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

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

你可以这样做:

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

其中 xmlXDocument

请注意,属性 Name返回一个具有 LocalNameNamespace的对象。这就是为什么你必须使用 Name.LocalName,如果你想比较的名称。

我使用的是 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);
}
}
}
}

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

我们知道上面说的是真的,乔恩从来不会错,现实生活中的愿望可以更进一步。

<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 属性模糊元素。

  1. 您可以通过访问名称空间和名称来找到它们,如下所示

     doc.Descendants().Where(p => p.Name.LocalName == "OTA_AirAvailRQ").Attributes("EchoToken").FirstOrDefault().Value
    
  2. 您可以通过属性内容值 就像这个找到它。

这是基于 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方法的更多细节,请在这里查看。