9日 12月 2019

集合と範囲 [...]

角括弧 […] 内の複数の文字または文字クラスは “指定された中の任意の文字を探す” ことを意味します。

集合

例えば、[eao] は3文字 'a', 'e', または 'o' のいずれかを意味します。

それは 集合 と呼ばれます。集合は通常の文字と併せて正規表現の中で使うことができます。:

// [t or m], 次に "op" となる文字列を見つける
alert( "Mop top".match(/[tm]op/gi) ); // "Mop", "top"

集合には複数の文字がありますが、マッチした中での1文字に相当することに注意してください。

従って、下の例ではマッチするものはありません:

// "V" に続き [o or i], その後 "la" となる文字列を見つける
alert( "Voila".match(/V[oi]la/) ); // null, マッチしない

パターンは次のように想定します:

  • V,
  • 次に文字 [oi]1つ,
  • 次に la.

なので、Vola もしくは Vila がマッチします。

範囲

角括弧は 文字の範囲 を含むこともあります。

例えば、[a-z]a から z までの範囲の文字で、 [0-5]0 から 5 までの数字です。

下の例では、x に続いて2桁の数字または A から F までの文字を探しています:

alert( "Exception 0xAF".match(/x[0-9A-F][0-9A-F]/g) ); // xAF

ここでは [0-9A-F] には2つの範囲があります: 0 から 9 の数値か、A から F の文字を探します。

小文字も見つけたければ、範囲 a-f を追加することもできます: [0-9A-Fa-f]。あるいはフラグ i を追加します。

また、[…] の中で文字クラスを使用することも可能です。

例えば、単語文字 \w やハイフン - を見つけたい場合は、[\w-] となります。

複数のクラスを連結することも可能です。例えば [\s\d] は “空白文字 or 数値” を意味します。

文字クラスは特定の文字集合の短縮形です

例:

  • \d[0-9] と同じです,
  • \w[a-zA-Z0-9_] と同じです,
  • \s[\t\n\v\f\r ] に他のユニコードの空白文字を加えたものと同じです。

例: 多言語 \w

文字クラス \w[a-zA-Z0-9_] の短縮形なので、中国語やキリル文字などを見つけることはできません。

任意の言語の文字を探す、よりユニバーサルなパターンを記述することができます。Unicode プロパティを使用すると簡単です: [\p{Alpha}\p{M}\p{Nd}\p{Pc}\p{Join_C}].

これを読み解いていきましょう。\w と同様、次の Unicode プロパティに沿った文字を含む、独自のセットを作成しています。:

  • Alphabetic (Alpha) – 文字,
  • Mark (M) – アクセント e.g ', ~,
  • Decimal_Number (Nd) – 数字,
  • Connector_Punctuation (Pc) – アンダースコア '_' 及び同様の文字,
  • Join_Control (Join_C) – 2つの特別なコード 200c200d。アラビア語などで、合字で使われます。

使用例:

let regexp = /[\p{Alpha}\p{M}\p{Nd}\p{Pc}\p{Join_C}]/gu;

let str = `Hi 你好 12`;

// すべての文字と数字を探します
alert( str.match(regexp) ); // H,i,你,好,1,2

もちろんこのパターンは編集できます: Unicode プロパティを追加したり、削除するなど。Unicode プロパティに関する詳細は Unicode(ユニコード): フラグ "u" とクラス \p{...} で説明しています。

Unicode プロパティは Edge と Firefox ではサポートされていません

Unicode プロパティ p{…} はまだ Edge and Firefox では実装されていません。どうしても必要な場合は、ライブラリ XRegExp を利用してください。

あるいは、関心のある言語の文字範囲を利用します。例えば、キリル文字は [а-я] です。

範囲を除外する

通常の範囲に加えて、 [^…] のような、範囲を “除外” するものもあります。

それらは開始時にキャレット文字 ^ で指定され、指定されたもの以外の文字 と一致します。

例:

  • [^aeyo]'a', 'e', 'y' または 'o' を除く任意の文字.
  • [^0-9] – 数字以外の任意の文字, \D と同じです.
  • [^\s] – 任意の非空白文字, \S と同じです.

下の例では、文字、数字または空白以外の文字を探します:

alert( "alice15@gmail.com".match(/[^\d\sA-Z]/gi) ); // @ and .

[…] でのエスケープ

通常、特別な文字そのままを見つけたい場合は、 \. のようにエスケープする必要があります。また、バックスラッシュが必要であれば \\ とします。

角括弧の中では、エスケープせずに大部分の特殊文字を使用することができます。:

  • 記号 . + ( ) はエスケープする必要はありません。
  • ハイフン - は先頭または末尾(範囲を定義しない場合)ではエスケープされません。
  • キャレット ^ は先頭(除外を意味します)でのみエスケープされます。
  • 閉じ角括弧 ] は常にエスケープされます(その記号を見つける必要がある場合)。

つまり、角括弧で何か意味するものを除き、すべての特別な文字はエスケープなしで利用できます。

角括弧内のドット . は単なるドットを意味します。パターン [.,] は、ドットあるいはカンマのいずれかの文字を探します。

以下の例では、正規表現 [-().^+]-().^+ のいずれかの文字を探します。:

// No need to escape
let regexp = /[-().^+]/g;

alert( "1 + 2 - 3".match(regexp) ); // +, - にマッチします

…ただし、“念のため” にエスケープしたとしても害はありません。:

// 全部エスケープ
let regexp = /[\-\(\)\.\^\+]/g;

alert( "1 + 2 - 3".match(regexp) ); // 問題なく動作します: +, -

範囲とフラグ “u”

集合の中にサロゲートペアがある場合、正しく動作させるためにフラグ u が必要になります。

例えば、文字列 𝒳[𝒳𝒴] を探してみましょう。:

alert( '𝒳'.match(/[𝒳𝒴]/) ); // [?] のような変な文字が表示されます
// (検索は正しく実行されず、文字の片方が返却されます)

結果は正しくありません。なぜなら、正規表現はデフォルトではサロゲートペアのことは “知らない” からです。

正規表現のエンジンは [𝒳𝒴] は2文字ではなく、4文字だと考えます:

  1. 𝒳 の左半分 (1),
  2. 𝒳 の右半分 (2),
  3. 𝒴 の左半分 (3),
  4. 𝒴 の右半分 (4).

次のようにしてそれぞれのコードを確認することができます:

for(let i=0; i<'𝒳𝒴'.length; i++) {
  alert('𝒳𝒴'.charCodeAt(i)); // 55349, 56499, 55349, 56500
};

したがって、上の例では 𝒳 の左半分を探して表示します。

フラグ u を追加すると、正しい振る舞いとなります。:

alert( '𝒳'.match(/[𝒳𝒴]/u) ); // 𝒳

[𝒳-𝒴] といった範囲を探す際にも同様の状況が起こります。

フラグ u を追加し忘れた場合、エラーになります。:

'𝒳'.match(/[𝒳-𝒴]/); // Error: Invalid regular expression

理由は、フラグ u がない場合、サロゲートペアは2文字として認識されるため、[𝒳-𝒴][<55349><56499>-<55349><56500>] (すべてのサロゲートペアがコードに置換される)と解釈されます。これで、範囲 56499-55349 が正しくないことが簡単にわかります: その開始コード 56499 は終わり 55349 よりも大きいです。これがエラーの正式な理由です。

フラグ u があると、パターンは正しく動作します。:

// 𝒳 から 𝒵 を探します
alert( '𝒴'.match(/[𝒳-𝒵]/u) ); // 𝒴

タスク

正規表現 /Java[^script]/ があります。

文字列 Java はマッチするでしょうか? JavaScript だとどうでしょう?

解答: いいえ, はい.

  • Java では何もマッチしません。なぜなら [^script] は “指定されたもの以外の任意の文字” を意味するからです。そのため、正規表現は "Java" に続くそのような文字を探しますが、文字列は終わりなので該当するものはありません。

    alert( "Java".match(/Java[^script]/) ); // null
  • マッチします。正規表現は大文字小文字を区別するため、[^script] は文字 "S" にマッチします。

    alert( "JavaScript".match(/Java[^script]/) ); // "JavaS"

時間は hours:minutes もしくは hours-minutes というフォーマットで表せます。いずれの時も分も2桁です: 09:0021-30.

時間を見つける正規表現を書いてください:

let reg = /your regexp/g;
alert( "Breakfast at 09:00. Dinner at 21-30".match(reg) ); // 09:00, 21-30

P.S. このタスクでは、時間は常に正しいと想定するので、“45:67” のような正しくない文字列をフィルタする必要はありません。あとでそれらも扱っていきます。

解答: \d\d[-:]\d\d.

let reg = /\d\d[-:]\d\d/g;
alert( "Breakfast at 09:00. Dinner at 21-30".match(reg) ); // 09:00, 21-30

ダッシュ '-' は角括弧の中で特別な意味を持っていますが、先頭や末尾のときではなく、他の文字の間にある場合のみなので、エスケープする必要はないことに注意してください。

チュートリアルマップ

コメント

コメントをする前に読んでください…
  • 自由に記事への追加や質問を投稿をしたり、それらに回答してください。
  • 数語のコードを挿入するには、<code> タグを使ってください。複数行の場合は <pre> を、10行を超える場合にはサンドボックスを使ってください(plnkr, JSBin, codepen…)。
  • 記事の中で理解できないことがあれば、詳しく説明してください。