简体   繁体   English

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

[英]Convert flat XML to hierarchical structure using LINQ

I have an XML document in a flat format: 我有一个平面格式的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>

I want to convert it to hierarchical tree-like structure like this: 我想将其转换为像这样的分层树状结构:

<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>

Is there an elegant way to achieve it using XmlDocument/XDocument ? 有没有使用XmlDocument/XDocument实现它的优雅方法? Any help would greatly appreciated. 任何帮助将不胜感激。

Try a recursive algorithm 尝试递归算法

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);
            }

        }
    }

}

You can either use XPaths or Linq to get this done. 您可以使用XPaths或Linq来完成此任务。 Since I used XPath recently I am going to show you how to go this way. 自从我最近使用XPath以来,我将向您展示如何使用这种方式。

(You might have to add some references to System.Xml in the reference browser.) (您可能必须在引用浏览器中向System.Xml添加一些引用。)

Check this sample code (including comments for hints): 检查以下示例代码(包括注释的提示):

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);
        }
    }
}

As you can see from the output all the changes you have done to the elements are already reflected in the XDocument. 从输出中可以看到,您对元素所做的所有更改已反映在XDocument中。 That's because when working with the X-classes all changed are made in place. 这是因为在使用X类时,所有更改都已就位。

Edit: You will need to change some code to include this into your application, eg the CreateDocument method but that should be easy enough :) 编辑:您将需要更改一些代码以将其包含在您的应用程序中,例如CreateDocument方法,但这应该很容易:)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM