Reputation: 31
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
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
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
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
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