正規表現でHTMLタグを<a.*?</a>でマッチさせるのは間違っている

正規表現でHTMLタグを<a.*?</a>でマッチさせるのは間違っている

正規表現<a.*?</a>は間違い

はてなブックマークを見ていたら以下のような記事があった。

正規表現:最短一致でマッチさせる表現まとめ。

例えば、HTMLファイル内で1つの独立したアンカータグを検出したいときに、「<a.*</a>」と言った正規表現だと、最長一致を探してしまい、うまく行きません。(例えば、「<a href=”/home”>ホーム</a><a href=”/page”>ページ</a>」では、その文字列すべて(2つのアンカータグ)をマッチしてしまいます・・)

ここで、量指定子に「?」を付け加えて、最短一致でマッチさせれば、個々のアンカー要素を取り出すことができます。

var re = new RegExp("<a.*?</a>");
var result = re.test('<a href="/">This is Target String</a>')
 
if (result) {
  alert('マッチしました!');
}

…以上のような内容なのだったのだが、この正規表現は正しくない。

まず、new RegExp("<a.*?</a>")の部分なのだが「<a href=”/home”>ホーム</a><a href=”/page”>ページ</a>」の最短一致を探して検出するのであればgフラグが必要だ。

これがないと2つ目以降が検出されない。

// このように記述するのが正しい
// 内容によってはmフラグも必要なケースもある
var re = new RegExp("<a.*?</a>", "g");

gフラグなしの場合

gフラグありの場合

さらに<a.*?</a>という正規表現だとaddressタグがある場合、これがマッチしてしまう。

addressタグがある場合

正しいHTMLタグ正規表現

HTMLタグ正規表現はどのように書けば正しくマッチするのか。

答えは以下の通り

var re = /<a(?: .+?)?>.*?<\/a>/g;

HTMLタグ正規表現サンプル

なぜこれでaタグだけがマッチされるかというと、<a(?: .+?)?>の(?: .+?)?の部分が括弧部分の0か1回のマッチになり、0の場合は<a>のように属性なしのaタグをマッチし、1回の場合は<a .+?>と同じになり、属性ありのaタグがマッチされるためだ。

<a .+?>はaのあとに半角スペースが入っているため、この正規表現であればaddressなどのaタグ以外のタグがマッチされることはない。