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

时间:2010-02-23 16:29:21

标签: c# recursion treeview design-patterns

如何将此文本文件内容转换为可以绑定到 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;
        }
    }
}

6 个答案:

答案 0 :(得分:2)

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

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

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

编辑:在对代码进行测试之后,我已经找到了一个更好的版本。我仍然无法保证它在任何情况下都能正常工作。

答案 1 :(得分:1)

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

查看here示例。或here


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


关于您要添加的更改

if (topObject.Indent < oo.Indent) 
{ 
  topObject.OutlineObjects.Add(oo); 
  stack.Push(oo); 
} 
else 
{ 
  stack.Pop(); 
  stack.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); 
}

答案 2 :(得分:0)

简单。

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

所以,算法:

  1. 行创建对象
  2. 找到缩进级别(根对象将为0)
  3. 如果父项列表的元素数量少于+1级,则添加“null”元素,直到它足够(这意味着对于第一个根对象,添加“null”元素以使其具有1个元素)
  4. 使用您在1中创建的新对象替换该列表中的元素#level。(由于列表从0开始,根对象将是第一个)
  5. 如果等级是> 0,将其作为子项添加到父级[level-1],如果级别== 0,则将其添加为根对象
  6. 这应该会给你树的结构。您需要在每个对象中保留一个子列表。

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

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

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

答案 3 :(得分:0)

复合模式是我想到的第一件事......

答案 4 :(得分:0)

这是我的尝试,这是你最初的努力和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;
        }
    }
}

答案 5 :(得分:0)

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

我知道你发布这个已经有一段时间了;我无法找到原件,但我找到了一份存档here

我为了简洁起见对其进行了一些修改,并将其转换为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