XDocument.Descendants 无法区分父/子元素

问题描述 投票:0回答:2
c# xml linq-to-xml
2个回答
0
投票

您有一个具有递归模式的

XDocument
,其中
<Member>
后代包含嵌套的
<Member>
后代。您想要遍历文档,返回顶级
<Member>
元素及其最顶层
<Member>
后代,然后从这些后代中提取一些数据。目前您正在使用
XDocument.Descendants()
遍历文档,这不能很好地方便地捕获父母和孩子的分组。

一种方法是使用方法

XElementExtensions.DescendantsUntil(this XElement root, Func<XElement, bool> predicate, bool includeSelf)
this answerHow to find highest level descendants with a given name to find the topmost
<Member>
elements, then for each of those,再次使用
DescendantsUntil()
找到他们最顶层的
<Member>
后代。

首先定义

DescendantsUntil()
如下:

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)
    {
        if (predicate == null)
            throw new ArgumentNullException(nameof(predicate));
        return GetDescendantsUntil(root, predicate, false);
    }

    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.
            var 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 => node switch
        {
            XContainer container => container.FirstNode?.NextSibling<TNode>(true),
            _ => null,
        };

    public static TNode? NextSibling<TNode>(this XNode node) where TNode : XNode =>
        node.NextSibling<TNode>(false);

    public static TNode? NextSibling<TNode>(this XNode node, bool includeSelf) where TNode : XNode
    {
        for (var current = (includeSelf ? node : node.NextNode); current != null; current = current.NextNode)
        {
            var nextTNode = current as TNode;
            if (nextTNode != null)
                return nextTNode;
        }
        return null;
    }
    //Taken from this answer https://stackoverflow.com/a/46016931/3744182
    //To https://stackoverflow.com/questions/46007738/how-to-find-first-descendant-level
    //With added nullability annotations and updated syntax
}

现在你可以做:

XNamespace ns = "http://www.siemens.com/automation/Openness/SW/Interface/v4";
XName name = ns + "Member";

var members = doc
    .Root.DescendantsUntil(e => e.Name == name)
    .Select(e => (Parent: e, Children : e.DescendantsUntil(c => c.Name == name).ToList()))
    //.Where(i => i.Children.Count > 0) // Uncomment to filter out <Member> elements with no child members.
    .ToList();

members.ForEach(i => Console.WriteLine("Parent: \"{0}\", Children: {1}", 
                                       i.Parent.Attribute("Name")?.Value, 
                                       i.Children.Count == 0 
                                       ? "None" :
                                       string.Join(",", 
                                                   i.Children.Select(c => $"\"{c.Attribute("Name")?.Value}\""))));

哪个打印

Parent: "3bool1", Children: "bool1","bool2","bool3"
Parent: "int7", Children: None

如果您对没有

<Member Name="int7"
子元素的
<Member>
元素不感兴趣,您可以通过取消注释上面的
Where()
子句来过滤掉这些元素:

    .Where(i => i.Children.Count > 0) // Uncomment to filter out <Member> elements with no child members.

演示小提琴这里.


-1
投票

您可以修改您的代码以实现此目的,如下所示:

  1. 添加条件过滤具有子元素的“Member”元素:
var memb = doc.Descendants(ns + "Member")
              .Where(m => m.Attribute("Datatype").Value.Contains("Bool") && 
                          m.Descendants(ns + "Member").Any())
              .ToList();
  1. 遍历选中的成员并提取子元素:
foreach (var member in memb)
{
    var children = member.Descendants(ns + "Member")
                         .Select(m => m.Attribute("Name").Value)
                         .ToList();
    Console.WriteLine("{0}:\t{1}", member.Attribute("Name").Value, string.Join(", ", children));
}

输出将是:

3bool1: bool1, bool2, bool3
© www.soinside.com 2019 - 2024. All rights reserved.