2019年12月24日

論理和指定子(Alternation) (OR) |

論理和指定子は、実際には単純な “OR” である正規表現の用語です。

正規表現では、縦線の文字 | で表現されます。

例えば、プログラム言語を探す必要があるとします: HTML, PHP, Java, または JavaScript です。

対応する正規表現は次の通りです: html|php|java(script)?.

利用例:

let reg = /html|php|css|java(script)?/gi;

let str = "First HTML appeared, then CSS, then JavaScript";

alert( str.match(reg) ); // 'HTML', 'CSS', 'JavaScript'

私たちは既に同様のことを知っています – 角括弧です。gr[ae]ygray または grey にマッチするように、複数の文字から選択することができます。

論理和指定子は文字レベルで動作するのではなく、式のレベルで動作します。正規表現 A|B|CA, B または C の式のいずれか、を意味します。

例:

  • gr(a|e)y はまさに gr[ae]y と同じ意味です。
  • gra|eygra または ey を意味します。

パターンの一部分に対して論理和指定子を適用する場合は、括弧でそれらを囲みます:

  • I love HTML|CSSI love HTML または CSS にマッチします。
  • I love (HTML|CSS)I love HTML または I love CSS にマッチします。

時間の正規表現

前のチャプターで、12:00 のような形式 hh:mm の時間を探す正規表現を構築するタスクがありました。しかし、単純な \d\d:\d\d はあまりにも要領を得ません。これは 25:99 も時間として許容します。

どうすればより良い正規表現を作れるでしょうか?

その方法として、私たちはより慎重なマッチングを適用することができます。まず、時(hour):

  • 最初の数字が 01 の場合、その後は任意の数値になります: [01]\d
  • そうではなく、最初の数字が 2 の場合、次は [0-3] でなければなりません。
  • (最初の数字がそれ以外のものは許可されません)

論理和指定子を使用した、両方を含む正規表現は次の通りです: [01]\d|2[0-3].

次に、分 は 0 から 59 までである必要があります。正規表現では [0-5] と記述することができます。: 最初の数値 0-5, その後に任意の数字を続きます。

時と分をあわせると次のパターンになります: [01]\d|2[0-3]:[0-5]\d.

ほぼほぼできていますがまだ問題があります。論理和指定子 |[01]\d2[0-3]:[0-5]\d の間です。

つまり、このようになります

[01]\d  |  2[0-3]:[0-5]\d

このパターンは [01]\d あるいは 2[0-3]:[0-5]\d を探します。

しかし、これは間違いです。OR は正規表現の “時” の部分にのみ使用される必要があります。[01]\d OR 2[0-3] とするために。“時” を括弧で囲むことで正しくしましょう: ([01]\d|2[0-3]):[0-5]\d

最終的な解決策です:

let reg = /([01]\d|2[0-3]):[0-5]\d/g;

alert("00:00 10:10 23:59 25:99 1:2".match(reg)); // 00:00,10:10,23:59

タスク

多くのプログラミング言語があります。例えば Java, JavaScript, PHP, C, C++。

文字列 Java JavaScript PHP C++ C からそれらを見つける正規表現を作成してください:

let reg = /your regexp/g;

alert("Java JavaScript PHP C++ C".match(reg)); // Java JavaScript PHP C++ C

最初のアイデアは | の間に言語をリストすることです。

しかし、それは正しく動作しません:

let reg = /Java|JavaScript|PHP|C|C\+\+/g;

let str = "Java, JavaScript, PHP, C, C++";

alert( str.match(reg) ); // Java,Java,PHP,C,C

正規表現演算子は論理和指定子を1つずつ探します。つまり、まず Java があるかをチェックし、なければ JavaScript を探します。

結果として、JavaScript は見つかりません。Java が最初にチェックされるからです。

CC++ も同じです。

この問題には2つの解法があります:

  1. より長いマッチを最初にチェックするよう順番を変更する: JavaScript|Java|C\+\+|C|PHP.
  2. 同じスタートのバリアントをマージする: Java(Script)?|C(\+\+)?|PHP.

動作:

let reg = /Java(Script)?|C(\+\+)?|PHP/g;

let str = "Java, JavaScript, PHP, C, C++";

alert( str.match(reg) ); // Java,JavaScript,PHP,C,C++

“bbタグ” は [tag]...[/tag] のように見えるもので、ここでは tagb, url または quote のいずれかです。

例:

[b]text[/b]
[url]http://google.com[/url]

BBタグはネストできます。しかし次の例のように、自身の中でネストすることはできません。:

通常:
[url] [b]http://google.com[/b] [/url]
[quote] [b]text[/b] [/quote]

不可:
[b][b]text[/b][/b]

タグは改行を含むことができ、それは普通のことです:

[quote]
  [b]text[/b]
[/quote]

それらの内容をもつすべての BBタグを見つける正規表現を作成してください。

例:

let reg = /your regexp/g;

let str = "..[url]http://google.com[/url]..";
alert( str.match(reg) ); // [url]http://google.com[/url]

タグがネストしている場合は外側のタグを必要とします(コンテンツ内で検索を続けたい場合):

let reg = /your regexp/g;

let str = "..[url][b]http://google.com[/b][/url]..";
alert( str.match(reg) ); // [url][b]http://google.com[/b][/url]

開始タグは \[(b|url|quote)\] です.

次に、閉じタグまでの内容を見つけるため – 改行を含む任意の文字にマッチするパターン [\s\S]*? を追加し、その後閉じタグへの後方参照を追加しましょう。

完全なパターンは: \[(b|url|quote)\][\s\S]*?\[/\1\].

動作:

let reg = /\[(b|url|quote)\][\s\S]*?\[\/\1\]/g;

let str = `
  [b]hello![/b]
  [quote]
    [url]http://google.com[/url]
  [/quote]
`;

alert( str.match(reg) ); // [b]hello![/b],[quote][url]http://google.com[/url][/quote]

閉じタグ [/\1] ではスラッシュをエスケープしなければならないことに注意してください。通常、スラッシュはパターンの終了を意味するためです。

ダブルクォート "..." 内の文字列を見つける正規表現を作成してください。

重要なことは、文字列は JavaScript の文字列がするのと同じ方法でエスケープをサポートする必要があるということです。例えば、引用符は \" として挿入でき、改行は \n 、スラッシュ自身は \\ です。

let str = "Just like \"here\".";

エスケープされた引用符 \" は文字列を終了しないことが重要です。

そのため、途中でエスケープされた引用符は無視して、ある引用符から他の引用符に目を向ける必要があります。

これがこのタスクの肝心な部分です。そうでなければ答えは明らかです。

マッチさせる文字列の例です:

.. "test me" ..
.. "Say \"Hello\"!" ... (内側にエスケープされた引用符がある)
.. "\\" ..  (内側にダブルスラッシュがある)
.. "\\ \"" ..  (内側にダブルスラッシュとエスケープされた引用符がある)

JavaScript では、次のように、そのまま文字列として渡すにはスラッシュを2重にする必要があります:

let str = ' .. "test me" .. "Say \\"Hello\\"!" .. "\\\\ \\"" .. ';

// メモリ内部の文字列
alert(str); //  .. "test me" .. "Say \"Hello\"!" .. "\\ \"" ..

解答: /"(\\.|[^"\\])*"/g.

1つずつ見ていきましょう:

  • まず、開始の引用符 " を探します。
  • 次にバックスラッシュ \\ がある場合(技術的に、パターンの中ではスラッシュは特別な文字であるため2重にしなければなりません。そのため、実際にはこれは1つのスラッシュを意味します)、その後は任意の文字で問題ありません(ドット)。
  • そうでない場合、引用符(これは文字列の終わりを意味します)とバックスラッシュ(単独のバックスラッシュを防ぐため。バックスラッシュはその後の別のシンボルとともにのみ使用されます)を除く任意の文字を取ります: [^"\\]
  • …終了の引用符まで続きます。

動作:

let reg = /"(\\.|[^"\\])*"/g;
let str = ' .. "test me" .. "Say \\"Hello\\"!" .. "\\\\ \\"" .. ';

alert( str.match(reg) ); // "test me","Say \"Hello\"!","\\ \""

タグ <style...> を見つける正規表現を書いてください。それは完全なタグにマッチする必要があります: 属性を持たない <style>, またはいくつかの属性をもつ <style type="..." id="...">

…しかし正規表現は <styler> にマッチしてはいけません!

例:

let reg = /your regexp/g;

alert( '<style> <styler> <style test="...">'.match(reg) ); // <style>, <style test="...">

パターンの開始は明らかです: <style.

…しかし次に単純に <style.*?> と書くことはできません。なぜなら <styler> がマッチするからです。

<style の後にスペースがあり、その後必要に応じてなにかが続く、もしくは終了である > が必要です。

正規表現はこのようになります: <style(>|\s.*?>).

動作:

let reg = /<style(>|\s.*?>)/g;

alert( '<style> <styler> <style test="...">'.match(reg) ); // <style>, <style test="...">
チュートリアルマップ