如何使用递归将文本解析为树结构?

时间:2015-11-18 06:05:36

标签: c# parsing recursion

我是C#的新手,我完全陷入一个解析问题,我想使用递归但是我这样做的尝试让我无处可去。

我想阅读具有以下格式的文件:

root:
   fileA
   sub1:
      fileB
      fileC
      sub2:
         fileD
         fileE
      fileF
   fileG
   sub3:
      fileH

基本上,结束冒号(:)的行应该代表目录,不以冒号结束的行应该代表其父目录中的文件: fileA fileG 属于 root 目录, fileB fileC fileF 位于 sub1 目录,依此类推(位置由缩进/空格确定)。

因此,我想阅读这个文件,以及比我目前正在做的更好的方式具有类似结构的更复杂的文件(for循环和if语句的可怕混乱)。我正在为目录和文件使用简单的自定义类(我没有使用.NET类除了StreamReader以逐行读取文本文件)

我曾经在python中做过类似的事情,但由于某种原因,我无法用C#来解决这个问题,这很愚蠢,因为这是一个抽象的问题,语言特定的实现应该不重要。我想重要的是我对如何在这样的情况下最好地应用递归的理解不足。我是在正确的轨道上吗?我只是觉得有一种更优雅的方法可以将这个文件解析为我自己定义的类(在示例文本中保留树结构),我认为递归就是答案。我只是看不到它。

任何帮助都会受到赞赏,即使它不是一个答案,更多的是朝着正确的方向猛烈推动。或者轻轻一推。

C#中的示例代码尝试使用递归(不完整,但我希望它能够解决我想要做的事情):

public void buildDirectoryFromFile(string file)
{
    string line;
    StreamReader data = new StreamReader(file);           

    int depth = 0;
    while ((line = data.ReadLine()) != null)
    {
        depth = line.Length - line.TrimStart(' ').Length;
        parseTextIntoTree(line, depth);
    }            
}  


public void parseTextIntoTree(string line, int depth)
{    
   if (line.Contains(':'))
   {
      //Totally lost      
   }
   else
   {
       //Totally lost
   }         
}  

这种情况下的深度是指空格/缩进。字符串和左边距之间的空格越多,越深越'它在树上。

4 个答案:

答案 0 :(得分:1)

我没有太多解析布局风格或空白敏感语言的经验,但从我所经历的这样的任务不属于通常的解析解决方案。至少你必须超越无上下文的语言。

我决定为布局规则示例建模的方式是二叉树。节点是行内容。向左下降表示保持相同的缩进级别,而向右下降表示增加缩进级别。

 root
/   \
ε   fileA
   /     \
  sub1:   ε
  |    \___
  |        |
  fileG     fileB – ε
  |         |     
  sub3:     fileC – ε
  | \       |
  ε  fileH  sub2: —+
            |      |
            fileF  fileD
                   |
                   fileE

我已将您的源文件建模为这样一棵树。您还会注意到,就行顺序而言,树首先向右下降,然后向左下降。

我们现在拥有的是一种以大括号方式查看源的方法,与布局样式不同,可以使用任何语言分析工具进行解析。例如,假设我们要生成由解析器使用的标记。正如你的直觉暗示的那样,这可以很容易地递归完成。

  1. 发出根节点的令牌(如果节点是ε,那么我们不发出令牌)。
  2. 发出一个开放式支撑,然后递归右侧子树。
  3. 发出一个紧密的支撑,然后递归左边的子树。
  4. 就树遍历而言,这接近于从右到左的预订。但是,我们也在按顺序添加一个紧密支撑。

    我现在遵循此算法来标记示例。为简单起见,我只使用原始来源中的名称作为令牌名称,并将{}添加为令牌。

    (recursion_depth.step_number)
    (1.1) root
    (1.2) root {
    (2.1) root { fileA
    (2.2) root { fileA {
    (2.3) root { fileA { }
    (3.1) root { fileA { } sub1:
    (3.2) root { fileA { } sub1: {
    (4.1) root { fileA { } sub1: { fileB
    etc.
    

    最后到达(格式化为清晰)

    root {
      fileA { }
      sub1: {
        fileB { }
        fileC { }
        sub2: {
          fileD { }
          fileE { }
        }
        fileF { }
      }
      fileG { }
      sub3: {
        fileH { }
      }
    }
    

    从这里开始,我希望如何为您的语言构建抽象语法树更清楚。如果你想构建我提到的树,那么考虑保持一堆缩进级别。这需要一些思考,但我会告诉你。

答案 1 :(得分:0)

我们试试这个:

public void buildDirectoryFromFile(string file)
{
    string line;
    StreamReader data = new StreamReader(file);           

    List<string> lines = new List<string>();
    while ((line = data.ReadLine()) != null)
    {
        lines.Add(line);

    }    
    int lineProcessed = 0;
    ParseTextIntoTree(lines, ref lineProcessed, 0);        
}  

public const int PadCount = 3; // Your padding length in text file

public void ParseTextIntoTree(List<string> lineList, ref int lineProcessed, int depth)
{
    while(lineProcessed < lineList.Count)
    {
        string line = lineList[lineProcessed++];
        int lineDepth = line.Length - line.TrimStart(' ').Length;

        if(lineDepth < depth)
        {
            // If the current depth is lower than the desired depth
            // end of directory structure is reached. Do backtrack
            // and reprocess the line in upper depth
            lineProcessed--;
            return;
        }

        if(line.EndsWith(":"))
        {
            // Do something, perhaps create directory?
            ParseTextIntoTree(lineList, ref lineProcessed, depth + PadCount);
        }
        else
        {
            // Do something, perhaps create file?
        }
    }
}

答案 2 :(得分:0)

此代码适用于您发布的文件:

     //The top-level Node
    TreeNode root;

    //Temp value for processing in parseTextIntoTree
    TreeNode ParentNode;

    //Temp value for processing in parseTextIntoTree
    int actualdepth = -1;

    //The depth step between two layers
    int depthdifference = 3;

    public void buildDirectoryFromFile(string file)
    {
        string line;
        StreamReader data = new StreamReader(file);

        int depth = 0;
        while ((line = data.ReadLine()) != null)
        {
            depth = line.Length - line.TrimStart(' ').Length;
            parseTextIntoTree(line, depth);
        }

        this.treeView1.Nodes.Add(root);
    }

    public void parseTextIntoTree(string line, int depth)
    {
        //At the beginning define the root node
        if (root == null)
        {
            root = new TreeNode(line);
            ParentNode = root;
            actualdepth = depth;
        }
        else
        {
            //Search the parent node for the current depth
            while (depth <= actualdepth)
            {
                //One step down
                ParentNode = ParentNode.Parent;
                actualdepth -= depthdifference;
            }

            ParentNode = ParentNode.Nodes.Add(line.Trim());
            actualdepth = depth;
        }
    }

答案 3 :(得分:0)

试试这个。由于您指定了深度,因此将引用空格/缩进。因此

TreeStruc = @"root:
               fileA
               sub1:
                  fileB
                  fileC
                  sub2:
                     fileD
                     fileE
                  fileF
               fileG
               sub3:
                  fileH";
TreeStruc = Regex.Replace(TreeStruc, " ", "-");
TreeStruc = Regex.Replace(TreeStruc, "---", "-");
using(StringReader reader = new StringReader(TreeStruc))
{
    string line;
    while((line = reader.ReadLine()) != null)
    {
        int cnt = line.Count(f => f == '-');
        String str2Replace = new String('-', cnt);
        line = Regex.Replace(line, str2Replace == "" ? "-" : str2Replace, cnt.ToString() + "~ ");
        updatedTreeStruc = updatedTreeStruc + line + "\n";
    }
}

它会显示如下结果:

root:
1~ fileA
1~ sub1:
2~ fileB
2~ fileC
2~ sub2:
3~ fileD
3~ fileE
2~ fileF
1~ fileG
1~ sub3:
2~ fileH

现在从这里开始,我们已经知道了字符串前面的数字的深度。

我们可以用同样的方式分析字符串是文件夹还是文件。