【问题标题】:Create XML based on text tree基于文本树创建 XML
【发布时间】:2014-01-31 21:23:29
【问题描述】:

我需要从这样的列表中选择:

/home
/home/room1
/home/room1/subroom
/home/room2
/home/room2/miniroom
/home/room2/bigroom
/home/room2/hugeroom
/home/room3

到一个 xml 文件。我曾尝试使用 LINQ to XML 来执行此操作,但我最终会感到困惑,不知道从那里做什么。非常感谢任何帮助!

编辑:

我希望 XML 文件看起来像这样:

<home>
   <room1>
      <subroom>This is a subroom</subroom>
   </room1>
   <room2>
      <miniroom>This is a miniroom</miniroom>
      <bigroom>This is a bigroom</bigroom>
      <hugeroom>This is a hugeroom</hugeroom>
   </room2>
   <room3></room3>
</home>

如果标签(“这是一个子房间”等)是可选的,则里面的文本是可选的,但真的很好!

【问题讨论】:

  • 您希望 xml 看起来如何?
  • 我会把它贴在我原来的帖子里。对不起。
  • 编辑您的问题并在其中放置“这就是我的 XML 的样子:[您想要的 XML 结果]”
  • 我已将其放入我的原始帖子中。
  • 好的,在你的文本中是“这是一个子房间”(或迷你房间等) - 这是从哪里来的?

标签: c# xml linq tree


【解决方案1】:

好的,伙计,这是一个解决方案。

一些注释和解释。

您的文本结构可以分成几行,然后再用斜杠分割成 XML 节点的名称。如果你以这种方式思考文本,你会得到一个“行”列表,分解成一个列表 名字。

/home

首先,第一行/home是XML的根;我们可以摆脱它,只创建一个以该名称作为根元素的 XDocument 对象;

var xDoc = new XDocument("home");

当然我们不想硬编码,但这只是一个例子。现在,开始真正的工作:

/home/room1/
/home/room1/bigroom
etc...

作为List&lt;T&gt;,它看起来像这样

myList = new List<List<string>>();
... [ add the items ]
myList[0][0] = home
myList[0][1] = room1

myList[1][0] = home
myList[1][1] = room1
myList[1][2] = bigroom

所以我们可以做的就是使用string.Split()多次将你的文本分成几行,然后分成每行的部分,最后得到一个包含@的多维数组样式List&lt;T&gt; 987654330@ 对象,在本例中为 List&lt;List&lt;string&gt;&gt;

首先让我们创建容器对象:

var possibleNodes = new List<List<string>>();

接下来,我们应该拆分行。我们将保存文本的变量称为“文本”。

var splitLines = text
    .Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)
    .ToList();

这给了我们一个列表,但我们的行仍然没有分解。让我们用斜杠 (/) 字符再次拆分它们。这是我们构建节点名称的地方。我们可以在 ForEach 中执行此操作,然后将其添加到我们的可能节点列表中:

splitLines.ForEach(l => 
    possibleNodes.Add(l
        .Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries)
        .ToList()
    )
);

现在,我们需要知道 XML 的深度。您的文字显示将有 3 个深度节点。节点深度是任何一个给定行节点的最大深度,现在存储在List&lt;List&lt;string&gt;&gt;;我们可以使用.Max() 方法得到这个:

var nodeDepth = possibleNodes.Max(n => n.Count);

最后的设置步骤:我们不需要第一行,因为它只是“家”,它将是我们的根节点。我们可以只创建一个XDocument 对象并将其第一行作为Root 的名称:

// Create the root node
XDocument xDoc = new XDocument(new XElement(possibleNodes[0][0]));

// We don't need it anymore
possibleNodes.RemoveAt(0);

好的,这里是真正的工作发生的地方,让我解释一下规则:

  1. 我们需要遍历外部列表和每个内部列表。
  2. 我们可以使用列表索引来了解要添加到哪个节点或要忽略哪些名称
  3. 我们需要保持适当的层次结构,而不是重复节点,一些 Xlinq 在这里可以提供帮助

循环 - 有关详细说明,请参阅 cmets:

// This gets us looping through the outer nodes
for (var i = 0; i < possibleNodes.Count; i++) 
{
    // Here we go "sideways" by going through each inner list (each broken down line of the text)
    for (var ii = 1; ii < nodeDepth; ii++)
    {
        // Some lines have more depth than others, so we have to check this here since we are looping on the maximum
        if (ii < possibleNodes[i].Count)
        {
            // Let's see if this node already exists
            var existingNode = xDoc.Root.Descendants().FirstOrDefault(d => d.Name.LocalName == (possibleNodes[i][ii]));

            // Let's also see if a parent node was created in the previous loop iteration. 
            // This will tell us whether to add the current node at the root level, or under another node
            var parentNode = xDoc.Root.Descendants().FirstOrDefault(d => d.Name.LocalName == (possibleNodes[i][ii - 1]));

            // If the current node has already been added, we do nothing (this if statement is not entered into)
            // Otherwise, existingNode will be null and that means we need to add the current node
            if (null == existingNode)
            {
                // Now, use parentNode to decide where to add the current node
                if (null == parentNode)
                {
                    // The parent node does not exist; therefore, the current node will be added to the root node.
                    xDoc.Root.Add(new XElement(possibleNodes[i][ii]));
                }
                else
                {
                    // There IS a parent node for this node! 
                    // Therefore, we must add the current node to the parent node 
                    // (remember, parent node is the previous iteration of the inner for loop on nodeDepth )
                    var newNode = new XElement(possibleNodes[i][ii]);
                    parentNode.Add(newNode);

                    // Add "this is a" text (bonus!) -- only adding this text if the current node is the last one in the list.
                    if (possibleNodes[i].Count -1 == ii)
                    {
                        newNode.Add(new XText("This is a " + newNode.Name.LocalName));
                    }
                }
            }
        }
    }
}

这里的好处是这段代码可以使用任意数量的节点并构建您的 XML。

要检查它,XDocument 有一个漂亮的 .ToString() 覆盖实现,它只会吐出它持有的所有 XML,所以你要做的就是:

Console.Write(xDoc.ToString());

而且,您会得到以下结果: (注意我添加了一个测试节点以确保它适用于超过 3 个级别)

您将在下面找到整个程序以及您的测试文本等,作为一个可行的解决方案:

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

namespace XmlFromTextString
{
    class Program
    {
        static void Main(string[] args)
        {
            // This simulates text from a file; note that it must be flush to the left of the screen or else the extra spaces 
            // add unneeded nodes to the lists that are generated; for simplicity of code, I chose not to implement clean-up of that and just 
            // ensure that the string literal is not indented from the left of the Visual Studio screen.
            string text =
@"/home
/home/room1
/home/room1/subroom
/home/room2
/home/room2/miniroom
/home/room2/test/thetest
/home/room2/bigroom
/home/room2/hugeroom
/home/room3";

            var possibleNodes = new List<List<string>>();

            var splitLines = text
                .Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)
                .ToList();

            splitLines.ForEach(l => 
                possibleNodes.Add(l
                    .Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries)
                    .ToList()
                )
            );

            var nodeDepth = possibleNodes.Max(n => n.Count);

            // Create the root node
            XDocument xDoc = new XDocument(new XElement(possibleNodes[0][0]));

            // We don't need it anymore
            possibleNodes.RemoveAt(0);

            // This gets us looping through the outer nodes
            for (var i = 0; i < possibleNodes.Count; i++)  
            {
                // Here we go "sideways" by going through each inner list (each broken down line of the text)
                for (var ii = 1; ii < nodeDepth; ii++)
                {
                    // Some lines have more depth than others, so we have to check this here since we are looping on the maximum
                    if (ii < possibleNodes[i].Count)
                    {
                        // Let's see if this node already exists
                        var existingNode = xDoc.Root.Descendants().FirstOrDefault(d => d.Name.LocalName == (possibleNodes[i][ii]));

                        // Let's also see if a parent node was created in the previous loop iteration. 
                        // This will tell us whether to add the current node at the root level, or under another node
                        var parentNode = xDoc.Root.Descendants().FirstOrDefault(d => d.Name.LocalName == (possibleNodes[i][ii - 1]));

                        // If the current node has already been added, we do nothing (this if statement is not entered into)
                        // Otherwise, existingNode will be null and that means we need to add the current node
                        if (null == existingNode)
                        {
                            // Now, use parentNode to decide where to add the current node
                            if (null == parentNode)
                            {
                                // The parent node does not exist; therefore, the current node will be added to the root node.
                                xDoc.Root.Add(new XElement(possibleNodes[i][ii]));
                            }
                            else
                            {
                                // There IS a parent node for this node! 
                                // Therefore, we must add the current node to the parent node 
                                // (remember, parent node is the previous iteration of the inner for loop on nodeDepth )
                                var newNode = new XElement(possibleNodes[i][ii]);
                                parentNode.Add(newNode);

                                // Add "this is a" text (bonus!) -- only adding this text if the current node is the last one in the list.
                                if (possibleNodes[i].Count -1 == ii)
                                {
                                    newNode.Add(new XText("This is a " + newNode.Name.LocalName));
                                    // For the same default text on all child-less nodes, us this:
                                    // newNode.Add(new XText("This is default text"));

                                }
                            }
                        }
                    }
                }
            }

            Console.Write(xDoc.ToString());
            Console.ReadKey();
        }
    }
}

【讨论】:

  • 为什么忽略最后一个节点(room3):var i = 1; i &lt; possibleNodes.Count - 1; i++?
  • 现在修复了,我也注意到了
  • 我对迟到的回复表示歉意,但这绝对是完美的。非常感谢,也谢谢你的详细回复。这真的很有帮助。
  • 嘿!我正在尝试对其进行编辑,以便没有孩子的所有内容都获得相同的默认文本,有什么建议吗?
  • @user3258945:请参阅我的答案底部的更新(在评论中).. 唯一没有孩子的是相同的节点,其中有“这是一个......”...... . 如果您希望文本相同,请从该行中删除 + newNode.Name.LocalName,并将“This is a”更改为您想要的默认文本。
【解决方案2】:

LINQ 魔法的时间到了?

// load file into string[]
var input = File.ReadAllLines("TextFile1.txt");

// in case you have more than one home in your file
var homes =
    new XDocument(
        new XElement("root",
             from line in input
             let items = line.Split(new[] { "/" }, StringSplitOptions.RemoveEmptyEntries)
             group items by items[0] into g
             select new XElement(g.Key,
                 from rooms in g.OrderBy(x => x.Length).Skip(1)
                 group rooms by rooms[1] into g2
                 select new XElement(g2.Key,
                     from name in g2.OrderBy(x => x.Length).Skip(1)
                     select new XElement(name[2], string.Format("This is a {0}", name[2]))))));

// get the right home
var home = new XDocument(homes.Root.Element("home"));

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-19
    • 1970-01-01
    • 2016-05-09
    • 2019-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多