Hans, morderé el anzuelo y desarrollaré mi respuesta anterior. Dijiste que querías "algo más completo", así que espero que no te importe la respuesta larga, solo intento complacer. Comencemos con algunos antecedentes.
En primer lugar, esta es una excelente pregunta. A menudo hay preguntas sobre la coincidencia de ciertos patrones, excepto en ciertos contextos (por ejemplo, dentro de un bloque de código o entre paréntesis). Estas preguntas a menudo dan lugar a soluciones bastante incómodas. Entonces, su pregunta sobre múltiples contextos es un desafío especial.
Sorpresa
Sorprendentemente, existe al menos una solución eficiente que es general, fácil de implementar y un placer de mantener. Se trabaja con todos los sabores de expresiones regulares que le permiten inspeccionar los grupos de captura en su código. Y resulta que responde a una serie de preguntas comunes que al principio pueden sonar diferentes a las suyas: "hacer coincidir todo excepto Donuts", "reemplazar todas menos ...", "hacer coincidir todas las palabras excepto las de la lista negra de mi madre", "ignorar etiquetas "," coinciden con la temperatura a menos que estén en cursiva "...
Lamentablemente, la técnica no es muy conocida: estimo que en veinte preguntas de SO que podrían usarla, solo una tiene una respuesta que la menciona, lo que significa tal vez una de cada cincuenta o sesenta respuestas. Vea mi intercambio con Kobi en los comentarios. La técnica se describe con cierta profundidad en este artículo que la llama (con optimismo) el "mejor truco de expresiones regulares". Sin entrar en muchos detalles, intentaré darles una idea clara de cómo funciona la técnica. Para obtener más detalles y ejemplos de código en varios idiomas, le animo a que consulte ese recurso.
Una variación más conocida
Existe una variación que usa la sintaxis específica de Perl y PHP que logra lo mismo. Lo verá en SO en manos de maestros de expresiones regulares como CasimiretHippolyte y HamZa . Le contaré más sobre esto a continuación, pero mi enfoque aquí está en la solución general que funciona con todos los tipos de expresiones regulares (siempre que pueda inspeccionar los grupos de captura en su código).
Gracias por todos los antecedentes, zx81 ... ¿Pero cuál es la receta?
Hecho clave
El método devuelve la coincidencia en la captura del Grupo 1. No le importa en absoluto el partido en general.
De hecho, el truco consiste en hacer coincidir los diversos contextos que no queremos (encadenar estos contextos usando la |
OR / alternancia) para "neutralizarlos". Después de comparar todos los contextos no deseados, la parte final de la alternancia coincide con lo que sí queremos y lo captura al grupo 1.
La receta general es
Not_this_context|Not_this_either|StayAway|(WhatYouWant)
Esto coincidirá Not_this_context
, pero en cierto sentido esa coincidencia va a la basura, porque no miraremos las coincidencias generales: solo miramos las capturas del Grupo 1.
En su caso, con sus dígitos y sus tres contextos para ignorar, podemos hacer:
s1|s2|s3|(\b\d+\b)
Tenga en cuenta que debido a que en realidad hacemos coincidir s1, s2 y s3 en lugar de tratar de evitarlos con cambios de opinión, las expresiones individuales para s1, s2 y s3 pueden permanecer claras como el día. (Son las subexpresiones a cada lado de a |
)
La expresión completa se puede escribir así:
(?m)^.*\.$|\([^\)]*\)|if\(.*?//endif|(\b\d+\b)
Vea esta demostración (pero concéntrese en los grupos de captura en el panel inferior derecho).
Si mentalmente intenta dividir esta expresión regular en cada |
delimitador, en realidad es solo una serie de cuatro expresiones muy simples.
Para los sabores que admiten el espacio libre, esto se lee particularmente bien.
(?mx)
### s1: Match line that ends with a period ###
^.*\.$
| ### OR s2: Match anything between parentheses ###
\([^\)]*\)
| ### OR s3: Match any if(...//endif block ###
if\(.*?//endif
| ### OR capture digits to Group 1 ###
(\b\d+\b)
Esto es excepcionalmente fácil de leer y mantener.
Extendiendo la expresión regular
Cuando desee ignorar más situaciones s4 y s5, agréguelas en más alternancias a la izquierda:
s4|s5|s1|s2|s3|(\b\d+\b)
¿Como funciona esto?
Los contextos que no desea se agregan a una lista de alternancias a la izquierda: coincidirán, pero estas coincidencias generales nunca se examinan, por lo que emparejarlas es una forma de ponerlas en un "contenedor de basura".
Sin embargo, el contenido que desea se captura en el Grupo 1. Luego, debe verificar mediante programación que el Grupo 1 esté configurado y no vacío. Esta es una tarea de programación trivial (y luego hablaremos sobre cómo se hace), especialmente considerando que te deja con una expresión regular simple que puedes entender de un vistazo y revisar o ampliar según sea necesario.
No siempre soy un fanático de las visualizaciones, pero esta hace un buen trabajo al mostrar lo simple que es el método. Cada "línea" corresponde a una coincidencia potencial, pero solo la línea de fondo se captura en el Grupo 1.
Demostración de Debuggex
Variación Perl / PCRE
En contraste con la solución general anterior, existe una variación para Perl y PCRE que a menudo se ve en SO, al menos en manos de dioses regex como @CasimiretHippolyte y @HamZa. Es:
(?:s1|s2|s3)(*SKIP)(*F)|whatYouWant
En tu caso:
(?m)(?:^.*\.$|\([^()]*\)|if\(.*?//endif)(*SKIP)(*F)|\b\d+\b
Esta variación es un poco más fácil de usar porque el contenido que coincide en los contextos s1, s2 y s3 simplemente se omite, por lo que no es necesario inspeccionar las capturas del Grupo 1 (observe que los paréntesis han desaparecido). Los partidos solo contienenwhatYouWant
Tenga en cuenta que (*F)
, (*FAIL)
y (?!)
son la misma cosa. Si quisieras ser más oscuro, podrías usar(*SKIP)(?!)
demo para esta versión
Aplicaciones
A continuación, se muestran algunos problemas comunes que esta técnica a menudo puede resolver fácilmente. Notará que la elección de palabras puede hacer que algunos de estos problemas suenen diferentes, mientras que en realidad son prácticamente idénticos.
- ¿Cómo puedo hacer coincidir foo excepto en cualquier lugar de una etiqueta como
<a stuff...>...</a>
?
- ¿Cómo puedo hacer coincidir foo excepto en una
<i>
etiqueta o un fragmento de JavaScript (más condiciones)?
- ¿Cómo puedo hacer coincidir todas las palabras que no están en esta lista negra?
- ¿Cómo puedo ignorar algo dentro de un bloque SUB ... END SUB?
- ¿Cómo puedo hacer coincidir todo excepto ... s1 s2 s3?
Cómo programar las capturas del grupo 1
No lo hizo en cuanto al código, pero, para completarlo ... El código para inspeccionar el Grupo 1 obviamente dependerá del idioma que elija. En cualquier caso, no debería agregar más de un par de líneas al código que usaría para inspeccionar las coincidencias.
En caso de duda, le recomiendo que consulte la sección de ejemplos de código del artículo mencionado anteriormente, que presenta código para bastantes idiomas.
Alternativas
Dependiendo de la complejidad de la pregunta y del motor de expresiones regulares utilizado, existen varias alternativas. Estos son los dos que pueden aplicarse a la mayoría de situaciones, incluidas múltiples condiciones. En mi opinión, ninguno es tan atractivo como la s1|s2|s3|(whatYouWant)
receta, aunque solo sea porque la claridad siempre gana.
1. Reemplace y luego haga coincidir.
Una buena solución que suena hacky pero funciona bien en muchos entornos es trabajar en dos pasos. Una primera expresión regular neutraliza el contexto que desea ignorar reemplazando cadenas potencialmente conflictivas. Si solo desea hacer coincidir, puede reemplazar con una cadena vacía y luego ejecutar su coincidencia en el segundo paso. Si desea reemplazar, primero puede reemplazar las cadenas que se ignorarán con algo distintivo, por ejemplo, rodeando sus dígitos con una cadena de ancho fijo de @@@
. Después de este reemplazo, es libre de reemplazar lo que realmente deseaba, luego tendrá que revertir sus @@@
cadenas distintivas .
2. Lookarounds.
Su publicación original demostró que comprende cómo excluir una sola condición utilizando métodos alternativos. Dijiste que C # es genial para esto, y tienes razón, pero no es la única opción. Los tipos de expresiones regulares de .NET que se encuentran en C #, VB.NET y Visual C ++, por ejemplo, así como el regex
módulo aún experimental para reemplazar re
en Python, son los únicos dos motores que conozco que admiten la búsqueda hacia atrás de ancho infinito. Con estas herramientas, una condición en una mirada hacia atrás puede encargarse de mirar no solo hacia atrás, sino también al partido y más allá del partido, evitando la necesidad de coordinar con un mirar hacia adelante. ¿Más condiciones? Más revisiones.
Reciclando la expresión regular que tenía para s3 en C #, todo el patrón se vería así.
(?!.*\.)(?<!\([^()]*(?=\d+[^)]*\)))(?<!if\(\D*(?=\d+.*?//endif))\b\d+\b
Pero a estas alturas ya sabes que no estoy recomendando esto, ¿verdad?
Eliminaciones
@HamZa y @Jerry han sugerido que mencione un truco adicional para los casos en los que solo busca eliminar WhatYouWant
. ¿Recuerdas que la receta para combinar WhatYouWant
(capturarla en el Grupo 1) era s1|s2|s3|(WhatYouWant)
, verdad? Para eliminar todas las instancias de WhatYouWant
, cambie la expresión regular a
(s1|s2|s3)|WhatYouWant
Para la cadena de reemplazo, usa $1
. Lo que sucede aquí es que para cada instancia s1|s2|s3
que coincide, el reemplazo $1
reemplaza esa instancia por sí misma (referenciada por $1
). Por otro lado, cuando WhatYouWant
coincide, se reemplaza por un grupo vacío y nada más y, por lo tanto, se elimina. Vea esta demostración , gracias @HamZa y @Jerry por sugerir esta maravillosa adición.
Reemplazos
Esto nos lleva a los reemplazos, de los que hablaré brevemente.
- Cuando reemplace con nada, vea el truco "Eliminaciones" arriba.
- Al reemplazar, si usa Perl o PCRE, use la
(*SKIP)(*F)
variación mencionada anteriormente para que coincida exactamente con lo que desea y realice un reemplazo directo.
- En otros sabores, dentro de la llamada a la función de reemplazo, inspeccione la coincidencia usando una devolución de llamada o lambda, y reemplace si el Grupo 1 está configurado. Si necesita ayuda con esto, el artículo ya mencionado le proporcionará código en varios idiomas.
¡Que te diviertas!
¡No, espera, hay más!
Ah, no, lo guardaré para mis memorias en veinte volúmenes, que se publicarán la próxima primavera.
\K
no es una sintaxis php especial. Por favor, explique y aclare lo que quiere decir. Si su objetivo es decirnos que no necesita una solución "complicada", tiene que decir qué es complicado para usted y por qué.