2009-07-28 10 views
7

Esta es una de las cosas más difíciles que he intentado hacer. Con los años he buscado pero puedo ’ t encontrar una manera de hacer esto — coincide con una cadena no rodeada por un carácter dado, como comillas o mayor/menor que los símbolos.Regex para que coincida con los valores que no están rodeados por otro char?

Una expresión regular como esta podría coincidir con las URL no en los enlaces HTML, los valores SQL table.column no entre comillas, y muchas otras cosas.

Example with quotes: 
Match [THIS] and "something with [NOT THIS] followed by" or even [THIS]. 

Example with <,>, & " 
Match [URL] and <a href="[NOT URL]">or [NOT URL]</a> 

Example with single quotes: 
WHERE [THIS] LIKE '%[NOT THIS]' 

Básicamente, ¿cómo hacer coincidir una cuerda (THIS) cuando no está rodeada por un char dado?

\b(?:[^"'])([^"']+)(?:[^"'])\b 

Aquí es un patrón de prueba: una expresión regular como lo que estoy pensando en coincidiría sólo la primera "cita".

Para citar: "¡Cita, no sea para que te cite!"

+0

Depende de qué tipo de expresiones regulares que está utilizando - si es o no permite la búsqueda hacia delante positivo/negativo/detrás –

+0

estaba asumiendo funciones de expresiones regulares completos como PHP, Perl, etc ... – Xeoncross

Respuesta

14

La mejor solución dependerá de lo que sepa sobre la entrada. Por ejemplo, si está buscando cosas que no están entre comillas dobles, ¿eso quiere decir que las comillas dobles siempre estarán correctamente equilibradas? ¿Se pueden escapar con barras invertidas o enviándolas entre comillas simples?

Suponiendo el caso más simple - sin anidación, hay escape - se podría utilizar una búsqueda hacia delante como esto:

preg_match('/THIS(?=(?:(?:[^"]*+"){2})*+[^"]*+\z)/') 

Después de encontrar el objetivo (ESTA), la búsqueda hacia delante básicamente cuenta las comillas dobles después de eso señalar hasta el final de la cadena. Si hay un número impar de ellos, la coincidencia debe haberse producido dentro de un par de comillas dobles, por lo que no es válida (la búsqueda anticipada falla).

Como has descubierto, este problema no se adapta bien a las expresiones regulares; es por eso que todas las soluciones propuestas dependen de las características que no se encuentran en real expresiones regulares, como grupos de captura, estimaciones, cuantificadores reacios y posesivos. Yo ni siquiera probar esto sin possessive quantifiers o atomic groups.

EDIT: Para ampliar esta solución para dar cuenta de las comillas dobles que se pueden escapar con barras invertidas, sólo tiene que reemplazar las partes de la expresión regular que responden a "cualquier cosa que no es una comilla doble":

[^"] 

con "todo lo que no es una cita o una barra invertida, o una barra invertida seguida de nada":

(?:[^"\\]|\\.) 

como las secuencias barra invertida-escape son relativamente raros, vale la pena para que coincida con el mayor número de caracteres sin escape como sea posible mientras está en esa parte de la expresión regular:

(?:[^"\\]++|\\.) 

Poniendo todo junto, se convierte en la expresión regular:

'/THIS\d+(?=(?:(?:(?:[^"\\]++|\\.)*+"){2})*+(?:[^"\\]++|\\.)*+$)/' 

aplicados a su cadena de prueba:

'Match THIS1 and "NOT THIS2" but THIS3 and "NOT "THIS4" or NOT THIS5" ' + 
'but \"THIS6\" is good and \\\\"NOT THIS7\\\\".' 

... Debe coincidir 'THIS1', 'THIS3', 'THIS4' y 'THIS6'.

+0

Este fue un gran comienzo en el tema, pero me temo que solo busca "ESTO" cuando hay tres comillas (") alejadas del final de la cadena. – Xeoncross

+0

¡Vaya! Olvidé un paréntesis. Pruébalo ahora. –

+0

Muy impresionante. Con soporte para un escape char esto podría ser suficiente! preg_match_all ('/ [^ "] + (? = (?: (?: [^"] * + ") {2}) * + [^ "] * + \ z)/', $ string, $ matches); – Xeoncross

3

Es un poco difícil. Hay formas, siempre y cuando no necesite realizar un seguimiento de la anidación. Por ejemplo, vamos a evitar cosas citado:

^((?:[^"\\]|\\.|"(?:[^"\\]|\\.)*")*?)THIS 

O, explicando:

^  Match from the beginning 
( Store everything from the beginning in group 1, if I want to do replace 
    (?: Non-grouping aggregation, just so I can repeat it 
     [^"\\] Anything but quote or escape character 
     |  or... 
     \\.  Any escaped character (ie, \", for example) 
     |  or... 
     "  A quote, followed by... 
     (?:  ...another non-grouping aggregation, of... 
      [^"\\] Anything but quote or escape character 
      |  or... 
      \\.  Any escaped character 
     )*  ...as many times as possible, followed by... 
     "  A (closing) quote 
    )*? As many as necessary, but as few as possible 
)  And this is the end of group 1 
THIS Followed by THIS 

Ahora, hay otras maneras de hacer esto, pero, tal vez, no es tan flexible. Por ejemplo, si desea encontrar ESTO, siempre que no haya una secuencia "//" o "#" anterior, en otras palabras, un ESTO fuera de un comentario, puede hacerlo así:

(?<!(?:#|//).*)THIS 

Aquí, (?<!...) es un aspecto negativo detrás. No coincidirá con estos caracteres, pero probará que no aparecen antes de ESTO.

Como para cualquier arbitrariamente estructuras anidadas - n ( cerrado por n ), por ejemplo - que no pueden ser representados por las expresiones regulares. Perl puede hacerlo, pero no es una expresión regular.

+0

Es posible, si n * * es finito (y práctico si * n * es pequeño), pero no si el anidamiento puede ser arbitrariamente profundo. –

+0

Eso es pedante, pero que así sea. Fijo. –

1

Bueno, las expresiones regulares son simplemente la herramienta incorrecta para esto, por lo que es bastante natural que sea difícil.

Las cosas "rodeadas" por otras cosas no son reglas válidas para las gramáticas regulares. La mayoría (uno podría decir, todo serio) marcado y lenguajes de programación no son regulares. Siempre que no exista anidación, es posible que pueda simular un analizador sintáctico con una expresión regular, pero asegúrese de comprender lo que está haciendo.

Para HTML/XML, solo use un resp. HTML. Analizador XML; esos existen para casi cualquier lenguaje o marco web; usándolos típicamente implica solo unas pocas líneas de código. En el caso de las tablas, es posible que pueda utilizar un analizador CSV o, en caso de necesidad, desplegar su propio analizador que extraiga las partes dentro/fuera de las comillas. Después de extraer las partes que le interesan, puede usar la comparación de cadenas simples o expresiones regulares para obtener sus resultados.

+0

+1 justo lo que iba a señalar. Básicamente, este problema es difícil, al igual que perforar un agujero con un martillo es difícil. – cletus

+0

"Surrounded" es una regla bastante válida para los idiomas regulares. Anidar y desanudar, eso no es. –

+0

@Daniel: las reglas válidas en gramáticas regulares (derecha) son solo aquellas reglas que tienen exactamente un terminal no-terminal en el lado izquierdo, y el hilo vacío, o un terminal, o un terminal seguido de un no terminal en el lado derecho lado. – Svante

0

Después de pensar en anidar elementos ("a" esto y "esto" ") y elementos de barra invertida" \ "THIS \" "parece que realmente es cierto que esto no es un trabajo para regex. Lo único que se me ocurre para resolver este problema sería un analizador regex como char-by-char que marcaría $ quote_level = ###; al encontrar e ingresar una cita válida o una comilla secundaria. De esta manera, mientras está en esa parte de la cadena sabría si estaba dentro de un personaje dado, incluso si se escapó por una barra o lo que sea.

Supongo que con un analizador de char por char así podría marcar la posición de la cadena de inicio/finalización comillas para que pueda dividir la cadena por segmentos de cotización y solo procese las que están fuera de las comillas.

Aquí hay un ejemplo de cómo este analizador debería ser lo suficientemente inteligente como para manejar niveles anidados.

Match THIS and "NOT THIS" but THIS and "NOT "THIS" or NOT THIS" but \"THIS\" is good. 

//Parser "greedy" looking for nested levels 
Match THIS and " 
      NOT THIS" 
       but THIS and " 
         NOT " 
          THIS" 
           or NOT THIS" 
             but \"THIS\" is good 

//Parser "ungreedy" trying to close nested levels 
Match THIS and "  " but THIS and " " THIS "   " but \"THIS\" is good. 
       NOT THIS    NOT   or NOT THIS 


//Parser closing levels correctly. 
Match THIS and "  " but THIS and "     " but \"THIS\" is good. 
       NOT THIS    NOT " " or NOT THIS 
              THIS 
0

Como señaló Alan M, puede usar expresiones regulares para buscar un número impar que le informe de su posición dentro o fuera de una cadena dada. Tomando el ejemplo de las citas, parecemos muy cerca de una solución a este problema. Lo único que queda es manejar citas escapadas.(Estoy seguro de que las comillas anidadas son casi imposibles).

$string = 'Match THIS1 and "NOT THIS2" but THIS3 and "NOT "THIS4" or NOT THIS5" but \"THIS6\" is good and \\\\"NOT THIS7\\\\".'; 


preg_match_all('/[^"]+(?=(?:(?:(?:[^"\\\]++|\\\.)*+"){2})*+(?:[^"\\\]++|\\\.)*+$)/', $string, $matches); 

Array (
     [0] => Match THIS1 and 
     [1] => but THIS3 and 
     [2] => THIS4 
     [3] => but 
     [4] => THIS6 
     [5] => is good and \\ 
     [6] => NOT THIS7\ 
     [7] => . 
    ) 
+0

Expandí mi respuesta para lidiar con citas escapadas. –

Cuestiones relacionadas