普通の人のためのJavaScript正規表現
正規表現は、regexまたはregexpとも呼ばれ、取り組むのが難しい問題です。 独自の正規表現を書くことにまだ100% c慣れていなくても、恥ずかしがらないでください。慣れが必要です。 この記事の終わりまでに、Stack Overflowのコピーパスタにそれほど依存することなく、JavaScriptで独自の式を揺るがすことができるようになることを願っています。
正規表現を作成するための最初のステップは、正規表現を呼び出す方法を理解することです。 JavaScriptでは、正規表現は標準の組み込みオブジェクトです。 このため、いくつかの方法で新しいRegExpオブジェクトを作成できます。
- 文字通り、
/expression/.match('string to test against') newキーワードと文字列引数、new RegExp('expression')newキーワードとリテラル、new RegExp(/expression/)
これらのメソッドを組み合わせて使用して、基本的に同じジョブを実行することを示します。
正規表現の目標
私の例では、名、名前、ドメイン名を含む文字列を使用します。 現実の世界では、この例にはもっと多くのことを考える必要があります。 名前の扱いに関しては、微妙な点がたくさんありますが、ここでは取り上げません。
ダッシュボードを作成していて、ログインしているユーザーの名前を表示したいとします。 私は自分に返されるデータを制御できないので、自分が持っているものでやらなければなりません。
aaron.arney:alligator.ioをAaron Arney [Alligator]に変換する必要があります。
正規表現は、多くのロジックを1つの凝縮されたオブジェクトに適合させます。 これは混乱を引き起こす可能性があります。 式を疑似コードの形式に分解することをお勧めします。 これにより、何がいつ発生する必要があるかを確認できます。
- 名を抽出する
- 姓を抽出する
- ドメイン名を抽出する
- 文字列を目的のテンプレート形式にフォーマットします
First Last [Domain]
名の一致
文字列を正規表現と一致させるには、リテラル文字列を渡すだけです。 式の最後にあるiはフラグです。 特にiフラグは、case insensitiveを表します。 これは、文字列の大文字小文字を無視する式を意味します。
const unformattedName = 'aaron.arney:alligator.io'; const found = unformattedName.match(/aaron/i); console.log(found); // expected output: Array [ "aaron" ]
これはうまく機能しますが、ユーザーの名前が常に「アーロン」になるとは限らないため、この場合は適切なアプローチではありません。 ここで、プログラムで一致する文字列を調べます。
とりあえず、名のマッチングに焦点を当てましょう。 単語を個々の文字に分解します、あなたは何を見ますか?
「アーロン」という名前は、5つの英字で構成されています。 すべての名はのみ5文字ですか? いいえ。ただし、名の範囲は1〜15文字であると想定するのが妥当です。 azの範囲の文字を表すために、[a-z]を使用します。
ここで、この文字クラスを使用するように式を更新すると…
const unformattedName = 'aaron.arney:alligator.io'; const found = unformattedName.match(/[a-z]/i); console.log(found); // expected output: Array [ "a" ]
文字列から「aaron」を抽出する代わりに、「a」のみを返します。 正規表現は可能な限り一致させないようにするため、これは良いことです。 制限の15までの数字と一致する文字を繰り返すには、中括弧を使用します。 これは、前のトークンである「az」と1〜15回一致するように監視している式を示しています。
const unformattedName = 'aaron.arney:alligator.io';
const unformattedNameTwo = 'montgomery.bickerdicke:alligator.io';
const unformattedNameThree = 'a.lila:alligator.io';
const exp = new RegExp(/[a-z]{1,15}/, 'i');
const found = unformattedName.match(exp);
const foundTwo = unformattedNameTwo.match(exp);
const foundThree = unformattedNameThree.match(exp);
console.log(found);
// expected output: Array [ "aaron" ]
console.log(foundTwo);
// expected output: Array [ "montgomery" ]
console.log(foundThree);
// expected output: Array [ "a" ]
姓の一致
姓の抽出は、最初の式をコピーして貼り付けるのと同じくらい簡単です。 一致すると、名前と名前の両方ではなく、同じ値が返されることに気付くでしょう。
文字列を文字ごとに分割すると、名前を完全に区切ることができます。 これを説明するために、式に終止符を追加します。
ここでは注意が必要です。 .は、式の2つのことのうちの1つを意味する場合があります。
.-改行以外の任意の文字に一致します\.-に一致します。
このコンテキストでいずれかのバージョンを使用すると、同じ結果が生成されますが、常にそうであるとは限りません。 eslint のようなツールは、エスケープシーケンス\を不要としてマークすることがありますが、申し訳ありませんが安全だと言っています。
const unformattedName = 'aaron.arney:alligator.io';
const exp = new RegExp(/[a-z]{1,15}\.[a-z]{1,15}/, 'i');
const found = unformattedName.match(exp);
console.log(found);
// expected output: Array [ "aaron.arney" ]
文字列を2つの項目に分割し、式によってピリオドが返されないようにすることを好むため、capturing groupsを使用できるようになりました。 これらは括弧()で示され、返される式の部分をラップアラウンドします。 それらを名前式と姓式にラップすると、新しい結果が得られます。
キャプチャグループを使用するための構文は単純です:(expression)。 姓と名のみを返したいので、は終止符ではないので、式を括弧で囲みます。
const unformattedName = 'aaron.arney:alligator.io';
const exp = new RegExp(/([a-z]{1,15})\.([a-z]{1,15})/, 'i');
const found = unformattedName.match(exp);
console.log(found);
// expected output: Array [ "aaron.arney", "aaron", "arney" ]
ドメイン名の照合
「alligator.io」を抽出するために、これまでに使用した文字クラスを使用します。 もちろん、少し変更を加えます。
ドメイン名とTLDの検証は難しいビジネスです。 解析するドメインは常に> 3 && < 25文字であると偽ります。 TLDは常に> 1 && < 10です。 これらを接続すると、新しい出力が得られます。
const unformattedName = 'aaron.arney:alligator.io';
const exp = new RegExp(/([a-z]{1,15})\.([a-z]{1,15}):([a-z]{3,25}\.[a-z]{2,10})/, 'i');
const found = unformattedName.match(exp);
console.log(found);
// expected output: Array [ "aaron.arney:alligator.io", "aaron", "arney", "alligator.io" ]
ショートカット
表現の「長い道のり」をお見せしました。 ここで、同じテキストをキャプチャする、より冗長でない表現を作成する方法を紹介します。 +数量詞を使用することにより、前のトークンをできるだけ多く繰り返すように式に指示できます。 行き止まりに達するまで、この場合は終止符に達するまで続きます。 この式では、globalを表すgフラグも導入されます。 これは、検索を最短回数ではなく、可能な限り繰り返したいという表現を示しています。
// With the global flag 'aaron.arney:alligator.io'.match(/[a-z]+/ig); // expected output: Array(4) [ "aaron", "arney", "alligator", "io" ] // Without the global flag 'aaron.arney:alligator.io'.match(/[a-z]+/i); // expected output: Array(4) [ "aaron" ]
出力のフォーマット
文字列をフォーマットするには、Stringオブジェクトでreplaceメソッドを使用します。 replaceメソッドは2つの引数を取ります。
RegExp | String-正規表現オブジェクトまたはリテラルRegExp | function-正規表現または関数
const unformattedName = 'aaron.arney:alligator.io';
// The "long" way
const exp = new RegExp(/([a-z]{1,15})\.([a-z]{1,15}):([a-z]{3,25}\.[a-z]{2,10})/, 'i');
unformattedName.replace(exp, '$1 $2 [$3]');
// expected output: "aaron arney [alligator.io]"
// A slightly shorter way
unformattedName.replace(/([a-z]+)\.([a-z]+):([a-z]+\.[a-z]{2,10})/ig, '$1 $2 [$3]');
// expected output: "aaron arney [alligator.io]"
上記のスニペットでは、$1、$2、$3は、replaceメソッドによって解釈される特殊なパターンです。
$1-The first result from the match array=>A reference to the first parenthesized group$2-The second result from the match array=>A reference to the second parenthesized group$n-などなど
単語を大文字にするために、別の正規表現を使用できます。 上記のように出力をフォーマットする代わりに、関数を渡します。 この関数は、指定された引数を大文字にして返します。
ここでは、anchors、alternation、および新しいキャラクタークラス[^]の2つの新しいパーツを紹介します。
[^abc]-a、b、またはcではありません\b-単語の境界ab|cd-論理「OR」、abまたはcdと一致します
// Capitalize the words "aaron arney [alligator.io]".replace(/(^\b[a-z])|([^\.]\b[a-z])/g, (char) => char.toUpperCase()); // expected output: "Aaron Arney [Alligator.io]"
この表現を2つの部分に分解すると…
(^\b[a-z])-文字列の最初の文字をキャプチャします。^は、文字列の先頭に一致することを示します。|([^\.]\b[a-z])-または、が終止符.で開始しない新しい単語と一致します。これはTLDです。
探索を続ける
これは、正規表現の力のほんの少しの味です。 私が試した例は改善可能ですが、どうやって?
- 表現が冗長すぎませんか? 単純化しすぎていませんか?
- エッジケースをカバーしていますか?
- ネイティブメソッドを使用した巧妙な文字列操作に置き換えることができますか?
ここで、学んだ知識を取り入れて、それらの質問に答えようとします。 次のリソースを調べて、旅と実験に役立ててください。