5

疑似コードでの正規表現の知識は十分にありますが、php regex perl でやりたいことを翻訳するのに苦労しています。
preg_match を使用して式の一部を抽出しようとしています。
次の文字列が${classA.methodA.methodB(classB.methodC(classB.methodD)))}あり、2 つのことを行う必要があります。

を。構文を検証する

  • ${classA.methodA.methodB(classB.methodC(classB.methodD)))} 有効
  • ${classA.methodA.methodB} 有効
  • ${classA.methodA.methodB()} 有効ではありません
  • ${methodB(methodC(classB.methodD)))} 有効ではありません

b. ${classA.methodA.methodB(classB.methodC(classB.methodD)))}返される情報を抽出する必要があります

 1. classA
 2. methodA
 3. methodB(classB.methodC(classB.methodD)))

このコードを作成しました

$expression = '${myvalue.fdsfs.fsdf.blo(fsdf.fsfds(fsfs.fs))}';
$pattern = '/\$\{(?:([a-zA-Z0-9]+)\.)(?:([a-zA-Z\d]+)\.)*([a-zA-Z\d.()]+)\}/';
if(preg_match($pattern, $expression, $matches))
{
    echo 'found'.'<br/>';
    for($i = 0; $i < count($matches); $i++)
        echo $i." ".$matches[$i].'<br/>';
}

結果は次のとおりです。
見つかった
0 ${myvalue.fdsfs.fsdf.blo(fsdf.fsfds(fsfs.fs))}
1 myvalue
2 fsdf
3 blo(fsdf.fsfds(fsfs.fs))

明らかに、反復的なメソッドを抽出するのが難しく、適切に検証されていません (正直なところ、他の問題を解決したら最後に残しました)。そのため、空の括弧が許可され、括弧が開かれたかどうかを確認していません。閉じなければなりません。

皆さんありがとう

アップデート

X m.buettner

ご協力いただきありがとうございます。私はあなたのコードをすばやく試しましたが、バイパスすることはできますが、非常に小さな問題が発生します。この問題は、ここに投稿しなかった以前のコードの 1 つと同じです。これは、この文字列を試したときです。

$expression = '${myvalue.fdsfs}';

パターン定義を使用すると、次のように表示されます。

found
0 ${myvalue.fdsfs}
1 myvalue.fdsfs
2 myvalue
3 
4 fdsfs

ご覧のとおり、3 行目は存在しない空白として捉えられています。なぜそうしていたのか理解できなかったので、方法を教えてもらえますか、それともPHPの正規表現の制限のためにそれと一緒に暮らす必要がありますか?

とは言え、ありがとうとしか言いようがありません。あなたは私の問題に答えただけでなく、パターンを開発する際に従うべき適切な道筋について多くの提案をして、できるだけ多くの情報を入力しようとしました.

最後に、私が(愚かな)1つの小さな重要なケースを追加するのを忘れていました。これは、複数のパラメーターをコンマで割ったものです。

$expression = '${classA.methodAA(classB.methodBA(classC.methodCA),classC.methodCB)}';
$expression = '${classA.methodAA(classB.methodBA(classC.methodCA),classC.methodCB,classD.mehtodDA)}';

有効である必要があります。

私はこれに編集しました

    $expressionPattern =             
        '/
        ^                   # beginning of the string
        [$][{]              # literal ${
        (                   # group 1, used for recursion
          (                 # group 2 (class name)
            [a-z\d]+        # one or more alphanumeric characters
          )                 # end of group 2 (class name)
          [.]               # literal .
          (                 # group 3 (all intermediate method names)
            (?:             # non-capturing group that matches a single method name
              [a-z\d]+      # one or more alphanumeric characters
              [.]           # literal .
            )*              # end of method name, repeat 0 or more times
          )                 # end of group 3 (intermediate method names);
          (                 # group 4 (final method name and arguments)
            [a-z\d]+        # one or or more alphanumeric characters
            (?:             # non-capturing group for arguments
              [(]           # literal (
              (?1)          # recursively apply the pattern inside group 1
                (?:     # non-capturing group for multiple arguments        
                  [,]       # literal ,
                  (?1)      # recursively apply the pattern inside group 1 on parameters
                )*          # end of multiple arguments group; repeat 0 or more times
              [)]           # literal )
            )?              # end of argument-group; make optional
          )                 # end of group 4 (method name and arguments)  
        )                   # end of group 1 (recursion group)
        [}]                 # literal }
        $                   # end of the string
        /ix';   

X カジミール エ ヒッポリュテ

あなたの提案も良いですが、このコードを使用するときは少し複雑な状況を暗示しています。コード自体は簡単に理解できますが、柔軟性が低下します。そうは言っても、将来必ず役立つ多くの情報を私に与えてくれました.

X デノマレス

サポートしていただきありがとうございますが、これを試すとコードが落ちます:

$sourcestring='${classA1.methodA0.methodA1.methodB1(classB.methodC(classB.methodD))}';

結果は次のとおりです。

Array

( [0] => 配列 ( [0] => ${classA1.methodA0.methodA1.methodB1(classB.methodC(classB.methodD))} )

[1] => Array
    (
        [0] => classA1
    )

[2] => Array
    (
        [0] => methodA0
    )

[3] => Array
    (
        [0] => methodA1.methodB1(classB.methodC(classB.methodD))
    )

)

そのはず

    [2] => Array
    (
        [0] => methodA0.methodA1
    )

[3] => Array
    (
        [0] => methodB1(classB.methodC(classB.methodD))
    )

)

また

[2] => Array
    (
        [0] => methodA0
    )

[3] => Array
    (
        [0] => methodA1
    )

[4] => Array
    (
        [0] => methodB1(classB.methodC(classB.methodD))
    )

)
4

3 に答える 3

6

これは大変なことです。再帰パターンは正規表現で可能な範囲を超えていることが多く、たとえ可能であったとしても、理解と維持が非常に困難な表現につながる可能性があります。

PHP を使用しているため、再帰的な正規表現構造を実際にサポートする PCRE を使用しています(?n)。再帰パターンは非常に規則的であるため、正規表現を使用してある程度実用的な解決策を見つけることができます。

すぐに言及する必要がある 1 つの警告: レベルごとに任意の数の「中間」メソッド呼び出しを許可するため (スニペットfdsfsおよびfsdf)、これらすべてを個別のキャプチャで取得することはできません。それは PCRE ではまったく不可能です。各一致は、パターンに含まれる左括弧の量によって決定される同じ有限数のキャプチャを常に生成します。キャプチャ グループが繰り返し使用される場合 (たとえば、 のようなものを使用([a-z]+\.)+)、グループが使用されるたびに、前のキャプチャが上書きされ、最後のインスタンスのみが取得されます。したがって、すべての「中間」メソッド呼び出しをまとめてキャプチャし、explodeその結果だけをキャプチャすることをお勧めします。

同様に、一度に複数のネスト レベルのキャプチャを取得することはできませんでした (必要に応じて)。したがって、目的のキャプチャ (最後のキャプチャにすべてのネスト レベルが含まれる) が唯一のオプションです。その後、その最後の一致にパターンを再度適用して、レベルをさらに下げることができます。

実際の式については次のとおりです。

$pattern = '/
    ^                     # beginning of the string
    [$][{]                # literal ${
    (                     # group 1, used for recursion
      (                   # group 2 (class name)
        [a-z\d]+          # one or more alphanumeric characters
      )                   # end of group 2 (class name)
      [.]                 # literal .
      (                   # group 3 (all intermediate method names)
        (?:               # non-capturing group that matches a single method name
          [a-z\d]+        # one or more alphanumeric characters
          [.]             # literal .
        )*                # end of method name, repeat 0 or more times
      )                   # end of group 3 (intermediate method names);
      (                   # group 4 (final method name and arguments)
        [a-z\d]+          # one or or more alphanumeric characters
        (?:               # non-capturing group for arguments
          [(]             # literal (
          (?1)            # recursively apply the pattern inside group 1
          [)]             # literal )
        )?                # end of argument-group; make optional
      )                   # end of group 4 (method name and arguments)  
    )                     # end of group 1 (recursion group)
    [}]                   # literal }
    $                     # end of the string
    /ix';

xいくつかの一般的な注意事項: 複雑な式 (およびそれをサポートする正規表現フレーバー) の場合は、空白とコメントを導入して式を希望どおりにフォーマットできるようにするため、常に free-spacing修飾子を使用してください。それらがないと、パターンは次のようになります。

'/^[$][{](([a-z\d]+)[.]((?:[a-z\d]+[.])*)([a-z\d]+(?:[(](?1)[)])?))[}]$/ix'

正規表現を自分で書いたことがあり、そのプロジェクトに取り組んだのは自分だけだとしても、今から 1 か月後にこれを理解してみてください。

次に、大文字と小文字を区別しない修飾子を使用して、パターンを少し単純化しましたi。文字の大文字のバリエーションを省略できるため、混乱が解消されます。

第 3 に、可能であれば文字をエスケープするために[$]、 や などの単一文字クラスを使用していることに注意してください。[.]これは単に好みの問題であり、バックスラッシュのバリエーションを自由に使用できます。私は個人的に文字クラスの読みやすさを好みます (そして、ここにいる他の人が同意しないことを知っています) ので、このオプションも紹介したいと思います。

第 4 に、パターンの周りにアンカーを追加したので、${...}.

最後に、再帰はどのように機能するのでしょうか? キャプチャ グループを参照するという点で(?n)、 backreference に似ています(左から右に括弧を開くことによってカウントされます)。違いは、後方参照は group によって一致したものに再度一致しようとするのに対し、パターンを再度適用することです。これは任意の文字に 2 回連続して一致しますが、任意の文字に一致してからパターンを再度適用するため、別の任意の文字に一致します。これらの構造の 1 つをth グループ内で使用すると、再帰が発生します。またはパターン全体を指します。それがすべての魔法です。\nnn(?n)(.)\1(.)(?1)(?n)n(?0)(?R)

入力に適用された上記のパターン

 '${abc.def.ghi.jkl(mno.pqr(stu.vwx))}'

キャプチャが発生します

0 ${abc.def.ghi.jkl(mno.pqr(stu.vwx))}
1 abc.def.ghi.jkl(mno.pqr(stu.vwx))
2 abc
3 def.ghi.
4 jkl(mno.pqr(stu.vwx))

実際に期待した出力にはいくつかの違いがあることに注意してください。

0一致全体 (この場合は入力文字列のみ) です。PHP は常にこれを最初に報告するため、これを取り除くことはできません。

1再帰部分を囲む最初のキャプチャ グループです。これは出力には必要ありませんが、(?n)残念ながら非キャプチャ グループを参照できないため、これも必要です。

2必要に応じてクラス名です。

3中間メソッド名と末尾のピリオドのリストです。これを使用explodeすると、これからすべてのメソッド名を簡単に抽出できます。

4オプションの (再帰的な) 引数リストを持つ最終的なメソッド名です。これで、必要に応じてパターンを再度適用できます。完全に再帰的なアプローチでは、パターンを少し変更したい場合があることに注意してください。つまり、パターン全体が最終的なキャプチャとまったく同じ (再帰的) パターンになるように、別の最初のステップで${andを取り除き、代わりに. 次に、メソッド名と括弧を一致させて削除し、最後のキャプチャで括弧がなくなるまで繰り返します。}(?0)(?1)

再帰の詳細については、PHP の PCRE ドキュメントを参照してください。


最後のポイントを説明するために、すべての要素を再帰的に抽出するスニペットを次に示します。

if(!preg_match('/^[$][{](.*)[}]$/', $expression, $matches))
    echo 'Invalid syntax.';
else
    traverseExpression($matches[1]);

function traverseExpression($expression, $level = 0) {
    $pattern = '/^(([a-z\d]+)[.]((?:[a-z\d]+[.])*)([a-z\d]+(?:[(](?1)[)])?))$/i';
    if(preg_match($pattern, $expression, $matches)) {
        $indent = str_repeat(" ", 4*$level);
        echo $indent, "Class name: ", $matches[2], "<br />";
        foreach(explode(".", $matches[3], -1) as $method)
            echo $indent, "Method name: ", $method, "<br />";
        $parts = preg_split('/[()]/', $matches[4]);
        echo $indent, "Method name: ", $parts[0], "<br />";
        if(count($parts) > 1) {
            echo $indent, "With arguments:<br />";
            traverseExpression($parts[1], $level+1);
        }
    }
    else
    {
        echo 'Invalid syntax.';
    }
}

パターンをワンライナーとして使用することはお勧めしませんが、この回答はすでに十分に長いことに注意してください。

于 2013-06-07T17:47:52.720 に答える
4

同じパターンで検証と抽出を行うことができます。例:

$subjects = array(
'${classA.methodA.methodB(classB.methodC(classB.methodD))}',
'${classA.methodA.methodB}',
'${classA.methodA.methodB()}',
'${methodB(methodC(classB.methodD))}',
'${classA.methodA.methodB(classB.methodC(classB.methodD(classC.methodE)))}',
'${classA.methodA.methodB(classB.methodC(classB.methodD(classC.methodE())))}'
);

$pattern = <<<'LOD'
~
# definitions
(?(DEFINE)(?<vn>[a-z]\w*+))

# pattern
^\$\{
    (?<classA>\g<vn>)\.
    (?<methodA>\g<vn>)\.
    (?<methodB>
        \g<vn> ( 
            \( \g<vn> \. \g<vn> (?-1)?+ \)
        )?+
    )
}$

~x
LOD;

foreach($subjects as $subject) {
    echo "\n\nsubject: $subject";
    if (preg_match($pattern, $subject, $m))
        printf("\nclassA: %s\nmethodA: %s\nmethodB: %s",
            $m['classA'], $m['methodA'], $m['methodB']);
    else
        echo "\ninvalid string";    
}

正規表現の説明:
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯

パターンの最後に、パターン内のスペース、改行、およびコメントを許可する修飾子 x が表示されます。

最初に、パターンは名前付きグループvn(変数名) の定義から始まります。ここで、すべてのパターンで classA または methodB がどのように見えるかを定義できます。次に、すべてのパターンでこの定義を参照できます\g<vn>

クラスに異なるタイプの名前が必要かどうかを定義し、他の定義を追加するメソッドを定義できることに注意してください。例:

(?(DEFINE)(?<cn>....))  # for class name
(?(DEFINE)(?<mn>....))  # for method name 

パターン自体:

(?<classA>\g<vn>)vnで定義されたパターンを使用し て、名前付きグループclassAでキャプチャします。

メソッドAでも同じ

methodBは、ネストされた括弧を含めることができるという別の原因です。これが、この部分に再帰パターンを使用する理由です。

詳細:

\g<vn>         # the method name (methodB)
(              # open a capture group
    \(         # literal opening parenthesis
    \g<vn> \. \g<vn> # for classB.methodC⑴
    (?-1)?+    # refer the last capture group (the actual capture group)
               # one or zero time (possessive) to allow the recursion stop
               # when there is no more level of parenthesis
    \)         # literal closing parenthesis
)?+            # close the capture group 
               # one or zero time (possessive)
               # to allow method without parameters

\g<vn>(?>\.\g<vn>)+⑴<i>複数のメソッドを許可する場合は、に置き換えることができます。

所有量指定子について:

+量指定子 ( ) の後に追加* + ?して所有格にすることができます。利点は、正規表現エンジンが、サブパターンと一致する他の方法をテストするためにバックトラックする必要がないことを認識していることです。正規表現はより効率的です。

于 2013-06-07T17:47:25.693 に答える
2

説明

${classA.methodA.methodB(classB.methodC(classB.methodD)))}この式は、または${classA.methodA.methodB}形式のみを照合してキャプチャします。

(?:^|\n|\r)[$][{]([^.(}]*)[.]([^.(}]*)[.]([^(}]*(?:[(][^}]+[)])?)[}](?=\n|\r|$)

ここに画像の説明を入力

グループ

グループ 0 は、最初のドル記号から閉じた波括弧までの一致全体を取得します。

  1. クラスを取得します
  2. 最初のメソッドを取得します
  3. 2 番目のメソッドを取得し、その後に右波括弧を除くすべてのテキストを取得します。このグループに空の丸括弧が含まれている場合()、この一致は失敗します

PHP コード例:

<?php
$sourcestring="${classA1.methodA1.methodB1(classB.methodC(classB.methodD)))}
${classA2.methodA2.methodB2}
${classA3.methodA3.methodB3()}
${methodB4(methodC4(classB4.methodD)))}
${classA5.methodA5.methodB5(classB.methodC(classB.methodD)))}";
preg_match_all('/(?:^|\n|\r)[$][{]([^.(}]*)[.]([^.(}]*)[.]([^(}]*(?:[(][^}]+[)])?)[}](?=\n|\r|$)/im',$sourcestring,$matches);
echo "<pre>".print_r($matches,true);
?>

$matches Array:
(
    [0] => Array
        (
            [0] => ${classA1.methodA1.methodB1(classB.methodC(classB.methodD)))}
            [1] => 
${classA2.methodA2.methodB2}
            [2] => 
${classA5.methodA5.methodB5(classB.methodC(classB.methodD)))}
        )

    [1] => Array
        (
            [0] => classA1
            [1] => classA2
            [2] => classA5
        )

    [2] => Array
        (
            [0] => methodA1
            [1] => methodA2
            [2] => methodA5
        )

    [3] => Array
        (
            [0] => methodB1(classB.methodC(classB.methodD)))
            [1] => methodB2
            [2] => methodB5(classB.methodC(classB.methodD)))
        )

)

免責事項

  • グループ内で何が起こっているかをわかりやすくするために、クラス名とメソッド名の末尾に数字を追加しました
  • OP で提供されているサンプル テキストには、左右の丸括弧のバランスが取れていません。
  • 許可されませ()んが、許可(())されます
于 2013-06-07T17:49:19.123 に答える