如何使用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

有什么建议吗?谢谢。

如何使用Linq读取分层xml并打印可以指示分层结构的结果

可以使用递归方法构建节点。请注意,由于.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();
            }
        }
    }
}
​