1

I have a XML like this:

<?xml version="1.0" encoding="utf-8"?>
<Document>
    <Interface>
        <Sections xmlns="http://www.siemens.com/automation/Openness/SW/Interface/v4">
            <Section Name="Static">
                <Member Name="3bool1" Datatype="&quot;3bool&quot;" Remanence="NonRetain" Accessibility="Public">
                    <AttributeList>
                        <BooleanAttribute Name="ExternalAccessible" SystemDefined="true">true</BooleanAttribute>
                        <BooleanAttribute Name="ExternalVisible" SystemDefined="true">true</BooleanAttribute>
                        <BooleanAttribute Name="ExternalWritable" SystemDefined="true">true</BooleanAttribute>
                        <BooleanAttribute Name="SetPoint" SystemDefined="true">false</BooleanAttribute>
                    </AttributeList>
                    <Sections>
                        <Section Name="None">
                            <Member Name="bool1" Datatype="Bool" />
                            <Member Name="bool2" Datatype="Bool" />
                            <Member Name="bool3" Datatype="Bool" />
                        </Section>
                    </Sections>
                </Member>
                <Member Name="int7" Datatype="Int" Remanence="NonRetain" Accessibility="Public">
                    <AttributeList>
                        <BooleanAttribute Name="ExternalAccessible" SystemDefined="true">true</BooleanAttribute>
                        <BooleanAttribute Name="ExternalVisible" SystemDefined="true">true</BooleanAttribute>
                        <BooleanAttribute Name="ExternalWritable" SystemDefined="true">true</BooleanAttribute>
                        <BooleanAttribute Name="SetPoint" SystemDefined="true">true</BooleanAttribute>
                    </AttributeList>
                </Member>
            </Section>
        </Sections>
    </Interface>
 </Document>

With the following code I can take all the descendants "Member" elements of bool "Datatype":

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

var memb = doc.Descendants(ns + "Member")
     .Select(f => f.Attribute("Datatype"))
     .Where(n => n.Value.Contains("Bool"))
     .ToList();

memb.ForEach(i => Console.WriteLine("{0}\t", i));

What I want to do is:

  1. Search if some "Member" elements have or not some "Member" child elements (composed data types);
  2. Extract all child elements of that specific parent element (in this case bool1, bool2, bool3)
dbc
  • 104,963
  • 20
  • 228
  • 340
  • What do you want to do when there are three or more levels of nesting of `` elements? Do you want to select only the lowest level, or both the lowest level and the intermediate levels as well? – dbc Apr 07 '23 at 16:07
  • Just to clarify, you want to select ` – dbc Apr 07 '23 at 19:18

1 Answers1

0

You have an XDocument with a recursive schema in which <Member> descendants contain nested <Member> descendants. You would like to iterate through the document, returning top-level <Member> elements grouped with their topmost <Member> descendants, then extract some data from those descendants. Currently you are using XDocument.Descendants() to iterate through the document, which doesn't do a good job of conveniently capturing the grouping of parents and children.

One way to do this would be to use the method XElementExtensions.DescendantsUntil(this XElement root, Func<XElement, bool> predicate, bool includeSelf) from this answer to How to find highest level descendants with a given name to find the topmost <Member> elements, then for each of those, use DescendantsUntil() again to find their topmost <Member> descendants.

First define DescendantsUntil() as follows:

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
}

And now you can do:

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}\""))));

Which prints

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

If you are not interested in the <Member Name="int7" element which has no <Member> children, you may filter such elements out by uncommenting the Where() clause above:

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

Demo fiddle here.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • Thanks a lot for your support, it works really fine for my application. Can I use this method to find children's children "member" elements? – fabrizio scarpetta Apr 11 '23 at 15:14
  • Sure, it enumerates the "topmost" descendants of a node with a certain name, so you could use it to find the topmost member descendants of any member. However, if you have **arbitrary** nesting levels of parent/child member elements and need to flatten that recursively somehow, I would suggesting asking a new question with a new [mcve] because it's not clear from your current question what would need to be returned. – dbc Apr 11 '23 at 15:23
  • Yes, I found out that I can have several (unlimited) nested "Member" elements. At the moment I just would like to add "Nephews" next to "Parent" and "Children". How can I use nested DescendantsUntil with "Select" method? I will write then a new question when I pass this step. Thank you again – fabrizio scarpetta Apr 12 '23 at 08:45
  • Here's new link: https://stackoverflow.com/questions/76002535/descendantsuntil-in-case-of-nested-elements – fabrizio scarpetta Apr 13 '23 at 10:13