Emoji表情处理二

18,476次阅读
没有评论

共计 5585 个字符,预计需要花费 14 分钟才能阅读完成。

前言

两个 emoji 表情可以拼接成一个 emoji,这就给我们处理和截取字符串增加了难度,之前我有文章, 截取 emoji 表情 ,具体可以看一下。

拼接规则如下:

👩 + ✈️ → 👩‍✈️
👨 + 💻 → 👨‍💻
👰 + ♂️ → 👰‍♂️
🐻 + ❄️ → 🐻‍❄️
🏴 + ☠️ → 🏴‍☠️
🏳️ + 🌈 → 🏳️‍🌈
👨 + 🦰 → 👨‍🦰(有意思的是:发色是通过 ZWJ 组合基础 emoji 实现,而肤色则是用肤色修饰符实现)👨🏻 + 🤝 + 👨🏼 → 👨🏻‍🤝‍👨🏼
👨 + ❤️ + 👨 → 👨‍❤️‍👨
👨 + ❤️ + 💋 + 👨 → 👨‍❤️‍💋‍👨
👨 + 👨 + 👧 → 👨‍👨‍👧
👨 + 👨 + 👧 + 👧 → 👨‍👨‍👧‍👧

具体可以看 https://unicode.org/Public/emoji/15.0/emoji-zwj-sequences.txt

可惜,有些 emoji 不是通过 ZWJ 组全 emoji 实现的,可能是因为没有赶上 ZWJ 定义的时机

🌂 + 🌧 ≠ ☔️
💄 + 👄 ≠ 💋
🐴 + 🌈 ≠ 🦄
👁 + 👁 ≠ 👀
👨 + 💀 ≠ 🧟
👩 + 🔍 ≠ 🕵️‍♀️
🦵 + 🦵 + 💪 + 💪 + 👂 + 👂 + 👃 + 👅 + 👀 + 🧠 ≠ 🧍

emoji 有特殊的修饰、拼接等规则

通过上面的描述,我们可以看到,在某些 emoji 字符后增加一个肤色修饰符就可以改变 emoji 的肤色、可以将多个 emoji 通过连接符拼接成一个 emoji,这些特殊规则使得在代码中判定 emoji 的长度、截取和对 emoji 做其他处理都比较困难。需要澄清的是:用一个 Unicode 字符修饰前一个字符不是 emoji 独有的,其他 Unicode 字符也存在,如:Ü,由大写字母 U(U+0055),后面跟一个连音符号(U+0308)组成。

基本 emoji 和常用 Unicode 字符毫无区别

每个 emoji 用对应一个 Unicode 码位,如:🌔 U+1F314 (对应 JS 中 UTF-16 编码是:”uD83CuDF14″),汉字 𠇔 U+201D4,对应 JS 中的 UTF-16 编码是 ”uD840uDDD4″

emoji 引入的问题

'中国人 123'.length = 6,'工作中👨‍💻'.length = 8

emoji 给编程带来的主要问题是视觉上看到的字符长度(后文称作视觉 length)与代码中获取的长度(后文称作技术 length)不相同,使得字符串截取等操作返回非预期内的结果,如:

'工作中👨‍💻'.substr(0,5) => '工作中👨','工作中👨‍💻'.substr(5)'=>'‍💻'

本质上在 emoji 出现之前 Unicode 编码就遇到了该问题,只不过 emoji 的普及让该问题更普遍。有的 emoji 长度为 1,有的长度可以达到 15。问题的根源是 Unicode 中可以用多个码点表示一个 emoji,如果所有 emoji 都用一个 Unicode 码点表示就不存在该问题。

相关正则及代码

上一篇文章已经列举了 emoji 的正则,本文补充一些。

/**
 * 字素簇截取、拆分
 */
// 是基于 unicode15.0 中的 emoji 标准,返回识别字符串中 emoji 的正则表达式
const regionalIndicator = 'uD83C[uDDE6-uDDFF]';
// 地区匹配
const emojiFlagSequence = `${regionalIndicator}${regionalIndicator}`;
// emoji 匹配
const emojiCharacter = '[u231A-u231B]|[u23E9-u23EC]|u23F0|u23F3|[u25FD-u25FE]|[u2614-u2615]|[u2648-u2653]|u267F|u2693|u26A1|[u26AA-u26AB]|[u26BD-u26BE]|[u26C4-u26C5]|u26CE|u26D4|u26EA|[u26F2-u26F3]|u26F5|u26FA|u26FD|u2705|[u270A-u270B]|u2728|u274C|u274E|[u2753-u2755]|u2757|[u2795-u2797]|u27B0|u27BF|[u2B1B-u2B1C]|u2B50|u2B55|uD83C[uDC04]|uD83C[uDCCF]|uD83C[uDD8E]|uD83C[uDD91-uDD9A]|uD83C[uDE01]|uD83C[uDE1A]|uD83C[uDE2F]|uD83C[uDE32-uDE36]|uD83C[uDE38-uDE3A]|uD83C[uDE50-uDE51]|uD83C[uDF00-uDF20]|uD83C[uDF2D-uDF35]|uD83C[uDF37-uDF7C]|uD83C[uDF7E-uDF93]|uD83C[uDFA0-uDFCA]|uD83C[uDFCF-uDFD3]|uD83C[uDFE0-uDFF0]|uD83C[uDFF4]|uD83C[uDFF8-uDFFF]|uD83D[uDC00-uDC3E]|uD83D[uDC40]|uD83D[uDC42-uDCFC]|uD83D[uDCFF-uDD3D]|uD83D[uDD4B-uDD4E]|uD83D[uDD50-uDD67]|uD83D[uDD7A]|uD83D[uDD95-uDD96]|uD83D[uDDA4]|uD83D[uDDFB-uDE4F]|uD83D[uDE80-uDEC5]|uD83D[uDECC]|uD83D[uDED0-uDED2]|uD83D[uDED5-uDED7]|uD83D[uDEDC-uDEDF]|uD83D[uDEEB-uDEEC]|uD83D[uDEF4-uDEFC]|uD83D[uDFE0-uDFEB]|uD83D[uDFF0]|uD83E[uDD0C-uDD3A]|uD83E[uDD3C-uDD45]|uD83E[uDD47-uDDFF]|uD83E[uDE70-uDE7C]|uD83E[uDE80-uDEBD]|uD83E[uDEBF-uDEC5]|uD83E[uDECE-uDEDB]|uD83E[uDEE0-uDEE8]|uD83E[uDEF0-uDEF8]';
// 肤色匹配
const emojiModifier = 'uD83C[uDFFB-uDFFF]';
// 匹配变形符号
const emojiPresentationSelector = 'uFE0F';
// 匹配键帽
const keycapSymbol = 'u20E3';
// 匹配标签符号
const tagSymbol = '(uDB40[uDC20-uDC7E])';
// 匹配结束标签符号
const termTagSymbol = 'uDB40uDC7F';
// 匹配零宽字符
const zeroWidthJoiner = 'u200D';

// 键帽变体序列
const kVSequence = `[#*0-9]uFE0F?u20E3`;
// 变体基础字符
const variationSequence = 'u00A9|u00AE|u203C|u2049|u2122|u2139|u2194|u2195|u2196|u2197|u2198|u2199|u21A9|u21AA|u2328|u23CF|u23ED|u23EE|u23EF|u23F1|u23F2|u23F8|u23F9|u23FA|u24C2|u25AA|u25AB|u25B6|u25C0|u25FB|u25FC|u2600|u2601|u2602|u2603|u2604|u260E|u2611|u2618|u261D|u2620|u2622|u2623|u2626|u262A|u262E|u262F|u2638|u2639|u263A|u2640|u2642|u265F|u2660|u2663|u2665|u2666|u2668|u267B|u267E|u2692|u2694|u2695|u2696|u2697|u2699|u269B|u269C|u26A0|u26A7|u26B0|u26B1|u26C8|u26CF|u26D1|u26D3|u26E9|u26F0|u26F1|u26F4|u26F7|u26F8|u26F9|u2702|u2708|u2709|u270C|u270D|u270F|u2712|u2714|u2716|u271D|u2721|u2733|u2734|u2744|u2747|u2763|u2764|u27A1|u2934|u2935|u2B05|u2B06|u2B07|u3030|u303D|u3297|u3299|uD83C[uDD70-uDD71]|uD83C[uDD7E-uDD7F]|uD83C[uDE02]|uD83C[uDE37]|uD83C[uDF21]|uD83C[uDF24-uDF2C]|uD83C[uDF36]|uD83C[uDF7D]|uD83C[uDF96-uDF97]|uD83C[uDF99-uDF9B]|uD83C[uDF9E-uDF9F]|uD83C[uDFCB-uDFCE]|uD83C[uDFD4-uDFDF]|uD83C[uDFF3]|uD83C[uDFF5]|uD83C[uDFF7]|uD83D[uDC3F]|uD83D[uDC41]|uD83D[uDCFD]|uD83D[uDD49-uDD4A]|uD83D[uDD6F]|uD83D[uDD70]|uD83D[uDD73-uDD79]|uD83D[uDD87]|uD83D[uDD8A-uDD8D]|uD83D[uDD90]|uD83D[uDDA5]|uD83D[uDDA8]|uD83D[uDDB1]|uD83D[uDDB2]|uD83D[uDDBC]|uD83D[uDDC2-uDDC4]|uD83D[uDDD1-uDDD3]|uD83D[uDDDC-uDDDE]|uD83D[uDDE1]|uD83D[uDDE3]|uD83D[uDDE8]|uD83D[uDDEF]|uD83D[uDDF3]|uD83D[uDDFA]|uD83D[uDECB]|uD83D[uDECD-uDECF]|uD83D[uDEE0-uDEE5]|uD83D[uDEE9]|uD83D[uDEF0]|uD83D[uDEF3]';

const emojiZwjElement = `(${emojiCharacter}|${variationSequence})(${emojiModifier}|${emojiPresentationSelector}${keycapSymbol}?|${tagSymbol}+${termTagSymbol}?)?`;
const myRegExp = `${emojiFlagSequence}|${kVSequence}|${emojiZwjElement}(${zeroWidthJoiner}${emojiZwjElement})*`;

/**
 * 识别 emoji 的正则
 * @param {string} modifier 正则修饰符,比如:'g'
 * @returns 一段正则。*
 * const reg1 = getRegExpEmoji(); // 正则:/p{emoji}/
 * reg1.test('😊'); // true
 *
 * const reg2 = getRegExpEmoji('g'); // 正则:/p{emoji}/g
 * '😊😊😊'.match(reg2); // ['😊', '😊', '😊']
 */
const getRegExpEmoji = (modifier?: string) => {
  try {const _reg = new RegExp(`${myRegExp}`, `${modifier || ''}`);
    return _reg;
  } catch (e) {console.error('getRegExpEmoji 提示:该系统不支持 new RegExp', e);
    return null;
  }
};

/**
 * 判断一段字符串中是否有 emoji
 *
 * @param {string} str 需要检查的字符串
 * @returns boolean 是否有 emoji,true: 有,false: 没有
 *
 * hasEmoji('123123123'); // false
 * hasEmoji('12312🇭🇰3123'); // true
 */
const hasEmoji = (str: string): boolean => {const _reg = new RegExp(`${myRegExp}`);
  return _reg ? _reg.test(str) : false;
};

/**
 * 匹配一段字符串中的 emoji
 * @param {string} str 需要匹配的字符串;* @param {string} modifier  new RegExp 的修饰符,传 g 的话就会匹配所有的 emoji 出来,不传的话只会匹配一个
 * @returns 匹配中的 emoji。*
 * matchEmoji('123123123'); // null
 *
 * matchEmoji('12312🇭🇰3123'); // ['🇭🇰']
 *
 * matchEmoji('12312🇭🇰31🇭🇰23', 'g'); // ['🇭🇰', '🇭🇰']
 */
const matchEmoji = (str: string, modifier?: string) => {const _reg = getRegExpEmoji(modifier);
  try {return _reg ? str.match(_reg) : null;
  } catch (e) {console.error('matchEmoji 提示:该系统不支持 new RegExp', e);
    return null;
  }
};

参考文章

https://unicode.org/emoji/charts/full-emoji-list.html

https://unicode.org/Public/emoji/15.0/

    正文完
     0
    Yojack
    版权声明:本篇文章由 Yojack 于1970-01-01发表,共计5585字。
    转载说明:
    1 本网站名称:优杰开发笔记
    2 本站永久网址:https://yojack.cn
    3 本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长进行删除处理。
    4 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
    5 本站所有内容均可转载及分享, 但请注明出处
    6 我们始终尊重原创作者的版权,所有文章在发布时,均尽可能注明出处与作者。
    7 站长邮箱:laylwenl@gmail.com
    评论(没有评论)