如何将此文本文件内容转换为可以绑定到 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;
}
}
}
答案 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
的列表。这样,您就可以在树视图中绑定到子集合。
对于解析,您应该维护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对象列表,每个级别一个,这些对象将作为父级。
所以,算法:
这应该会给你树的结构。您需要在每个对象中保留一个子列表。
另请注意,如果您希望它处理文件中的错误,上面的列表将需要额外的错误检查,如下所示:
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