Create a deep object graph with Linq for XML, refactoring?
I am writing a simple XML parser using LINQ to XML.
I want to have a TreeNode object (i.e. a simple tree structure) for each element in XML. I want every element to be strongly typed.
It looks ugly and redundant compared to the simple loop I used before (using System.XML). Is there a way to remove the excesses here?
XElement ops = XElement.Load(@"c:\temp\exp.xml");
Tree<Element> domain = new Tree<Element>();
domain.Root = new TreeNode<Element>();
var cells =
from cell in ops.Elements("cell")
select new
{
TreeNodeObj = new TreeNode<Element>
(new Cell((string)cell.Attribute("name"), (string)cell.Attribute("name"), null)),
XElem = cell
};
foreach (var cell in cells)
{
domain.Root.AddChild(cell.TreeNodeObj);
var agents =
from agent in cell.XElem.Elements("agent")
select new
{
TreeNodeObj = new TreeNode<Element>
(new Agent((string)agent.Attribute("name"), (string)agent.Attribute("name"), null)),
XElem = agent
};
foreach (var agent in agents)
{
cell.TreeNodeObj.AddChild(agent.TreeNodeObj);
var nas =
from na in agent.XElem.Elements("node-agent")
select new
{
TreeNodeObj = new TreeNode<Element>
(new NodeAgent((string)na.Attribute("name"), (string)na.Attribute("name"), null)),
XElem = agent
};
foreach (var na in nas)
{
agent.TreeNodeObj.AddChild(na.TreeNodeObj);
}
}
}
source to share
It's hard to answer this question completely without sample data and actual types, but I would refactor it as shown below.
In the original example, I am assuming that we do not want to mess with entity constructors ( Agent
etc.) and want to keep a separate model " TreeNode<T>
" by putting our objects inside the tree (instead of modifying entities to model things as related collections). I also assumed that we can accept more freedoms with TreeNode<T>
than we can with objects, so I introduced a constructor that accepts IEnumerable<...>
, as it allows us to use with LINQ subqueries:
XElement ops = XElement.Load(@"c:\temp\exp.xml");
Tree<Element> domain = new Tree<Element>(
from cell in ops.Elements("cell")
select new TreeNode<Element>(
new Cell(
(string)cell.Attribute("name"),
(string)cell.Attribute("name"), null
),
from agent in cell.Elements("agent")
select new TreeNode<Element>(
new Agent(
(string)agent.Attribute("name"),
(string)agent.Attribute("name"), null
),
from na in agent.Elements("node-agent")
select new TreeNode<Element>(
new NodeAgent(
(string)na.Attribute("name"),
(string)na.Attribute("name"), null
)
)
)
)
);
With the framework code below:
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
class Tree<T>
{
public TreeNode<T> Root { get; set; }
public Tree() { }
public Tree(IEnumerable<TreeNode<T>> children)
{
Root = new TreeNode<T>(children);
}
}
class TreeNode<T>
{
private List<TreeNode<T>> children;
public IList<TreeNode<T>> Children
{
get
{
if (children == null) children = new List<TreeNode<T>>();
return children;
}
}
private readonly T value;
public TreeNode() { }
public TreeNode(T value) { this.value = value; }
public TreeNode(T value, IEnumerable<TreeNode<T>> children)
: this(children)
{
this.value = value;
}
public TreeNode(IEnumerable<TreeNode<T>> children)
{
children = new List<TreeNode<T>>(children);
}
}
class Element { }
class Cell : Element {
public Cell(string x, string y, string z) { }
}
class Agent : Element {
public Agent(string x, string y, string z) { }
}
class NodeAgent : Element {
public NodeAgent(string x, string y, string z) { }
}
static class Program
{
static void Main()
{
XElement ops = XElement.Load(@"c:\temp\exp.xml");
Tree<Element> domain = new Tree<Element>(
from cell in ops.Elements("cell")
select new TreeNode<Element>(
new Cell(
(string)cell.Attribute("name"),
(string)cell.Attribute("name"), null
),
from agent in cell.Elements("agent")
select new TreeNode<Element>(
new Agent(
(string)agent.Attribute("name"),
(string)agent.Attribute("name"), null
),
from na in agent.Elements("node-agent")
select new TreeNode<Element>(
new NodeAgent(
(string)na.Attribute("name"),
(string)na.Attribute("name"), null
)
)
)
)
);
}
}
source to share
Without your classes and original xml, it will be difficult for you to provide you with the exact code you need, but this is how I like to structure my XML parsing:
XDocument d = XDocument.Parse(@"<a id=""7""><b><c name=""foo""/><c name=""bar""/></b><b/><b2/></a>");
var ae = d.Root;
var a = new A
{
Id = (int)ae.Attribute("id"),
Children = new List<B>(ae.Elements("b").Select(be => new B
{
Children = new List<C>(be.Elements("c").Select(ce => new C
{
Name = (string)ce.Attribute("name")
}))
}))
};
Given the xml:
<a>
<b>
<c name="foo"/>
<c name="bar"/>
</b>
<b/>
<b2/>
</a>
and classes:
class A
{
public int Id { get; set; }
public List<B> Children { get; set; }
}
class B
{
public List<C> Children { get; set; }
}
class C
{
public string Name { get; set; }
}
source to share