正規表現の最短マッチを覚える
今さらながら正規表現の「最短マッチ」を紹介。
「HTML 文字列を対象に href
属性の値だけを正規表現で取得したいなー」
と思った時に、以下のように書いた。
const text = '<a href="index.html">Index</a>';
const match = text.match((/href="(.*)"/u))[1];
console.log(match);
// → index.html
うむ、うまく行った。
…と思いきや、次のような文字列だと思ったようにいかない。
const text = '<a href="index.html" target="_blank">Index</a>'; // ← ココが違う
const match = text.match((/href="(.*)"/u))[1];
console.log(match);
// → index.html" target="_blank
// ↑ あれ?
href
属性の終わりのダブルクォートではなく、その後ろに続く target
属性の終わりのダブルクォートまでが取得されてしまった。
正規表現は、デフォルトでは「欲張りなマッチ (Greedy Match)」と呼ばれる挙動をするそうだ。すなわち「できるだけ長くマッチした文字列を拾う」ので、
"(.*)"
↑このようにダブルクォートに囲まれた文字列を検索しようとした時に、なるべく長くマッチするよう調整されて、target
属性の終わりのところにあるダブルクォート文字までが拾われてしまったのだった。
.*
とは書いたものの、すぐ次に登場するダブルクォートまでを対象にしたいんだよ〜! という時は、正規表現の最短マッチという仕組みを使う。
書き方は簡単で、.*
と書いた部分を .*?
と書くだけ。
"(.*?)"
実際に違いを見てみよう。
まずは「欲張りなマッチ」の挙動をおさらい。
[
'<a href="index.html">Index</a>',
'<a href="index.html" target="_blank">Index</a>',
'<a href="index.html">Index</a> | <a href="top.html">Top</a>'
].forEach((text) => {
const match = text.match((/href="(.*)"/u))[1];
console.log(match);
});
// → index.html
// → index.html" target="_blank
// → index.html">Index</a> | <a href="top.html
続いて最短マッチ。
[
'<a href="index.html">Index</a>',
'<a href="index.html" target="_blank">Index</a>',
'<a href="index.html">Index</a> | <a href="top.html">Top</a>'
].forEach((text) => {
const match = text.match((/href="(.*?)"/u))[1]; // ← 最短マッチ .*? を利用
console.log(match);
});
// → index.html
// → index.html
// → index.html
最短マッチなら、キチンと href
属性の値部分が取得できた。