2011-06-24 14 views
47

De this q/a, deduje que coincidir todas las instancias de una expresión dada no comillas internas, es imposible. Es decir, no puede coincidir con las comillas escapadas (por ejemplo, "this whole \"match\" should be taken"). Si hay una manera de hacerlo que yo no sé, eso resolvería mi problema.Alternativa a regex: coincide con todas las instancias que no están dentro de las cotizaciones

Sin embargo, me gustaría saber si existe alguna alternativa eficiente que pueda usarse en JavaScript. Lo he pensado un poco, pero no puedo ofrecer soluciones elegantes que funcionen en la mayoría de los casos, si no en todos.

Específicamente, solo necesito la alternativa para trabajar con los métodos .split() y .replace(), pero si pudiera ser más general, sería lo mejor.

Por ejemplo:
una cadena de entrada de:
+bar+baz"not+or\"+or+\"this+"foo+bar+
reemplazando + con #, cotizaciones no en el interior, volvería:
#bar#baz"not+or\"+or+\"this+"foo#bar#

+0

Está claro que usted sabe lo que no puede hacer, pero no está claro por su pregunta lo que está tratando de hacer. ¿Puedes dar un ejemplo de tu problema y el resultado deseado? – cordsen

+0

Agregó un ejemplo, para aclarar un poco. – Azmisov

Respuesta

80

En realidad, puede hacer coincidir todas las instancias de una expresión regular que no estén dentro de comillas para ninguna cadena, donde cada cita de apertura se cierra de nuevo. Diga, como en su ejemplo anterior, quiere hacer coincidir \+.

La observación clave aquí es que una palabra está fuera de las comillas si hay un número par de citas a continuación. Esto se puede modelar como una afirmación de anticipación:

\+(?=([^"]*"[^"]*")*[^"]*$) 

Ahora, no desea contar las comillas escapadas. Esto se pone un poco más complicado. En lugar de [^"]*, que avanzó a la siguiente cita, también debe considerar las barras diagonales inversas y usar [^"\\]*. Después de llegar a una barra diagonal inversa o una comilla, debe ignorar el siguiente carácter si encuentra una barra diagonal inversa, o bien avanzar a la siguiente comilla no guardada.Eso se ve como (\\.|"([^"\\]*\\.)*[^"\\]*"). En combinación, se llega a

\+(?=([^"\\]*(\\.|"([^"\\]*\\.)*[^"\\]*"))*[^"]*$) 

que admitir que es un poco críptica. =)

+2

¡Gracias! No pensé que fuera posible. Entiendo el 100% de la teoría, aproximadamente el 60% de la expresión regular, y estoy por debajo del 0% cuando se trata de escribirla por mi cuenta. Oh, bueno, tal vez uno de estos días. – Azmisov

+0

Oye, ¿hay alguna forma de hacer que la expresión regular funcione con el método .split() de JavaScript? Parece ignorar la bandera global ... – Azmisov

+1

No importa, simplemente olvidé poner el '?:' Dentro de todos los paréntesis: '\ + (? = (?: [^" \\] * (?: \\. | "(?: [^" \\] * \\.) * [^ "\\] *")) * [^ "] * $)' – Azmisov

5

Usted puede hacerlo en tres pasos.

  1. Utilice una regex global replace para extraer todo el contenido de cuerdas del cuerpo en una mesa auxiliar.
  2. Haga su traducción coma
  3. Usar una expresión regular reemplazo global para intercambiar los cuerpos cadena de nuevo

código de abajo

// Step 1 
var sideTable = []; 
myString = myString.replace(
    /"(?:[^"\\]|\\.)*"/g, 
    function (_) { 
     var index = sideTable.length; 
     sideTable[index] = _; 
     return '"' + index + '"'; 
    }); 
// Step 2, replace commas with newlines 
myString = myString.replace(/,/g, "\n"); 
// Step 3, swap the string bodies back 
myString = myString.replace(/"(\d+)"/g, 
    function (_, index) { 
     return sideTable[index]; 
    }); 

Si ejecuta que después de ajustar

myString = '{:a "ab,cd, efg", :b "ab,def, egf,", :c "Conjecture"}'; 

se debe obtener

{:a "ab,cd, efg" 
:b "ab,def, egf," 
:c "Conjecture"} 

Funciona, porque después del paso 1,

myString = '{:a "0", :b "1", :c "2"}' 
sideTable = ["ab,cd, efg", "ab,def, egf,", "Conjecture"]; 

lo que la única comas en miCadena son cadenas externas. Paso 2, luego gira

myString = '{:a "0"\n :b "1"\n :c "2"}' 

y finalmente reemplazamos las cadenas que solo contienen números con su contenido original.

+0

+1 para una solución elegante no regex. Sin embargo, la expresión regular es un poco más flexible para lo que estoy haciendo. – Azmisov

40

Azmisov, resucitando esta pregunta porque dijiste que estabas buscando any efficient alternative that could be used in JavaScript y any elegant solutions that would work in most, if not all, cases.

Suele haber una solución simple y general que no se mencionó.

En comparación con las alternativas, la expresión regular para esta solución es sorprendentemente simple:

"[^"]+"|(\+) 

La idea es que ignoramos partido, pero nada entre comillas para neutralizar ese contenido (en el lado izquierdo de la alternancia). En el lado derecho, capturamos toda la + que no fueron neutralizados en el Grupo 1, y la función de reemplazar examina el Grupo 1. Aquí está el código completo de trabajo:

<script> 
var subject = '+bar+baz"not+these+"foo+bar+'; 
var regex = /"[^"]+"|(\+)/g; 
replaced = subject.replace(regex, function(m, group1) { 
    if (!group1) return m; 
    else return "#"; 
}); 
document.write(replaced); 

Online demo

Puede use el mismo principio para unir o dividir. Vea la pregunta y el artículo en la referencia, que también le indicará ejemplos de código.

Espero que esto te dé una idea diferente de una forma muy general de hacer esto. :)

¿Qué pasa con Empty Strings?

Lo anterior es una respuesta general para mostrar la técnica. Se puede ajustar según sus necesidades exactas. Si le preocupa que su texto puede contener cadenas vacías, solo cambia el cuantificador dentro de la expresión de cadena de captura +-*:

"[^"]*"|(\+) 

Ver demo.

¿Qué hay de las cotizaciones de escape?

Una vez más, lo anterior es una respuesta general para mostrar la técnica. No solo puede "ignorar esta coincidencia". La expresión regular se puede adaptar a sus necesidades, puede agregar varias expresiones para ignorar. Por ejemplo, si desea asegurarse de que las comillas escapadas se ignoran adecuadamente, puede comenzar agregando una alternancia \\"| en frente de las otras dos para hacer coincidir (e ignorar) las comillas dobles escapadas.

A continuación, dentro de la sección "[^"]*" que captura el contenido de cadenas entre comillas dobles, se puede añadir una alternancia para asegurar comillas dobles escapado se hacen coincidir antes de su " tiene la oportunidad de convertirse en un centinela de cierre, convirtiéndolo en "(?:\\"|[^"])*"

La expresión resultante tiene tres ramas:

  1. \\" para igualar y ignoran
  2. "(?:\\"|[^"])*" para igualar y ignoran
  3. (\+) a la altura, captura y manejar

Tenga en cuenta que en otros sabores de expresiones regulares, podríamos hacer este trabajo con más facilidad de búsqueda hacia atrás, pero JS no lo soporte.

La expresión regular completa se convierte en:

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

Ver regex demo y full script.

Referencia

  1. How to match pattern except in situations s1, s2, s3
  2. How to match a pattern unless...
+4

Este enfoque es en realidad mejor que el modo de mirar hacia adelante sugerido por @Jens. Es más fácil de escribir y tiene un rendimiento mucho mejor. No me di cuenta y usé la mirada hacia adelante Hasta que llegué a un problema de rendimiento que para que coincida con un texto de 1.5M, la forma de mirar hacia adelante usó unos 90 segundos, mientras que este enfoque solo necesitó 600ms. – Gildor

+1

Sí, esto es mejor =) – Jens

+0

I fo y que esto solo funcionó al cambiar la 5ta línea de su ejemplo a 'if (group1 === undefined) return m;'. Vale la pena señalar que estaba buscando espacios; no más signos. – shennan

0

Aunque la respuesta por ZX81 parece ser el mejor rendimiento y una limpia, que needes estas correcciones para coger correctamente las comillas escapado:

var subject = '+bar+baz"not+or\\"+or+\\"this+"foo+bar+'; 

y

var regex = /"(?:[^"\\]|\\.)*"|(\+)/g; 

también el ya mencionado "grupo 1 === indefinido" o "! Grupo1". Especialmente 2. Parece importante tomar realmente todo lo que se pide en la pregunta original.

Sin embargo, se debe mencionar que este método implícitamente requiere que la cadena no tenga comillas escapadas fuera de los pares de comillas no escaneados.

Cuestiones relacionadas