Robert Smith Robert Smith - 2 months ago 17
C# Question

Checking descendants of parent from a HashSet of values using LINQ

I have a HashSet with xml child nodes I want to retrieve. I am using the following linq (not working) to try to get all childs and return parent also:

xml:

<team>
<bob>a</bob>
<mary>b</mary>
<joe>c</joe>
</team>
<team>
<john>d</john>
</team>


HashSet:

HashSet<string> people = new HashSet<string> { "mary", "joe", "john" };


LINQ:

IEnumerable<XElement> result = from d in doc.Descendants("team")
where people.Contains(d.Descendants().Name.LocalName)
select d;


Should Return:

<team>
<mary>b</mary>
<joe>c</joe>
</team>
<team>
<john>d</john>
</team>


But its not working. Any help would be appreciated. Thanks.

Update #1

I am adding a complete example, which returns nothing:

public void test1()
{
var xml =
@"<?xml version=""1.0"" encoding=""utf-8""?>
<hereistheroot>
<team>
<bob>a</bob>
<mary>b</mary>
<joe>c</joe>
</team>
<team>
<john>d</john>
</team>
</hereistheroot>";
XElement doc = XElement.Parse(xml);

HashSet<string> people = new HashSet<string> { "mary", "joe", "john" };

IEnumerable<XElement> result = from d in doc.Descendants("team")
where people.Contains(d.Name.LocalName)
select d;

}


It returns nothing. It should return two XElement objects:

First one:

<team>
<mary>b</mary>
<joe>c</joe>
</team>


Second One:

<team>
<john>d</john>
</team>


Note: For testing, remove the "Where" clause in the linq and it will correctly return the two XElement objects but that is the main problem: I need to get the "Where" clause to allow me to pick people based on the HashSet. Thanks.

Answer

It seems to me that you are actually trying to accomplish two different things here:

  1. Identify all the top-level team elements that contain child nodes having the names in your set.
  2. Construct a new XML DOM that contains only those top-level team elements, and in those elements have only those child nodes having the names in your set.

If that's correct, then the following will accomplish that:

IEnumerable<XElement> result = from d in doc.Descendants("team")
                               let validDescendants = d.Descendants()
                                   .Where(c => people.Contains(c.Name.LocalName)).ToArray()
                               where validDescendants.Length > 0
                               select new XElement(d.Name, validDescendants);

An alternative equivalent of the above, using more of the query expression syntax (i.e. to initialize validDescendants):

IEnumerable<XElement> result = from d in doc.Descendants("team")
                               let validDescendants =
                                    (from c in d.Descendants()
                                     where people.Contains(c.Name.LocalName)
                                     select c).ToArray()
                               where validDescendants.Length > 0
                               select new XElement(d.Name, validDescendants);

Note that this approach leaves the original doc tree unchanged, returning instead an enumeration of entirely new team elements having the configuration you desire.

If you'd rather just keep that doc tree and remove the child elements from the returned team elements, you could do something like this:

IEnumerable<XElement> result = from d in doc.Descendants("team")
                               where d.Descendants()
                                   .Any(c => people.Contains(c.Name.LocalName))
                               select d;

foreach (XElement element in result)
{
    foreach (XElement child in element.Descendants().ToList())
    {
        if (!people.Contains(child.Name.LocalName))
        {
            child.Remove();
        }
    }
}

Of course, in that example, it will leave untouched any team elements that didn't include at least one element having a name in your set. That's fixable too, of course, but since you seem to only want to retrieve elements that would be non-empty after your filtering, I'm assuming the first example above is more along the lines of what you want. I showed the second only as a variation, for you to build on if you have something more specific in mind that you haven't described fully in your question.

Comments