nothingtodisplay
nothingtodisplay

Reputation: 31

How to read a hierarchical xml with Linq and print the result that can indicate the hierachy

I have an XML file like below

<root>
<node id="1">
    <nodeName>node1</nodeName>
    <node id="2">
        <nodeName>node2</nodeName>
        <node id="21">
            <nodeName>node21</nodeName>
        </node>
        <node id="22">
            <nodeName>node22</nodeName>
        </node>
    </node>
    <node id="3">
        <nodeName>node3</nodeName>
        <node id="31">
            <nodeName>node31</nodeName>
        </node>
    </node>
    <node id="4">
        <nodeName>node4</nodeName>
        <node id="41">
            <nodeName>node41</nodeName>
        </node>
    </node>
</node>
</root>

I wonder how can I read out the xml file with the node hierarchy using Linq. I have a node class created for the node object.

class node{
    int id;
    string nodeName;
    List<node> children;
}

The output should be like

node1
    node2
        node21
        node22
    node3
        node31
    node4
        node41

Any suggestions please. Thanks.

Upvotes: 0

Views: 2085

Answers (4)

Charles Mager
Charles Mager

Reputation: 26223

You can use a recursive method to build your nodes. Note that as the convention in .NET is for pascal casing, I have changed the names in your node class and introduced a constructor:

public class Node
{
    public Node(int id, string name, IEnumerable<Node> children)
    {
        Id = id;
        Name = name;
        Children = children;
    }

    public int Id { get; private set; }
    public string Name { get; private set; }
    public IEnumerable<Node> Children { get; private set; }
}

You can then parse your XML to nodes as follows:

var doc = XDocument.Parse(xml);
var nodes = doc.Root.Elements().Select(NodeFrom).ToList();

Where NodeFrom is defined like so:

private static Node NodeFrom(XElement element)
{
    return new Node(
        (int) element.Attribute("id"),
        (string) element.Element("nodeName"),
        element.Elements("node").Select(NodeFrom).ToList()
        );
}

See a working demo here: https://dotnetfiddle.net/E5MQme

Upvotes: 1

jdweng
jdweng

Reputation: 34433

For complete solution see below. I tool a recusive method found at another posting for recursive reading a recursive XML file into a treeview and modified for your requirements.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;


namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            XmlDocument doc = new XmlDocument();
            Node root = new Node();
            string input =
                "<root>" +
                    "<node id=\"1\">" +
                        "<nodeName>node1</nodeName>" +
                        "<node id=\"2\">" +
                            "<nodeName>node2</nodeName>" +
                            "<node id=\"21\">" +
                                "<nodeName>node21</nodeName>" +
                            "</node>" +
                            "<node id=\"22\">" +
                                "<nodeName>node22</nodeName>" +
                            "</node>" +
                        "</node>" +
                        "<node id=\"3\">" +
                            "<nodeName>node3</nodeName>" +
                            "<node id=\"31\">" +
                                "<nodeName>node31</nodeName>" +
                            "</node>" +
                        "</node>" +
                        "<node id=\"4\">" +
                            "<nodeName>node4</nodeName>" +
                            "<node id=\"41\">" +
                                "<nodeName>node41</nodeName>" +
                            "</node>" +
                        "</node>" +
                    "</node>" +
                "</root>";

            XElement  element = XElement.Parse(input);
            doc = new XmlDocument();
            doc.LoadXml(element.ToString());
            XmlNode node = (XmlNode)doc;
            root.AddNode(node, root);


        }
    }
    public class Node
    {
        public static Node root { get; set; }
        public int id { get; set; }
        public string nodeName { get; set; }
        public List<Node> children { get; set; }
        public string text { get; set; }

        public void AddNode(XmlNode inXmlNode, Node inTreeNode)
        {
            // Loop through the XML nodes until the leaf is reached.
            // Add the nodes to the TreeView during the looping process.

            if (inXmlNode.HasChildNodes)
            {
                //Check if the XmlNode has attributes
                if (inXmlNode.Attributes != null)
                {
                    foreach (XmlAttribute att in inXmlNode.Attributes)
                    {
                        string name = att.Name;
                        if (name == "id")
                        {
                            inTreeNode.id = int.Parse(att.Value);
                        }
                    }
                }

                var nodeList = inXmlNode.ChildNodes;
                for (int i = 0; i < nodeList.Count; i++)
                {
                    XmlNode xNode = inXmlNode.ChildNodes[i];
                    Node tNode = null;
                    //use nodeName to determine if node is root;
                    if (inTreeNode.nodeName == null)
                    {
                        tNode = inTreeNode;
                    }
                    else
                    {
                        tNode = new Node();
                        if (inTreeNode.children == null)
                            inTreeNode.children = new List<Node>();
                        inTreeNode.children.Add(tNode);
                    }
                    tNode.nodeName = xNode.Name;
                    AddNode(xNode, tNode);
                }
            }
            else
            {
                // Here you need to pull the data from the XmlNode based on the
                // type of node, whether attribute values are required, and so forth.
                inTreeNode.text = (inXmlNode.OuterXml).Trim();
            }

        }
    }
}
​

Upvotes: 0

dbc
dbc

Reputation: 117275

You asked for how to do this with Linq. One option is to skip your node class entirely and use Linq to XML:

        var root = XElement.Parse(xmlString); // Load into an XElement
        var names1 = root
            .Descendants("nodeName") // Find all elements named "nodeName"
            .Select(n => new string(' ', 4 * (n.AncestorsAndSelf().Count() - 3)) + n.Value); // Output their value indented by the depth, subtracting 3 for outer wrapper elements.

If you wish to use your intermediate node class, you will need to extend Linq with, for instance, the following method to recursively traverse a hierarchy yielding an enumerable of the chain of all items from the top to the bottom at each node:

public static class RecursiveEnumerableExtensions
{
    static IEnumerable<T> PushHierarchy<T>(
        T value,
        Func<T, IEnumerable<T>> children,
        List<KeyValuePair<T, IEnumerator<T>>> list)
    {
        list.Add(new KeyValuePair<T, IEnumerator<T>>(value, children(value).GetEnumerator()));
        return list.Select(pair => pair.Key);
    }

    public static IEnumerable<IEnumerable<T>> TraverseHierarchy<T>(
        T root,
        Func<T, IEnumerable<T>> children)
    {
        var list = new List<KeyValuePair<T, IEnumerator<T>>>();
        try
        {
            yield return PushHierarchy(root, children, list);
            while (list.Count != 0)
            {
                var node = list[list.Count - 1];
                if (!node.Value.MoveNext())
                {
                    list.RemoveAt(list.Count - 1);
                    node.Value.Dispose();
                }
                else
                {
                    yield return PushHierarchy(node.Value.Current, children, list);
                }
            }
        }
        finally
        {
            foreach (var pair in list)
                pair.Value.Dispose();
        }
    }
}

And then use it like:

        var root2 = xmlString.LoadFromXML<root>();
        var names2 = RecursiveEnumerableExtensions.TraverseHierarchy(root2.node, n => n.children ?? Enumerable.Empty<node>()).Select(l => new string(' ', 4 * (l.Count() - 1)) + l.Last().nodeName);

Using the following to deserialize:

public class node
{
    public int id;
    public string nodeName;
    [System.Xml.Serialization.XmlElement("node")] // This is missing from your class definition
    public List<node> children;
}

public class root
{
    public node node { get; set; }
}

public static class XmlSerializationHelper
{
    public static T LoadFromXML<T>(this string xmlString)
    {
        using (StringReader reader = new StringReader(xmlString))
        {
            object result = new XmlSerializer(typeof(T)).Deserialize(reader);
            if (result is T)
            {
                return (T)result;
            }
        }
        return default(T);
    }
}

Upvotes: 0

StriplingWarrior
StriplingWarrior

Reputation: 156728

Hierarchical logic like this is usually easiest accomplished using a recursive method:

public void Output(IEnumerable<XElement> nodes, int depth)
{
    foreach(var node in nodes)
    {
        Console.WriteLine(new string('\t', depth) + node.Element("nodeName").Value);
        Output(node.Elements("node"), depth + 1);
    }
}

Usage:

var root = XElement.Parse(xml);
Output(root.Elements("node"), 0);

Upvotes: 1

Related Questions