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

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

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

【问题讨论】:

    标签: c# .net xml linq linq-to-xml


    【解决方案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/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 );
    }
    

    【讨论】:

    • 但是,如果我的源 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】:

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

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

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

      【讨论】:

      • 只是提到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属性模糊元素。

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

               doc.Descendants().Where(p => p.Name.LocalName == "OTA_AirAvailRQ").Attributes("EchoToken").FirstOrDefault().Value
              
            2. 您可以通过属性内容值找到它,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());
              }
              

              结果

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

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

              Adding and Editing Resources

              How to: Add or Remove Resources

              【讨论】:

              • 不想在这里刻薄,但你的例子没有显示孙子。 emailAddress 是电子邮件的子级。我想知道是否有一种方法可以在不使用命名空间的情况下使用后代?
              猜你喜欢
              • 1970-01-01
              • 2015-09-20
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多