2017-10-06 4 views
-5

私はid NAME { CONTENT }はネスト可能なオブジェクトのパターンであり、このC#で文字列からネストされた構造体を解析する方法は?

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

のような構造の文字列を持っています。オープンソースパッケージを使用してC#.NETにそのようなオブジェクトツリーを解析する方法

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

:所望の目標は次のようになりますか?

+1

再帰アルゴリズムを使用します。 – jdweng

+10

パーサーを書く...第2の考えでは、なぜこれを使うのですか? JSONまたはYAMLを使用します。すでに書かれていて利用可能なパーサ –

+0

文法は単に 'id NAME {CONTENT}'以上のものです。このデータを解析する方法の詳細を提供する必要があります。 – Enigmativity

答えて

3

ないこの問題を解決する最もパフォーマンスの方法、またクリーナー、また最も包括的な...おそらく短いです。

これはregexスタックを使用しています(.Net regexエンジンでのみ有効です)。

output

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

(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がバランスしている場合、これはチェックの別のテキストブックの問題に似ているようです。基本的には...あなたが開く括弧に遭遇したとき、文字列を解析し、スタックを取るオブジェクトが作成されていることを理解しています。 Closing括弧はオブジェクトを完全なものとしてマークするのに役立ちます。 T.S @

3

。コメントで指摘車輪の再発明するための良い理由はほとんど、教育目的から離れて、ありません。それは書き込みパーサに来るとき、それは、ハード権利を取得するために作るそれらのコーナーケースです:

  • フォーマットの検証、より具体的に割り当て、パフォーマンスのためにコードを最適化する
  • ユーザーに優しい メッセージを返し、並行性、...

つまり、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 
関連する問題