solo - extraer cadenas con expresiones regulares java
Error al compilar una expresión regular de Java detallada con clase de caracteres y límite de palabra (5)
¿Por qué este patrón no se compila:
Pattern.compile("(?x)[ ]//b");
Error
ERROR java.util.regex.PatternSyntaxException:
Illegal/unsupported escape sequence near index 8
(?x)[ ]/b
^
at java_util_regex_Pattern$compile.call (Unknown Source)
¿Mientras que los siguientes equivalentes funcionan?
Pattern.compile("(?x)// //b");
Pattern.compile("[ ]//b");
Pattern.compile(" //b");
¿Es esto un error en el compilador de expresiones regulares de Java, o me estoy perdiendo algo? Me gusta usar [ ]
en expresiones regulares verbales en lugar de backslash-backslash-space porque guarda algo de ruido visual. Pero aparentemente no son lo mismo!
PD: este problema no es sobre barras invertidas. Se trata de escapar de espacios en una expresión regular verbosa usando una clase de caracteres que contiene un solo espacio [ ]
lugar de usar una barra invertida.
De alguna manera, la combinación de expresiones regulares (?x)
y una clase de caracteres que contiene un solo espacio [ ]
desactiva el compilador y hace que no reconozca el límite de la palabra escape /b
Probado con Java hasta 1.8.0_151
Me gusta usar
[ ]
en expresiones regulares verbales en lugar de backslash-backslash-space porque guarda algo de ruido visual. Pero aparentemente no son lo mismo!
"[ ]"
es lo mismo que "// "
o incluso " "
.
El problema es que (?x)
al principio habilita el modo de comentarios . Como indica la documentation
Permite espacios en blanco y comentarios en patrón.
En este modo, el espacio en blanco se ignora y los comentarios incrustados que comienzan con#
se ignoran hasta el final de una línea.
El modo de comentarios también se puede habilitar a través de la expresión de bandera incrustada(?x)
.
En el modo de comentarios, la expresión regular "(?x)[ ]//b"
es lo mismo que "[]//b"
y no se compilará porque la clase de caracteres vacíos []
no se analiza como vacía, sino que se analiza como "[//]"
(clase de caracteres no cerrados que contiene un literal ]
).
Utilice " //b"
lugar. Alternativamente, puede preservar el espacio en el modo de comentarios escapándolo con una barra invertida: "(?x)[// ]//b"
o "(?x)// //b"
.
Una solución
Además de los espacios en blanco que se escapan por separado que son literalmente lo mismo que [ ]
, puede tener x
modo x
para las expresiones regulares completas pero deshabilitarlo mientras trabaja en patrones que necesitan espacios en blanco, en línea:
(?x)match-this-(?-x: with spaces )//b
^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^
`x` is on off on
o una alternativa sería usar los meta-caracteres qouting /Q.../E
:
(?x)match-this-/Q with s p a c e s /E//b
^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^ ^^^
`x` is on off on
¿Por qué una Exception
?
En el modo extendido o de comentario ( x
), los espacios en blanco se ignoran pero el manejo de espacios dentro de clases de caracteres en varios tipos se maneja de manera diferente.
Por ejemplo, en PCRE, todos los caracteres de espacios en blanco se ignoran, excepto los de una clase de caracteres. Eso significa que [ ]
es una expresión regular válida pero Java no tiene una excepción:
En este modo, el espacio en blanco se ignora ...
Período. Entonces, [ ]
es igual a este []
que no es válido y lanza una excepción PatternSyntaxException
.
Casi todos los tipos de expresiones regulares, excepto JavaScript, necesitan una clase de caracteres para tener al menos una unidad de datos. Tratan una clase de caracteres vacía como un conjunto no cerrado que necesita un corchete de cierre. Dicho esto, []]
es válido en la mayoría de los sabores.
Modo de espaciado libre en sabores diferentes en [ ]
:
-
PCRE
válido -
.NET
válido -
Perl
valido -
Ruby
valido -
TCL
válido -
Java 7
no válido -
Java 8
no válido
Analicemos qué sucede exactamente.
Eche un vistazo al código fuente de java.util.regex.Pattern
Permite espacios en blanco y comentarios en patrón. En este modo, el espacio en blanco se ignora y los comentarios incrustados que comienzan con # se ignoran hasta el final de una línea.
El modo de comentarios también se puede habilitar a través de la expresión de bandera incrustada (? X).
Tu regex te guiará en esta line
private void accept(int ch, String s) {
int testChar = temp[cursor++];
if (has(COMMENTS))
testChar = parsePastWhitespace(testChar);
if (ch != testChar) {
throw error(s);
}
}
Si observa su código, llame a parsePastWhitespace(testChar);
private int parsePastWhitespace(int ch) {
while (ASCII.isSpace(ch) || ch == ''#'') {
while (ASCII.isSpace(ch))//<----------------Here is the key of your error
ch = temp[cursor++];
if (ch == ''#'')
ch = parsePastLine();
}
return ch;
}
En su caso, tiene un espacio en blanco en su expresión regular (?x)[ ]//b
esto devolverá algo (no puedo analizarlo correctamente):
if (ch != testChar) {
throw error(s);
}
que no es igual a ch
y aquí una excepción es tirar
throw error(s);
Este es un error en el método peekPastWhitespace()
Java en la clase Pattern
. Rastreando todo este problema ... Decidí echar un vistazo a la implementación del Pattern
OpenJDK 8-b132 . Vamos a empezar a martillar esto desde la parte superior:
-
compile()
llama aexpr()
en la línea 1696 -
expr()
llamasequence()
en línea 1996 -
sequence()
llama aclazz()
en la línea 2063 ya que se cumplió el caso de[
-
clazz()
llama apeek()
en la línea 2509 -
peek()
llama apeekPastWhitespace()
en la línea 1830 ya queif(has(COMMENTS))
evalúa comotrue
(debido a que se agregó lapeekPastWhitespace()
x
(?x)
al comienzo del patrón) -
peekPastWhitespace()
(publicado abajo) salta todos los espacios en el patrón.
private int peekPastWhitespace(int ch) {
while (ASCII.isSpace(ch) || ch == ''#'') {
while (ASCII.isSpace(ch))
ch = temp[++cursor]
if (ch == ''#'') {
ch = peekPastLine();
}
}
return ch;
}
El mismo error existe en el método parsePastWhitespace()
.
Su expresión regular se interpreta como []//b
, que es la causa de su error porque /b
no es compatible con una clase de caracteres en Java. Además, una vez que solucionas el problema /b
, tu clase de personaje tampoco tiene un cierre ]
.
Qué puedes hacer para solucionar este problema:
-
//
Como mencionó el OP, simplemente use doble barra invertida y espacio
-
[// ]
Escapa del espacio dentro de la clase de caracteres para que se interprete literalmente -
[ ](?x)//b
Coloque el modificador en línea después de la clase de caracteres
Parece que debido a que el espacio de espacio libre (detallado) (?x)
en [ ]
se ignora, por lo que el motor de expresiones regulares ve su expresión regular como []//b
.
Si eliminamos //b
, se vería como []
y obtendríamos un error sobre la Unclosed character class
caracteres no puede estar vacía, por lo que ]
coloca directamente después de [
se trata como el primer carácter que pertenece a esa clase en lugar de un símbolo meta que está cerrando clase de personaje.
Por lo tanto, dado que [
está cerrado, el motor de expresiones regulares se considera /b
como colocado dentro de esa clase de caracteres. Pero /b
no puede colocarse allí (no representa carácter sino "lugar"), por lo que estamos viendo un error sobre "secuencia de escape no admitida" (dentro de la clase de carácter, pero esa parte se omitió).
En otras palabras, no puede usar [ ]
para escapar del espacio en modo detallado (al menos en Java). Deberá usar "// "
o "[// ]"
.