php - solo - probar expresiones regulares
Valor hexadecimal máximo en la expresión regular (5)
Sin usar el indicador u
, el rango hexadecimal que puede usarse es [/x{00}-/x{ff}]
, pero con el indicador u
sube a un valor de 4 bytes /x{7fffffff}
( [/x{00000000}-/x{7fffffff}]
).
Así que si ejecuto el siguiente código:
preg_match("/[/x{00000000}-/x{80000000}]+/u", $str, $match);
Obtendré este error :
Warning: preg_match(): Compilation failed: character value in /x{...} sequence is too large
Así que no puedo hacer coincidir una letra como 𡃁
con un valor hexadecimal equivalente de f0 a1 83 81
. La pregunta no es cómo hacer coincidir estas letras, sino cómo este rango y este límite provienen de que el modificador u
debe tratar las cadenas como UTF-16
PCRE soporta UTF-16 desde v8.30
echo PCRE_VERSION;
Versión PCRE con PHP 5.3.24 - 5.3.28, 5.4.14 - 5.5.7:
8.32 2012-11-30
Versión PCRE con PHP 5.3.19 - 5.3.23, 5.4.9 - 5.4.13:
8.31 2012-07-06
Por lo tanto, no puedo hacer coincidir una letra como value con un valor hexadecimal equivalente de f0 a1 83 81. La pregunta no es cómo hacer coincidir estas letras, sino cómo este rango y este límite provienen de que el modificador u debe tratar las cadenas como UTF-16
Estás mezclando dos conceptos que están causando esta confusión.
F0 A1 83 81
no es el valor hexadecimal del carácter 𡃁. Esta es la forma en que UTF-8 codifica el punto de código para ese carácter en el flujo de bytes.
Es correcto que PHP admita puntos de código UTF-16 para el patrón /x{}
, pero los valores dentro de {
y }
representan puntos de código UTF-16 y no los bytes reales utilizados para codificar el carácter dado en el flujo de bytes.
Así que el mayor valor posible que puede usar con /x{}
es en realidad 10FFFF
.
Y para hacer coincidir 𡃁 con PHP, debe usar el punto de código que, como lo sugiere @minitech en su comentario, es /x{0210c1}
.
Explicación adicional citada en la sección "Validez de las cadenas" de la documentación de PCRE .
Se comprueba toda la cadena antes de que tenga lugar cualquier otro procesamiento. Además de verificar el formato de la cadena, hay una verificación para asegurarse de que todos los puntos de código se encuentren en el rango U + 0 a U + 10FFFF, excluyendo el área sustituta. Los puntos de código denominados "sin carácter" no se excluyen porque el corrigendum # 9 de Unicode deja claro que no deberían estarlo.
Los caracteres en el "Área de Sustitución" de Unicode están reservados para su uso por UTF-16, donde se usan en pares para codificar puntos de código con valores superiores a 0xFFFF. Los puntos de código que están codificados por pares de UTF-16 están disponibles independientemente en las codificaciones UTF-8 y UTF-32. (En otras palabras, todo lo relacionado con el sustituto es un fudge para UTF-16 que desafortunadamente desordena UTF-8 y UTF-32).
Codificación Unicode y UTF-8, UTF-16, UTF-32
Unicode es un conjunto de caracteres, que especifica una asignación de caracteres a puntos de código, y las codificaciones de caracteres (UTF-8, UTF-16, UTF-32) especifican cómo almacenar los puntos de código Unicode.
En Unicode, un carácter se asigna a un solo punto de código, pero puede tener una representación diferente dependiendo de cómo esté codificado.
No quiero repetir de nuevo esta discusión, así que si aún no lo tiene claro, lea El Absoluto Mínimo. Todos los desarrolladores de software deben saber absolutamente, positivamente acerca de Unicode y los conjuntos de caracteres (¡sin excusas!) .
Utilizando el ejemplo de la pregunta, 𡃁
asigna al punto de código U+210C1
, pero se puede codificar como F0 A1 83 81
en UTF-8, D844 DCC1
en UTF-16 y 000210C1
en UTF-32.
Para ser precisos, el ejemplo anterior muestra cómo asignar un punto de código a unidades de código (forma de codificación de caracteres). Cómo se asignan las unidades de código a la secuencia de octetos es otra cuestión. Ver modelo de codificación Unicode.
Biblioteca PCRE de 8 bits, 16 bits y 32 bits.
Dado que PHP no ha adoptado PCRE2 todavía (versión 10.10), el texto citado es de la documentación del PCRE original.
Soporte para bibliotecas de 16 y 32 bits.
PCRE incluye soporte para cadenas de 16 bits en la versión 8.30 y cadenas de 32 bits de la versión 8.32, además de la biblioteca predeterminada de 8 bits.
Además de admitir cadenas de caracteres de 8 bits, PCRE también admite cadenas de 16 bits (de la versión 8.30) y cadenas de 32 bits (de la versión 8.32), por medio de dos bibliotecas adicionales. Pueden construirse tan bien como, o en lugar de, la biblioteca de 8 bits. [...]
Significado de 8 bits, 16 bits, 32 bits
8 bits, 16 bits y 32 bits aquí se refiere a la unidad de datos (unidad de código).
Las referencias a bytes y UTF-8 en este documento deben leerse como referencias a unidades de datos de 16 bits y UTF-16 cuando se usa la biblioteca de 16 bits, o unidades de datos de 32 bits y UTF-32 cuando se usa la biblioteca de 32 bits , a menos que se especifique lo contrario. Más detalles de las diferencias específicas para las bibliotecas de 16 y 32 bits se dan en las páginas pcre16 y pcre32.
Esto significa que la biblioteca de 8 bits / 16 bits / 32 bits espera que el patrón y la cadena de entrada sean secuencias de unidades de datos de 8 bits / 16 bits / 32 bits, o UTF-8 / UTF-16 / válido. Cuerdas UTF-32.
Diferentes APIs para diferente ancho de unidad de datos
PCRE proporciona 3 conjuntos de API idénticas para bibliotecas de 8 bits, 16 bits y 32 bits, diferenciadas por el prefijo ( pcre_
, pcre16_
y pcre_32
respectivamente).
Las funciones de 16 y 32 bits funcionan de la misma manera que sus contrapartes de 8 bits; simplemente usan diferentes tipos de datos para sus argumentos y resultados, y sus nombres comienzan con
pcre16_
opcre32_
lugar depcre_
. Para cada opción que tiene UTF8 en su nombre (por ejemplo,PCRE_UTF8
), hay nombres correspondientes de 16 y 32 bits con UTF8 reemplazado por UTF16 o UTF32, respectivamente. Esta instalación es de hecho sólo cosmética; Los nombres de las opciones de 16 y 32 bits definen los mismos valores de bits.
En PCRE2, se utiliza una convención de nomenclatura de funciones similar , donde la función de 8 bits / 16 bits / 32 bits tiene _8
, _32
, _32
sufijo respectivamente. Las aplicaciones que utilizan solo una unidad de código de ancho pueden definir PCRE2_CODE_UNIT_WIDTH
para usar el nombre genérico de la función sin el sufijo.
Modo UTF vs. modo no-UTF
Cuando se configura el modo UTF (a través de las opciones en el patrón (*UTF)
, (*UTF8)
, (*UTF16)
, (*UTF32)
1 o las opciones de compilación PCRE_UTF8
, PCRE_UTF16
, PCRE_UTF32
), todas las secuencias de unidades de datos se interpretan como secuencias de caracteres Unicode, que consisten en todos los puntos de código de U + 0000 a U + 10FFFF, excepto los sustitutos y BOM.
1 Las opciones de patrón (*UTF8)
, (*UTF16)
, (*UTF32)
solo están disponibles en la biblioteca correspondiente. No puede usar (*UTF16)
en la biblioteca de 8 bits, ni ninguna combinación no coincidente, ya que simplemente no tiene sentido. (*UTF)
está disponible en todas las bibliotecas y proporciona una manera portátil de especificar el modo UTF en el patrón.
En el modo UTF, el patrón (que es una secuencia de unidades de datos) se interpreta y valida como una secuencia de puntos de código Unicode al decodificar la secuencia como datos UTF-8 / UTF-16 / UTF-32 (según la API utilizada) , antes de que sea compilado. La cadena de entrada también se interpreta y se valida opcionalmente como una secuencia de puntos de código Unicode durante el proceso de coincidencia. En este modo, una clase de caracteres coincide con un punto de código Unicode válido.
Por otro lado , cuando el modo UTF no está configurado (modo no UTF), todas las operaciones funcionan directamente en las secuencias de la unidad de datos. En este modo, una clase de caracteres coincide con una unidad de datos, y excepto por el valor máximo que se puede almacenar en una sola unidad de datos, no hay restricción en el valor de una unidad de datos. Este modo se puede utilizar para hacer coincidir la estructura en datos binarios. Sin embargo, no use este modo cuando esté tratando con el carácter Unicode , bueno, a menos que esté bien con ASCII e ignore el resto de los idiomas.
Restricciones en los valores de los personajes.
Los caracteres que se especifican usando números octales o hexadecimales están limitados a ciertos valores, de la siguiente manera:
8-bit non-UTF mode less than 0x100 8-bit UTF-8 mode less than 0x10ffff and a valid codepoint 16-bit non-UTF mode less than 0x10000 16-bit UTF-16 mode less than 0x10ffff and a valid codepoint 32-bit non-UTF mode less than 0x100000000 32-bit UTF-32 mode less than 0x10ffff and a valid codepoint
Los puntos de código de Unicode no válidos son del rango 0xd800 a 0xdfff (los llamados puntos de código "sustitutos"), y 0xffef.
PHP y PCRE
Las funciones de PCRE en PHP se implementan mediante un envoltorio que traduce indicadores y llamadas específicos de PHP a API de PCRE (como se ve en la rama PHP 5.6.10).
El código fuente llama a la biblioteca de PCRE de 8 bits ( pcre_
), por lo que cualquier cadena que se pasa a la función preg_
se interpreta como una secuencia de unidades de datos de 8 bits (bytes). Por lo tanto, incluso si se construyen las bibliotecas PCRE de 16 y 32 bits, no se puede acceder a ellas a través de la API en el lado de PHP.
Como resultado, las funciones de PCRE en PHP esperan:
- ... una matriz de bytes en modo no UTF (predeterminado), que la biblioteca lee en "caracteres" de 8 bits y compila para que coincida con cadenas de "caracteres" de 8 bits.
- ... una matriz de bytes que contiene una cadena Unicode codificada en UTF-8, que la biblioteca lee en caracteres Unicode y compila para que coincida con las cadenas UTF-8 Unicode.
Esto explica el comportamiento como se ve en la pregunta:
- En el modo no UTF (sin el indicador
u
), el valor máximo en la secuencia de escape regex hexadecimal es FF (como se muestra en[/x{00}-/x{ff}]
) - En el modo UTF, cualquier valor más allá de 0x10ffff (como
/x{7fffffff}
) en la secuencia de escape hexadecimal regex simplemente no tiene sentido.
Código de ejemplo
Este código de ejemplo demuestra:
- Las cadenas PHP son solo matrices de bytes y no entienden nada acerca de la codificación.
- Las diferencias entre el modo UTF y el modo no UTF en la función PCRE.
- La función PCRE llama a la biblioteca de 8 bits
// NOTE: Save this file as UTF-8
// Take note of double-quoted string literal, which supports escape sequence and variable expansion
// The code won''t work correctly with single-quoted string literal, which has restrictive escape syntax
// Read more at: https://php.net/language.types.string
$str_1 = "/xf0/xa1/x83/x81/xf0/xa1/x83/x81";
$str_2 = "𡃁𡃁";
$str_3 = "/xf0/xa1/x83/x81/x81/x81/x81/x81/x81";
echo ($str_1 === $str_2)."/n";
var_dump($str_3);
// Test 1a
$match = null;
preg_match("//xf0/xa1/x83/x81+/", $str_1, $match);
print_r($match); // Only match 𡃁
// Test 1b
$match = null;
preg_match("//xf0/xa1/x83/x81+/", $str_2, $match);
print_r($match); // Only match 𡃁 (same as 1a)
// Test 1c
$match = null;
preg_match("//xf0/xa1/x83/x81+/", $str_3, $match);
print_r($match); // Match 𡃁 and the five bytes of 0x81
// Test 2a
$match = null;
preg_match("/𡃁+/", $str_1, $match);
print_r($match); // Only match 𡃁 (same as 1a)
// Test 2b
$match = null;
preg_match("/𡃁+/", $str_2, $match);
print_r($match); // Only match 𡃁 (same as 1b and 2a)
// Test 2c
$match = null;
preg_match("/𡃁+/", $str_3, $match);
print_r($match); // Match 𡃁 and the five bytes of 0x81 (same as 1c)
// Test 3a
$match = null;
preg_match("//xf0/xa1/x83/x81+/u", $str_1, $match);
print_r($match); // Match two 𡃁
// Test 3b
$match = null;
preg_match("//xf0/xa1/x83/x81+/u", $str_2, $match);
print_r($match); // Match two 𡃁 (same as 3a)
// Test 4a
$match = null;
preg_match("/𡃁+/u", $str_1, $match);
print_r($match); // Match two 𡃁 (same as 3a)
// Test 4b
$match = null;
preg_match("/𡃁+/u", $str_2, $match);
print_r($match); // Match two 𡃁 (same as 3b and 4a)
Dado que las cadenas PHP son simplemente una matriz de bytes, siempre que el archivo se guarde correctamente en alguna codificación compatible con ASCII, PHP simplemente leerá los bytes sin preocuparse por la codificación en la que se encontraba originalmente. El programador es totalmente responsable de la codificación y decodificando las cadenas correctamente.
- Lo que todo programador debe saber de forma absoluta y positiva sobre las codificaciones y los conjuntos de caracteres para trabajar con el texto (sección "Uso y abuso de las codificaciones de PHP")
- Conjuntos de caracteres / Problemas de codificación de caracteres
- Preguntas frecuentes sobre PHP Charset: ¿Qué codificación debo usar para mis archivos de origen?
Debido a la razón anterior, si guarda el archivo anterior en la codificación UTF-8, verá que $str_1
y $str_2
son la misma cadena. $str_1
se decodifica de la secuencia de escape, mientras que $str_2
se lee literalmente del código fuente. Como resultado, "//xf0/xa1/x83/x81+/u"
y "/𡃁+/u"
son la misma cadena debajo (también en el caso de "//xf0/xa1/x83/x81+/"
y "/𡃁+/"
).
La diferencia entre el modo UTF y el modo no UTF se muestra claramente en el ejemplo anterior:
-
"/𡃁+/"
se ve como una secuencia de caracteresF0 A1 83 81 2B
donde "carácter" es un byte. Por lo tanto, la expresión regular resultante coincide con la secuenciaF0 A1 83
seguida de un byte81
repite una o más veces. -
"/𡃁+/u"
se valida e interpreta como una secuencia de caracteres UTF-8U+210C1 U+002B
. Por lo tanto, la expresión regular resultante coincide con el punto de códigoU+210C1
repetido una o más veces en la cadena UTF-8.
Carácter Unicode coincidente
A menos que la entrada contenga otros datos binarios, se recomienda siempre activar el modo u
. El patrón tiene acceso a todas las facilidades para hacer coincidir correctamente los caracteres Unicode, y tanto la entrada como el patrón se validan como cadenas UTF válidas.
Nuevamente, usando 𡃁
como ejemplo, el ejemplo anterior muestra dos formas de especificar la expresión regular:
"//xf0/xa1/x83/x81+/u"
"/𡃁+/u"
El primer método no funciona con una cadena entre comillas simples: como la secuencia de escape /x
no se reconoce entre comillas simples, la biblioteca recibirá la cadena /xf0/xa1/x83/x81+
, que combina con el modo UTF coincidirá con U+00F0 U+00A1 U+0083
seguido de U+0081
repetido una o más veces. Aparte de eso, también es confuso para la siguiente persona que lee el código: ¿cómo se supone que deben saber que es un solo personaje de Unicode que se repite una o más veces?
El segundo método funciona bien e incluso se puede usar con una cadena entre comillas simples, pero es necesario guardar el archivo en codificación UTF-8, especialmente en el caso de caracteres como ÿ
, ya que el carácter también es válido en la codificación de un solo byte. Este método es una opción si desea hacer coincidir un solo carácter o una secuencia de caracteres. Sin embargo, como puntos finales del rango de caracteres, puede que no quede claro qué es lo que está intentando hacer coincidir. Compare az
, AZ
, 0-9
, א-ת
, a diferencia de 一-龥
(que coincide con la mayoría de los bloques de Ideógrafos CJK Unified (4E00–9FFF), excepto los puntos de código no asignados al final) o 一-十
(que es un intento incorrecto de hacer coincidir los caracteres chinos para el número del 1 al 10).
El tercer método es especificar el punto de código en el escape hexadecimal directamente:
"//x{210C1}/u"
''//x{210C1}/u''
Esto funciona cuando el archivo se guarda en cualquier codificación compatible con ASCII, funciona con cadenas de comillas simples y dobles, y también proporciona un punto de código claro en el rango de caracteres. Este método tiene la desventaja de no saber cómo se ve el personaje, y también es difícil de leer cuando se especifica una secuencia de caracteres Unicode.
"pero quiero saber sobre el límite hexadecimal máximo en una expresión regular": * en todos los modos utf: 0x10ffff * modo nativo de 8 bt: 0xff * modo nativo de 16 bits: 0xffff * modo nativo de 32 bits: 0x1fffffff
Como minitech sugiere en el primer comentario, debe usar el punto de código; para este carácter, es /x{210C1}
. Esa es también la forma codificada en UTF-32. F0 AF AB BF
es la secuencia codificada en UTF-8 (consulte http://www.unicode.org/cgi-bin/GetUnihanData.pl?codepoint=210C1 ).
Existen algunas versiones de PCRE donde puede usar valores hasta /x{7FFFFFFF}
. Pero realmente no sé qué podría emparejarse con eso.
Para citar http://www.pcre.org/pcre.txt :
En el modo UTF-16, el código de carácter es Unicode, en el rango de 0 a 0x10ffff , con la excepción de los valores en el rango de 0xd800 a 0xdfff porque son valores "sustitutos" que se usan en pares para codificar valores mayores que 0xffff.
[...]
En el modo UTF-32, el código de carácter es Unicode, en el rango de 0 a 0x10ffff , con la excepción de los valores en el rango de 0xd800 a 0xdfff porque son valores "sustitutos" que están mal formados en UTF-32.
0x10ffff
es el mayor valor que puedes usar para 0x10ffff
un personaje (eso es lo que extraigo de esto). Actualmente, 0x10ffff
es también el punto de código más grande definido en el estándar de Unicode (vea ¿Cuáles son algunas de las diferencias entre los UTF? ), Por lo que cada valor anterior no tiene ningún sentido (o simplemente no lo entiendo) ...
No estoy seguro de php pero realmente no hay gobernador en los puntos de código
así que no importa que solo haya unos 1.1 millones de válidos.
Eso está sujeto a cambios en cualquier momento, pero realmente no depende de los motores
para hacer cumplir eso. Hay cp''s reservados que son agujeros en el rango válido,
hay sustitutos en el rango válido, las razones son infinitas para allí
para no ser otra restricción que no sea el tamaño de palabra.
Para UTF-32, no puede pasar de 31 bits porque 32 es el bit de signo.
0x00000000 - 0x7FFFFFFF
Tiene sentido, ya que un unsigned int
como tipo de datos es el tamaño natural de los registros de hardware de 32 bits.
Para UTF-16, incluso más cierto, puede ver la misma limitación enmascarada a 16 bits. El bit 32 sigue siendo el bit de signo que deja 0x0000 - 0xFFFF
como un rango válido.
Por lo general, si usa un motor que soporta ICU, debería poder usarlo,
que convierte tanto la fuente como la expresión regular en UTF-32. Boost Regex es uno de esos motores.
editar:
Respecto a UTF-16
Supongo que cuando Unicode superó los 16 bits, hicieron un agujero en el rango de 16 bits para los pares sustitutos. Pero dejó solo 20 bits totales entre el par como utilizables.
10 bits en cada sustituto con los otros 6 utilizados para determinar hi o lo.
Parece que esto dejó a la gente de Unicode con un límite de 20 bits + un 0xFFFF adicional redondeado, a un total de 0x10FFFF puntos de código, con agujeros inutilizables.
Para poder convertir a una codificación diferente (8/16/32) todos los puntos de código
En realidad debe ser convertible. Por lo tanto el compatibile siempre atrasado de 20 bits es
La trampa que encontraron temprano, pero ahora debe vivir con.
En cualquier caso, los motores de expresiones regulares no aplicarán este límite en el corto plazo, probablemente nunca.
En cuanto a los sustitutos, son el agujero , y un sustituto literal mal formado no puede convertirse entre modos. Eso solo se refiere a un carácter codificado literal durante la conversión, no a una representación hexadecimal de uno. Por ejemplo, es fácil buscar un texto en modo UTF-16 (solo) para sustitutos no emparejados, o incluso emparejados.
Pero supongo que los motores de expresiones regulares no se preocupan realmente por los orificios o los límites, solo se preocupan por el modo en que se encuentra la cadena del sujeto. No, el motor no va a decir:
''Oye, espera, el modo es UTF-16. /x{210C1}
mejor que convierta /x{210C1}
a /x{D844}/x{DCC1}
. Espera, si hice eso, ¿qué debo hacer si su /x{210C1}+
cuantificado comienza a inyectar construcciones de /x{210C1}+
regulares a su alrededor? Peor aún, ¿y si está en una clase [/x{210C1}]
? No ... mejor limítalo a /x{FFFF}
.
Algunas conversiones sustitutas prácticas de pseudo-código que uso:
Definitions:
====================
10-bits
3FF = 000000 1111111111
Hi Surrogate
D800 = 110110 0000000000
DBFF = 110110 1111111111
Lo Surrogate
DC00 = 110111 0000000000
DFFF = 110111 1111111111
Conversions:
====================
UTF-16 Surrogates to UTF-32
if ( TESTFOR_SURROGATE_PAIR(hi,lo) )
{
u32Out = 0x10000 + ( ((hi & 0x3FF) << 10) | (lo & 0x3FF) );
}
UTF-32 to UTF-16 Surrogates
if ( u32In >= 0x10000)
{
u32In -= 0x10000;
hi = (0xD800 + ((u32In & 0xFFC00) >> 10));
lo = (0xDC00 + (u32In & 0x3FF));
}
Macro''s:
====================
#define TESTFOR_SURROGATE_HI(hs) (((hs & 0xFC00)) == 0xD800 )
#define TESTFOR_SURROGATE_LO(ls) (((ls & 0xFC00)) == 0xDC00 )
#define TESTFOR_SURROGATE_PAIR(hs,ls) ( (((hs & 0xFC00)) == 0xD800) && (((ls & 0xFC00)) == 0xDC00) )
//
#define PTR_TESTFOR_SURROGATE_HI(ptr) (((*ptr & 0xFC00)) == 0xD800 )
#define PTR_TESTFOR_SURROGATE_LO(ptr) (((*ptr & 0xFC00)) == 0xDC00 )
#define PTR_TESTFOR_SURROGATE_PAIR(ptr) ( (((*ptr & 0xFC00)) == 0xD800) && (((*(ptr+1) & 0xFC00)) == 0xDC00) )