Muchas implementaciones modernas de expresiones regulares interpretan la \w
taquigrafía de la clase de caracteres como "cualquier letra, dígito o puntuación de conexión" (generalmente: subrayado). De esta manera, una expresión regular como \w+
los partidos palabras como hello
, élève
, GOÄ_432
o gefräßig
.
Desafortunadamente, Java no lo hace. En Java, \w
se limita a [A-Za-z0-9_]
. Esto hace que las palabras coincidentes como las mencionadas anteriormente sean difíciles, entre otros problemas.
También parece que el \b
separador de palabras coincide en lugares donde no debería.
¿Cuál sería el equivalente correcto de un tipo .NET, compatible con Unicode \w
o \b
en Java? ¿Qué otros atajos necesitan "reescribirse" para que sean compatibles con Unicode?
java
regex
unicode
character-properties
Tim Pietzcker
fuente
fuente
Respuestas:
Código fuente
El código fuente para las funciones de reescritura que analizo a continuación está disponible aquí .
Actualización en Java 7
La
Pattern
clase actualizada de Sun para JDK7 tiene una nueva y maravillosa banderaUNICODE_CHARACTER_CLASS
, que hace que todo vuelva a funcionar correctamente. Está disponible como incrustable(?U)
para dentro del patrón, por lo que también puede usarlo con losString
envoltorios de la clase. También tiene definiciones corregidas para varias otras propiedades, también. Ahora rastrea The Unicode Standard, tanto en RL1.2 como en RL1.2a de UTS # 18: Expresiones regulares de Unicode . Esta es una mejora emocionante y dramática, y el equipo de desarrollo debe ser elogiado por este importante esfuerzo.Problemas Unicode de expresiones regulares de Java
El problema con Java expresiones regulares es que los escapes Perl 1.0 charclass - es decir
\w
,\b
,\s
,\d
y sus complementos - no son en Java extenderse a trabajar con Unicode. Sólo entre estos,\b
goza de cierta semántica extendidos, pero éstos mapa ni a\w
, ni a los identificadores de Unicode , ni a Unicode propiedades de salto de línea .Además, se accede a las propiedades POSIX en Java de esta manera:
Este es un verdadero desastre, porque significa que las cosas les gusta
Alpha
,Lower
ySpace
lo hacen no en el mapa de Java para el UnicodeAlphabetic
,Lowercase
oWhitespace
propiedades. Esto es extremadamente molesto. El soporte de propiedad Unicode de Java es estrictamente antemilenial , lo que significa que no admite ninguna propiedad Unicode que haya surgido en la última década.No poder hablar sobre los espacios en blanco correctamente es súper molesto. Considere la siguiente tabla. Para cada uno de esos puntos de código, hay una columna de resultados J para Java y una columna de resultados P para Perl o cualquier otro motor de expresiones regulares basado en PCRE:
¿Mira eso?
Prácticamente cada uno de esos resultados de espacios en blanco de Java es ̲w̲r̲o̲n̲g̲ según Unicode. Es un gran problema. Java simplemente está en mal estado, dando respuestas que están "mal" de acuerdo con la práctica existente y también de acuerdo con Unicode. ¡Además, Java ni siquiera te da acceso a las propiedades reales de Unicode! De hecho, Java no admite ninguna propiedad que corresponda al espacio en blanco Unicode.
La solución a todos esos problemas y más
Para tratar con este y muchos otros problemas relacionados, ayer escribí una función Java para reescribir una cadena de patrón que reescribe estos 14 escapes de charclass:
reemplazándolos con cosas que realmente funcionan para que coincida con Unicode de una manera predecible y consistente. Es solo un prototipo alfa de una sola sesión de pirateo, pero es completamente funcional.
La historia corta es que mi código reescribe esos 14 de la siguiente manera:
Algunas cosas a considerar ...
Eso utiliza para su
\X
definición lo que Unicode ahora se refiere como un grupo de grafemas heredados , no un grupo de grafemas extendido , ya que este último es bastante más complicado. Perl ahora usa la versión más elegante, pero la versión anterior sigue siendo perfectamente viable para las situaciones más comunes. EDITAR: Ver anexo en la parte inferior.Lo que debe hacer
\d
depende de su intención, pero el valor predeterminado es la definición de Uniode. Puedo ver la gente no siempre querer\p{Nd}
, pero a veces, ya sea[0-9]
o\pN
.Las dos definiciones de límites,
\b
y\B
, están escritas específicamente para usar la\w
definición.Esa
\w
definición es demasiado amplia, porque toma las letras parenizadas, no solo las encerradas en un círculo. LaOther_Alphabetic
propiedad Unicode no está disponible hasta JDK7, por lo que es lo mejor que puede hacer.Explorando límites
Los límites han sido un problema desde que Larry Wall acuñado por primera vez el
\b
y\B
sintaxis para hablar de ellos para Perl 1.0 en 1987. La clave para entender cómo\b
y\B
tanto el trabajo es disipar dos mitos generalizados sobre ellos:\w
caracteres de palabra, no para caracteres no de palabras.Un
\b
límite significa:Y todos estos se definen perfectamente de manera directa como:
(?<=\w)
.(?=\w)
.(?<!\w)
.(?!\w)
.Por lo tanto, dado que
IF-THEN
está codificado como unand
ed-togetherAB
en expresiones regulares, anor
esX|Y
, y porqueand
es mayor en prioridad queor
, eso es simplementeAB|CD
. Entonces, todo\b
eso significa que un límite se puede reemplazar de forma segura con:con lo
\w
definido de la manera adecuada.(Puede que te parezca extraño que los componentes
A
yC
sean opuestos. En un mundo perfecto, deberías poder escribir esoAB|D
, pero durante un tiempo estuve persiguiendo contradicciones de exclusión mutua en las propiedades Unicode, que creo que me he ocupado de , pero dejé la doble condición en el límite por si acaso. Además, esto lo hace más extensible si tienes ideas adicionales más adelante).Para los
\B
no límites, la lógica es:Permitiendo que todas las instancias de
\B
sean reemplazadas por:Esto realmente es cómo
\b
y\B
comportarse. Patrones equivalentes para ellos son\b
usando la((IF)THEN|ELSE)
construcción es(?(?<=\w)(?!\w)|(?=\w))
\B
usando la((IF)THEN|ELSE)
construcción es(?(?=\w)(?<=\w)|(?<!\w))
Pero las versiones con just
AB|CD
están bien, especialmente si carece de patrones condicionales en su lenguaje regex, como Java. ☹Ya verifiqué el comportamiento de los límites utilizando las tres definiciones equivalentes con un conjunto de pruebas que verifica 110.385.408 coincidencias por ejecución, y que he ejecutado en una docena de configuraciones de datos diferentes de acuerdo con:
Sin embargo, las personas a menudo quieren un tipo diferente de límite. Quieren algo que tenga en cuenta el espacio en blanco y el borde de la cadena:
(?:(?<=^)|(?<=\s))
(?=$|\s)
Arreglando Java con Java
El código que publiqué en mi otra respuesta proporciona esto y muchas otras comodidades. Esto incluye definiciones de palabras, guiones, guiones y apóstrofes en lenguaje natural, y un poco más.
También le permite especificar caracteres Unicode en puntos de código lógico, no en sustitutos idiotas UTF-16. ¡Es difícil enfatizar lo importante que es eso! Y eso es solo para la expansión de la cadena.
Para la sustitución de chargelass regex que hace que la charclass en sus expresiones regulares de Java finalmente funcione en Unicode, y funcione correctamente, tome la fuente completa desde aquí . Puedes hacerlo con tu gusto, por supuesto. Si lo arreglas, me encantaría saberlo, pero no tienes que hacerlo. Es muy corto Las entrañas de la función principal de reescritura de expresiones regulares son simples:
De todos modos, ese código es solo una versión alfa, cosas que pirateé durante el fin de semana. No se quedará así.
Para la versión beta pretendo:
doblar juntos la duplicación de código
Proporcionar una interfaz más clara con respecto a los escapes de cadena de escape sin aumento de escapes de expresiones regulares
proporcionar cierta flexibilidad en la
\d
expansión, y tal vez el\b
Proporcione métodos convenientes que se encarguen de dar la vuelta y llamar a Pattern.compile o String.matches o lo que sea para usted.
Para el lanzamiento de producción, debe tener javadoc y un conjunto de pruebas JUnit. Puedo incluir mi gigatester, pero no está escrito como pruebas JUnit.
Apéndice
Tengo buenas noticias y malas noticias.
La buena noticia es que ahora tengo una aproximación muy cercana a un clúster de grafema extendido para usar para mejorar
\X
.La mala noticia ☺ es que ese patrón es:
que en Java escribirías como:
¡Tschüß!
fuente
t
a @tchrist. Podría ir a mi cabeza. :)Es realmente desafortunado que
\w
no funcione. La solución propuesta\p{Alpha}
tampoco funciona para mí.Parece que
[\p{L}]
atrapa todas las letras Unicode. Entonces el equivalente Unicode de\w
debería ser[\p{L}\p{Digit}_]
.fuente
\w
también coincide con dígitos y más. Creo que solo por letras,\p{L}
funcionaría.\p{L}
es suficiente. También pensé que solo las letras eran el problema.[\p{L}\p{Digit}_]
debe capturar todos los caracteres alfanuméricos, incluido el guión bajo.\w
es definido por Unicode como mucho más amplio que solo\pL
y los dígitos ASCII, de todas las cosas tontas. Debe escribir[\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]
si desea un Unicode-aware\w
para Java, o simplemente puede usar miunicode_charclass
función desde aquí . ¡Lo siento!\pL
funciona (no es necesario adoptar accesorios de una letra). Sin embargo, rara vez lo desea, porque debe tener cuidado de que su coincidencia no obtenga respuestas diferentes solo porque sus datos están en el formulario de normalización Unicode D (también conocido como NFD, que significa descomposición canónica ) versus estar en NFC (NFD seguido de canónico) composición ). Un ejemplo es que el punto de código U + E9 ("é"
) está\pL
en forma NFC, pero su forma NFD se convierte en U + 65.301, por lo que coincide\pL\pM
. Puede un poco de evitar esto con\X
:(?:(?=\pL)\X)
, pero tendrá que mi versión de que para Java. :(En Java,
\w
y\d
no son compatibles con Unicode; solo coinciden con los caracteres ASCII[A-Za-z0-9_]
y[0-9]
. Lo mismo ocurre con\p{Alpha}
amigos (las "clases de caracteres" POSIX en las que se basan se supone que son sensibles a la configuración regional, pero en Java solo han coincidido con caracteres ASCII). Si desea hacer coincidir los "caracteres de palabras" de Unicode, debe deletrearlo, por ejemplo[\pL\p{Mn}\p{Nd}\p{Pc}]
, para letras, modificadores sin espaciado (acentos), dígitos decimales y puntuación de conexión.Sin embargo, Java
\b
es unicode-savvy; también usaCharacter.isLetterOrDigit(ch)
y verifica las letras acentuadas, pero el único carácter de "puntuación de conexión" que reconoce es el guión bajo. EDITAR: cuando pruebo su código de muestra, se imprime""
yélève"
como debería ( ver en ideone.com ).fuente
\b
es un experto en Unicode. Comete toneladas y toneladas de errores."\u2163="
,"\u24e7="
y"\u0301="
todos no coinciden con el patrón"\\b="
en Java, pero se supone que lo hacen, comoperl -le 'print /\b=/ || 0 for "\x{2163}=", "\x{24e7}=", "\x{301}="'
revela. Sin embargo, si (y solo si) intercambias mi versión de un límite de palabra en lugar del nativo\b
en Java, entonces todos funcionan también en Java.\b
la corrección de las palabras, solo señalaba que funciona con caracteres Unicode (como se implementa en Java), no solo con los\w
amigos y me gusta de ASCII . Sin embargo, funciona correctamente con respecto a\u0301
cuándo ese personaje está emparejado con un personaje base, como ene\u0301=
. Y no estoy convencido de que Java esté equivocado en este caso. ¿Cómo se puede considerar una marca de combinación como un carácter de palabra a menos que sea parte de un grupo de grafemas con una letra?\X
significa una no marca seguida de cualquier número de marcas, es problemática, porque debería poder describir todos los archivos como coincidentes/^(\X*\R)*\R?$/
, pero no puede hacerlo si tiene un\pM
al comienzo de el archivo, o incluso de una línea. Así que lo han extendido para que siempre coincida con al menos un personaje. Siempre lo hizo, pero ahora hace que el patrón anterior funcione. [... continúa ...]\b
sea parcialmente consciente de Unicode. Considere hacer coincidir la cadena"élève"
con el patrón\b(\w+)\b
. ¿Ves el problema?\w+
encuentra dos coincidencias:l
yve
, lo cual es bastante malo. Pero con los límites de las palabras no encuentra nada, porque\b
reconoceé
yè
como caracteres de palabras. Como mínimo,\b
y\w
debería ponerse de acuerdo sobre qué es un carácter de palabra y qué no lo es.