Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get parent and only one child node

Tags:

Lets say I have this xml:

<categories>
    <category text="Arts">
            <category text="Design"/>
            <category text="Visual Arts"/>
    </category>
    <category text="Business">
            <category text="Business News"/>
            <category text="Careers"/>
            <category text="Investing"/>
    </category>
    <category text="Comedy"/>
</categories>

I want to write a LINQ query that will return the category and it's parent category if it has any.

For example, if I was searching for "Business News" I would want it to return an XElement containing the following:

<category text="Business">
   <category text="Business News" />
</category>

If I only search for "Business", I would just want

<category text="Business" />

So far the best I can do is use LINQ to get the element I'm searching for, then check if the parent of the node I found is the root node and adjust accordingly. Is there a better way?

like image 615
Evan Avatar asked Dec 17 '09 19:12

Evan


People also ask

Is it possible for a node to have only one child in an expression tree?

It is also possible for a node to have only one child, as is the case with the unary minus operator. An expression tree, T, can be evaluated by applying the operator at the root to the values obtained by recursively evaluating the left and right subtrees.

Can a node be a parent and a child?

Yes it can. You just need to make sure that for all vertices c in the left subtree of p it must hold that val(c)\leq val(p), and similarily for the right subtree val(c)\geq val(p).

What do you call a node without a parent?

A node with no children is a leaf node; a node with children is a parent node.


2 Answers

The easy part is to get the path to the element:

IEnumerable<XElement> elementsInPath = 
    doc.Element("categories")
       .Descendants()
       .Where(p => p.Attribute("text").Value == "Design")
       .AncestorsAndSelf()
       .InDocumentOrder()
       .ToList();

The InDocumentOrder() is there to get the collection in the order of root, child, grandchild. The ToList() is there to avoid any unwanted effects in the next step.

Now, the less beautiful part, which maybe could be done in a more elegant way:

var newdoc = new XDocument();
XContainer elem = newdoc;
foreach (var el in elementsInPath))
{
    el.RemoveNodes();
    elem.Add(el);
    elem = elem.Elements().First();
}

That's it. Since each XElement keeps their child, we have to remove the children from each node in the path, and then we rebuild the tree.

like image 180
Jonatan Lindén Avatar answered Oct 12 '22 00:10

Jonatan Lindén


Given the input, and the requirements as stated, this will do what you want:

    public static class MyExtensions
    {
        public static string ParentAndSelf(this XElement self, XElement parent)
        {
            self.Elements().Remove();
            if (parent != null && parent.Name.Equals(self.Name))
            {
                parent.Elements().Remove();
                parent.Add(self);
                return parent.ToString();
            }
            else
                return self.ToString();
        }
    }

    class Program
    {
        [STAThread]
        static void Main()
        {
            string xml = 
            @"<categories>
                <category text=""Arts"">            
                    <category text=""Design""/>            
                    <category text=""Visual Arts""/>    
                </category>    
                <category text=""Business"">            
                    <category text=""Business News""/>            
                    <category text=""Careers""/>            
                    <category text=""Investing""/>    
                </category>    
                <category text=""Comedy""/>
            </categories>";

            XElement doc = XElement.Parse(xml);

            PrintMatch(doc, "Business News");
            PrintMatch(doc, "Business");
        }

        static void PrintMatch(XElement doc, string searchTerm)
        {
            var hit = (from category in doc
                   .DescendantsAndSelf("category")
                       where category.Attributes("text")
                       .FirstOrDefault()
                       .Value.Equals(searchTerm)
                       let parent = category.Parent
                       select category.ParentAndSelf(parent)).SingleOrDefault();

            Console.WriteLine(hit);
            Console.WriteLine();
        }
    }
like image 41
GalacticJello Avatar answered Oct 12 '22 01:10

GalacticJello