😀 絵文字以外を判定する正規表現 by emoji-regex 🔥

2024/07/13に公開

こんにちは。
正規表現の理解が浅いフロントエンジニアです。
※そのため内容が浅いです。

最近、フォームのバリデーションで 絵文字以外 かどうかを判定する機会がありました。

大抵の場合は 絵文字である を判定できれば特に困らないと思うのですが、せっかくなので正規表現を少し学ぶきっかけになるかと思いサンプルの React コンポーネントを作ってみました。

サンプル

以下のリポジトリ内にあげております。

https://github.com/yasuhiro-yamamoto/nextjs-playground/tree/main/src/components/NoEmojiField

絵文字の判定

絵文字自体の正規表現を作成するほど学習に時間をかけられなかったので、絵文字の判定には emoji-regex を利用しました。

以下は emoji-regexindex.js ですが、絵文字について複雑で包括的な正規表現を返してくれているようです。
https://github.com/mathiasbynens/emoji-regex/blob/main/index.js

絵文字以外という否定表現

絵文字である という判定をする正規表現は emoji-regex だけで取得できるのでそれを否定する正規表現を含めた関数を作成しテストしてみました。

判定用の関数

以下が emoji-regex にもとづいた絵文字以外かを判定する関数です。

import emojiRegex from "emoji-regex";

// emoji以外ならtrueを返す
export function checkNoEmoji(text: string): boolean {
  const regex = emojiRegex();
  const noEmoji = new RegExp(`^(?!.*(${regex.source})).*$`);
  return noEmoji.test(text);
}

正規表現の内訳

  • ^:文字列(行)の先頭から始まる
  • ():グループ化
  • ?!:否定先読み
    • これに続く条件に一致しない
  • .*:任意の文字が0回以上
  • (${regex.source}): emoji-regex が返す正規表現をグループ化
    • emoji-regex が返す正規表現が複雑で複合的なので、グループ化してないと他の正規表現とぶつかる
    • グループ化することでひとつのユニットとして扱うイメージ
  • .*$:任意の文字が0回以上で末尾 を表す

まとめてみる

  • 文字列(行)の先頭からスタート
  • 「任意の文字0回以上に続く + regex.sourceが返す正規表現に一致」という条件をグループ化
  • regex.sourceは複数の複雑な正規表現を返すためそれ自体もグループ化
    • こうすることでひとつのユニットとして扱い他の正規表現と干渉しないようにする
  • グループ内を先読み否定にする
    • 「絵文字を含まない」という正規表現になる
  • 判定対象を文字列(行)の末尾までにしたいのでグループ化の外に.*$を置く
    • これにより「絵文字が含まれていない場合にのみ全体にマッチする」を実現する
    • グループの中に.*$を含むと文字列(行)全体を必ず評価してしまうのでパフォーマンスに影響がある
    • あくまでも条件一致しかなった時点で判定できれば良いので全体を評価する必要がないためグループの外に置く

結果

うまく動作しています。

サンプルサイトで動作確認する

関数に対する単体テストも無事通過しました。

なお、テストデータはemoji-jaの絵文字データを流用させていただきました。

おわりに

感覚的に 書き方に限らず正規表現は全体評価をする というような勘違いをしていた気がするのですが、順次絞り込んでいく という視点に気づけたのが理解を深めるきっかけになったと思います。

知識不足を再認識したので、今後より理解を深めていきたいです。

おわり👋

Discussion