Regex JavaScript : la limite de mot \b échoue avec les caractères CJK
Problème
L'assertion \b (limite de mot) de JavaScript échoue silencieusement avec les caractères CJK (chinois/japonais/coréen). Le regex se compile sans erreur et s'exécute sans lever d'exception, mais il ne correspond jamais au texte CJK qui devrait correspondre. C'est particulièrement insidieux car :
- Aucune erreur n'est levée — le regex retourne simplement
false - Le même pattern fonctionne parfaitement pour le texte latin/ASCII
- Les caractères CJK du pattern sont corrects (vérifiés par comparaison directe de chaînes)
Contexte / Conditions déclenchantes
- Un pattern comme
/\b\u904B\u8EE2\u514D\u8A31\u8A3C\b/(japonais : 運転免許証) retournefalsesur du texte contenant exactement ces caractères - Un pattern comme
/\b\uC6B4\uC804\uBA74\uD5C8\uC99D\b/(coréen : 운전면허증) échoue de la même façon - Les fonctions de détection de texte (p. ex.
hasDriverLicenceCue(),inferIssuerFromTitleText()) retournent des résultats incorrects pour une entrée CJK alors qu'elles fonctionnent correctement pour tous les patterns en alphabet latin - Tout regex utilisant des limites
\bautour de caractères Unicode non-ASCII (affecte également le cyrillique, l'arabe, le thaï, etc.)
Cause racine
\b de JavaScript correspond à la limite entre un « caractère de mot » (\w = [a-zA-Z0-9_]) et
un « caractère non-mot » (\W). Les caractères CJK sont classés comme \W (caractères non-mot).
Quand \b apparaît avant un caractère CJK, il cherche une transition \w-vers-\W ou \W-vers-\w.
Mais si le caractère précédent est aussi \W (espace, ponctuation, début de chaîne, ou un autre caractère CJK),
la condition de limite est \W-vers-\W, ce que \b ne correspond PAS.
// Ceci ÉCHOUE — \b ne fonctionne pas avec CJK
/\b\u904B\u8EE2\u514D\u8A31\u8A3C\b/.test("運転免許証") // false!
// Ceci FONCTIONNE — pas de limites de mot
/\u904B\u8EE2\u514D\u8A31\u8A3C/.test("運転免許証") // true
Solution
Correction rapide : supprimer \b des patterns CJK
Supprimez simplement les assertions \b de tout pattern regex qui correspond aux caractères CJK :
// Avant (cassé) :
[/\b\u904B\u8EE2\u514D\u8A31\u8A3C\b/, "JAPAN"], // 運転免許証
[/\b\uC6B4\uC804\uBA74\uD5C8\uC99D\b/, "KOREA"], // 운전면허증
// Après (fonctionnant) :
[/\u904B\u8EE2\u514D\u8A31\u8A3C/, "JAPAN"], // 運転免許証
[/\uC6B4\uC804\uBA74\uD5C8\uC99D/, "KOREA"], // 운전면허증
Meilleure correction : utiliser des limites Unicode-aware (si disponible)
Pour les environnements supportant le flag v (ES2024+), vous pouvez utiliser les echappements de propriétés Unicode :
// Approche Unicode-aware (nécessite le support du flag /v) :
/(?<=^|[\s\p{P}])\u904B\u8EE2\u514D\u8A31\u8A3C(?=$|[\s\p{P}])/v
Alternative : limite manuelle avec lookbehind/lookahead
// Limite manuelle qui fonctionne avec CJK :
/(?<!\p{L})\u904B\u8EE2\u514D\u8A31\u8A3C(?!\p{L})/u
Pour les tableaux de patterns mixtes latin/CJK
Quand vous avez un tableau de patterns où certains sont en latin et d'autres en CJK, utilisez \b uniquement pour
les patterns latins :
const patterns = [
[/\bDRIVER'?S?\s+LICEN[CS]E\b/, "match"], // Latin — \b fonctionne
[/\bFÜHRERSCHEIN\b/, "match"], // Latin+diacritiques — \b fonctionne (Ü est \W mais le contexte aide)
[/\u904B\u8EE2\u514D\u8A31\u8A3C/, "match"], // CJK — \b non nécessaire
[/\uC6B4\uC804\uBA74\uD5C8\uC99D/, "match"], // CJK — \b non nécessaire
];
Vérification
// Test que la correspondance CJK fonctionne :
console.log(/\u904B\u8EE2\u514D\u8A31\u8A3C/.test("運転免許証")); // true
console.log(/\uC6B4\uC804\uBA74\uD5C8\uC99D/.test("운전면허증")); // true
// Vérifier qu'il ne correspond pas faux aux sous-chaînes que vous ne voulez pas :
console.log(/\u904B\u8EE2\u514D\u8A31\u8A3C/.test("別の運転免許証テスト")); // true (la correspondance de sous-chaîne est généralement fine pour CJK)
Exemple
Cas réel d'une fonction de détection de type de document :
function hasDriverLicenceCue(text) {
const combined = text.toUpperCase();
return (
/\bDRIVER'?S?\s+LICEN[CS]E\b/.test(combined) || // Anglais
/\bFÜHRERSCHEIN\b/.test(combined) || // Allemand
/\bPERMIS DE CONDUIRE\b/.test(combined) || // Français
/\u904B\u8EE2\u514D\u8A31\u8A3C/.test(combined) || // Japonais (pas de \b!)
/\uC6B4\uC804\uBA74\uD5C8\uC99D/.test(combined) // Coréen (pas de \b!)
);
}
Notes
- Cela affecte TOUS les scripts Unicode non-ASCII, pas seulement CJK : cyrillique, arabe, thaï, devanagari, etc.
- Le flag
u(unicode) ne corrige PAS ceci — le comportement de\bavec\w/\Wreste inchangé. - Supprimer
\bdes patterns CJK est généralement sûr car les caractères CJK sont peu susceptibles d'apparaître comme sous-chaînes au sein d'autres mots dans des contextes non liés. - Le package npm
regexp-cjkfournit des utilitaires regex sensibles aux CJK si vous avez besoin de correspondances plus sophistiquées. - Lors du débogage : si un regex fonctionne pour "DRIVER'S LICENCE" mais pas pour "運転免許証", suspectez d'abord
\b.