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 bandera UNICODE_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 los String
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:
POSIX syntax Java syntax
[[:Lower:]] \p{Lower}
[[:Upper:]] \p{Upper}
[[:ASCII:]] \p{ASCII}
[[:Alpha:]] \p{Alpha}
[[:Digit:]] \p{Digit}
[[:Alnum:]] \p{Alnum}
[[:Punct:]] \p{Punct}
[[:Graph:]] \p{Graph}
[[:Print:]] \p{Print}
[[:Blank:]] \p{Blank}
[[:Cntrl:]] \p{Cntrl}
[[:XDigit:]] \p{XDigit}
[[:Space:]] \p{Space}
Este es un verdadero desastre, porque significa que las cosas les gusta Alpha
, Lower
y Space
lo hacen no en el mapa de Java para el Unicode Alphabetic
, Lowercase
o Whitespace
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:
Regex 001A 0085 00A0 2029
J P J P J P J P
\s 1 1 0 1 0 1 0 1
\pZ 0 0 0 0 1 1 1 1
\p{Zs} 0 0 0 0 1 1 0 0
\p{Space} 1 1 0 1 0 1 0 1
\p{Blank} 0 0 0 0 0 1 0 0
\p{Whitespace} - 1 - 1 - 1 - 1
\p{javaWhitespace} 1 - 0 - 0 - 1 -
\p{javaSpaceChar} 0 - 0 - 1 - 1 -
¿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:
\w \W \s \S \v \V \h \H \d \D \b \B \X \R
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:
\s => [\u0009-\u000D\u0020\u0085\u00A0\u1680\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]
\S => [^\u0009-\u000D\u0020\u0085\u00A0\u1680\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]
\v => [\u000A-\u000D\u0085\u2028\u2029]
\V => [^\u000A-\u000D\u0085\u2028\u2029]
\h => [\u0009\u0020\u00A0\u1680\u180E\u2000-\u200A\u202F\u205F\u3000]
\H => [^\u0009\u0020\u00A0\u1680\u180E\u2000\u2001-\u200A\u202F\u205F\u3000]
\w => [\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]
\W => [^\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]
\b => (?:(?<=[\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])(?![\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])|(?<![\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])(?=[\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]))
\B => (?:(?<=[\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])(?=[\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])|(?<![\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])(?![\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]))
\d => \p{Nd}
\D => \P{Nd}
\R => (?:(?>\u000D\u000A)|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029])
\X => (?>\PM\pM*)
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. La Other_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:
- Ellos son sólo alguna vez en busca de
\w
caracteres de palabra, no para caracteres no de palabras.
- No buscan específicamente el borde de la cuerda.
Un \b
límite significa:
IF does follow word
THEN doesn't precede word
ELSIF doesn't follow word
THEN does precede word
Y todos estos se definen perfectamente de manera directa como:
- sigue la palabra es
(?<=\w)
.
- precede a la palabra es
(?=\w)
.
- no sigue la palabra es
(?<!\w)
.
- no precede a la palabra es
(?!\w)
.
Por lo tanto, dado que IF-THEN
está codificado como un and
ed-together AB
en expresiones regulares, an or
es X|Y
, y porque and
es mayor en prioridad que or
, eso es simplemente AB|CD
. Entonces, todo \b
eso significa que un límite se puede reemplazar de forma segura con:
(?:(?<=\w)(?!\w)|(?<!\w)(?=\w))
con lo \w
definido de la manera adecuada.
(Puede que te parezca extraño que los componentes A
y C
sean opuestos. En un mundo perfecto, deberías poder escribir eso AB|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:
IF does follow word
THEN does precede word
ELSIF doesn't follow word
THEN doesn't precede word
Permitiendo que todas las instancias de \B
sean reemplazadas por:
(?:(?<=\w)(?=\w)|(?<!\w)(?!\w))
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:
0 .. 7F the ASCII range
80 .. FF the non-ASCII Latin1 range
100 .. FFFF the non-Latin1 BMP (Basic Multilingual Plane) range
10000 .. 10FFFF the non-BMP portion of Unicode (the "astral" planes)
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:
- borde izquierdo como
(?:(?<=^)|(?<=\s))
- borde derecho como
(?=$|\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:
switch (code_point) {
case 'b': newstr.append(boundary);
break; /* switch */
case 'B': newstr.append(not_boundary);
break; /* switch */
case 'd': newstr.append(digits_charclass);
break; /* switch */
case 'D': newstr.append(not_digits_charclass);
break; /* switch */
case 'h': newstr.append(horizontal_whitespace_charclass);
break; /* switch */
case 'H': newstr.append(not_horizontal_whitespace_charclass);
break; /* switch */
case 'v': newstr.append(vertical_whitespace_charclass);
break; /* switch */
case 'V': newstr.append(not_vertical_whitespace_charclass);
break; /* switch */
case 'R': newstr.append(linebreak);
break; /* switch */
case 's': newstr.append(whitespace_charclass);
break; /* switch */
case 'S': newstr.append(not_whitespace_charclass);
break; /* switch */
case 'w': newstr.append(identifier_charclass);
break; /* switch */
case 'W': newstr.append(not_identifier_charclass);
break; /* switch */
case 'X': newstr.append(legacy_grapheme_cluster);
break; /* switch */
default: newstr.append('\\');
newstr.append(Character.toChars(code_point));
break; /* switch */
}
saw_backslash = false;
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:
(?:(?:\u000D\u000A)|(?:[\u0E40\u0E41\u0E42\u0E43\u0E44\u0EC0\u0EC1\u0EC2\u0EC3\u0EC4\uAAB5\uAAB6\uAAB9\uAABB\uAABC]*(?:[\u1100-\u115F\uA960-\uA97C]+|([\u1100-\u115F\uA960-\uA97C]*((?:[[\u1160-\u11A2\uD7B0-\uD7C6][\uAC00\uAC1C\uAC38]][\u1160-\u11A2\uD7B0-\uD7C6]*|[\uAC01\uAC02\uAC03\uAC04])[\u11A8-\u11F9\uD7CB-\uD7FB]*))|[\u11A8-\u11F9\uD7CB-\uD7FB]+|[^[\p{Zl}\p{Zp}\p{Cc}\p{Cf}&&[^\u000D\u000A\u200C\u200D]]\u000D\u000A])[[\p{Mn}\p{Me}\u200C\u200D\u0488\u0489\u20DD\u20DE\u20DF\u20E0\u20E2\u20E3\u20E4\uA670\uA671\uA672\uFF9E\uFF9F][\p{Mc}\u0E30\u0E32\u0E33\u0E45\u0EB0\u0EB2\u0EB3]]*)|(?s:.))
que en Java escribirías como:
String extended_grapheme_cluster = "(?:(?:\\u000D\\u000A)|(?:[\\u0E40\\u0E41\\u0E42\\u0E43\\u0E44\\u0EC0\\u0EC1\\u0EC2\\u0EC3\\u0EC4\\uAAB5\\uAAB6\\uAAB9\\uAABB\\uAABC]*(?:[\\u1100-\\u115F\\uA960-\\uA97C]+|([\\u1100-\\u115F\\uA960-\\uA97C]*((?:[[\\u1160-\\u11A2\\uD7B0-\\uD7C6][\\uAC00\\uAC1C\\uAC38]][\\u1160-\\u11A2\\uD7B0-\\uD7C6]*|[\\uAC01\\uAC02\\uAC03\\uAC04])[\\u11A8-\\u11F9\\uD7CB-\\uD7FB]*))|[\\u11A8-\\u11F9\\uD7CB-\\uD7FB]+|[^[\\p{Zl}\\p{Zp}\\p{Cc}\\p{Cf}&&[^\\u000D\\u000A\\u200C\\u200D]]\\u000D\\u000A])[[\\p{Mn}\\p{Me}\\u200C\\u200D\\u0488\\u0489\\u20DD\\u20DE\\u20DF\\u20E0\\u20E2\\u20E3\\u20E4\\uA670\\uA671\\uA672\\uFF9E\\uFF9F][\\p{Mc}\\u0E30\\u0E32\\u0E33\\u0E45\\u0EB0\\u0EB2\\u0EB3]]*)|(?s:.))";
¡Tschüß!