【问题标题】:How to get parent and only one child node如何获取父节点和只有一个子节点
【发布时间】:2009-12-17 19:34:43
【问题描述】:

假设我有这个 xml:

<categories>
    <category text="Arts">
            <category text="Design"/>
            <category text="Visual Arts"/>
    </category>
    <category text="Business">
            <category text="Business News"/>
            <category text="Careers"/>
            <category text="Investing"/>
    </category>
    <category text="Comedy"/>
</categories>

我想编写一个 LINQ 查询,它将返回类别及其父类别(如果有的话)。

例如,如果我正在搜索“商业新闻”,我希望它返回一个包含以下内容的 XElement:

<category text="Business">
   <category text="Business News" />
</category>

如果我只搜索“业务”,我只想

<category text="Business" />

到目前为止,我能做的最好的事情是使用 LINQ 来获取我正在搜索的元素,然后检查我找到的节点的父节点是否是根节点并进行相应的调整。有没有更好的办法?

【问题讨论】:

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


    【解决方案1】:

    简单的部分是获取元素的路径:

    IEnumerable<XElement> elementsInPath = 
        doc.Element("categories")
           .Descendants()
           .Where(p => p.Attribute("text").Value == "Design")
           .AncestorsAndSelf()
           .InDocumentOrder()
           .ToList();
    

    InDocumentOrder() 用于按照根、子、孙的顺序获取集合。 ToList() 可以避免在下一步中产生任何不良影响。

    现在,不太漂亮的部分,也许可以用更优雅的方式完成:

    var newdoc = new XDocument();
    XContainer elem = newdoc;
    foreach (var el in elementsInPath))
    {
        el.RemoveNodes();
        elem.Add(el);
        elem = elem.Elements().First();
    }
    

    就是这样。由于每个 XElement 都保留其子节点,因此我们必须从路径中的每个节点中移除子节点,然后重建树。

    【讨论】:

      【解决方案2】:

      鉴于输入和所述要求,这将满足您的要求:

          public static class MyExtensions
          {
              public static string ParentAndSelf(this XElement self, XElement parent)
              {
                  self.Elements().Remove();
                  if (parent != null && parent.Name.Equals(self.Name))
                  {
                      parent.Elements().Remove();
                      parent.Add(self);
                      return parent.ToString();
                  }
                  else
                      return self.ToString();
              }
          }
      
          class Program
          {
              [STAThread]
              static void Main()
              {
                  string xml = 
                  @"<categories>
                      <category text=""Arts"">            
                          <category text=""Design""/>            
                          <category text=""Visual Arts""/>    
                      </category>    
                      <category text=""Business"">            
                          <category text=""Business News""/>            
                          <category text=""Careers""/>            
                          <category text=""Investing""/>    
                      </category>    
                      <category text=""Comedy""/>
                  </categories>";
      
                  XElement doc = XElement.Parse(xml);
      
                  PrintMatch(doc, "Business News");
                  PrintMatch(doc, "Business");
              }
      
              static void PrintMatch(XElement doc, string searchTerm)
              {
                  var hit = (from category in doc
                         .DescendantsAndSelf("category")
                             where category.Attributes("text")
                             .FirstOrDefault()
                             .Value.Equals(searchTerm)
                             let parent = category.Parent
                             select category.ParentAndSelf(parent)).SingleOrDefault();
      
                  Console.WriteLine(hit);
                  Console.WriteLine();
              }
          }
      

      【讨论】:

        【解决方案3】:

        如果你构建一个迭代器,问题会容易得多:

        public static IEnumerable<XElement> FindElements(XElement d, string test)
        {
            foreach (XElement e in d.Descendants()
                .Where(p => p.Attribute("text").Value == test))
            {
                yield return e;
                if (e.Parent != null)
                {
                    yield return e.Parent;
                }
            }
        }
        

        在任何你会使用 Linq 查询的地方使用它,例如:

        List<XElement> elms = FindElement(d, "Visual Arts").ToList();
        

        foreach (XElement elm in FindElements(d, "Visual Arts"))
        {
           ...
        }
        

        编辑:

        我现在看到上面的代码提供的不是提问者所要求的。但是提问者要求的东西在我看来有点奇怪,因为他想要返回的XElement是一个全新的对象,而不是现有文档中的东西。

        不过,服务是光荣的。凝视我的作品,你们强大而绝望:

        XElement result = doc.Descendants()
                             .Where(x => x.Attribute("text").Value == test)
                             .Select(
                                 x => x.Parent != null && x.Parent.Attribute("text") != null
                                        ? new XElement(
                                                x.Parent.Name,
                                                new XAttribute("text", x.Parent.Attribute("text").Value),
                                                new XElement(
                                                    x.Name, 
                                                    new XAttribute("text", x.Attribute("text").Value)))
                                        : new XElement(
                                            x.Name, 
                                            new XAttribute("text", x.Attribute("text").Value)))
                            .FirstOrDefault();
        

        【讨论】:

        • 这会返回父级的所有元素,就像 Sander 的回答一样。
        • 修改后的查询比当前评分最高的查询有效并且更简洁。干得好。
        【解决方案4】:

        我没有测试过,但应该是这样的:

        XDocument xmlFile;
        
        return from c in xmlFile.Descendants("category")
               where c.Attribute("text").Value == "Business News"
               select c.Parent ?? c;
        

        ?? 运算符返回父 XElement,如果是 null,则返回“c”。

        编辑:这个解决方案返回你想要的,但我不确定它是否是最好的,因为它变得相当复杂:

        var cat = from c in doc.Descendants("category")
                  where c.Attribute("text").Value == "Business News"
                  let node = c.Parent ?? c
                  select c.Parent == null
                             ? c // Parent null, just return child
                             : new XElement(
                                   "category",
                                   c.Parent.Attributes(), // Copy the attributes
                                   c                      // Add single child
                                   );
        

        【讨论】:

        • 这不起作用,它只是返回整个文档。指某东西的用途 ??不过是个好主意。
        • 好的,下一步,我测试一下! :)
        • 它实际上返回父级,包括所有子级,而不是整个文档。
        【解决方案5】:
        var text = "Car";
        
        var el = from category in x.Descendants("category")
                 from attribute in category.Attributes("text")
                 where attribute.Value.StartsWith(text)
                 select attribute.Parent.Parent;
        
        
        Console.WriteLine(el.FirstOrDefault());
        

        输出:

        <category text="Business">...
        

        即使没有这样的元素,或者没有这样的属性,这个也可以工作。

        【讨论】:

        • 总是给我“枚举没有结果”。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多