26 de agosto de 2022

Unicode: bandera "u" y clase \p{...}

JavaScript utiliza codificación Unicode para las cadenas. La mayoría de los caracteres están codificados con 2 bytes, esto permite representar un máximo de 65536 caracteres.

Ese rango no es lo suficientemente grande como para codificar todos los caracteres posibles, es por eso que algunos caracteres raros se codifican con 4 bytes, por ejemplo como 𝒳 (X matemática) o 😄 (una sonrisa), algunos sinogramas, etc.

Aquí los valores unicode de algunos caracteres:

Carácter Unicode conteo de Bytes en unicode
a 0x0061 2
0x2248 2
𝒳 0x1d4b3 4
𝒴 0x1d4b4 4
😄 0x1f604 4

Entonces los caracteres como a e ocupan 2 bytes, mientras que los códigos para 𝒳, 𝒴 y 😄 son más largos, tienen 4 bytes.

Hace mucho tiempo, cuando se creó el lenguaje JavaScript, la codificación Unicode era más simple: no había caracteres de 4 bytes. Por lo tanto, algunas características del lenguaje aún los manejan incorrectamente.

Por ejemplo, aquí length interpreta que hay dos caracteres:

alert('😄'.length); // 2
alert('𝒳'.length); // 2

…Pero podemos ver que solo hay uno, ¿verdad? El punto es que length maneja 4 bytes como dos caracteres de 2 bytes. Eso es incorrecto, porque debe considerarse como uno solo (el llamado “par sustituto”, puede leer sobre ellos en el artículo Strings).

Por defecto, las expresiones regulares manejan los “caracteres largos” de 4 bytes como un par de caracteres de 2 bytes cada uno. Y, como sucede con las cadenas, eso puede conducir a resultados extraños. Lo veremos un poco más tarde, en el artículo No se encontró el artículo "regexp-character-sets-and-range".

A diferencia de las cadenas, las expresiones regulares tienen la bandera u que soluciona tales problemas. Con dicha bandera, una expresión regular maneja correctamente los caracteres de 4 bytes. Y podemos usar la búsqueda de propiedades Unicode, que veremos a continuación.

Propiedades Unicode \p{…}

Cada carácter en Unicode tiene varias propiedades. Describen a qué “categoría” pertenece el carácter, contienen información diversa al respecto.

Por ejemplo, si un carácter tiene la propiedad Letter, significa que pertenece a un alfabeto (de cualquier idioma). Y la propiedad Number significa que es un dígito: tal vez árabe o chino, y así sucesivamente.

Podemos buscar caracteres por su propiedad, usando \p{...}. Para usar \p{...}, una expresión regular debe usar también u.

Por ejemplo, \p{Letter} denota una letra en cualquiera de los idiomas. También podemos usar \p{L}, ya que L es un alias de Letter. Casi todas las propiedades tienen alias cortos.

En el ejemplo a continuación se encontrarán tres tipos de letras: inglés, georgiano y coreano.

let str = "A ბ ㄱ";

alert( str.match(/\p{L}/gu) ); // A,ბ,ㄱ
alert( str.match(/\p{L}/g) ); // null (sin coincidencia, como no hay bandera "u")

Estas son las principales categorías y subcategorías de caracteres:

  • Letter (Letra) L:
    • lowercase (minúscula) Ll
    • modifier (modificador) Lm,
    • titlecase (capitales) Lt,
    • uppercase (mayúscula) Lu,
    • other (otro) Lo.
  • Number (número) N:
    • decimal digit (dígito decimal) Nd,
    • letter number (número de letras) Nl,
    • other (otro) No.
  • Punctuation (puntuación) P:
    • connector (conector) Pc,
    • dash (guión) Pd,
    • initial quote (comilla inicial) Pi,
    • final quote (comilla final) Pf,
    • open (abre) Ps,
    • close (cierra) Pe,
    • other (otro) Po.
  • Mark (marca) M (acentos etc):
    • spacing combining (combinación de espacios) Mc,
    • enclosing (encerrado) Me,
    • non-spacing (sin espaciado) Mn.
  • Symbol (símbolo) S:
    • currency (moneda) Sc,
    • modifier (modificador) Sk,
    • math (matemática) Sm,
    • other (otro) So.
  • Separator (separador) Z:
    • line (línea) Zl,
    • paragraph (párrafo)Zp,
    • space (espacio) Zs.
  • Other (otros) C:
    • control Cc,
    • format (formato) Cf,
    • not assigned (sin asignación) Cn,
    • private use (uso privado) Co,
    • surrogate (sustituto) Cs.

Entonces, por ejemplo si necesitamos letras en minúsculas, podemos escribir \p{Ll}, signos de puntuación: \p{P} y así sucesivamente.

También hay otras categorías derivadas, como:

  • Alphabetic (alfabético) (Alfa), incluye letras L, más números de letras Nl (por ejemplo, Ⅻ – un carácter para el número romano 12), y otros símbolos Other_Alphabetic (OAlpha).
  • Hex_Digit incluye dígitos hexadecimales: 0-9, a-f.
  • …Y así.

Unicode admite muchas propiedades diferentes, la lista completa es muy grande, estas son las referencias:

Ejemplo: números hexadecimales

Por ejemplo, busquemos números hexadecimales, escritos como xFF dondeF es un dígito hexadecimal (0…9 o A…F).

Un dígito hexadecimal se denota como \p{Hex_Digit}:

let regexp = /x\p{Hex_Digit}\p{Hex_Digit}/u;

alert("número: xAF".match(regexp)); // xAF

Ejemplo: sinogramas chinos

Busquemos sinogramas chinos.

Hay una propiedad Unicode Script (un sistema de escritura), que puede tener un valor: Cyrillic, Greek, Arabic, Han (chino), etc. lista completa.

Para buscar caracteres de un sistema de escritura dado, debemos usar Script=<value>, por ejemplo para letras cirílicas: \p{sc=Cyrillic}, para sinogramas chinos: \p{sc=Han}, y así sucesivamente:

let regexp = /\p{sc=Han}/gu; // devuelve sinogramas chinos

let str = `Hello Привет 你好 123_456`;

alert( str.match(regexp) ); // 你,好

Ejemplo: moneda

Los caracteres que denotan una moneda, como $, , ¥, tienen la propiedad unicode \p{Currency_Symbol}, el alias corto: \p{Sc}.

Usémoslo para buscar precios en el formato “moneda, seguido de un dígito”:

let regexp = /\p{Sc}\d/gu;

let str = `Precios: $2, €1, ¥9`;

alert( str.match(regexp) ); // $2,€1,¥9

Más adelante, en el artículo Cuantificadores +, *, ? y {n} veremos cómo buscar números que contengan muchos dígitos.

Resumen

La bandera u habilita el soporte de Unicode en expresiones regulares.

Eso significa dos cosas:

  1. Los caracteres de 4 bytes se manejan correctamente: como un solo carácter, no dos caracteres de 2 bytes.
  2. Las propiedades Unicode se pueden usar en las búsquedas: \p{…}.

Con las propiedades Unicode podemos buscar palabras en determinados idiomas, caracteres especiales (comillas, monedas), etc.

Mapa del Tutorial

Comentarios

lea esto antes de comentar…
  • Si tiene sugerencias sobre qué mejorar, por favor enviar una propuesta de GitHub o una solicitud de extracción en lugar de comentar.
  • Si no puede entender algo en el artículo, por favor explique.
  • Para insertar algunas palabras de código, use la etiqueta <code>, para varias líneas – envolverlas en la etiqueta <pre>, para más de 10 líneas – utilice una entorno controlado (sandbox) (plnkr, jsbin, codepen…)