LoginSignup
18
5

More than 1 year has passed since last update.

Rubyで文字列に絵文字が含まれるかどうかを判定する方法(ruby-jp slackで2021/11/08に行った質問回答まとめ)

Last updated at Posted at 2021-11-08

ruby-jp slackで質問したところ、識者の皆さんから回答をいただけたので、まとめます。回答いただいた皆さん、ありがとうございました。

端的な回答1

unicode-emoji gemを使う。(筆者未検証)

端的な回答2

Unicode プロパティによる文字クラス指定を利用するのが便利、しかし文字コード沼は深い。筆者の今回の用途では以下の判定(Githubにも上げました)でほぼ大丈夫そう。

main.rb
PATTERN = /[\p{Emoji}\p{Emoji_Component}&&[:^ascii:]]/

STRINGS_WITH_EMOJI = [
  '🍣',
  '0️⃣',
  "👩‍👩‍👧‍👧", "☃️", "🇵","🏻", "😴", "▶️", "🛌🏽", "🇵🇹", "🏴󠁧󠁢󠁳󠁣󠁴󠁿", "2️⃣", "🤾🏽‍♀️",
].freeze

STRINGS_WITHOUT_EMOJI = [
  '1aあア@',
  '東京都江東区新木場2丁目2−10',
  '黒木 慎介',
].freeze

result = true

STRINGS_WITH_EMOJI.each do |s|
  unless s.match?(PATTERN)
    puts s + 'は絵文字を含むと判定されませんでした'
    result = false
  end
end

STRINGS_WITHOUT_EMOJI.each do |s|
  if s.match?(PATTERN)
    puts s + 'は絵文字を含むと判定されてしまいました'
    result = false
  end
end

if result
  puts '判定は成功しました'
end

ruby3.0.1(仕事で使っているバージョン)にて確認。

解説

正規表現の文字クラス

Rubyの正規表現では文字クラスを利用でき、これを活用することで、半角数字とかアルファベットの小文字とかの様々な判定ができます。

しかし、ドキュメントに記載されている中には、絵文字の文字クラスはありません。

Unicodeプロパティによる文字クラス指定

Unicodeの1つ1つの文字にはプロパティがあり、「これは数字であるか」「これは16進数で使うか」「これは大文字か」などの情報が管理されています。
これをRubyの正規表現からも利用することができます。Rubyの正規表現でサポートされているプロパティの一覧を見ると、この中にEmojiがあるのが確認できます。

このEmojiプロパティ(「これは絵文字であるか」ですね)を使って

'🍣'.match?(/\p{Emoji}/) # => true
'foo'.match?(/\p{Emoji}/) # => false

で概ねうまく判定できるのですが、1つ落とし穴があります。

Emojiプロパティの中には、1(半角英数)が含まれる

1.to_s.match?(/\p{Emoji}/) # => true

動作確認をしていて気がついたのですが、半角数字(と、#*)はEmojiプロパティの中に含まれています。これは次に述べるUnicodeの仕様が関わっています。

Unicodeの組み合わせ文字

Unicodeには、既存の文字に続けて特殊な文字を書くことで、1つの別の文字になる組み合わせがあります。

その中の1つが、1(U+0031)と(U+20E3)を組み合わせた1️⃣です。

半角数字と#*はこの組み合わせの一部になるため、Emojiプロパティに含まれているようです。

なので、Emojiプロパティに含まれているけど半角数字や#*ではない文字を絵文字とみなすことができそうです。

'🍣'.match?(/[\p{Emoji}&&[^0-9#*]]/) # => true
'foo'.match?(/[\p{Emoji}&&[^0-9#*]]/) # => false
1.to_s.match?(/[\p{Emoji}&&[^0-9#*]]/) # => false

しかし、これもうまく行かないケースがあります。先述の組み合わせ文字です。

'1️⃣'.match?(/[\p{Emoji}&&[^0-9#*]]/) # => false

なお、他にもUnicodeには複雑な構成の絵文字が存在します(サンプルコードのSTRINGS_WITH_EMOJIにまとめて記載しています)が、これらについてはEmojiプロパティでmatchできるようでした。

Emoji_Componentプロパティ

1️⃣1に分けて判定されますが、がEmojiプロパティに含まれないため、先述の正規表現ではmatch出来ませんでした。

上記のUnicode公式サイトで各文字のプロパティが確認できます。(このサイトめっちゃ便利、教えてくださったima1zumiさん本当にありがとうございます)

Emoji_Componentプロパティには含まれているようです。組み合わせ文字用の文字はこのプロパティでカバーできそうです。

ついでに、0-9#*を少し一般化して、ASCII文字とします。

'🍣'.match?(/[\p{Emoji}\p{Emoji_Component}&&[:^ascii:]]/) # => true
'foo'.match?(/[\p{Emoji}\p{Emoji_Component}&&[:^ascii:]]/) # => false
1.to_s.match?(/[\p{Emoji}\p{Emoji_Component}&&[:^ascii:]]/) # => false
'1️⃣'.match?(/[\p{Emoji}\p{Emoji_Component}&&[:^ascii:]]/) # => true

うまくいってそうです。

バイト長での判定はできるか?

バイト長が4だったら絵文字 という判定をしている人も過去にいたのですが、バイト長が4の文字には漢字の一部も含まれるので、少なくとも筆者の今回の用途には不向きのようでした。

18
5
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
5