A regexed több emojit is egy karakternek nézheti: a Unicode v flag

A JavaScript Unicode v flagje halmazműveleteket és több kódpontból álló emoji-sorozatokat hoz a regexekbe. A negált property escape-eknél azonban a jelentés is változhat.

Egy űrlapon „pontosan egy emojit” kérni ártatlan feladatnak hangzik, egészen addig, amíg valaki beilleszt egy család emojit. A 👨‍👩‍👧‍👦 a felhasználó szemében egy jel, a JavaScript szemében viszont több Unicode-kódpontból és ZWJ-ből összerakott sorozat. A /^.$/u tehát nem fogja elfogadni. A reguláris kifejezések v flagje pont erre a fajta szakadékra ad új eszközöket – és közben néhány régi Unicode-szabály jelentését is átírja.

Unicode-karakterhalmazok metszetét és több kódpontból álló emoji-sorozatot ábrázoló technikai diagram

Nem csak a u flag új verziója

A nyelvi specifikáció a v-t UnicodeSets módnak nevezi. A u-hoz hasonlóan Unicode-tudatosan értelmezi a mintát, de a karakterosztályokat valódi halmazműveletekkel bővíti. Metszetet írhatunk &&-tel, kivonást ---szal. Ez nem pusztán esztétika: a korábbi, egymásra pakolt negatív lookaheadek helyett a szabály ott van, ahol a karakterhalmazt olvassuk.

const latinBetuk = /^[\p{Script_Extensions=Latin}&&\p{Letter}]+$/v;

console.log(latinBetuk.test("árvíztűrő")); // true
console.log(latinBetuk.test("Δelta"));      // false
console.log(latinBetuk.test("abc42"));       // false

Itt az egyik oldal a latin írásrendszerhez kapcsolódó karakterek halmaza, a másik a betűké; a metszetük kell. A v módú karakterosztályok ennél összetettebb halmazokat is tudnak, de nem érdemes minden validátort ilyen matematikai rejtvénnyé változtatni. Akkor jó, ha a domain szabálya tényleg egy karakterhalmaz.

Itt rögtön van egy kevésbé látványos csavar is: a v nem normalizálja a szöveget. Az é lehet egy kódpont, vagy e és egy külön kombináló ékezet; a fenti „csak betű” minta az utóbbit elutasítja. Névmezőben ez váratlan hiba, nem szigorú validáció. Ha a domainben helyes a kanonikus összehasonlítás, validálás előtt normalizálj NFC-re. Jelszót, tokent vagy aláírt üzenetet viszont ne alakíts át csak azért, hogy a regexed kényelmesebben érezze magát.

const dekomponalt = "e\u0301";

console.log(latinBetuk.test(dekomponalt));                  // false
console.log(latinBetuk.test(dekomponalt.normalize("NFC"))); // true

A meglepetés: egy \p néha nem karaktert talál

A v mód Unicode-tulajdonságai között úgynevezett „string property” is lehet. Az RGI_Emoji például egyetlen regex-találatként képes kezelni egy szabványos emoji-sorozatot. Ezt a Unicode property escape-ek leírása is külön kiemeli: ilyen tulajdonság csak pozitív \p-vel használható.

const egyEmoji = /^(?:\p{RGI_Emoji})$/v;

console.log(egyEmoji.test("👨‍👩‍👧‍👦"));  // true
console.log(egyEmoji.test("a👨‍👩‍👧‍👦")); // false

Na, ezt nem tudtam: a „karakterosztály” mögötti mechanizmus itt több kódpont hosszú sztringet is fogyaszthat. Ez mégsem univerzális grapheme-cluster számláló. Az RGI_Emoji az Unicode által nyilvántartott emoji-sorozatokra vonatkozik; tetszőleges, felhasználó által összerakott látható karakterek mérésére továbbra is az Intl.Segmenter a tisztább választás. Emoji-only mezőnél viszont sokkal pontosabb, mint egy tiltólista vagy a „két UTF-16 egység biztos emoji” iskola.

A i és a negálás csapdája

A v nem teljesen kompatibilis átnevezése a u-nak. Unicode property escape és kis-nagybetű-független keresés együtt egy ritka, de lényeges eltérést hoz. u módban a motor előbb kiegészíti a \P{Lowercase_Letter} halmazt, és csak utána hajtja végre a case foldingot; v-ben a case folding előbb történik. Emiatt ez a program más eredményt ad:

console.log(/\P{Lowercase_Letter}/iu.test("a")); // true
console.log(/\P{Lowercase_Letter}/iv.test("a")); // false

A részletes szabályt és a pontos példát az MDN dokumentációja is rögzíti. A tanulság prózaibb, mint maga az algoritmus: ha egy régi u-s mintát v-re cserélsz, a \P + i kombinációt teszteld valódi mintaadatokkal. Itt a „szigorúbb Unicode” nem garantálja ugyanazt a validációt.

Productionben: célzottan, nem divatból

Én productionben használom, ha a terméknek tényleg Unicode-halmazon alapuló szabálya van: névleges betűkészlet, emoji-reakció vagy dokumentált azonosító-formátum. Meglévő u-s regexek tömeges átírására nem. A v szebb lehet, de a rövidebb regex nem automatikusan érthetőbb regex; az olvashatóság továbbra is emberi feature.

Ha a kliensköröd nem garantálja a v támogatását, ne küldj le olyan bundle-t, amely már a parse-oláskor elhasal. Feature detecthez elég egy fordítási próba, a szerveroldali validációt pedig ne váltsd ki vele:

const hasUnicodeSets = (() => {
  try {
    new RegExp("\\p{RGI_Emoji}", "v");
    return true;
  } catch {
    return false;
  }
})();

Dinamikusan összerakott mintánál a bemenetet továbbra is escape-elni kell; erre már van szabványos RegExp.escape(). A v jó eszköz szöveg- és emoji-szabályokhoz, de nem XSS-védelem, nem jogosultságkezelés és nem a backend helyett dolgozó őr. Egy regexnek is lehet túl nagy a hatásköre – csak általában nem kap hozzá architektúra-diagramot.

Források

Leave a Reply

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük