regex_replaceを安全簡潔に書く小技

  • Movable Type技術情報
  • 2011-04-22

 MT テンプレートタグの出力結果を Perl の強力な正規表現でゴニョゴニョできる regex_replace モデファイア。みなさん、活用されていますか? この記事では、この regex_replace モディファイアをより簡潔に記述し、より便利に使える小技を紹介します。

 本題に入る前に、regex_replace モディファイアの実装をさらっと見ておきます。モディファイアの処理コードは、/lib/MT/Template/Tags/Filters.pm にある _fltr_regex_replace 関数です。最初に、引数の型を調べて、配列変数以外は受け付けません。次に、配列の一つ目の要素($val->[0])を検索パターンとし、配列の二つ目の要素($val->[1])を置換後パターンとしています。検索パターンについては、グローバル処理フラグは特別扱いで、e オプションはセキュリティの面から無視されます。それ以外のオプションはそのまま渡されるようです。置換後のパターンについては、ちょこっと下ごしらえもされただけで、そのまま s/// に渡されて置換後のパターンとして使われています。

 もう少し、寄り道をします。regex_replace モディファイアには検索パターン文字列と置換文字列を配列で指定しますが、MT テンプレートタグでの引数の渡し方による制限があります。これは、/lib/MT//Builder.pmcompile 関数にその処理が記述されており、正規表現式を用いてテンプレートタグ中の引数部分を抽出しています。その正規表現式はかなり複雑ですが、端的に言えば「ダブルクォート文字で始まりダブルクォート文字で閉じられるまで、またはシングルクォート文字で始まりシングルクォート文字で閉じられるまでが一つの引数」ということになります。
 一般に C 言語や Perl などの多くのプログラミング言語では、クォーテーションされた文字列中にクォート文字自身が含まれる場合、? などのエスケープ文字を用いてクォート文字をエスケープすることができますが、MT の引数処理部分では、そういったことが考慮されていません。ダブルクォート文字で始まり、次にダブルクォート文字を見つけると、エスケープの有無に関わらず、無条件で一つの引数の終わりと認識します。つまり、次のような引数指定は不正になります。

# ダブルクォート文字(")をハイフン文字(-)に置換したい
regex_replace="/"/g","-" → ダメ
# エスケープしてみる
regex_replace="/?"/g","-" → ダメ
# エスケープが足りなかったか?
regex_replace="/??"/g","-" → やっぱりダメ

 この問題は以下のように記述することで回避できます。ダブルクォート文字をパラメータ指定の終端として認識されないようにするため、シングルクォート文字を用いてパラメータを指定する方法です。逆にシングルクォート文字が文字列に含まれる場合は、反対にダブルクォート文字でパラメータを指定すればOKです。

# ダブルクォート文字(")をハイフン文字(-)に置換したい
regex_replace='/"/g',"-"

 しかし、この方法にも問題が残っており、パラメータ文字列にシングルクォート文字とダブルクォート文字が混在する場合には、同様の問題が発生します。そこで、以下のような記述方法をおすすめします。

<MTSetVarBlock name="regex0">/["']/g</MTSetVarBlock>
<MTSetVarBlock name="regex1">***</MTSetVarBlock>
<MTUnless regex_replace="$regex0","$regex1">"Tom and Jerry"</MTUnless>
 → ***Tom and Jerry***

 検索パターンと置換パターンを一旦、変数に保持し、この変数を regex_replace の引数に渡すようにします。この方法を用いることで以下のようなメリットが考えられます。

  • 文字列中にシングルクォート文字とダブルクォート文字が混在していても、regex_replace の指定でそれを意識する必要がない
  • 複雑になりがちな正規表現式を分離して記述することで、メンテナンス性が向上する
  • 長大な正規表現式になると、テンプレート開始タグだけで冗長になり、後続のテンプレート全体の視認性が悪くなる
  • MT テンプレートタグを用いて変数展開などを行った結果を正規表現式に取込める