如何使用Linq读取分层xml并打印可以指示分层结构的结果
本文关键字:分层 指示 结构 结果 Linq 何使用 读取 xml 打印 | 更新日期: 2023-09-27 18:09:53
我有一个像下面这样的XML文件
<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>
我想知道如何使用Linq读取节点层次结构的xml文件。我已经为节点对象创建了一个节点类。
class node{
int id;
string nodeName;
List<node> children;
}
输出应该像
node1
node2
node21
node22
node3
node31
node4
node41
有什么建议吗?谢谢。
可以使用递归方法构建节点。请注意,由于.NET中的约定是使用pascal大小写,因此我更改了node
类中的名称,并引入了一个构造函数:
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; }
}
然后可以将XML解析为节点,如下所示:
var doc = XDocument.Parse(xml);
var nodes = doc.Root.Elements().Select(NodeFrom).ToList();
其中NodeFrom
的定义如下:
private static Node NodeFrom(XElement element)
{
return new Node(
(int) element.Attribute("id"),
(string) element.Element("nodeName"),
element.Elements("node").Select(NodeFrom).ToList()
);
}
查看这里的工作演示:https://dotnetfiddle.net/E5MQme
像这样的层次逻辑通常最容易使用递归方法完成:
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);
}
}
用法:
var root = XElement.Parse(xml);
Output(root.Elements("node"), 0);
你问如何用Linq做到这一点。一种选择是完全跳过node
类,使用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.
如果您希望使用中间的node
类,则需要扩展Linq,例如,使用以下方法递归地遍历层次结构,从而在每个节点上从上到下生成所有项目链的可枚举对象:
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();
}
}
}
然后像这样使用:
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);
使用以下语句反序列化:
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);
}
}
完整的解决方案见下文。我使用了在另一篇文章中发现的递归方法,将递归XML文件读取到树视图中,并根据您的需求进行修改。
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();
}
}
}
}