2013-06-07 16 views
5

私は擬似コードで正規表現の知識を持っていますが、私はPHP regex perlでやりたいことを翻訳するのに困っています。
私は式の一部を抽出するためにpreg_matchを使用しようとしています。
は、私は次の文字列${classA.methodA.methodB(classB.methodC(classB.methodD)))}を持っていると私は2つの物事実行する必要があります。
PHP Regex preg_matchの抽出


  • ${classA.methodA.methodB(classB.methodC(classB.methodD)))}有効な構文を検証
  • ${classA.methodA.methodB}有効
  • ${classA.methodA.methodB()}有効ではありません
  • ${methodB(methodC(classB.methodD)))}有効ではありません

b。私は、私が作成した
  2にmethodA
  3. methodB(classB.methodC(classB.methodD)))

を、それらの情報 ${classA.methodA.methodB(classB.methodC(classB.methodD)))}を抽出する必要が

  1.クラスAを返す必要がありますこのコードは

$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))
明らか

私は反復メソッドを抽出するのが難しいですし、正当に検証していません(正直なところ、他の問題を解決したら最後に残しました)ので、空のかっこが許され、かっこが開かれたかどうかをチェックしません閉じなければならない。

おかげであなたの助けのためのすべての

UPDATE

X m.buettner

感謝。私は速くあなたのコードを試しましたが、それは非常に小さな問題を与えますが、私はそれを渡すことができます。問題は、私はこの文字列をしようとしているここに投稿していない私の前にコードのいずれかの同じである:あなたのパターン定義と

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

それは示しています

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

することができますように3行目が存在しない空白としてキャッチされていることを参照してください。私はそれがなぜそうしていたのか理解できませんでしたので、PHPの正規表現の限界のためにどうやってやっていかなければならないのかを教えてください。

私はちょうどあなたに感謝を伝えることができると言いました。あなたは私の問題に答えただけでなく、できるだけ多くの情報を入力して、パターンを開発するときの適切な道筋について多くの提案をしました。

最後に一つは、私は(愚かな)ので

$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 Denomales

ご支援のおかげで、私はこれをしようとすると、コードが落ちる:

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

結果は次のとおりです。

Array 

( [0] =>配列 ( [0] => $ {classA1.methodA0.methodA1.methodB1(classB.methodC(cl assB.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)) 
    ) 

) 

答えて

6

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

PHPを使用しているため、実際には再帰正規表現構文(?n)をサポートしています。あなたの再帰パターンがかなり規則的であるので、正規表現を使ってやや実用的な解を見つけることが可能です。

私はすぐに言及しなければならない注意点:レベルごとに任意の数の「中間」メソッド呼び出し(スニペットのfdsfsfsdf)を使用すると、これらをすべて別々のキャプチャで取得することはできません。それは単に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修飾子を使用し、表現をあなたの欲望に合わせるためのコメント。それらがなければ、パターンは次のようになります。

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

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

第2に、大文字と小文字を区別しないi修飾子を使用してパターンを少し簡略化しました。大文字の大文字小文字を省略することができます。

第3に、可能であれば、私は[$][.]のような単一文字クラスをエスケープすることに注意してください。これは単に趣味の問題であり、バックスラッシュの亜種を自由に使用することができます。私は個人的にはキャラクタークラスの読みやすさを好むだけです(私はここでは他の人が同意していないことを知っています)ので、このオプションについてもお伝えしたいと思います。

第4に、${...}の外部に無効な構文が存在しないように、パターンの周りにアンカーを追加しました。

最後に、再帰はどのように機能しますか? (?n)は、キャプチャグループn(左から括弧をつけて数えます)を参照する点で、逆参照\nに似ています。違いは、逆参照がグループnによって一致したものを再び照合しようとしているのに対して、(?n)がパターンを再度適用する点です。つまり、(.)\1は任意の文字を2回連続して照合しますが、(.)(?1)は任意の文字と一致してからパターンを再適用し、別の任意の文字と一致します。 n番目のグループ内にある(?n)構造のいずれかを使用すると、再帰が発生します。 (?0)または(?R)はパターン全体を指します。それがすべての魔法です。

0がある:入力

'${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)) 

注意を発生しますマッチ全体(この場合は入力ストリングのみ) PHPは常にこれを最初に報告するので、それを取り除くことはできません。

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

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

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

4は、オプションの(再帰的な)引数リストを持つ最終メソッド名です。今度はこれを取って、必要に応じてパターンを再度適用してください。完全に再帰的なアプローチの場合は、パターンをわずかに変更することをお勧めします。パターン全体が最終的な捕捉とまったく同じ(再帰的)パターンを有するように、別々の第一工程における${}を取り除き、あなたは(?0)代わりに(?1)使用することができる:すなわち。次に、最後のキャプチャで括弧がなくなるまで、メソッド名と括弧を一致させて削除し、繰り返します。再帰の詳細については

PHP's PCRE documentationを見ています。私はワンライナーなどのパターンが、この答えを使用することをお勧めしていないことを、改めて

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.'; 
    } 
} 

注:ここでは、私の最後のポイントを説明するために


は、再帰的にすべての要素を抽出し、スニペットですすでに十分な長さです。

+0

私はこの$式= '$ {myvalue.fdsfsを}' してみてください。 – user2463968

+0

@ user2463968それでは?クラス名を取得し、中間メソッドも引数も持たない最終メソッドを取得します。それはあなたの意図ではありませんか? –

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";  
} 

正規表現lanes:

パターンの最後には、パターン内にスペース、改行、および注釈を許可する修飾子xがあります。

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

クラスとメソッドに別の定義を追加する場合は、定義することができます。例:

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

パターン自体:という名前のグループ内

(?<classA>\g<vn>)キャプチャため VN

同じことで定義されたパターンとクラスA methodA

methodBは、ネストされた括弧を含むことができるため、 sis、それは私がこの部分に再帰的なパターンを使用する理由です。

詳細:

\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>)+でそれを置き換えることができます。所有数量について

:あなたはそれを所有するために数量詞(*+?)後+を追加することができます

、利点は、正規表現エンジンは、それが他の方法をテストするためにバックトラックする必要がないことを知っているということですサブパターンと一致するようにします。正規表現は、より効率的です。

+0

は27秒で、それを私にビートが許可されます禁止されますが:という名前のキャプチャグループのためにD +1。私はm.buettner @同様 –

+0

その習慣を作る必要があります。私たちは歴史的なトピックを作っていると思います! –

+0

あなたの提案も良いですが、このコードを使用するとちょっと複雑な状況を意味します。つまり、コード自体は理解しやすいが、柔軟性が低くなるということだ。それはまた、私に将来的に役立つことができる多くの情報を与えてくれたと言っています。 – user2463968

2

説明

この表現が一致してのみ${classA.methodA.methodB(classB.methodC(classB.methodD)))}または${classA.methodA.methodB}フォーマットをキャプチャします。

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

enter image description here

グループ

グループ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で提供
  • サンプルテキストを開いたり閉じ丸括弧をバランス持たないグループで何が起こっているかillistrate支援します。
  • ()(())