Extraire les emojis d’un texte
Par Christophe Porteneuve • Publié le 14 mai 2020
• 3 min
This page is also available in English.
En route pour le onzième article de notre série quotidienne « 19 pépites de JS pur ». Et aujourd’hui, on va parler d’emojis. Ils emojis sont partout, mais pas simples à identifier, extraire, regrouper, etc. au sein d’une chaîne de caractères. Leur liste est en augmentation constante, et dans les String
JavaScript, ils sont systématiquement représentés par des surrogate pairs, en raison de leurs codepoints élevés… Heureusement, une nouveauté ES2018 nous change la vie !
Dans la série…
Extrait de la liste des articles :
- Vive les séparateurs numériques !
- Trier proprement des textes
- Extraire les emojis d’un texte (cet article)
- Définir proprement des paramètres nommés optionnels
const
is the newvar
- …au-delà, c’est la surprise ! (mais la liste est déjà calée)…
Emojis, Unicode et surrogate pairs
Dans notre article de « pépite » n°5, « Strings et Unicode en JavaScript », on a déjà parlé en détail de la gestion d’Unicode dans le type String
. On a vu notamment que le texte y est encodé en UTF-16, avec des codets de 16 bits (2 octets), ce qui nécessite, pour les codepoints au-delà de U+FFFF, la combinaison de deux codets successifs, individuellement invalides, en ce qu’on appelle une surrogate pair.
C’est notamment le cas des Emojis, dont la quasi-totalité des codepoints utilisent la plage U+1Fxxx ainsi que de nombreux modificateurs allant jusqu’aux plages U+Exxxx.
Une telle diversité fait qu’évidemment, repérer de manière fiable les Emojis dans une String
est assez pénible « à la main » ; la regexp « classique » pour y parvenir serait particulièrement chaude (et pas hyper performante)…
Le drapeau Unicode des regexps
Depuis ES2015, on a le drapeau u
sur les regexp, qui active la prise en charge d’Unicode.
Avant ES2018, ça permettait « juste » de pouvoir utiliser la syntaxe de littéral codepoint (c’est à dire \u{XXXXX}
) en plus de la syntaxe historique de littéral codet (\uXXXX
). Mais depuis ES2018, on a la possibilité d’établir des correspondances, positives ou négatives, sur les propriétés Unicode.
Les propriétés Unicode
Le standard Unicode assigne à chaque codepoint toute une série de propriétés. Ce sont des catégories croisées, si tu préfères. Par exemple, prenons le glyphe U+2778 : ❸ (affectueusement dénommé Dingbat Negative Circled Digit Three). Il a entre autres les propriétés suivantes :
- Script :
Common
/Zyyy
(voici une [liste issue de la spec ES](https://tc39.es/ecma262/#table-unicode-script-values) et [une autre de l’excellent site Compart](https://www.compart.com/en/unicode/scripts)) - General Category :
Other_Number
/No
(voici [liste spec ES](https://tc39.es/ecma262/#table-unicode-general-category-values) et [liste Compart](https://www.compart.com/en/unicode/category)).
Du coup, il est aussi dans `Number` / `N`, une catégorie plus générique.
Au fait, la plupart des valeurs de propriétés existent en forme longue (telle que Other_Number
) et courte (ex. No
). Comme toujours, préfère la version lisible (longue) pour produire un code un peu plus maintenable…
Unicode Property Escapes à la rescousse !
ES2018 nous amène donc une nouvelle syntaxe dans les regexps pour correspondre à des propriétés Unicode : les Unicode Property Escapes. La syntaxe est \p{…}
. Comme toujours avec les séquences d’échappement dans une regexp, on a la version positive en minuscule, et la version négative en majuscule (\P{…}
). Exactement comme \s
dit « whitespace » et \S
dit « tout sauf du whitespace ».
Les propriétés peuvent être binaires (oui/non ; liste spec ES) ou plus générales (toutes les autres, telles que General_Category
ou Script
). Pour les binaires, préciser leur nom suffit ; pour les autres, il faudra préciser aussi une valeur.
Nombre de valeurs de scripts ou de catégorie peuvent être utilisées directement comme « propriétés binaires » dans la syntaxe. Par exemple, on peut tout aussi bien dire \p{Emoji}
que \p{Script=Emoji}
. Ainsi, parmi les « binaires » bien utiles, on retrouve Alphabetic
, Uppercase
, Lowercase
, Number
(notamment Decimal_Number
), Diacritic
, Emoji
, White_Space
(qui couvre plus de cas exotiques que le \w
traditionnel)…
Voici donc notre solution pour extraire toute séquence d’Emojis dans un texte !
'Super 🎉 J’adore ! 🤗😍'.match(/\p{Emoji}+/gu)
// => ['🎉', '🤗😍']
Mais évidemment ça peut être super cool pour plein d’autres besoins :
'42 (oui, 𝟜𝟚) ou ٤٢, ou encore ೪೨, est la réponse…'.match(
/\p{Decimal_Number}+/gu
)
// => ['42', '𝟜𝟚', '٤٢', '೪೨']
(Eh oui, qu’ils soient ASCII, double-barrés mathématiques, indo-arabes ou kannada, ça reste des chiffres décimaux…)
La doc du MDN est comme toujours excellente (mais j’ai lié la version originale anglophone ici car la VF n’est qu’au tout début de son effort de traduction).
Cadeau bonus : le drapeau singleline/dotall
Parmi les nombreuses améliorations de la syntaxe de regexp apportées depuis ES2018, cette même édition nous a aussi apporté le très attendu drapeau s
, pour “single line” (également connu sous le nom “dotall”), qui permet à la classe de caractère quelconque .
(point) de correspondre aussi aux sauts de lignes et retours chariots :
'<p>un\ndeux</p>'.match(/<p>.*<\/p>/) // => null
'<p>un\ndeux</p>'.match(/<p>.*<\/p>/s) // => ['<p>un\ndeux</p>, …]
Avant ça, on était obligés de recourir à des hacks pas très lisibles (à la place du .
), tels que [^]
(classe signifiant « tout sauf rien ») ou [\s\S]
(qui disait « tous les whitespaces et tous les non-whitespaces »), ce qui masquait un peu l’intention…