如何将文本文件大纲列表转换为对象的递归集合?

发布于 2024-08-22 20:17:09 字数 3528 浏览 4 评论 0原文

如何将此文本文件内容转换为可以绑定到TreeView对象递归集合?即我想最终得到 3 个对象 的集合,第一个名为 countries 的集合,它包含三个子对象的集合:france德国意大利等等...

答案:感谢所有为此提供帮助的人,这是我成功解析此文本的代码大纲到 XAML 树中: http://tanguay.info/web/ index.php?pg=codeExamples&id=358

countries
-france
--paris
--bordeaux
-germany
-italy
subjects
-math
--algebra
--calculus
-science
--chemistry
--biology
other
-this
-that

下面的代码是我所得到的,但它没有正确处理父母的多个孩子。

using System;
using System.Collections.Generic;
using System.Text;

namespace TestRecursive2342
{
    class Program
    {
        static void Main(string[] args)
        {
            List<OutlineObject> outlineObjects = new List<OutlineObject>();

            //convert file contents to object collection
            List<string> lines = Helpers.GetFileAsLines();
            Stack<OutlineObject> stack = new Stack<OutlineObject>();
            foreach (var line in lines)
            {
                OutlineObject oo = new OutlineObject(line);

                if (stack.Count > 0)
                {
                    OutlineObject topObject = stack.Peek();
                    if (topObject.Indent < oo.Indent)
                    {
                        topObject.OutlineObjects.Add(oo);
                        stack.Push(oo);
                    }
                    else
                    {
                        stack.Pop();
                        stack.Push(oo);                        
                    }

                }
                else
                {
                    stack.Push(oo);
                }

                if(oo.Indent == 0)
                    outlineObjects.Add(oo);
            }

            outlineObjects.ForEach(oo => Console.WriteLine(oo.Line));

            Console.ReadLine();
        }
    }

    public class OutlineObject
    {
        public List<OutlineObject> OutlineObjects { get; set; }
        public string Line { get; set; }
        public int Indent { get; set; }

        public OutlineObject(string rawLine)
        {
            OutlineObjects = new List<OutlineObject>();
            Indent = rawLine.CountPrecedingDashes();
            Line = rawLine.Trim(new char[] { '-', ' ', '\t' });
        }
    }

    public static class Helpers
    {
        public static List<string> GetFileAsLines()
        {
            return new List<string> {
                "countries",
                "-france",
                "--paris",
                "--bordeaux",
                "-germany",
                "-italy",
                "subjects",
                "-math",
                "--algebra",
                "--calculus",
                "-science",
                "--chemistry",
                "--biology",
                "other",
                "-this",
                "-that"};
        }

        public static int CountPrecedingDashes(this string line)
        {
            int tabs = 0;
            StringBuilder sb = new StringBuilder();
            foreach (var c in line)
            {
                if (c == '-')
                    tabs++;
                else
                    break;
            }
            return tabs;
        }
    }
}

How can I convert this text file content into a recursive collection of objects that I can bind to a TreeView? i.e. I want to end up with a collection of 3 objects, the first one called countries which has a collection of three child objects: france, germany, italy, and so on...

ANSWER: thanks to all who helped out on this, here's my code that successfully parses this text outline into a XAML tree: http://tanguay.info/web/index.php?pg=codeExamples&id=358

countries
-france
--paris
--bordeaux
-germany
-italy
subjects
-math
--algebra
--calculus
-science
--chemistry
--biology
other
-this
-that

The code below is as far as I got it, but it is not dealing with multiple children of parents correctly.

using System;
using System.Collections.Generic;
using System.Text;

namespace TestRecursive2342
{
    class Program
    {
        static void Main(string[] args)
        {
            List<OutlineObject> outlineObjects = new List<OutlineObject>();

            //convert file contents to object collection
            List<string> lines = Helpers.GetFileAsLines();
            Stack<OutlineObject> stack = new Stack<OutlineObject>();
            foreach (var line in lines)
            {
                OutlineObject oo = new OutlineObject(line);

                if (stack.Count > 0)
                {
                    OutlineObject topObject = stack.Peek();
                    if (topObject.Indent < oo.Indent)
                    {
                        topObject.OutlineObjects.Add(oo);
                        stack.Push(oo);
                    }
                    else
                    {
                        stack.Pop();
                        stack.Push(oo);                        
                    }

                }
                else
                {
                    stack.Push(oo);
                }

                if(oo.Indent == 0)
                    outlineObjects.Add(oo);
            }

            outlineObjects.ForEach(oo => Console.WriteLine(oo.Line));

            Console.ReadLine();
        }
    }

    public class OutlineObject
    {
        public List<OutlineObject> OutlineObjects { get; set; }
        public string Line { get; set; }
        public int Indent { get; set; }

        public OutlineObject(string rawLine)
        {
            OutlineObjects = new List<OutlineObject>();
            Indent = rawLine.CountPrecedingDashes();
            Line = rawLine.Trim(new char[] { '-', ' ', '\t' });
        }
    }

    public static class Helpers
    {
        public static List<string> GetFileAsLines()
        {
            return new List<string> {
                "countries",
                "-france",
                "--paris",
                "--bordeaux",
                "-germany",
                "-italy",
                "subjects",
                "-math",
                "--algebra",
                "--calculus",
                "-science",
                "--chemistry",
                "--biology",
                "other",
                "-this",
                "-that"};
        }

        public static int CountPrecedingDashes(this string line)
        {
            int tabs = 0;
            StringBuilder sb = new StringBuilder();
            foreach (var c in line)
            {
                if (c == '-')
                    tabs++;
                else
                    break;
            }
            return tabs;
        }
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

你的背包 2024-08-29 20:17:09
public class Item
{
    public string Name;
    public Item Parent;
}

List<Item> Collection = new List<Item>();

public void Main()
{
    var DataSource = data.InnerText;

    StreamReader Reader = new StreamReader(MapPath("_test2.txt"));
    int LastLevel = 0;

    while (Reader.EndOfStream == false) {
        var line = Reader.ReadLine();
        var Level = line.Where((System.Object c) => c == "-").Count;
        Item LastItem = default(Item);

        if (Collection.Count != 0) {
            LastItem = Collection.Last();
        }

        if (Level == 0) {
            Collection.Add(new Item { Name = line });
            LastLevel = 0;
        }
        else if (Level - LastLevel == 1) {
            Collection.Add(new Item { Name = line, Parent = LastItem });
            LastLevel += 1;
        }
        else if (Level == LastLevel) {
            Collection.Add(new Item { Name = line, Parent = LastItem.Parent });
        }
        else if (Level < LastLevel) {
            var LevelDiff = LastLevel - Level;
            Item Parent = LastItem;

            for (i = 0; i <= LevelDiff; i++) {
                Parent = Parent.Parent;
            }

            LastLevel = Level;
            Collection.Add(new Item { Name = line, Parent = Parent });
        }
    }

    Reader.Close();
}

这应该可以解决问题。我在你的文本文件上测试了它。可能存在一些错误。测试一下并判断它是否有效。

编辑:实际上,经过进一步测试后发现这并不能按预期工作。您需要添加更多逻辑才能使其正常工作。我把这个留给你。

编辑:在对代码进行更多测试后,我得到了一个效果更好的版本。我仍然不能保证它在所有情况下都有效。

public class Item
{
    public string Name;
    public Item Parent;
}

List<Item> Collection = new List<Item>();

public void Main()
{
    var DataSource = data.InnerText;

    StreamReader Reader = new StreamReader(MapPath("_test2.txt"));
    int LastLevel = 0;

    while (Reader.EndOfStream == false) {
        var line = Reader.ReadLine();
        var Level = line.Where((System.Object c) => c == "-").Count;
        Item LastItem = default(Item);

        if (Collection.Count != 0) {
            LastItem = Collection.Last();
        }

        if (Level == 0) {
            Collection.Add(new Item { Name = line });
            LastLevel = 0;
        }
        else if (Level - LastLevel == 1) {
            Collection.Add(new Item { Name = line, Parent = LastItem });
            LastLevel += 1;
        }
        else if (Level == LastLevel) {
            Collection.Add(new Item { Name = line, Parent = LastItem.Parent });
        }
        else if (Level < LastLevel) {
            var LevelDiff = LastLevel - Level;
            Item Parent = LastItem;

            for (i = 0; i <= LevelDiff; i++) {
                Parent = Parent.Parent;
            }

            LastLevel = Level;
            Collection.Add(new Item { Name = line, Parent = Parent });
        }
    }

    Reader.Close();
}

This should do the trick. I tested it on your text file. There might be some bugs. Test it and tell if it works.

EDIT: Actually after further testing it turns out this does not work as expected. You need to add more logic to make it work. I leave that to you.

EDIT: After testing the code a bit more I have come to a version that works better. I still cannot guarantee that It will work under all circumstances.

寄与心 2024-08-29 20:17:09

您应该使您的 OutlineObject 包含子 OutlineObject 列表。这样您就可以绑定到树视图中的子集合。

请查看此处示例。或者此处


为了进行解析,您应该维护嵌套对象的 Stack
当您读取下一个 OutlineObject 时,请查看堆栈中最后一个 OutlineObject 的深度。如果您的级别更高,您可以将自己添加为该 OutlineObject 的子级,并将您的 OutlineObject 压入堆栈。如果您的级别相同,则删除该 OutlineObject 并推送您的对象。如果您的级别更大,请删除顶部堆栈 OutlineObject,然后重复检查。


关于您要添加的更改

if (topObject.Indent < oo.Indent) 
{ 
  topObject.OutlineObjects.Add(oo); 
  堆栈.Push(oo); 
} 
别的 
{ 
  堆栈.Pop(); 
  堆栈.Push(oo); 
}

...此代码不检查新对象的级别小于堆栈顶部的级别的情况。你需要:

...
else if (topObject.Indent == oo.Indent) 
{ 
  stack.Pop(); 
  stack.Push(oo); 
} 
else 
{ 
  while (stack.Top().Indent >= oo.Indent) 
    stack.Pop(); 
  stack.Push(oo); 
}

You should make your OutlineObject contain a list of child OutlineObjects. This way you can bind to the child collection in tree views.

Look here for an example. Or here.


For parsing, you should maintain a Stack<OutlineObject> of your nested objects.
When you read next OutlineObject, look at the depth of the last OutlineObject in the stack. If your level is greater, you add yourself as a child of that OutlineObject, and push your OutlineObject onto the stack. If your level is the same, you remove that OutlineObject and push your object instead. If your level is bigger, you remove that top stack OutlineObject, and repeat the check.


Regarding your change to add

if (topObject.Indent < oo.Indent) 
{ 
  topObject.OutlineObjects.Add(oo); 
  stack.Push(oo); 
} 
else 
{ 
  stack.Pop(); 
  stack.Push(oo); 
}

...this code doesn't check for the case when the level of new object is smaller than the level of stack top. You'll need:

...
else if (topObject.Indent == oo.Indent) 
{ 
  stack.Pop(); 
  stack.Push(oo); 
} 
else 
{ 
  while (stack.Top().Indent >= oo.Indent) 
    stack.Pop(); 
  stack.Push(oo); 
}
望喜 2024-08-29 20:17:09

简单的。

创建一个 OutlineObject 对象列表,每个级别一个,这些对象将作为父级。

因此,算法:

  1. 从行创建对象
  2. 查找缩进级别(对于根对象,缩进级别为 0)
  3. 如果父级列表的元素数量少于 level+1,则添加“null”元素,直到有足够的元素(这意味着对于第一个根对象,添加一个“null”元素以使其具有 1 个元素)将该
  4. 列表中的元素 #level 替换为您在 1 中创建的新对象。(由于该列表是从 0 开始的,因此根对象将是第一个)
  5. 如果级别> 0,将其作为子级添加到parents[level-1],如果level == 0,将其添加为根对象

这应该会给你你的树结构。您需要在每个对象中保留一个子列表。

另请注意,如果您希望上面的列表处理文件中的错误,则需要进行额外的错误检查,如下所示:

root
-child 1
--child 2
another root
--child 3 (note that we skipped a level)

在这种情况下,最后一个子级将添加为“child 1”的子级,而不是“另一个根”的子级”。

Simple.

Create a list of OutlineObject objects, one for each level, these will serve as the parents.

So, the algorithm:

  1. Create the object from the line
  2. Find the level of indentation (which will be 0 for root objects)
  3. If the list of parents has less than level+1 number of elements, add "null" elements until it has enough (which means that for the first root object, add a "null" element to make it have 1 element)
  4. Replace element #level in that list with the new object you created in 1. (since the list is 0-based, the root objects will be the first ones)
  5. If level is > 0, add it as a child to parents[level-1], if level == 0, add it as a root object

This should give you your tree structure. You will need to keep a child-list in each object.

Also note that the above list will need additional error checking if you want it to handle errors in the file, like this:

root
-child 1
--child 2
another root
--child 3 (note that we skipped a level)

In this case, the last child there will be added as a child of "child 1", not of "another root".

ぃ弥猫深巷。 2024-08-29 20:17:09

复合模式是我首先想到的......

The Composite pattern is the first thing that comes to mind for me...

白日梦 2024-08-29 20:17:09

这是我的尝试,它是你最初的努力加上 Diamandiev 的方法的结合。我还添加了一个递归 Output() 方法,它将有效地重现原始输入文件。

不幸的是,我无法完全理解堆栈方法,但有兴趣查看一个有效的示例。

请注意,这仅允许您给定的节点嵌套 3 层的示例。如果超过这个值,则需要修改 else if ((oo.Indent - lastItem.Indent) < 0) 检查。

using System;
using System.Collections.Generic;
using System.Text;

namespace TestRecursive2342
{
    class Program
    {
        static void Main(string[] args)
        {
            List<OutlineObject> outlineObjects = new List<OutlineObject>();

            //convert file contents to object collection 
            List<string> lines = Helpers.GetFileAsLines();

            OutlineObject lastItem = new OutlineObject();
            bool processOk = true;

            foreach (var line in lines)
            {
                OutlineObject oo = new OutlineObject(line);

                if (lastItem.Indent != -1)
                {
                    if (oo.Indent == 0 && lastItem.Indent != 0)
                    {
                        // we've got a new root node item, so add the last item's top level parent to the list
                        while (lastItem.parent != null)
                            lastItem = lastItem.parent;

                        outlineObjects.Add(lastItem);
                    }
                    else if ((oo.Indent - lastItem.Indent) == 1)
                    {
                        // new item is one level lower than the last item
                        oo.parent = lastItem;
                        lastItem.OutlineObjects.Add(oo);
                    }
                    else if (oo.Indent == lastItem.Indent)
                    {
                        // new item is at the same level as the last item
                        oo.parent = lastItem.parent;
                        lastItem.parent.OutlineObjects.Add(oo);
                    }
                    else if ((oo.Indent - lastItem.Indent) < 0)
                    {
                        // new item is above the last item, but not a root node
                        // NB: this only allows for an item to be two levels above the last item
                        oo.parent = lastItem.parent.parent;
                        lastItem.parent.parent.OutlineObjects.Add(oo);
                    }
                    else if ((oo.Indent - lastItem.Indent) > 1)
                    {
                        // missing node check
                        Console.WriteLine("ERROR: missing node in input file between \"{0}\" and \"{1}\"", lastItem.Line, oo.Line);
                        processOk = false;
                        break;
                    }
                }

                lastItem = oo;
            }

            if (processOk)
            {
                // flush the last item
                while (lastItem.parent != null)
                    lastItem = lastItem.parent;

                outlineObjects.Add(lastItem);

                outlineObjects.ForEach(oo => oo.Output());
            }

            Console.ReadLine();
        }
    }

    public class OutlineObject
    {
        public OutlineObject parent { get; set; }
        public List<OutlineObject> OutlineObjects { get; set; }

        public string Line { get; set; }
        public int Indent { get; set; }

        public void Output()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append('-', this.Indent);
            sb.Append(this.Line);

            Console.WriteLine(sb);

            foreach (OutlineObject oChild in this.OutlineObjects)
            {
                oChild.Output();
            }
        }

        public OutlineObject()
        {
            parent = null;
            OutlineObjects = new List<OutlineObject>();
            Line = "";
            Indent = -1;
        }

        public OutlineObject(string rawLine)
        {
            OutlineObjects = new List<OutlineObject>();
            Indent = rawLine.CountPrecedingDashes();
            Line = rawLine.Trim(new char[] { '-', ' ', '\t' });
        }
    }

    public static class Helpers
    {
        public static List<string> GetFileAsLines()
        {
            return new List<string> { 
                "countries", 
                "-france", 
                "--paris", 
                "--bordeaux", 
                "-germany", 
                "-italy", 
                "subjects", 
                "-math", 
                "--algebra", 
                "--calculus", 
                "-science", 
                "--chemistry", 
                "--biology", 
                "other", 
                "-this", 
                "-that"};
        }

        public static int CountPrecedingDashes(this string line)
        {
            int tabs = 0;

            foreach (var c in line)
            {
                if (c == '-')
                    tabs++;
                else
                    break;
            }
            return tabs;
        }
    }
}

Here's my attempt which is a combination of your original effort plus diamandiev's approach. I also added a recursive Output() method which will effectively reproduce the original input file.

Unfortunately I couldn't quite fathom the stack approach, but would be interested to see a working example.

Note that this only allows for your given example of nodes being nested 3 levels deep. Any more than that will require a modification to the else if ((oo.Indent - lastItem.Indent) < 0) check.

using System;
using System.Collections.Generic;
using System.Text;

namespace TestRecursive2342
{
    class Program
    {
        static void Main(string[] args)
        {
            List<OutlineObject> outlineObjects = new List<OutlineObject>();

            //convert file contents to object collection 
            List<string> lines = Helpers.GetFileAsLines();

            OutlineObject lastItem = new OutlineObject();
            bool processOk = true;

            foreach (var line in lines)
            {
                OutlineObject oo = new OutlineObject(line);

                if (lastItem.Indent != -1)
                {
                    if (oo.Indent == 0 && lastItem.Indent != 0)
                    {
                        // we've got a new root node item, so add the last item's top level parent to the list
                        while (lastItem.parent != null)
                            lastItem = lastItem.parent;

                        outlineObjects.Add(lastItem);
                    }
                    else if ((oo.Indent - lastItem.Indent) == 1)
                    {
                        // new item is one level lower than the last item
                        oo.parent = lastItem;
                        lastItem.OutlineObjects.Add(oo);
                    }
                    else if (oo.Indent == lastItem.Indent)
                    {
                        // new item is at the same level as the last item
                        oo.parent = lastItem.parent;
                        lastItem.parent.OutlineObjects.Add(oo);
                    }
                    else if ((oo.Indent - lastItem.Indent) < 0)
                    {
                        // new item is above the last item, but not a root node
                        // NB: this only allows for an item to be two levels above the last item
                        oo.parent = lastItem.parent.parent;
                        lastItem.parent.parent.OutlineObjects.Add(oo);
                    }
                    else if ((oo.Indent - lastItem.Indent) > 1)
                    {
                        // missing node check
                        Console.WriteLine("ERROR: missing node in input file between \"{0}\" and \"{1}\"", lastItem.Line, oo.Line);
                        processOk = false;
                        break;
                    }
                }

                lastItem = oo;
            }

            if (processOk)
            {
                // flush the last item
                while (lastItem.parent != null)
                    lastItem = lastItem.parent;

                outlineObjects.Add(lastItem);

                outlineObjects.ForEach(oo => oo.Output());
            }

            Console.ReadLine();
        }
    }

    public class OutlineObject
    {
        public OutlineObject parent { get; set; }
        public List<OutlineObject> OutlineObjects { get; set; }

        public string Line { get; set; }
        public int Indent { get; set; }

        public void Output()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append('-', this.Indent);
            sb.Append(this.Line);

            Console.WriteLine(sb);

            foreach (OutlineObject oChild in this.OutlineObjects)
            {
                oChild.Output();
            }
        }

        public OutlineObject()
        {
            parent = null;
            OutlineObjects = new List<OutlineObject>();
            Line = "";
            Indent = -1;
        }

        public OutlineObject(string rawLine)
        {
            OutlineObjects = new List<OutlineObject>();
            Indent = rawLine.CountPrecedingDashes();
            Line = rawLine.Trim(new char[] { '-', ' ', '\t' });
        }
    }

    public static class Helpers
    {
        public static List<string> GetFileAsLines()
        {
            return new List<string> { 
                "countries", 
                "-france", 
                "--paris", 
                "--bordeaux", 
                "-germany", 
                "-italy", 
                "subjects", 
                "-math", 
                "--algebra", 
                "--calculus", 
                "-science", 
                "--chemistry", 
                "--biology", 
                "other", 
                "-this", 
                "-that"};
        }

        public static int CountPrecedingDashes(this string line)
        {
            int tabs = 0;

            foreach (var c in line)
            {
                if (c == '-')
                    tabs++;
                else
                    break;
            }
            return tabs;
        }
    }
}
温柔一刀 2024-08-29 20:17:09

多么好的解决方案啊!这可以成为一个方便的小实用程序。太完美了。

我知道你发布这篇文章已经有一段时间了;我无法找到原件,但我找到了存档的副本 此处

为了简洁起见,我对其进行了一些修改,并将其翻译为 VB.NET,供那些可能感兴趣的人使用。

最终结果如下:

Module Main
  Sub Main()

    With New Test
      .Render()
    End With

    Console.WriteLine()
    Console.Write("Press any key to exit...")
    Console.ReadKey()
  End Sub
End Module

测试

Public Class Test
  Private ReadOnly Tree As Tree

  Public Sub New()
    Me.Tree = New Tree(Me.Text, "-", 1)
  End Sub



  Public Sub Render()
    Me.Render(Me.Tree.Nodes)
  End Sub



  Public Sub Render(Nodes As List(Of Node))
    Nodes.ForEach(Sub(Node As Node)
                    Console.WriteLine("{0}{1}", Space(Node.Level), Node.Text)

                    Me.Render(Node.Nodes)
                  End Sub)
  End Sub



  Private ReadOnly Property Text As String
    Get
      Return _
        "TEST DATA" & vbCrLf &
        "countries" & vbCrLf &
        "-france" & vbCrLf &
        "--paris" & vbCrLf &
        "--bordeaux" & vbCrLf &
        "-germany" & vbCrLf &
        "--hamburg" & vbCrLf &
        "--berlin" & vbCrLf &
        "--hannover" & vbCrLf &
        "--munich" & vbCrLf &
        "-italy" & vbCrLf &
        "subjects" & vbCrLf &
        "-math" & vbCrLf &
        "--algebra" & vbCrLf &
        "--calculus" & vbCrLf &
        "-science" & vbCrLf &
        "--chemistry" & vbCrLf &
        "--biology" & vbCrLf &
        "other" & vbCrLf &
        "-this" & vbCrLf &
        "-that"
    End Get
  End Property
End Class

Public Class Tree
  Private Level As Integer

  Public Sub New(Text As String, LevelIndicator As String)
    Me.New(Text, LevelIndicator, 0)
  End Sub



  Public Sub New(Text As String, LevelIndicator As String, StartingIndex As Integer)
    Me.Load(Text, LevelIndicator, StartingIndex)
  End Sub



  Public ReadOnly Property Nodes As List(Of Node)
    Get
      Return _Nodes
    End Get
  End Property
  Private ReadOnly _Nodes As New List(Of Node)



  Private Sub Load(Text As String, LevelIndicator As String, StartingIndex As Integer)
    Dim iLevel As Integer
    Dim oParents As Stack(Of Node)
    Dim oNode As Node

    oParents = New Stack(Of Node)

    Text.ToLines(StartingIndex).ForEach(Sub(Line As String)
                                          oNode = New Node(Line, LevelIndicator)

                                          If oNode.Level = 0 Then ' Root '
                                            Me.Nodes.Add(oNode)
                                            oParents.Push(oNode)
                                            Me.Level = 0

                                          ElseIf oNode.Level - Me.Level > 1 Then ' Skipped generation(s) '
                                            Throw New FormatException("The outline structure is invalid.")

                                          ElseIf oNode.Level = Me.Level Then ' Sibling '
                                            oParents.Pop()
                                            Me.Level = oParents.SetNode(oNode, Me.Level)

                                          ElseIf oNode.Level - Me.Level = 1 Then ' Child '
                                            Me.Level = oParents.SetNode(oNode, Me.Level + 1)

                                          ElseIf oNode.Level < Me.Level Then ' Walk back up the stack '
                                            For iLevel = 0 To Me.Level - oNode.Level
                                              oParents.Pop()
                                            Next

                                            Me.Level = oParents.SetNode(oNode, oNode.Level)

                                          End If
                                        End Sub)
  End Sub
End Class

节点

Public Class Node
  Public Sub New(Line As String, LevelIndicator As String)
    _Level = Line.PrefixCount(LevelIndicator)
    _Text = Line.StripPrefix(LevelIndicator)
  End Sub



  Public ReadOnly Property Nodes As List(Of Node)
    Get
      Return _Nodes
    End Get
  End Property
  Private ReadOnly _Nodes As New List(Of Node)



  Public ReadOnly Property Level As Integer
    Get
      Return _Level
    End Get
  End Property
  Private ReadOnly _Level As Integer



  Public ReadOnly Property Text As String
    Get
      Return _Text
    End Get
  End Property
  Private ReadOnly _Text As String
End Class

扩展

Public Module Extensions
  <Extension>
  Public Function PrefixCount(Text As String, Prefix As String) As Integer
    Dim iIndex As Integer

    PrefixCount = 0

    Do While Text.StartsWith(Prefix)
      iIndex = Text.IndexOf(Prefix)

      If iIndex = -1 Then
        Exit Do
      Else
        Text = Text.Substring(iIndex + Prefix.Length)
        PrefixCount += 1
      End If
    Loop
  End Function



  <Extension>
  Public Function StripPrefix(Text As String, Prefix As String) As String
    StripPrefix = Text

    Do While StripPrefix.StartsWith(Prefix)
      StripPrefix = StripPrefix.Substring(Prefix.Length)
    Loop
  End Function



  <Extension>
  Public Function ToLines(Text As String, StartingIndex As Integer) As List(Of String)
    Return Split(Text, vbCrLf).Where(Function(Line As String)
                                       Return Line.IsNullOrWhiteSpace = False
                                     End Function).Skip(StartingIndex).ToList
  End Function



  <Extension>
  Public Function SetNode(Parents As Stack(Of Node), Node As Node, Level As Integer) As Integer
    Parents.Peek.Nodes.Add(Node)
    Parents.Push(Node)

    Return Level
  End Function



  <Extension>
  Public Function ToFormat(Template As String, ParamArray Values As Object()) As String
    Return String.Format(Template, Values)
  End Function
End Module

What a great solution! This could make for a handy little utility. It's perfect.

I know it's been a while since you posted this; I wasn't able to locate the original but I found a copy archived here.

I've modified it a bit for brevity and translated it to VB.NET for those who may be interested.

Here's the end result:

Main

Module Main
  Sub Main()

    With New Test
      .Render()
    End With

    Console.WriteLine()
    Console.Write("Press any key to exit...")
    Console.ReadKey()
  End Sub
End Module

Test

Public Class Test
  Private ReadOnly Tree As Tree

  Public Sub New()
    Me.Tree = New Tree(Me.Text, "-", 1)
  End Sub



  Public Sub Render()
    Me.Render(Me.Tree.Nodes)
  End Sub



  Public Sub Render(Nodes As List(Of Node))
    Nodes.ForEach(Sub(Node As Node)
                    Console.WriteLine("{0}{1}", Space(Node.Level), Node.Text)

                    Me.Render(Node.Nodes)
                  End Sub)
  End Sub



  Private ReadOnly Property Text As String
    Get
      Return _
        "TEST DATA" & vbCrLf &
        "countries" & vbCrLf &
        "-france" & vbCrLf &
        "--paris" & vbCrLf &
        "--bordeaux" & vbCrLf &
        "-germany" & vbCrLf &
        "--hamburg" & vbCrLf &
        "--berlin" & vbCrLf &
        "--hannover" & vbCrLf &
        "--munich" & vbCrLf &
        "-italy" & vbCrLf &
        "subjects" & vbCrLf &
        "-math" & vbCrLf &
        "--algebra" & vbCrLf &
        "--calculus" & vbCrLf &
        "-science" & vbCrLf &
        "--chemistry" & vbCrLf &
        "--biology" & vbCrLf &
        "other" & vbCrLf &
        "-this" & vbCrLf &
        "-that"
    End Get
  End Property
End Class

Tree

Public Class Tree
  Private Level As Integer

  Public Sub New(Text As String, LevelIndicator As String)
    Me.New(Text, LevelIndicator, 0)
  End Sub



  Public Sub New(Text As String, LevelIndicator As String, StartingIndex As Integer)
    Me.Load(Text, LevelIndicator, StartingIndex)
  End Sub



  Public ReadOnly Property Nodes As List(Of Node)
    Get
      Return _Nodes
    End Get
  End Property
  Private ReadOnly _Nodes As New List(Of Node)



  Private Sub Load(Text As String, LevelIndicator As String, StartingIndex As Integer)
    Dim iLevel As Integer
    Dim oParents As Stack(Of Node)
    Dim oNode As Node

    oParents = New Stack(Of Node)

    Text.ToLines(StartingIndex).ForEach(Sub(Line As String)
                                          oNode = New Node(Line, LevelIndicator)

                                          If oNode.Level = 0 Then ' Root '
                                            Me.Nodes.Add(oNode)
                                            oParents.Push(oNode)
                                            Me.Level = 0

                                          ElseIf oNode.Level - Me.Level > 1 Then ' Skipped generation(s) '
                                            Throw New FormatException("The outline structure is invalid.")

                                          ElseIf oNode.Level = Me.Level Then ' Sibling '
                                            oParents.Pop()
                                            Me.Level = oParents.SetNode(oNode, Me.Level)

                                          ElseIf oNode.Level - Me.Level = 1 Then ' Child '
                                            Me.Level = oParents.SetNode(oNode, Me.Level + 1)

                                          ElseIf oNode.Level < Me.Level Then ' Walk back up the stack '
                                            For iLevel = 0 To Me.Level - oNode.Level
                                              oParents.Pop()
                                            Next

                                            Me.Level = oParents.SetNode(oNode, oNode.Level)

                                          End If
                                        End Sub)
  End Sub
End Class

Node

Public Class Node
  Public Sub New(Line As String, LevelIndicator As String)
    _Level = Line.PrefixCount(LevelIndicator)
    _Text = Line.StripPrefix(LevelIndicator)
  End Sub



  Public ReadOnly Property Nodes As List(Of Node)
    Get
      Return _Nodes
    End Get
  End Property
  Private ReadOnly _Nodes As New List(Of Node)



  Public ReadOnly Property Level As Integer
    Get
      Return _Level
    End Get
  End Property
  Private ReadOnly _Level As Integer



  Public ReadOnly Property Text As String
    Get
      Return _Text
    End Get
  End Property
  Private ReadOnly _Text As String
End Class

Extensions

Public Module Extensions
  <Extension>
  Public Function PrefixCount(Text As String, Prefix As String) As Integer
    Dim iIndex As Integer

    PrefixCount = 0

    Do While Text.StartsWith(Prefix)
      iIndex = Text.IndexOf(Prefix)

      If iIndex = -1 Then
        Exit Do
      Else
        Text = Text.Substring(iIndex + Prefix.Length)
        PrefixCount += 1
      End If
    Loop
  End Function



  <Extension>
  Public Function StripPrefix(Text As String, Prefix As String) As String
    StripPrefix = Text

    Do While StripPrefix.StartsWith(Prefix)
      StripPrefix = StripPrefix.Substring(Prefix.Length)
    Loop
  End Function



  <Extension>
  Public Function ToLines(Text As String, StartingIndex As Integer) As List(Of String)
    Return Split(Text, vbCrLf).Where(Function(Line As String)
                                       Return Line.IsNullOrWhiteSpace = False
                                     End Function).Skip(StartingIndex).ToList
  End Function



  <Extension>
  Public Function SetNode(Parents As Stack(Of Node), Node As Node, Level As Integer) As Integer
    Parents.Peek.Nodes.Add(Node)
    Parents.Push(Node)

    Return Level
  End Function



  <Extension>
  Public Function ToFormat(Template As String, ParamArray Values As Object()) As String
    Return String.Format(Template, Values)
  End Function
End Module
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文