您有一个具有递归模式的
XDocument
,其中 <Member>
后代包含嵌套的 <Member>
后代。您想要遍历文档,返回顶级 <Member>
元素及其最顶层 <Member>
后代,然后从这些后代中提取一些数据。目前您正在使用 XDocument.Descendants()
遍历文档,这不能很好地方便地捕获父母和孩子的分组。
一种方法是使用方法
XElementExtensions.DescendantsUntil(this XElement root, Func<XElement, bool> predicate, bool includeSelf)
从this answer到How 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.
演示小提琴这里.
您可以修改您的代码以实现此目的,如下所示:
var memb = doc.Descendants(ns + "Member")
.Where(m => m.Attribute("Datatype").Value.Contains("Bool") &&
m.Descendants(ns + "Member").Any())
.ToList();
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