使用LINQ将flat XML转换为层次结构

时间:2017-09-14 17:07:54

标签: c# .net xml linq xmldocument

我有一个 flat 格式的XML文档:

<root>
   <node-one>
      <parent-id />
      <node-id>1</node-id>
      <value>foo</value>
   </node-one>
   <node-two>
      <parent-id>1</parent-id>
      <node-id>2</node-id>
      <value>bar</value>
   </node-two>
   <node-three>
      <parent-id>1</parent-id>
      <node-id>3</node-id>
      <value>baz</value>
   </node-three>
   <node-four>
      <parent-id>3</parent-id>
      <node-id>4</node-id>
      <value>qux</value>
   </node-four>
</root>

我想将它转换为像这样的分层树状结构:

<root>
   <node-one>
      <parent-id />
      <node-id>1</node-id>
      <value>foo</value>
      <node-two>
         <parent-id>1</parent-id>
         <node-id>2</node-id>
         <value>bar</value>
      </node-two>
      <node-three>
         <parent-id>1</parent-id>
         <node-id>3</node-id>
         <value>baz</value>
         <node-four>
            <parent-id>3</parent-id>
            <node-id>4</node-id>
            <value>qux</value>
         </node-four>
      </node-three>
   </node-one>
</root>

使用XmlDocument/XDocument是否有一种优雅的方式来实现它? 任何帮助都会非常感激。

2 个答案:

答案 0 :(得分:1)

尝试递归算法

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


namespace ConsoleApplication4
{
    class Program
    {
        const string FILENAME = @"c:\temp\test.xml";
        static List<XElement> nodes;
        static void Main(string[] args)
        {
            XDocument doc = XDocument.Load(FILENAME);

            nodes = doc.Root.Elements().ToList();

            XElement parent = new XElement("root");
            RecursvieAdd(parent, "");
            XDocument doc2 = new XDocument(parent);
        }
        static void RecursvieAdd(XElement parent, string parentId)
        {
            foreach(XElement child in nodes.Where(x => (string)x.Element("parent-id") == parentId))
            {
               XElement newChild = new XElement(child);
               parent.Add(newChild);
               string id = (string)child.Element("node-id");
               RecursvieAdd(newChild, id);
            }

        }
    }

}

答案 1 :(得分:0)

您可以使用XPaths或Linq来完成此操作。自从我最近使用XPath以来,我将向您展示如何采用这种方式。

(您可能必须在参考浏览器中添加对System.Xml的一些引用。)

检查此示例代码(包括提示的注释):

using System;
using System.Linq;
using System.Xml.Linq;
using System.Xml.XPath;

namespace xmlTest
{
    internal class Program
    {
        public static void Main(string[] args)
        {
            var document = CreateDocument();

            // Gets all children of the root element whose name starts with "node".
            var nodeElements = document.XPathSelectElements("/root/*[starts-with(name(), 'node')]").ToList();
            // Creates Tuples in the fashion: { 1, $NODE_ONE }, { 2, $NODE_TWO }, ... 
            // This is done because some values might be skipped.
            var indexedNodes = nodeElements.Select(x => new Tuple<int, XElement>(int.Parse(x.Descendants("node-id").First().Value), x)).ToList();

            foreach(var indexedNode in indexedNodes)
            {
                var parentId = GetParentNodeId(indexedNode.Item2);
                if (parentId != null)
                {
                    // Remove the node from its parent.
                    indexedNode.Item2.Remove();
                    // Add the node to the new parent.
                    var newParent = indexedNodes.First(x => x.Item1 == parentId).Item2;
                    newParent.Add(indexedNode.Item2);
                }
            }
            Console.WriteLine(document.ToString());
        }

        static int? GetParentNodeId(XElement element) {
            try {
                var parentId = int.Parse(element.Descendants("parent-id").First().Value);
                return parentId;
            }
            catch // Add some appropriate error handling here. 
            {
                return null;
            }
        }

        private static XDocument CreateDocument()
        {
            const string xml =
                "<root> <node-one> <parent-id /> <node-id>1</node-id> <value>foo</value> </node-one> <node-two> <parent-id>1</parent-id> <node-id>2</node-id> <value>bar</value> </node-two> <node-three> <parent-id>1</parent-id> <node-id>3</node-id> <value>baz</value> </node-three> <node-four> <parent-id>3</parent-id> <node-id>4</node-id> <value>qux</value> </node-four> </root>";
            return XDocument.Parse(xml);
        }
    }
}

从输出中可以看出,您对元素所做的所有更改都已反映在XDocument中。这是因为在使用X-classes时,所有更改都已到位。

编辑:您需要更改一些代码才能将其包含在您的应用中,例如: CreateDocument方法,但这应该很容易:)