regex 为什么Unicode emoji属性转义匹配数字?

sg3maiej  于 2023-05-23  发布在  其他
关注(0)|答案(2)|浏览(169)

我发现了一个很棒的方法来检测表情符号,使用正则表达式,不使用“巨大的魔法范围”,使用Unicode property escape

console.log(/\p{Emoji}/u.test('flowers 🌼🌺🌸')) // true
console.log(/\p{Emoji}/u.test('flowers')) // false

但是当我分享这个知识in this answer时,@Bronzdragon注意到\p{Emoji}也匹配数字!为什么会这样?数字不是emoji吗?

console.log(/\p{Emoji}/u.test('flowers 123')) // unexpectdly true

// regex-only workaround by @Bonzdragon
const regex = /(?=\p{Emoji})(?!\p{Number})/u;
console.log(
  regex.test('flowers'), // false, as expected
  regex.test('flowers 123'), // false, as expected
  regex.test('flowers 123 🌼🌺🌸'), // true, as expected
  regex.test('flowers 🌼🌺🌸'), // true, as expected
)

// more readable workaround
const hasEmoji = str => {
  const nbEmojiOrNumber = (str.match(/\p{Emoji}/gu) || []).length;
  const nbNumber = (str.match(/\p{Number}/gu) || []).length;
  return nbEmojiOrNumber > nbNumber;
}
console.log(
  hasEmoji('flowers'), // false, as expected
  hasEmoji('flowers 123'), // false, as expected
  hasEmoji('flowers 123 🌼🌺🌸'), // true, as expected
  hasEmoji('flowers 🌼🌺🌸'), // true, as expected
)
ovfsdjhp

ovfsdjhp1#

根据这篇文章,digtis,#*,ZWJ和其他一些字符包含Emoji属性设置为 Yes,这意味着数字被认为是有效的emoji字符

0023          ; Emoji_Component      #  1.1  [1] (#️)       number sign
002A          ; Emoji_Component      #  1.1  [1] (*️)       asterisk
0030..0039    ; Emoji_Component      #  1.1 [10] (0️..9️)    digit zero..digit nine
200D          ; Emoji_Component      #  1.1  [1] (‍)        zero width joiner
20E3          ; Emoji_Component      #  3.0  [1] (⃣)       combining enclosing keycap
FE0F          ; Emoji_Component      #  3.2  [1] ()        VARIATION SELECTOR-16
1F1E6..1F1FF  ; Emoji_Component      #  6.0 [26] (🇦..🇿)    regional indicator symbol letter a..regional indicator symbol letter z
1F3FB..1F3FF  ; Emoji_Component      #  8.0  [5] (🏻..🏿)    light skin tone..dark skin tone
1F9B0..1F9B3  ; Emoji_Component      # 11.0  [4] (🦰..🦳)    red-haired..white-haired
E0020..E007F  ; Emoji_Component      #  3.1 [96] (󠀠..󠁿)      tag space..cancel tag

例如,1是一个数字,但当它与U+FE0FU+20E3字符组合时,它就变成了一个emoji:1️:

console.log("1\uFE0F\u20E3 2\uFE0F\u20E3 3\uFE0F\u20E3 4\uFE0F\u20E3 5\uFE0F\u20E3 6\uFE0F\u20E3 7\uFE0F\u20E3 8\uFE0F\u20E3 9\uFE0F\u20E3 0\uFE0F\u20E3")

如果你想避免匹配数字,使用Extended_Pictographic Unicode category class:
Extended_Pictographic字符包含除一些Emoji_Components之外的所有Emoji字符。
因此,您可以使用/\p{Extended_Pictographic}/gu来测试大多数emoji,或者使用/\p{Extended_Pictographic}/u来测试单个emoji,或者使用/[\p{Extended_Pictographic}\u{1F3FB}-\u{1F3FF}\u{1F9B0}-\u{1F9B3}]/u来匹配emoji以及浅色皮肤到深色皮肤模式的字符和红发到白发的字符:

const regex_emoji = /[\p{Extended_Pictographic}\u{1F3FB}-\u{1F3FF}\u{1F9B0}-\u{1F9B3}]/u;
console.log( regex_emoji.test('flowers 123') );     // => false
console.log( regex_emoji.test('flowers 🌼🌺🌸') ); // => true
a11xaf1n

a11xaf1n2#

使用\p{Emoji}的问题之一是Unicode将Emoji定义为 * 字符属性 *,这意味着它只捕获单个字符或代码点。因此,\p{Emoji}似乎可以解决你的问题,只要你只对单码点表情符号(如🫱(U+1FAF1))进行测试,但这是误导。
然而,Unicode定义的绝大多数emoji由多个代码点组成,因此无法与\p{Emoji}匹配。例如:(🫱🏿‍🫲🏻U+1FAF1U +1F3FF U+200D U+1FAF2U +1F3FB)。

const reEmojiCharacter = /^\p{Emoji}$/u;
reEmojiCharacter.test('🫱'); // → true
reEmojiCharacter.test('🫱🏿‍🫲🏻'); // → false

幸运的是,Unicode定义了几个字符串的属性,你猜到了,它们并不局限于一个代码点。名为RGI_Emoji的字符串属性包含了所有官方推荐用于通用交换的表情符号,并且很可能是你真正想要的,而不是Emoji
在JavaScript正则表达式中,启用the v flag时可以使用字符串的属性。

const reEmoji = /^\p{RGI_Emoji}$/v;
reEmoji.test('🫱'); // → true
reEmoji.test('🫱🏿‍🫲🏻'); // → true

相关问题