3

C# でいくつかの入力を解析していますが、RegEx 処理で壁にぶつかっています。

免責事項: 私は正規表現の専門家ではありませんが、さらに学習しています。

次のような入力文字列があります。

ObjectType [プロパティ 1 = 値 1、プロパティ 2 = 値 2、プロパティ 3 = 別のオブジェクト タイプ [プロパティ 4 = 値 4]]

(不自然な値ですが、重要なことは、これらをネストできることです)。

文字列をトークン化するために次のことを行っています。

Regex Tokenizer = new Regex(@"([=\[\]])|(,\s)");
string[] tokens = Tokenizer.Split(s);

これで約98%正解です。これにより、既知の区切り記号で文字列が分割され、カンマの後に空白が続きます。

上記の例のトークンは次のとおりです。

ObjectType
[
property1
=
value1
,   
property2
=
value2
,
property3
=
AnotherObjectType
[
property4
=
some value4
]
]

しかし、私には2つの問題があります:

1) プロパティ値にはコンマを含めることができます。これは有効な入力です:

ObjectType [property1=This is a valid value, and should be combined,, property2=value2, property3=AnotherObjectType [property4=value4]]

property1= の後のトークンを次のようにしたいと思います。

This is a valid value, and should be combined,

そして、トークン内の空白を保持したいと思います。現在、コンマが見つかったときに分割されます。

2) 分割すると、カンマ トークンに空白が含まれます。可能であればこれを取り除きたいのですが、これはそれほど重要ではありません。

私はさまざまなオプションを試しましたが、それらはすべて私を部分的にそこに導きました。私が持っていた最も近いものはこれです:

    Regex Tokenizer = new Regex(@"([=\[\]])|(,\s)|([\w]*\s*(?=[=\[\]]))|(.[^=]*(?=,\s))");

区切り記号、コンマの後に空白、単語文字の後にリテラルの前の空白、およびコンマと空白の前のテキスト (= 記号は含まれません) を一致させるために。

分割を呼び出す代わりに一致を取得すると、次のようになります。

ObjectType
[
property1
=
value1
,   
property2
=
value2
,
property3
=
AnotherObjectType
[
property4
=
value4
]
]

property4 からの情報が欠落していることに注意してください。より複雑な入力では、次のようにトークンに閉じ括弧が含まれることがあります。 value4] なぜそうなっているのかわかりません。これを改善する方法についてのアイデアはありますか?

ありがとう、フィル

4

2 に答える 2

0

2 つの正規表現と再帰関数を使用してこれを行うことができますが、注意点が 1 つあります。特殊文字はエスケープする必要があります。私が見る限り"=""[""]"は特別な意味を持っているため、"\"これらの文字をプロパティ値の一部として表示するには、これらの文字の前に a を挿入する必要があります。コンマは「特別」とは見なされないことに注意してください。文字列の前のコンマ"property="は無視されますが、それ以外は特別な方法では扱われません (実際、プロパティ間ではオプションです)。

入力

ObjectType
[
    property1=value1,val\=value2   
    property2=value2 \[property2\=this is not an object\], property3=
        AnotherObjectType [property4=some 
value4]]

正規表現

「複雑な」タイプを検出するための正規表現 (タイプ名で始まり、その後に角括弧が続きます)。正規表現には、角括弧のバランスを取るメカニズムが含まれており、各開き括弧が閉じ括弧とペアになっていることを確認します (一致が早すぎたり遅すぎたりしないようにするため)。

^\s*(?<TypeName>\w+)\s*\[(?<Properties>([^\[\]]|\\\[|\\\]|(?<!\\)\[(?<Depth>)|(?<!\\)\](?<-Depth>))*(?(Depth)(?!)))\]\s*$

複合型内のプロパティを検出するための正規表現。これには、下位複合型のプロパティが誤って親によって消費されないように、バランスのとれた角括弧も含まれていることに注意してください。

(?<PropertyName>\w+)\s*=\s*(?<PropertyValue>([^\[\]]|\\\[|\\\]|(?<!\\)\[(?<Depth>)|(?<!\\)\](?<-Depth>))*?(?(Depth)(?!))(?=$|(?<!\\)\]|,?\s*\w+\s*=))

コード

private static Regex ComplexTypeRegex = new Regex( @"^\s*(?<TypeName>\w+)\s*\[(?<Properties>([^\[\]]|\\\[|\\\]|(?<!\\)\[(?<Depth>)|(?<!\\)\](?<-Depth>))*(?(Depth)(?!)))\]\s*$" );
private static Regex PropertyRegex = new Regex( @"(?<PropertyName>\w+)\s*=\s*(?<PropertyValue>([^\[\]]|\\\[|\\\]|(?<!\\)\[(?<Depth>)|(?<!\\)\](?<-Depth>))*?(?(Depth)(?!))(?=$|(?<!\\)\]|,?\s*\w+\s*=))" );

private static string Input = 
    @"ObjectType" + "\n" +
    @"[" + "\n" +
    @"    property1=value1,val\=value2   " + "\n" +
    @"    property2=value2 \[property2\=this is not an object\], property3=" + "\n" +
    @"        AnotherObjectType [property4=some " + "\n" + 
    @"value4]]";

static void Main( string[] args )
{
    Console.Write( Process( 0, Input ) );
    Console.WriteLine( "\n\nPress any key..." );
    Console.ReadKey( true );
}

private static string Process( int level, string input )
{
    var l_complexMatch = ComplexTypeRegex.Match( input );

    var l_indent = string.Join( "", Enumerable.Range( 0, level * 3 ).Select( i => " " ).ToArray() );

    var l_output = new StringBuilder();

    l_output.AppendLine( l_indent + l_complexMatch.Groups["TypeName"].Value );

    foreach ( var l_match in PropertyRegex.Matches( l_complexMatch.Groups["Properties"].Value ).Cast<Match>() )
    {
        l_output.Append( l_indent + "@" + l_match.Groups["PropertyName"].Value + " = " );

        var l_value = l_match.Groups["PropertyValue"].Value;

        if ( Regex.IsMatch( l_value, @"(?<!\\)\[" ) )
        {
            l_output.AppendLine();
            l_output.Append( Process( level + 1, l_value ) );
        }
        else
        {
            l_output.AppendLine( "\"" + l_value + "\"" );
        }

    }

    return l_output.ToString();
}

出力

ObjectType
@property1 = "value1,val\=value2  "
@property2 = "value2 \[property2\=this is not an object\]"
@property3 = 
   AnotherObjectType
   @property4 = "some value4"

区切り文字をエスケープできない場合、人間でさえそのような文字列を解析できるとは思えません。たとえば、プロパティ 3 の値がリテラル文字列と見なされるべきか、複雑な型と見なされるべきかを、人間はどのようにして確実に知ることができるでしょうか?

于 2013-02-06T16:18:30.727 に答える
0

これは、レクサーおよびパーサー ツールを使用して答えるのが最も簡単です。多くの人は、これらの「単純な」ユースケースには複雑すぎると主張していますが、私は常に、それらの方が明確で推論しやすいと感じています. 愚かな if ロジックに行き詰まることはありません。

C# の場合、GPLEXGPPGが適しているようです。それらを使用する理由については、こちらを参照してください。

あなたの場合、文法があります。これは、コンテキストに基づいて異なるトークン間の相互作用を定義する方法です。また、選択した言語とツールチェーンでこの文法を実装するための詳細もあります。文法は比較的簡単に定義できます。すでに非公式に定義しています。詳細はトリッキーな部分です。文法ビットを書き出す定義済みの方法を読み取り、実際にそれを実行するためのコードを生成するだけのフレームワークがあればいいと思いませんか?

これが、これらのツールが簡単に機能する方法です。ドキュメントは非常に短いので、最初から時間をかけてすべてを読んでください。

要するに、スキャナーとパーサーを宣言します。スキャナはテキスト ストリーム/ファイルを取り込み、一致するまでさまざまな正規表現と比較します。その一致はトークンとしてパーサーに渡されます。次に、次のトークンが照合され、テキスト ストリームが空になるまで繰り返し渡されます。

一致した各トークンには、任意の C# コードを添付できます。パーサーの各ルールも同様です。

私は通常 C# を使用しませんが、かなりの数のレクサーとパーサーを作成しました。原則はどの言語でも同じです。これはあなたの問題に対する最善の解決策であり、キャリアを通じて何度も何度も助けてくれるでしょう。

于 2013-02-06T19:08:40.620 に答える