regex - probar - expresiones regulares javascript
Contraer y capturar un patrón de repetición en una sola expresión de expresiones regulares (5)
¡Leé esto primero!
Esta publicación es para mostrar la posibilidad en lugar de respaldar el enfoque "todo regex" del problema. El autor ha escrito 3-4 variaciones, cada una tiene una falla sutil que es difícil de detectar, antes de llegar a la solución actual.
Para su ejemplo específico, hay otra solución mejor que es más fácil de mantener, como emparejar y dividir la coincidencia a lo largo de los delimitadores.
Esta publicación trata sobre tu ejemplo específico. Realmente dudo que sea posible una generalización completa, pero la idea subyacente es reutilizable para casos similares.
Resumen
- .NET admite la captura de patrones repetitivos con la clase
CaptureCollection
. - Para lenguajes que admiten
/G
y look-behind, podemos construir una expresión regular que funcione con la función de coincidencia global. No es fácil escribirlo completamente correcto y fácil de escribir una expresión regular sutilmente defectuosa. - Para lenguajes sin soporte
/G
y look-behind: es posible emular/G
con^
, masticando la cadena de entrada después de una sola coincidencia. (No está cubierto en esta respuesta).
Solución
Esta solución supone que el motor de expresiones regulares admite el límite de coincidencia de /G
, el (?=pattern)
anticipación (?=pattern)
y el (?=pattern)
espera (?<=pattern)
. Los sabores regex de Java, Perl, PCRE, .NET y Ruby son compatibles con todas las características avanzadas anteriores.
Sin embargo, puede ir con su expresión regular en .NET. Dado que .NET admite la captura de todas las instancias de que CaptureCollection
con un grupo de captura que se repite a través de la clase CaptureCollection
.
Para su caso, se puede hacer en una expresión regular, con el uso del límite de coincidencia /G
y la anticipación para limitar el número de repeticiones:
(?:start:(?=/w+(?:-/w+){2,9}:end)|(?<=-)/G)(/w+)(?:-|:end)
DEMO . La construcción es /w+-
repetida, luego /w+:end
.
(?:start:(?=/w+(?:-/w+){2,9}:end)|(?!^)/G-)(/w+)
DEMO . La construcción es /w+
para el primer elemento, luego -/w+
repetida. (Gracias a ka ᵠ por la sugerencia). Esta construcción es más simple para razonar sobre su corrección, ya que hay menos alternancias.
/G
límite de coincidencia es especialmente útil cuando necesita hacer tokenización, donde debe asegurarse de que el motor no salte y haga coincidir los elementos que deberían haber sido inválidos.
Explicación
Vamos a desglosar la expresión regular:
(?:
start:(?=/w+(?:-/w+){2,9}:end)
|
(?<=-)/G
)
(/w+)
(?:-|:end)
La parte más fácil de reconocer es (/w+)
en la línea anterior al último, que es la palabra que desea capturar.
La última línea también es bastante fácil de reconocer: la palabra a emparejar puede ir seguida de -
o :end
.
Permito que la expresión regular comience libremente a coincidir en cualquier parte de la cadena . En otras palabras, start:...:end
puede aparecer en cualquier lugar de la cadena y en cualquier cantidad de veces; la expresión regular simplemente coincidirá con todas las palabras. Solo necesita procesar la matriz devuelta para separar de dónde provienen los tokens coincidentes.
En cuanto a la explicación, el comienzo de las expresiones regulares comprueba la presencia del start:
cadena start:
y las siguientes comprobaciones anticipadas comprueban que el número de palabras está dentro del límite especificado y finaliza con :end
. O eso, o comprobamos que el personaje antes del partido anterior es un -
, y continuamos desde el partido anterior.
Para la otra construcción:
(?:
start:(?=/w+(?:-/w+){2,9}:end)
|
(?!^)/G-
)
(/w+)
Todo es casi lo mismo, excepto que coincidimos start:/w+
primero antes de coincidir con la repetición de la forma -/w+
. A diferencia de la primera construcción, donde combinamos start:/w+-
first, y las instancias repetidas de /w+-
(o /w+:end
para la última repetición).
Es bastante complicado hacer que esta expresión regular funcione para hacer coincidir en medio de la cadena:
Necesitamos verificar el número de palabras entre
start:
y:end
(como parte del requisito de la expresión regular original)./G
coincide con el comienzo de la cadena también!(?!^)
es necesario para prevenir este comportamiento. Sin ocuparse de esto, la expresión regular puede producir una coincidencia cuando no hay ningúnstart:
Para la primera construcción, el look-behind
(?<=-)
ya previene este caso ((?!^)
Está implícito en(?<=-)
).Para la primera construcción
(?:start:(?=/w+(?:-/w+){2,9}:end)|(?<=-)/G)(/w+)(?:-|:end)
, tenemos que asegurarnos de que no coincidamos con nada divertido después de:end
. El look-behind es para ese propósito: previene cualquier basura después de:end
coincidencia.La segunda construcción no se topa con este problema, ya que nos quedaremos atascados en
:
(de:end
) después de haber emparejado todos los tokens intermedios.
Versión de Validación
Si quiere hacer la validación de que la cadena de entrada sigue el formato (sin elementos adicionales adelante y atrás) y extraer los datos, puede agregar anclas como tales:
(?:^start:(?=/w+(?:-/w+){2,9}:end$)|(?!^)/G-)(/w+)
(?:^start:(?=/w+(?:-/w+){2,9}:end$)|(?!^)/G)(/w+)(?:-|:end)
(Look-behind tampoco es necesario, pero aún necesitamos (?!^)
Para evitar que /G
coincida con el inicio de la cadena).
Construcción
Para todos los problemas en los que desea capturar todas las instancias de una repetición, no creo que exista una forma general de modificar la expresión regular. Un ejemplo de un caso "difícil" (¿o imposible?) De conversión es cuando una repetición tiene que retroceder uno o más ciclos para cumplir con ciertas condiciones para que coincidan.
Cuando la expresión regular original describe la cadena de entrada completa (tipo de validación), generalmente es más fácil de convertir en comparación con una expresión regular que intenta hacer coincidir desde el medio de la cadena (tipo coincidente). Sin embargo, siempre puede hacer una coincidencia con la expresión regular original, y convertimos el problema de tipo de coincidencia a problema de tipo de validación.
Creamos dicha expresión regex siguiendo estos pasos:
- Escriba una expresión regular que cubra la parte antes de la repetición (por ej.,
start:
. Vamos a llamar a este prefijo regex . - Coincide y captura la primera instancia. (por ejemplo,
(/w+)
)
(En este punto, la primera instancia y el delimitador deberían coincidir) - Agregue el
/G
como una alternancia. Por lo general, también es necesario evitar que coincida con el inicio de la cadena. - Agregue el delimitador (si hay alguno). (por ejemplo,
-
)
(Después de este paso, el resto de los tokens también deberían haber coincidido, excepto el último tal vez) - Agregue la parte que cubre la parte después de la repetición (si es necesario) (por ej .
:end
). Vamos a llamar a la parte después del sufijo de repetición regex (no importa si lo agregamos a la construcción). - Ahora la parte difícil. Debes verificar que:
- No hay otra manera de comenzar una partida, aparte del prefijo regex . Toma nota de la rama
/G
- No hay forma de iniciar ninguna coincidencia después de que se haya hecho coincidir la expresión regular del sufijo . Tome nota de cómo la rama
/G
inicia una coincidencia. - Para la primera construcción, si mezcla el sufijo regex (por ejemplo
:end
) con delimitador (por ejemplo,-
) en una alternancia, asegúrese de no terminar permitiendo el sufijo regex como delimitador.
- No hay otra manera de comenzar una partida, aparte del prefijo regex . Toma nota de la rama
Sigo chocando con situaciones en las que necesito capturar una cantidad de tokens de una cadena y, después de innumerables intentos, no pude encontrar una forma de simplificar el proceso.
Entonces digamos que el texto es:
start: test-test-lorem-ipsum-sir-doloret-etc-etc-something: final
Este ejemplo tiene 8 elementos adentro, pero dice que podría tener entre 3 y 10 elementos.
Idealmente me gustaría algo como esto:
start:(?:(/w+)-?){3,10}:end
agradable y limpio, PERO solo captura el último partido. mira aquí
Usualmente uso algo como esto en situaciones simples:
start:(/w+)-(/w+)-(/w+)-?(/w+)?-?(/w+)?-?(/w+)?-?(/w+)?-?(/w+)?-?(/w+)?-?(/w+)?:end
3 grupos obligatorios y otros 7 opcionales debido al límite máximo de 10, pero esto no se ve "bien" y sería un dolor escribir y rastrear si el límite máximo era 100 y las coincidencias eran más complejas. demo
Y lo mejor que pude hacer hasta ahora:
start:(/w+)-((?1))-((?1))-?((?1))?-?((?1))?-?((?1))?-?((?1))?-?((?1))?:end
más corto especialmente si las coincidencias son complejas, pero aún son largas. demo
¿Alguien logró hacer que funcionara como una solución solo para expresiones regex sin programación ?
Me interesa sobre todo cómo se puede hacer esto en PCRE, pero otros sabores también estarían bien.
Actualizar:
El propósito es validar una coincidencia y capturar tokens individuales dentro de la match 0
solo con RegEx, sin ninguna limitación de OS / Software / Lenguaje de Programación
Actualización 2 (recompensa):
Con la ayuda de @ nhahtdh, llegué al RegExp a continuación usando /G
:
(?:start:(?=(?:[/w]+(?:-|(?=:end))){3,10}:end)|(?!^)/G-)([/w]+)
demo aún más corta, pero se puede describir sin repetir el código
También estoy interesado en el sabor de ECMA y no es compatible con /G
preguntándome si hay otra forma, especialmente sin usar el modificador /g
.
Aunque teóricamente podría ser posible escribir una sola expresión, es mucho más práctico hacer coincidir los límites exteriores primero y luego realizar una división en la parte interna.
En ECMAScript lo escribiría así:
''start:test-test-lorem-ipsum-sir-doloret-etc-etc-something:end''
.match(/^start:([/w-]+):end$/)[1] // match the inner part
.split(''-'') // split inner part (this could be a split regex as well)
En PHP:
$txt = ''start:test-test-lorem-ipsum-sir-doloret-etc-etc-something:end'';
if (preg_match(''/^start:([/w-]+):end$/'', $txt, $matches)) {
print_r(explode(''-'', $matches[1]));
}
Cuando combinas:
- Su observación: cualquier tipo de repetición de un único grupo de captura dará como resultado una sobrescritura de la última captura, devolviendo así solo la última captura del grupo de captura.
- El conocimiento: cualquier tipo de captura basada en las partes, en lugar de la totalidad, hace que sea imposible establecer un límite en la cantidad de veces que se repetirá el motor de expresiones regulares. El límite debería ser metadata (no regex).
- Con un requisito de que la respuesta no puede involucrar programación (bucle), ni una respuesta que implique simplemente copiar y pegar grupos de captura como lo ha hecho en su pregunta.
Se puede deducir que no se puede hacer.
Actualización: hay algunos motores de expresiones regulares para los cuales p. 1 no es necesariamente cierto. En ese caso, la expresión regular que ha indicado start:(?:(/w+)-?){3,10}:end
hará el trabajo ( source ).
No estoy seguro de poder hacerlo de esa manera, pero puede usar la bandera global para encontrar todas las palabras entre los dos puntos, ver:
Sin embargo, usted tendría que validar la cantidad de grupos. Sin la bandera global, solo obtiene una coincidencia única, no todas las coincidencias, cambie {3,10}
por {1,5}
y obtendrá el resultado ''señor'' en su lugar.
import re
s = "start:test-test-lorem-ipsum-sir-doloret-etc-etc-something:end"
print re.findall(r"(/b/w+?/b)(?:-|:end)", s)
produce
[''test'', ''test'', ''lorem'', ''ipsum'', ''sir'', ''doloret'', ''etc'', ''etc'', ''something'']
Por supuesto, puede usar la expresión regular en esta cadena entre comillas.
"(?<a>//w+)-(?<b>//w+)-(?:(?<c>//w+)" /
"(?:-(?<d>//w+)(?:-(?<e>//w+)(?:-(?<f>//w+)" /
"(?:-(?<g>//w+)(?:-(?<h>//w+)(?:-(?<i>//w+)" /
"(?:-(?<j>//w+))?" /
")?)?)?" /
")?)?)?" /
")"
¿Es una buena idea? No, no lo creo