この章では、正規表現のためのJavaScript APIの概要を説明します。正規表現がどのように機能するかについて、ある程度の知識があることを前提としています。もしそうでない場合は、Web上に優れたチュートリアルがたくさんあります。2つの例を挙げます。
ここで使用する用語は、ECMAScript仕様の文法をほぼ反映しています。理解しやすくするために、時々逸脱します。
一般的なアトムの構文は次のとおりです。
\ ^ $ . * + ? ( ) [ ] { } |バックスラッシュを前に付けることでエスケープできます。例えば
> /^(ab)$/.test('(ab)')
false
> /^\(ab\)$/.test('(ab)')
true追加の特殊文字は次のとおりです。
文字クラス [...] の内部
-
疑問符 (?...) で始まるグループの内部
: = ! < >
山括弧は、XRegExpライブラリでのみ使用されます(第30章を参照)、グループに名前を付けるために使用されます。
. (ドット)改行文字(改行、キャリッジリターンなど)を除く任意のJavaScript文字(UTF-16コードユニット)に一致します。本当に任意の文字に一致させるには、[\s\S]を使用します。例えば
> /./.test('\n')
false
> /[\s\S]/.test('\n')
true\f (フォームフィード)、\n (ラインフィード、改行)、\r (キャリッジリターン)、\t (水平タブ)、および\v (垂直タブ)が含まれます。\0 はNUL文字(\u0000)に一致します。\cA – \cZ。\u0000 – \xFFFF (Unicodeコードユニット;第24章を参照)。\x00 – \xFF。[«charSpecs»] は、少なくとも1つの charSpecs に一致する任意の単一の文字に一致します。[^«charSpecs»] は、どの charSpecs にも一致しない任意の単一の文字に一致します。次の構成はすべて文字仕様です。
ソース文字はそれ自体に一致します。ほとんどの文字はソース文字です(他の場所で特別な文字であっても)。そうでない文字は3つだけです。
\ ] -
通常どおり、バックスラッシュでエスケープします。エスケープせずにダッシュに一致させたい場合は、左括弧の直後の最初の文字であるか、後述する範囲の右側である必要があります。
クラスエスケープ:前にリストした文字エスケープおよび文字クラスエスケープのいずれも許可されています。追加のエスケープが1つあります。
\b):文字クラスの外側では、\b は単語の境界に一致します。文字クラス内では、制御文字のバックスペースに一致します。-)、その後にソース文字またはクラスエスケープで構成されます。文字クラスの使用を説明するために、この例ではISO 8601標準でフォーマットされた日付を解析します。
functionparseIsoDate(str){varmatch=/^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.exec(str);// Other ways of writing the regular expression:// /^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$/// /^(\d\d\d\d)-(\d\d)-(\d\d)$/if(!match){thrownewError('Not an ISO date: '+str);}console.log('Year: '+match[1]);console.log('Month: '+match[2]);console.log('Day: '+match[3]);}
そして、これは相互作用です。
> parseIsoDate('2001-12-24')
Year: 2001
Month: 12
Day: 24グループの構文は次のとおりです。
(«pattern») はキャプチャグループです。pattern に一致するものは、後方参照または一致操作の結果としてアクセスできます。(?:«pattern») は非キャプチャグループです。pattern は引き続き入力に対して一致しますが、キャプチャとして保存されません。したがって、グループには参照できる番号(例えば、後方参照経由)がありません。\1、\2 などは、後方参照として知られています。これらは、以前に一致したグループを参照します。バックスラッシュの後の数字は1以上の任意の整数にすることができますが、最初の数字は0であってはなりません。
この例では、後方参照はダッシュの前後のaの量が同じであることを保証します。
> /^(a+)-\1$/.test('a-a')
true
> /^(a+)-\1$/.test('aaa-aaa')
true
> /^(a+)-\1$/.test('aa-a')
falseこの例では、後方参照を使用してHTMLタグに一致させます(明らかに、HTMLを処理するには通常、適切なパーサーを使用する必要があります)。
> var tagName = /<([^>]+)>[^<]*<\/\1>/;
> tagName.exec('<b>bold</b>')[1]
'b'
> tagName.exec('<strong>text</strong>')[1]
'strong'
> tagName.exec('<strong>text</stron>')
null任意のアトム(文字クラスやグループを含む)に、量指定子を続けることができます。
? は、0回または1回一致することを意味します。* は、0回以上一致することを意味します。+ は、1回以上一致することを意味します。{n} は、正確に n 回一致することを意味します。{n,} は、n 回以上一致することを意味します。{n,m} は、少なくとも n 回、最大 m 回一致することを意味します。デフォルトでは、量指定子は貪欲です。つまり、可能な限り多くの一致を探します。先行する量指定子(中括弧内の範囲を含む)のいずれかに疑問符(?)を付けることで、非貪欲な一致(可能な限り少なく)にできます。例えば:
> '<a> <strong>'.match(/^<(.*)>/)[1] // greedy 'a> <strong' > '<a> <strong>'.match(/^<(.*?)>/)[1] // reluctant 'a'
したがって、.*? は、次のアトムが次に現れるまですべてに一致するのに便利なパターンです。例えば、次に示すのは、先ほど示したHTMLタグの正規表現(.*?の代わりに[^<]*を使用)のより簡潔なバージョンです。
/<(.+?)>.*?<\/\1>/次のリストに示すアサーションは、入力内の現在の位置に関するチェックです。
| ^ |
| 入力の先頭でのみ一致します。 |
| 入力の末尾でのみ一致します。 |
| 単語の境界でのみ一致します。 |
| 単語の境界にない場合のみ一致します。 |
| 肯定先読み: |
(?!«pattern»)
> /\bell\b/.test('hello')
false
> /\bell\b/.test('ello')
false
> /\bell\b/.test('ell')
true否定先読み:pattern が次に続くものと一致しない場合のみ一致します。pattern は先読みするためだけに使用されますが、それ以外は無視されます。
> /\Bell\B/.test('ell')
false
> /\Bell\B/.test('hell')
false
> /\Bell\B/.test('hello')
true\b で単語境界に一致させます。この例では、\B を使用して単語の内側に一致させます。
後読みはサポートされていません。後読みの手動実装では、手動で実装する方法を説明します。
論理和
> /^aa|bb$/.test('aaxx')
true
> /^aa|bb$/.test('xxbb')
true論理和演算子(|)は、2つの選択肢を区切ります。論理和が一致するには、どちらかの選択肢が一致する必要があります。選択肢はアトムです(オプションで量指定子を含みます)。
/^(aa|bb)$/
演算子は非常に緩やかにバインドするため、選択肢が広がりすぎないように注意する必要があります。例えば、次の正規表現は、aaで始まるか、bbで終わるすべての文字列に一致します。
/^a(a|b)b$/
^や$よりもさらに緩やかにバインドされ、2つの選択肢は^aaとbb$です。'aa'と'bb'の2つの文字列に一致させるには、括弧が必要です。同様に、'aab'と'abb'の文字列に一致させる場合は
JavaScriptの正規表現では、Unicodeのサポートが非常に限られています。特にアストラルプレーンのコードポイントに関しては、注意が必要です。第24章で詳細を説明します。
リテラルまたはコンストラクターのいずれかで正規表現を作成し、フラグを使用してその動作を構成できます。
リテラルとコンストラクター | リテラル | |
|
| コンストラクター(2番目の引数はオプション) |
new RegExp('xyz', 'i')
実行時にコンパイルされます。
functionfoo(){/[/;}
リテラルとコンストラクターは、コンパイルされるタイミングが異なります。
functionfoo(){newRegExp('[');}
リテラルはロード時にコンパイルされます。次のコードは、評価されるときに例外を引き起こします。
したがって、通常はリテラルを使用する必要がありますが、正規表現を動的に組み立てたい場合は、コンストラクターが必要です。
| フラグ | フラグは、正規表現リテラルのサフィックスであり、正規表現コンストラクターのパラメーターです。それらは正規表現の一致動作を変更します。次のフラグが存在します。 | 短い名前 |
|
| g |
|
| i |
|
| m |
multiline
^ と終了演算子 $ は、入力文字列全体ではなく、各行に一致します。フラグ: 設定されているフラグを示すブール値
global: /g フラグが設定されていますか?ignoreCase: /i フラグが設定されていますか?multiline: /m フラグが設定されていますか?複数回マッチングするためのデータ (フラグ /g が設定されている場合)
lastIndex は、次回検索を継続するインデックスです。以下は、フラグのインスタンスプロパティにアクセスする例です。
> var regex = /abc/i; > regex.ignoreCase true > regex.multiline false
この例では、まずリテラルで、次にコンストラクターで同じ正規表現を作成し、test() メソッドを使用して文字列と一致するかどうかを判断します。
> /abc/.test('ABC')
false
> new RegExp('abc').test('ABC')
falseこの例では、大文字と小文字を区別しない正規表現を作成します (フラグ /i)
> /abc/i.test('ABC')
true
> new RegExp('abc', 'i').test('ABC')
truetest() メソッドは、正規表現 regex が文字列 str と一致するかどうかを確認します。
regex.test(str)
test() は、フラグ /g が設定されているかどうかによって動作が異なります。
フラグ /g が設定されていない場合、メソッドは str のどこかに一致するものがあるかどうかを確認します。例:
> var str = '_x_x'; > /x/.test(str) true > /a/.test(str) false
フラグ /g が設定されている場合、メソッドは str 内の regex の一致の数だけ true を返します。プロパティ regex.lastIndex には、最後の一致後のインデックスが含まれています。
> var regex = /x/g; > regex.lastIndex 0 > regex.test(str) true > regex.lastIndex 2 > regex.test(str) true > regex.lastIndex 4 > regex.test(str) false
search() メソッドは、str 内で regex との一致を検索します。
str.search(regex)
一致するものがある場合は、見つかったインデックスが返されます。それ以外の場合、結果は -1 です。検索が実行されるため、regex のプロパティ global と lastIndex は無視されます (lastIndex は変更されません)。
例:
> 'abba'.search(/b/) 1 > 'abba'.search(/x/) -1
search() の引数が正規表現でない場合は、正規表現に変換されます。
> 'aaab'.search('^a+b+$')
0次のメソッド呼び出しは、regex を str と照合しながらグループをキャプチャします。
varmatchData=regex.exec(str);
一致するものがない場合、matchData は null です。それ以外の場合、matchData は、2 つの追加プロパティを持つ一致結果である配列です。
input は、完全な入力文字列です。index は、一致が見つかったインデックスです。フラグ /g が設定されていない場合、最初の一致のみが返されます。
> var regex = /a(b+)/;
> regex.exec('_abbb_ab_')
[ 'abbb',
'bbb',
index: 1,
input: '_abbb_ab_' ]
> regex.lastIndex
0フラグ /g が設定されている場合、exec() を繰り返し呼び出すと、すべての一致が返されます。戻り値 null は、一致がなくなったことを示します。プロパティ lastIndex は、次回の一致が継続される場所を示します。
> var regex = /a(b+)/g; > var str = '_abbb_ab_'; > regex.exec(str) [ 'abbb', 'bbb', index: 1, input: '_abbb_ab_' ] > regex.lastIndex 6 > regex.exec(str) [ 'ab', 'b', index: 7, input: '_abbb_ab_' ] > regex.lastIndex 10 > regex.exec(str) null
ここでは、一致をループ処理します。
varregex=/a(b+)/g;varstr='_abbb_ab_';varmatch;while(match=regex.exec(str)){console.log(match[1]);}
次の出力が得られます。
bbb b
次のメソッド呼び出しは、regex を str と照合します。
varmatchData=str.match(regex);
regex のフラグ /g が設定されていない場合、このメソッドは RegExp.prototype.exec() のように動作します。
> 'abba'.match(/a/) [ 'a', index: 0, input: 'abba' ]
フラグが設定されている場合、メソッドは str 内のすべての一致する部分文字列 (つまり、すべての一致のグループ 0) を持つ配列を返します。一致するものがない場合は null を返します。
> 'abba'.match(/a/g) [ 'a', 'a' ] > 'abba'.match(/x/g) null
replace() メソッドは、文字列 str 内で search との一致を検索し、それらを replacement で置換します。
str.replace(search,replacement)
2 つのパラメーターを指定する方法はいくつかあります。
検索
文字列または正規表現のいずれか。
/g フラグを使用して正規表現を使用する必要があります。これは予期せず、大きな落とし穴です。global フラグを使用してください。そうしないと、正規表現との一致を試みるのは 1 回のみになります。置換
文字列または関数のいずれか。
replacement が文字列の場合、その内容は一致箇所を置換するためにそのまま使用されます。唯一の例外は、ドル記号 ($) という特殊文字で、いわゆる置換ディレクティブを開始します:
$n は、一致箇所からグループ n を挿入します。n は 1 以上である必要があります ($0 には特別な意味はありません)。一致する部分文字列
$` (バッククォート) は、一致の前のテキストを挿入します。$& は、完全に一致した箇所を挿入します。$' (アポストロフィ) は、一致の後のテキストを挿入します。$$ は、単一の $ を挿入します。この例では、一致する部分文字列とその接頭辞および接尾辞を参照します。
> 'axb cxd'.replace(/x/g, "[$`,$&,$']") 'a[a,x,b cxd]b c[axb c,x,d]d'
この例では、グループを参照します。
> '"foo" and "bar"'.replace(/"(.*?)"/g, '#$1#') '#foo# and #bar#'
replacement が関数の場合、一致箇所を置換する文字列を計算します。この関数には、次のシグネチャがあります。
function(completeMatch,group_1,...,group_n,offset,inputStr)
completeMatch は前述の $& と同じであり、offset は一致箇所が見つかった場所を示し、inputStr は照合対象です。したがって、特別な変数 arguments を使用してグループにアクセスできます (グループ 1 は arguments[1] など)。例:
> function replaceFunc(match) { return 2 * match }
> '3 apples and 5 oranges'.replace(/[0-9]+/g, replaceFunc)
'6 apples and 10 oranges'正規表現の /g フラグが設定されている場合、その正規表現で呼び出されるメソッドがすべての結果を返すために複数回呼び出される必要がある場合は問題があります。これは、次の 2 つのメソッドの場合です。
RegExp.prototype.test()
RegExp.prototype.exec()
次に、JavaScript は、結果のシーケンスへのポインターとして、イテレーターとして正規表現を悪用します。これにより、問題が発生します。
/g 正規表現はインライン化できない例:
// Don’t do that:varcount=0;while(/a/g.test('babaa'))count++;
上記のループは無限になります。なぜなら、ループ反復ごとに新しい正規表現が作成され、結果の反復が再開されるからです。したがって、コードを書き換える必要があります。
varcount=0;varregex=/a/g;while(regex.test('babaa'))count++;
別の例を次に示します。
// Don’t do that:functionextractQuoted(str){varmatch;varresult=[];while((match=/"(.*?)"/g.exec(str))!=null){result.push(match[1]);}returnresult;}
上記の関数を呼び出すと、再び無限ループが発生します。正しいバージョンは次のとおりです (lastIndex が 0 に設定される理由は後で説明します)。
varQUOTE_REGEX=/"(.*?)"/g;functionextractQuoted(str){QUOTE_REGEX.lastIndex=0;varmatch;varresult=[];while((match=QUOTE_REGEX.exec(str))!=null){result.push(match[1]);}returnresult;}
関数の使用
> extractQuoted('"hello", "world"')
[ 'hello', 'world' ]とにかくインライン化しないのがベストプラクティスです (そうすれば、正規表現にわかりやすい名前を付けることができます)。ただし、簡単なハックでも、それを行うことはできないことを認識する必要があります。
/g 正規表現test() および exec() を複数回呼び出すコードは、パラメーターとして渡された正規表現に注意する必要があります。フラグ /g はアクティブにする必要があり、安全のために、lastIndex をゼロに設定する必要があります (次の例で説明します)。/g 正規表現 (例: 定数)lastIndex プロパティをゼロに設定する必要があります (次の例で説明します)。反復処理は lastIndex に依存するため、そのような正規表現は、同時に複数の反復処理で使用することはできません。次の例は、問題 2 を示しています。これは、文字列 str 内の正規表現 regex の一致数をカウントする関数のナイーブな実装です。
// Naive implementationfunctioncountOccurrences(regex,str){varcount=0;while(regex.test(str))count++;returncount;}
この関数の使用例を次に示します。
> countOccurrences(/x/g, '_x_x') 2
最初の問題は、正規表現の /g フラグが設定されていない場合、この関数が無限ループに入ることです。例:
countOccurrences(/x/,'_x_x')// never terminates
2 番目の問題は、regex.lastIndex が 0 でない場合、関数が正しく機能しないことです。これは、そのプロパティが検索を開始する場所を示すためです。例:
> var regex = /x/g; > regex.lastIndex = 2; > countOccurrences(regex, '_x_x') 1
次の実装では、2 つの問題を修正します。
functioncountOccurrences(regex,str){if(!regex.global){thrownewError('Please set flag /g of regex');}varorigLastIndex=regex.lastIndex;// storeregex.lastIndex=0;varcount=0;while(regex.test(str))count++;regex.lastIndex=origLastIndex;// restorereturncount;}
より簡単な代替手段は、match() を使用することです。
functioncountOccurrences(regex,str){if(!regex.global){thrownewError('Please set flag /g of regex');}return(str.match(regex)||[]).length;}
落とし穴が 1 つあります。それは、/g フラグが設定されていて、一致がない場合、str.match() が null を返すことです。前述のコードでは、match() の結果が真理値でない場合に [] を使用することで、その落とし穴を回避しています。
このセクションでは、JavaScript で正規表現を操作するためのヒントとコツをいくつか紹介します。
時々、正規表現を手動で組み立てる際、与えられた文字列をそのまま使用したい場合があります。つまり、特殊文字(例えば、*、[)は特殊文字として解釈されるべきではなく、すべてエスケープされる必要があるということです。JavaScriptには、このようなクォーティングを行うための組み込み機能はありませんが、次のような動作をする独自の関数quoteTextをプログラムすることができます。
> console.log(quoteText('*All* (most?) aspects.'))
\*All\* \(most\?\) aspects\.このような関数は、複数の出現箇所で検索と置換を行う必要がある場合に特に便利です。この場合、検索対象の値は、globalフラグが設定された正規表現である必要があります。quoteText()を使用すると、任意の文字列を使用できます。この関数は次のようになります。
functionquoteText(text){returntext.replace(/[\\^$.*+?()[\]{}|=!<>:-]/g,'\\$&');}
すべての特殊文字はエスケープされます。これは、括弧や角括弧内にある複数の文字をクォートしたい可能性があるためです。
^や$のようなアサーションを使用しない場合、ほとんどの正規表現メソッドはどこにでもパターンを見つけます。例えば:
> /aa/.test('xaay')
true
> /^aa$/.test('xaay')
falseまれなケースですが、すべてにマッチングする、または何にもマッチングしない正規表現が必要になることがあります。例えば、関数がフィルタリングに使用される正規表現をパラメータとして持つ場合があります。そのパラメータが欠落している場合、デフォルト値として、すべてにマッチングする正規表現を与えます。
空の正規表現はすべてにマッチングします。次のように、その正規表現に基づいてRegExpのインスタンスを作成できます。
> new RegExp('').test('dfadsfdsa')
true
> new RegExp('').test('')
trueただし、空の正規表現リテラルは//となり、JavaScriptではコメントとして解釈されます。したがって、リテラルで最も近いものは、/(?:)/(空の非キャプチャグループ)です。このグループはすべてにマッチングしますが、何もキャプチャせず、exec()によって返される結果に影響を与えません。JavaScript自体も、空の正規表現を表示するときに上記の表現を使用します。
> new RegExp('')
/(?:)/空の正規表現には逆があります。それは、何もマッチングしない正規表現です。
> var never = /.^/;
> never.test('abc')
false
> never.test('')
false先読みはアサーションです。先読みと同様に、パターンを使用して入力内の現在の位置について何かを確認しますが、それ以外は無視されます。先読みとは対照的に、パターンのマッチは、現在の位置で終了する必要があります(そこから開始するのではなく)。
次の関数は、文字列'NAME'の各出現箇所をパラメータnameの値に置き換えますが、その出現箇所の前に引用符がない場合に限ります。引用符は、現在のマッチの前の文字を「手動で」確認することで処理します。
functioninsertName(str,name){returnstr.replace(/NAME/g,function(completeMatch,offset){if(offset===0||(offset>0&&str[offset-1]!=='"')){returnname;}else{returncompleteMatch;}});}
> insertName('NAME "NAME"', 'Jane')
'Jane "NAME"'
> insertName('"NAME" NAME', 'Jane')
'"NAME" Jane'もう1つの方法は、正規表現でエスケープする可能性のある文字を含めることです。次に、検索対象の文字列に一時的にプレフィックスを追加する必要があります。そうしないと、その文字列の先頭にあるマッチを見逃す可能性があります。
functioninsertName(str,name){vartmpPrefix=' ';str=tmpPrefix+str;str=str.replace(/([^"])NAME/g,function(completeMatch,prefix){returnprefix+name;});returnstr.slice(tmpPrefix.length);// remove tmpPrefix}
アトム(「アトム:一般」を参照)
.(ドット)は、改行文字(例えば、改行)を除くすべてにマッチングします。本当にすべてにマッチングするには、[\s\S]を使用します。文字クラスエスケープ
\dは数字([0-9])にマッチングします。\Dは非数字([^0-9])にマッチングします。\wはラテン文字の英数字とアンダースコア([A-Za-z0-9_])にマッチングします。\Wは他のすべての文字にマッチングします。\sはすべての空白文字(スペース、タブ、改行など)にマッチングします。\Sはすべての非空白文字にマッチングします。文字クラス(文字の集合):[...]と[^...]
[abc](\ ] -を除くすべての文字は自分自身にマッチングします)[\d\w][A-Za-z0-9]グループ
(...)、後方参照:\1(?:...)欲張り
? * +
{n} {n,} {n,m}
?を付けます。^ $\b \B(?=...)(パターンが次に来る必要があり、それ以外は無視されます)(?!...)(パターンが次に来てはならず、それ以外は無視されます)選言:|
正規表現の作成(「正規表現の作成」を参照)
/xyz/i(ロード時にコンパイル)new RegExp('xzy', 'i')(実行時にコンパイル)フラグ(「フラグ」を参照)
/g(いくつかの正規表現メソッドに影響を与えます)/i/m(^と$は、入力全体ではなく、行ごとにマッチングします)メソッド
regex.test(str):マッチがあるか?(「RegExp.prototype.test:マッチがあるか?」を参照)
/gが設定されていない:どこかにマッチがあるか?/gが設定されている:マッチがあるたびにtrueを返します。str.search(regex):どのインデックスにマッチがあるか?(「String.prototype.search:どのインデックスにマッチがあるか?」を参照)
regex.exec(str):キャプチャグループ(セクション「RegExp.prototype.exec:キャプチャグループ」を参照)
/gが設定されていない:最初のマッチのみのキャプチャグループ(一度だけ呼び出されます)/gが設定されている:すべてのマッチのキャプチャグループ(繰り返し呼び出されます。マッチがなくなるとnullを返します)
str.match(regex):キャプチャグループまたはすべての一致する部分文字列を返す(「String.prototype.match:キャプチャグループまたはすべての一致する部分文字列を返す」を参照)
/gが設定されていない:キャプチャグループ/gが設定されている:配列内のすべての一致する部分文字列を返します
str.replace(search, replacement):検索と置換(「String.prototype.replace:検索と置換」を参照)
search:文字列または正規表現(後者を使用する場合は、/gを設定してください!)replacement:文字列($1などを使用)または文字列を返す関数(arguments[1]はグループ1など)/gフラグの使用に関するヒントについては、「フラグ /g に関する問題」を参照してください。
Mathias Bynens (@mathias) と Juan Ignacio Dopazo (@juandopazo) は、出現回数を数えるためにmatch()とtest()を使用することを推奨し、Šime Vidas (@simevidas) は、マッチがない場合にmatch()を使用する際に注意するよう警告しました。グローバルフラグが無限ループを引き起こすという落とし穴は、Andrea Giammarchi (@webreflection) の講演から得られました。Claude Pache は、quoteText()でより多くの文字をエスケープするようにと教えてくれました。