2017-10-06 131 views
-5

我有結構性這樣如何從C#中的字符串解析嵌套結構?

id i { 
    any data; any [con=tent] 
    id j { 
     any inner data 
    } 
    id k { 
     bla 
     id m { 
      any 
     } 
    thing } 
} 

id NAME { CONTENT }是嵌套對象的模式的字符串。期望的目標將是這樣的:

public class Node { 
    public List<Node> InnerNodes; 
    public string Contents; 
    public string Name; 
} 

如何使用開源軟件包使用C#.NET解析這樣的對象樹?

+1

使用遞歸算法。 – jdweng

+10

第二個想法寫一個解析器......爲什麼要用這個?使用JSON或YAML。解析器已經寫入並可用 –

+0

這裏的語法不僅僅是'id NAME {CONTENT}'。您需要提供如何解析此數據的完整詳細信息。 – Enigmativity

回答

3

不是最高效的方式來解決這個問題,也沒有清潔劑,也不是最全面的...但也許更短。

這使用正則表達式堆棧(我認爲只能用.Net正則表達式引擎)。 A good tutorial about those, applied to nested constructions matching here

void Main() 
{ 
    var input = @"id i { 
     any data; any [con=tent] 
     id j { 
      any inner data 
     } 
     id k { 
      bla 
      id m { 
       any 
      } 
     thing } 
    }"; 
    Process(input).Dump(); 
} 

public class Node { 
    public List<Node> InnerNodes; 
    public string Contents; 
    public string Name; 
} 

Regex reg = new Regex(@" 
    id\s+(?<name>\w+)\s*\{(?<body>(?<DEPTH>) 
     (?>(?<DEPTH>)\{ | \}(?<-DEPTH>) | (?(DEPTH)[^\{\}]* |))* 
     )\}(?<-DEPTH>)(?(DEPTH)(?!)) 
    ", RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled); 

List<Node> Process(string body){ 
    return reg.Matches(body) 
     .Cast<Match>() 
     .Select(x=>new Node{ 
      Name=x.Groups["name"].Value, 
      Contents=x.Groups["body"].Value.Trim(), 
      InnerNodes=Process(x.Groups["body"].Value), 
     }).ToList(); 
} 

其輸出

output

(NB:所述.Dump()是,僅僅轉儲所生成的對象的Linqpad擴展)

-3

它看起來就像你正在試圖分析包含C#兼容的指令串。根據您打算如何處理這些信息做,它可能是有意義的使用Roslyn compiler for static analysis

你可以得到一個語法樹是這樣的...如何操作樹

using Microsoft.CodeAnalysis; 
using Microsoft.CodeAnalysis.CSharp; 
using Microsoft.CodeAnalysis.CSharp.Syntax; 

SyntaxTree tree = CSharpSyntaxTree.ParseText(
@"using System; 
using System.Collections.Generic; 

public class Node { 
    public List<Node> InnerNodes; 
    public string Contents; 
    public string Name; 
}"); 

var root = (CompilationUnitSyntax)tree.GetRoot(); 

說明在GitHub Wiki中提供。

https://github.com/dotnet/roslyn/wiki/Getting-Started-C%23-Syntax-Analysis

1

你有幾個選擇:

  1. 學習寫,寫,對於文檔類型的分析器。有很多像HackerRank這樣的在線資源可以幫助你做到這一點。如果你想採取這條路線,我會建議看一下弦樂部分,因爲他們中的很多人都可以幫助教授這種操作。雖然這是迄今爲止最困難的道路,但您學習它會更好。

  2. 使用庫。 C#中有許多不同的解析器庫,每個解析器庫都有自己的優點和缺點。我此刻的個人最喜歡的是SuperPower,這似乎是最簡單的一元解析庫只選擇和使用,而不必知道了一大堆關於函數式編程或一般的解析。

如果我更瞭解情況,我可以提供更好的指導,但是您提供的信息非常薄。這將是有用的信息:

  • 大小/速度要求。
  • 文檔結構的更精確的定義。
2

這似乎相似,如果Paranthesis是平衡檢查的另一個教科書問題。基本上是一個堆棧,解析字符串,當遇到開頭的括號時......理解一個對象正在被創建。並且關閉支架有助於將對象標記爲完整。

3

@ T.S。在評論中指出,除了教育目的,很少有重新發明輪子的好理由。當談到寫作解析器,正是這些極端案例,這使得它很難得到正確:格式

  • 驗證,更具體的返回友好 消息給用戶
  • 優化代碼的性能,分配,併發性,...

這就是說,在C#7支持模式匹配的情況下,基本的管道應該很容易實現。第一站,Node類,加入初始化。

public class Node 
{ 
    public Node(string name) 
    { 
     Name = name; 
     InnerNodes = new List<Node>(); 
    } 
    public string Name { get; } 
    public string Contents { get; set; } 
    public List<Node> InnerNodes { get; } 
} 

接下來,爲我們的語法的不同部分創建包裝。

internal abstract class Token 
{ 
} 

internal class OpenNodeToken : Token 
{ 
    public OpenNodeToken(string name) { Name = name; } 
    public string Name { get; } 
} 

internal class CloseNodeToken : Token 
{ 
} 

internal class ContentToken : Token 
{ 
    public ContentToken(string text) { Text = text; } 
    public string Text { get; } 
} 

還有一個幫助器類,用於將輸入字符串轉換爲令牌序列。

internal static class Tokenizer 
{ 
    public static IEnumerable<Token> Scan (string expression) 
    { 
     var words = new Queue<string> 
      (expression.Split(new[] { ' ', '\n', '\r', '\t' }, StringSplitOptions.RemoveEmptyEntries)); 

     while (words.Any()) 
     { 
      string word = words.Dequeue(); 
      switch (word) 
      { 
       case "id": 
        yield return new OpenNodeToken(words.Dequeue()); 
        words.Dequeue(); 
        break; 

       case "}": 
        yield return new CloseNodeToken(); 
        break; 

       default: 
        yield return new ContentToken(word); 
        break; 
      } 
     } 
    } 
} 

我爲了離隊多串一氣呵成的id情況下使用的Queue

最後,解析器,其中模式匹配允許簡潔的代碼。

public static class Parser 
{ 
    private static Node currNode; 
    private static Stack<Node> prevNodes; 
    private static IEnumerable<Token> tokens; 

    static Parser() 
    { 
     prevNodes = new Stack<Node>(); 
    } 

    public static Node Deserialize(string input) 
    { 
     tokens = Tokenizer.Scan(input); 
     if (!(tokens.FirstOrDefault() is OpenNodeToken rootToken)) 
      throw new FormatException("Missing root node"); 
     currNode = new Node(rootToken.Name); 

     foreach(Token token in tokens.Skip(1)) 
     { 
    switch (token)   
      { 
       case ContentToken c: 
        string s = string.IsNullOrEmpty(currNode.Contents) ? c.Text : " " + c.Text; 
        currNode.Contents += s; 
        break; 

       case OpenNodeToken n: 
        prevNodes.Push(currNode); 
        currNode = new Node(n.Name); 
        break; 

       case CloseNodeToken c: 
        if (prevNodes.Any()) 
        { 
         Node childNode = currNode; 
         currNode = prevNodes.Pop(); 
         currNode.InnerNodes.Add(childNode); 
        } 
        break; 

       default: throw new NotImplementedException(token.GetType().Name); 
      } 
     } 
     return currNode; 
    } 
} 

下面我們用一個Stack解析孩子之前父節點推入。一旦孩子的右括號被滿足,我們彈出堆棧的父親並將孩子添加到它的集合中。

如上所述,一個體面的解析器也應該覆蓋角落案例,我已經將這些實現的樂趣留給了讀者。爲了測試解析器,我在下面設置了控制檯項目。

namespace MySimpleParser 
{ 
    class Program 
    { 
     public static void Main(string[] args) 
     { 
      string s = GetInput(); 
      try 
      { 
       Node root = Parser.Deserialize(s); 
       PrintBranch(root, 1); 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine($"{ex.GetType().Name}: {ex.Message}"); 
      } 

      Console.ReadLine(); 
     } 

     internal static string GetInput() 
     { 
      return @"id i { 
         any data; any [con=tent] 
         id j { 
          any inner data 
         } 
         id k { 
          bla 
          id m { 
           any 
          } 
          thing 
         } 
        }"; 
     } 

     internal static void PrintNode(Node n, int depth) 
     { 
      string indent = new string('-', 3 * depth); 
      Console.WriteLine($"{indent} Name: {n.Name}"); 
      Console.WriteLine($"{indent} Contents: {n.Contents}"); 
      Console.WriteLine($"{indent} Child Nodes: {n.InnerNodes.Count}"); 
     } 

     internal static void PrintBranch(Node root, int depth) 
     { 
      PrintNode(root, depth); 
      foreach (Node child in root.InnerNodes) PrintBranch(child, depth + 1); 
     } 
    } 
} 

輸出

--- Name: i 
--- Contents: any data; any [con=tent] 
--- Child Nodes: 2 
------ Name: j 
------ Contents: any inner data 
------ Child Nodes: 0 
------ Name: k 
------ Contents: bla thing 
------ Child Nodes: 1 
--------- Name: m 
--------- Contents: any 
--------- Child Nodes: 0