連続した空行(空白文字を含む)を置換する正規表現

連続する空行の置換

O'Reilly Japan - 詳説 正規表現 第3版に、連続した空行(これは、任意の空白文字を含む場合がある)を<p>に置換する正規表現として、

$text =~ s/^\s*$/<p>/mg;

というものが掲載されている(p.67)。これは、空白文字を含む連続した空行を含む文字列...with.\n\n\n\t \nTherefore......with.\n<p>\nTherefore...のように置換すると説明されていて、実際この例ならうまくいく。

3つ以上連続する改行が正しく置換できない

しかし、a\n\n\nbのように改行が3つ以上続く例はa\n<p><p>\nbと置換される。改行がいくつでも<p>は2つだ。『詳説 正規表現』は10年近く前の本なので、Perlの仕様変更があったのかもしれないけど、ちょっと調べた程度ではどうしてこのような挙動になるのかはよくわからなかった。ちなみにa\n\n\n\t\n\nba\n<p><p>\nbに置換されたりしてますますよく分からない。

パターン修飾子\mを付けた場合に連続する改行がどう解釈されるのか曖昧とかそんなことが理由じゃないかと思っていたけど、どうやらこれは/gを付けてグローバルマッチをさせた場合に起こるらしかった。/gを付けた場合に実際に何が起こるか、という点を正確に理解できていないのかもしれない。

/gを指定せずs/^\s*$/<p>/mとすれば予想通りの動きをする(もちろん1箇所しか置換できない)。

ということは、

while($text =~ s/^\s*$/<p>/m){}

とかやってやれば期待通りの置換ができる。

別解

とはいえ/gがあったりなかったりで挙動が変わるものを使うのは気持ちが悪いので、他の方法を探していたところStack Overflowに答えを見つけた(cf. Perl Regex To Condense Multiple Line Breaks - Stack Overflow)。

Perl 5.10から導入された文字クラスとして、\R\hがある。\Rは改行っぽいものにマッチする文字クラス(cf.perlrebackslash - Perl 正規表現逆スラッシュシーケンスとエスケープ - perldoc.jp)で、\hは水平方向の空白文字(タブとかスペースとか)にマッチする文字クラス(cf. perlrecharclass - Perl 正規表現文字クラス - perldoc.jp)であって、これを組み合わせると、連続した空行の置換は、

$text =~ s/\R(\h*\R)+/\n<p>\n/mg;

と書ける。\R\hは使えない言語もあるかもしれないが、こちらの方が何をやっているのか明確という印象はある。

他の言語

ところで、他の言語でも同じような現象が起こるかというとそうではないっぽい。途中で面倒になったのであんまり試してないけど。。

R

例えばR、gsub()で試してみると問題なく置換できる。

Python3

PythonもOK。

Go

Goもいけた。

JavaScript

試した範囲でPerlと同じ現象が起こるのはJavaScriptだった。/gをオプションで指定するタイプだと発生するっぽい?