10

テンプレートシステムのマッチングを行う正規表現を持っていますが、残念ながら、いくつかの控えめなルックアップでApache(Windowsで実行されている)がクラッシュするようです。私はこの問題を調査しましたが、スタックサイズなどを増やすためのいくつかの提案がありますが、どれもうまくいかないようで、一般的にバグを将来に押しやるだけなので、制限を増やすことでそのような問題に対処するのは本当に好きではありません.

とにかく、正規表現を変更してファウルの可能性を低くする方法についてのアイデアはありますか?

アイデアは、最も内側のブロック (この場合は{block:test}This should be caught first!{/block:test}) をキャッチして、開始タグと終了タグを str_replace し、ブロックがなくなるまで正規表現全体を再実行することです。

正規表現:

~(?P<opening>{(?P<inverse>[!])?block:(?P<name>[a-z0-9\s_-]+)})(?P<contents>(?:(?!{/?block:[0-9a-z-_]+}).)*)(?P<closing>{/block:\3})~ism

サンプル テンプレート:

<div class="f_sponsors s_banners">
    <div class="s_previous">&laquo;</div>
    <div class="s_sponsors">
        <ul>
            {block:sponsors}
            <li>
                <a href="{var:url}" target="_blank">
                    <img src="image/160x126/{var:image}" alt="{var:name}" title="{var:name}" />
                </a>
            {block:test}This should be caught first!{/block:test}
            </li>
            {/block:sponsors}
        </ul>
    </div>
    <div class="s_next">&raquo;</div>
</div>

ロングショットだと思います。:(

4

3 に答える 3

4

解決策は単一の正規表現でなければなりませんか? より効率的なアプローチは、単に最初に出現するもの{/block:(単純な文字列検索または正規表現の可能性があります) を探し、そのポイントから逆方向に検索して一致する開始タグを見つけ、スパンを適切に置き換えて、なくなるまで繰り返すことです。より多くのブロック。毎回、テンプレートの先頭から始まる最初の終了タグを探すと、最も深くネストされたブロックが得られます。

ミラー イメージ アルゴリズムも同様に機能します。最後の開始タグを探し、そこから対応する終了タグを検索します。

<?php

$template = //...

while(true) {
  $last_open_tag = strrpos($template, '{block:');
  $last_inverted_tag = strrpos($template, '{!block:');
  // $block_start is the index of the '{' of the last opening block tag in the
  // template, or false if there are no more block tags left
  $block_start = max($last_open_tag, $last_inverted_tag);
  if($block_start === false) {
    // all done
    break;
  } else {
    // extract the block name (the foo in {block:foo}) - from the character
    // after the next : to the character before the next }, inclusive
    $block_name_start = strpos($template, ':', $block_start) + 1;
    $block_name = substr($template, $block_name_start,
        strcspn($template, '}', $block_name_start));

    // we now have the start tag and the block name, next find the end tag.
    // $block_end is the index of the '{' of the next closing block tag after
    // $block_start.  If this doesn't match the opening tag something is wrong.
    $block_end = strpos($template, '{/block:', $block_start);
    if(strpos($template, $block_name.'}', $block_end + 8) !== $block_end + 8) {
      // non-matching tag
      print("Non-matching tag found\n");
      break;
    } else {
      // now we have found the innermost block
      // - its start tag begins at $block_start
      // - its content begins at
      //   (strpos($template, '}', $block_start) + 1)
      // - its content ends at $block_end
      // - its end tag ends at ($block_end + strlen($block_name) + 9)
      //   [9 being the length of '{/block:' plus '}']
      // - the start tag was inverted iff $block_start === $last_inverted_tag
      $template = // do whatever you need to do to replace the template
    }
  }
}

echo $template;
于 2012-08-15T08:43:43.297 に答える
4

atomic group: (?>...)またはを使用possessive quantifiers: ?+ *+ ++..して、バックトラッキングを抑制/制限し、unrolling loopテクニックによってマッチングを高速化できます。私の解決策:

\{block:(\w++)\}([^<{]++(?:(?!\{\/?block:\1\b)[<{][^<{]*+)*+)\{/block:\1\}

http://regexr.com?31p03からテストしました。

マッチ{block:sponsors}...{/block:sponsors}:
\{block:(sponsors)\}([^<{]++(?:(?!\{\/?block:\1\b)[<{][^<{]*+)*+)\{/block:\1\}
http://regexr.com?31rb3

マッチ{block:test}...{/block:test}:
\{block:(test)\}([^<{]++(?:(?!\{\/?block:\1\b)[<{][^<{]*+)*+)\{/block:\1\}
http://regexr.com?31rb6

他の解決策:
PCRE ソース コードでは、次のコメントを削除できますconfig.h
/* #undef NO_RECURSE */

からの次のテキスト コピーconfig.h:
PCRE は再帰関数呼び出しを使用して、一致中のバックトラッキングを処理します。これは、スタックのサイズが制限されているシステムで問題になることがあります。match() 関数で再帰を使用しないバージョンを取得するには、NO_RECURSE を定義します。代わりに、pcre_recurse_malloc() を使用してスチームによって独自のスタックを作成し、ヒープからメモリを取得します。

pcre.backtrack_limitまたは、(http://www.php.net/manual/en/pcre.configuration.php)pcre.recursion_limitから変更することもできますphp.ini

于 2012-08-07T18:09:51.843 に答える
4

これを試してください:

'~(?P<opening>\{(?P<inverse>[!])?block:(?P<name>[a-z0-9\s_-]+)\})(?P<contents>[^{]*(?:\{(?!/block:(?P=name)\})[^{]*)*)(?P<closing>\{/block:(?P=name)\})~i'

または、読み取り可能な形式で:

'~(?P<opening>
  \{
  (?P<inverse>[!])?
  block:
  (?P<name>[a-z0-9\s_-]+)
  \}
)
(?P<contents>
  [^{]*(?:\{(?!/block:(?P=name)\})[^{]*)*
)
(?P<closing>
  \{
  /block:(?P=name)
  \}
)~ix'

最も重要な部分は(?P<contents>..)グループにあります。

[^{]*(?:\{(?!/block:(?P=name)\})[^{]*)*

最初に、関心のある唯一の文字は左中かっこなので、他の文字を[^{]*. が表示されて初めて、それがタグ{の始まりかどうかを確認します。{/block}そうでない場合は、先に進んでそれを消費し、次のもののスキャンを開始し、必要に応じて繰り返します。

RegexBuddy を使用して、{block:sponsors}タグの先頭にカーソルを置いてデバッグすることで、各正規表現をテストしました。{/block:sponsors}次に、終了タグから終了ブレースを削除して、一致の失敗を強制し、再度デバッグしました。正規表現が成功するまでに 940 ステップ、失敗するまでに 2265 ステップかかりました。私の場合、成功するまでに 57 ステップ、失敗するまでに 83 ステップかかりました。

余談ですが、ドット ( )sを使用していないため修飾子を削除し、修飾子は必要なかったので削除しました。@DaveRandom の優れた提案の代わりに、名前付き後方参照も使用しまし た。そして、中かっこ (と) をすべてエスケープしたのは、その方が読みやすいからです。.m(?P=name)\3{}


編集:最も内側の名前付きブロックに一致させたい場合は、正規表現の中央部分を次から変更します。

(?P<contents>
  [^{]*(?:\{(?!/block:(?P=name)\})[^{]*)*
)

...これに(@Kobiのコメントで提案されているように):

(?P<contents>
  [^{]*(?:\{(?!/?block:[a-z0-9\s_-]+\})[^{]*)*
)

もともと、(?P<opening>...)グループは最初に見た開始タグを取得し、(?P<contents>..)グループが見つけたものと一致する終了タグでない限り、他のタグを含め、何でも消費していました(?P<opening>...)。(その後、(?P<closing>...)グループは先に進み、それを消費します。)

現在、グループは、名前が何であれ、タグの開始または終了 (先頭の に注意してください)(?P<contents>...)に一致することを拒否します。したがって、正規表現は最初にタグの一致を開始しますが、タグに遭遇すると、その一致を破棄し、開始タグの検索に戻ります。タグで再び開始し、今度は終了タグが見つかったときに一致を正常に完了します。/?{block:sponsors}{block:test}{block:test}{/block:test}

このように説明すると非効率に聞こえますが、実際にはそうではありません。前に説明した、中括弧以外を丸呑みにするトリックは、これらの誤った開始の影響をかき消します。ほとんどすべての位置で否定的な先読みを行っていましたが、今では{. @godspeedlee が提案したように、所有量指定子を使用することもできます。

(?P<contents>
  [^{]*+(?:\{(?!/?block:[a-z0-9\s_-]+\})[^{]*+)*+
)

...後で返さなければならないものは何も消費しないことがわかっているからです。これにより、処理が少し速くなりますが、実際には必要ありません。

于 2012-08-08T02:52:55.613 に答える