如何获取父节点和仅一个子节点

时间:2009-12-17 19:34:44

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

让我说我有这个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来获取我正在搜索的元素,然后检查我找到的节点的父节点是否是根节点并相应地进行调整。还有更好的方法吗?

5 个答案:

答案 0 :(得分:3)

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

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

InDocumentOrder()用于按照root,child,grandchild的顺序获取集合。 ToList()用于避免在下一步中产生任何不良影响。

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

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

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

答案 1 :(得分:1)

如果构建迭代器,问题会轻松得多:

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

答案 2 :(得分:1)

根据所述的输入和要求,这将满足您的需求:

    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 :(得分:0)

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

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

答案 4 :(得分:0)

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

即使没有这样的元素,或者没有这样的属性,这个也会起作用。