如何用一个给定的名字找到最高级别的后人[英] How to find highest level descendants with a given name

本文是小编为大家收集整理的关于如何用一个给定的名字找到最高级别的后人的处理/解决方法,可以参考本文帮助大家快速定位并解决问题,中文翻译不准确的可切换到English标签页查看源文。

问题描述

我正在寻找一种使用 linq 在 XML 树中查找第一个结果级别的方法.我拥有的 XML 如下所示:

<column>
    <row>
        <object/>
        <column column-id="1" column-name="abc">
            <row>
                <column>
                    <row>
                        <column column-id="2" column-name="abc"/>
                    </row>
                </column>
            </row>
        </column>
    </row>
    <row>
        <column column-id="3" column-name="abc">
            <row>
                <column/>
            </row>
        </column>
    </row>
</column>

现在我想获取列名为 abc 的所有第一级 columns.所以结果应该是:

<column column-id="1" column-name="abc">...</column>
<column column-id="3" column-name="abc">...</column>

我已经尝试过以下代码:

layout.Descendants("column")
      .Where(x => x.Attribute("column-name").Value.Equals("abc") && !x.Ancestors("column").Any());

当被搜索的 XElement layout 没有命名为 "column" 并且没有嵌套在任何名为 "column" 的容器元素中时,这可以正常工作.但事实上,我的 XElement 确实属于根元素名为 "column" 的文档,因此 x.Ancestors("column").Any() 表达式错误地过滤掉了所有匹配项.IE.可以使用上面的 XML 字符串通过初始化 layout 来重现该问题,如下所示:

var layout = XElement.Parse(xmlString);

我想将关系保留在变量中,因为我稍后必须进行更改.

有没有办法限制祖先选择器?

推荐答案

假设您事先不知道要查询的元素的精确深度,您要做的是将元素层次结构降到一个指定元素,并返回与给定条件匹配的 topmost 元素,在本例中名为 "column".

作为一种快速而简单的方法,您只能使用 TakeWhile()

var matches = layout
    .Descendants("column")
    .Where(x => (string)x.Attribute("column-name") == "abc" && !x.Ancestors().TakeWhile(a => a != layout).Any(a => a.Name == "column"));

一个更高效的通用解决方案是在 XElement 上引入一个扩展方法,该方法枚举给定元素的所有后代,返回与给定谓词匹配的最顶层元素.这通常很有用,例如如果您想查询接近深层 XML 层次结构顶部的后代,因为它可以避免不必要地下降到匹配的节点:

public static partial class XElementExtensions
{
    /// <summary>
    /// Enumerates through all descendants of the given element, returning the topmost elements that match the given predicate
    /// </summary>
    /// <param name="root"></param>
    /// <param name="filter"></param>
    /// <returns></returns>
    public static IEnumerable<XElement> DescendantsUntil(this XElement root, Func<XElement, bool> predicate, bool includeSelf = false)
    {
        if (predicate == null)
            throw new ArgumentNullException();
        return GetDescendantsUntil(root, predicate, includeSelf);
    }

    static IEnumerable<XElement> GetDescendantsUntil(XElement root, Func<XElement, bool> predicate, bool includeSelf)
    {
        if (root == null)
            yield break;
        if (includeSelf && predicate(root))
        {
            yield return root;
            yield break;
        }
        var current = root.FirstChild<XElement>();
        while (current != null)
        {
            var isMatch = predicate(current);
            if (isMatch)
                yield return current;

            // If not a match, get the first child of the current element.
            XElement next = (isMatch ? null : current.FirstChild<XElement>());

            if (next == null)
                // If no first child, get the next sibling of the current element.
                next = current.NextSibling<XElement>();

            // If no more siblings, crawl up the list of parents until hitting the root, getting the next sibling of the lowest parent that has more siblings.
            if (next == null)
            {
                for (var parent = current.Parent as XElement; parent != null && parent != root && next == null; parent = parent.Parent as XElement)
                {
                    next = parent.NextSibling<XElement>();
                }
            }

            current = next;
        }
    }

    public static TNode FirstChild<TNode>(this XNode node) where TNode : XNode
    {
        var container = node as XContainer;
        if (container == null)
            return null;
        return container.FirstNode.NextSibling<TNode>(true);
    }

    public static TNode NextSibling<TNode>(this XNode node) where TNode : XNode
    {
        return node.NextSibling<TNode>(false);
    }

    public static TNode NextSibling<TNode>(this XNode node, bool includeSelf) where TNode : XNode
    {
        if (node == null)
            return null;
        for (node = (includeSelf ? node : node.NextNode); node != null; node = node.NextNode)
        {
            var nextTNode = node as TNode;
            if (nextTNode != null)
                return nextTNode;
        }
        return null;
    }
}

然后像这样使用它:

var matches = layout
    .DescendantsUntil(x => x.Name == "column")
    .Where(x => (string)x.Attribute("column-name") == "abc");

扩展方法应该具有合理的性能,因为它避免了递归和复杂的嵌套 linq 查询.

示例 .Net fiddle 显示了这两个选项.

本文地址:https://www.itbaoku.cn/post/1556954.html

问题描述

I am looking for a way to find the first result level in a XML tree using linq. The XML I have is like the following:

<column>
    <row>
        <object/>
        <column column-id="1" column-name="abc">
            <row>
                <column>
                    <row>
                        <column column-id="2" column-name="abc"/>
                    </row>
                </column>
            </row>
        </column>
    </row>
    <row>
        <column column-id="3" column-name="abc">
            <row>
                <column/>
            </row>
        </column>
    </row>
</column>

Now I want to get all the first level columns where the column-name is abc. So the result should be:

<column column-id="1" column-name="abc">...</column>
<column column-id="3" column-name="abc">...</column>

I have tried already the following code:

layout.Descendants("column")
      .Where(x => x.Attribute("column-name").Value.Equals("abc") && !x.Ancestors("column").Any());

This works fine when the XElement layout being searched is not named "column" and is not nested inside any container elements named "column". But my XElement does, in fact, belong inside a document whose root element is named "column", so the x.Ancestors("column").Any() expression wrongly filters out all matches. I.e. the problem can be reproduced using the XML string above by initializing layout as follows:

var layout = XElement.Parse(xmlString);

I want to keep the relation in the variable because of changes I have to make later on.

Is there maybe a way limit the ancestors selector?

推荐答案

Assuming you don't know in advance the precise depth of the elements for which you are querying, what you want to do is to descend the element hierarchy underneath a specified element, and return the topmost elements that match a given condition, in this case having the name "column".

As a quick-and-dirty way to do this, you can only check for ancestors of the candidate matched column that are still descendants of layout by using TakeWhile()

var matches = layout
    .Descendants("column")
    .Where(x => (string)x.Attribute("column-name") == "abc" && !x.Ancestors().TakeWhile(a => a != layout).Any(a => a.Name == "column"));

A more performant, general solution would be to introduce an extension method on XElement that enumerates through all descendants of the given element, returning the topmost elements that match a given predicate. This would be generally useful e.g. in cases where one wants to query for descendants that are going to be near the top of a deep XML hierarchy, as it avoids descending unnecessarily into matched nodes:

public static partial class XElementExtensions
{
    /// <summary>
    /// Enumerates through all descendants of the given element, returning the topmost elements that match the given predicate
    /// </summary>
    /// <param name="root"></param>
    /// <param name="filter"></param>
    /// <returns></returns>
    public static IEnumerable<XElement> DescendantsUntil(this XElement root, Func<XElement, bool> predicate, bool includeSelf = false)
    {
        if (predicate == null)
            throw new ArgumentNullException();
        return GetDescendantsUntil(root, predicate, includeSelf);
    }

    static IEnumerable<XElement> GetDescendantsUntil(XElement root, Func<XElement, bool> predicate, bool includeSelf)
    {
        if (root == null)
            yield break;
        if (includeSelf && predicate(root))
        {
            yield return root;
            yield break;
        }
        var current = root.FirstChild<XElement>();
        while (current != null)
        {
            var isMatch = predicate(current);
            if (isMatch)
                yield return current;

            // If not a match, get the first child of the current element.
            XElement next = (isMatch ? null : current.FirstChild<XElement>());

            if (next == null)
                // If no first child, get the next sibling of the current element.
                next = current.NextSibling<XElement>();

            // If no more siblings, crawl up the list of parents until hitting the root, getting the next sibling of the lowest parent that has more siblings.
            if (next == null)
            {
                for (var parent = current.Parent as XElement; parent != null && parent != root && next == null; parent = parent.Parent as XElement)
                {
                    next = parent.NextSibling<XElement>();
                }
            }

            current = next;
        }
    }

    public static TNode FirstChild<TNode>(this XNode node) where TNode : XNode
    {
        var container = node as XContainer;
        if (container == null)
            return null;
        return container.FirstNode.NextSibling<TNode>(true);
    }

    public static TNode NextSibling<TNode>(this XNode node) where TNode : XNode
    {
        return node.NextSibling<TNode>(false);
    }

    public static TNode NextSibling<TNode>(this XNode node, bool includeSelf) where TNode : XNode
    {
        if (node == null)
            return null;
        for (node = (includeSelf ? node : node.NextNode); node != null; node = node.NextNode)
        {
            var nextTNode = node as TNode;
            if (nextTNode != null)
                return nextTNode;
        }
        return null;
    }
}

Then use it like:

var matches = layout
    .DescendantsUntil(x => x.Name == "column")
    .Where(x => (string)x.Attribute("column-name") == "abc");

The extension method should be reasonably performant as it avoids recursion and complex nested linq queries.

Sample .Net fiddle showing both options.