2008-09-26 11 views
7

Necesito reemplazar el carácter (decir) x con el carácter (decir) P en una cadena, pero solo si está contenido en una subcadena entre comillas. Un ejemplo hace que sea más claro:¿Puede Regex utilizarse para esta manipulación de cadena en particular?

axbx'cxdxe'fxgh'ixj'k -> axbx'cPdPe'fxgh'iPj'k 

Supongamos, en aras de la simplicidad, que cita siempre vienen en pares.

La forma obvia es simplemente procesar la cadena un carácter a la vez (un enfoque de máquina de estado simple);
Sin embargo, me pregunto si las expresiones regulares se pueden utilizar para hacer todo el procesamiento de una vez.

Mi idioma de destino es C#, pero supongo que mi pregunta se refiere a cualquier idioma que tenga compatibilidad incorporada o de biblioteca para expresiones regulares.

+0

Quitar 'regulares' –

Respuesta

8

que era capaz de hacer esto con Python:

>>> import re 
>>> re.sub(r"x(?=[^']*'([^']|'[^']*')*$)", "P", "axbx'cxdxe'fxgh'ixj'k") 
"axbx'cPdPe'fxgh'iPj'k" 

Lo que esto hace es utilizar la lucha no captura (= ...?) Para comprobar que el caracter x está dentro de una cadena entre comillas. Busca algunos caracteres que no sean las comillas hasta la siguiente cita, luego busca una secuencia de caracteres individuales o grupos de caracteres citados, hasta el final de la cadena.

Esto se basa en su suposición de que las comillas siempre están balanceadas. Esto tampoco es muy eficiente.

+0

-expresiones Considere también que es el re.sub () función que itera sobre la cadena. la expresión regular en sí misma solo coincide con la primera x dentro de las comillas. –

+0

No puedo imaginar cómo representaría la solución a este problema sin usar algo como re.sub(). Después de todo, una expresión regular por sí sola solo coincide, y la pregunta original sobre la sustitución. –

+0

tienes razón: no se puede hacer con un poco de "maquinaria extra", es por eso que muchos respondieron que "expresiones regulares simples" donde no es lo suficientemente potente –

1

No con regexp normal. Las expresiones regulares no tienen "memoria", por lo que no pueden distinguir entre citas "internas" o "externas".

se necesita algo más potente, por ejemplo usando gema sería lisa y llana:

'<repl>'=$0 
repl:x=P 
0

momento de romper sus esperanzas, pero se necesita un autómata de empujar hacia abajo para hacer eso. Hay más información aquí: Pushdown Automaton

En resumen, las expresiones regulares, que son máquinas de estado finito solo pueden leer y no tienen memoria mientras que el autómata pushdown tiene una pila y capacidades de manipulación.

Editar: ortografía ...

1

discusión similar sobre el texto equilibrado sustituye: Can regular expressions be used to match nested patterns?

Aunque se puede tratar esto en Vim, pero funciona bien sólo si la cadena está en una línea, y sólo hay un par de 's.

:%s:\('[^']*\)x\([^']*'\):\1P\2:gci 

Si hay un par más o incluso un desequilibrado ', entonces podría fallar. Así es como he incluido el c a.k.a. confirme el indicador en el comando ex.

Lo mismo se puede hacer con sed, sin interacción, o con awk para que pueda agregar algo de interacción.

Una posible solución es romper las líneas en pares de ' s, entonces puede hacerlo con la solución vim.

+0

Creo que esto solo reemplazará las primeras x que encuentre dentro de las comillas. Las x posteriores se corresponderán con el patrón sin apóstrofo [^ '] * – jop

+0

Tiene razón. Pero puede ser modificado para reemplazar todas las x. –

+0

Ver los comentarios en http://stackoverflow.com/questions/138552?sort=votes#138615 – jop

9

He convertido el código python de Greg Hewgill en C# y funcionó.

[Test] 
public void ReplaceTextInQuotes() 
{ 
    Assert.AreEqual("axbx'cPdPe'fxgh'iPj'k", 
    Regex.Replace("axbx'cxdxe'fxgh'ixj'k", 
     @"x(?=[^']*'([^']|'[^']*')*$)", "P")); 
} 

Esa prueba pasó.

1
Pattern:  (?s)\G((?:^[^']*'|(?<=.))(?:'[^']*'|[^'x]+)*+)x 
Replacement: \1P 
  1. \G — ancla cada partido en el final de la anterior, o el inicio de la cadena.
  2. (?:^[^']*'|(?<=.)) — Si está al principio de la cadena, haga coincidir la primera cita.
  3. (?:'[^']*'|[^'x]+)*+ — Coincidir cualquier bloque de caracteres sin comillas, o cualquier carácter (sin cita) hasta una 'x'.

Un barrido a través de la cadena fuente, a excepción de un solo carácter detrás de la mirada.

2

El truco es usar grupo sin captura para que coincida con la parte de la cadena siguiente el partido (personaje x) estamos buscando. Intentando hacer coincidir la cadena hasta x solo encontrará la primera o la última ocurrencia, dependiendo de si se usan cuantificadores no codiciosos. Aquí está la idea de Greg transpuesta a Tcl, con comentarios.

 
set strIn {axbx'cxdxe'fxgh'ixj'k} 
set regex {(?x)      # enable expanded syntax 
            # - allows comments, ignores whitespace 
      x      # the actual match 
      (?=      # non-matching group 
       [^']*'    # match to end of current quoted substring 
            ## 
            ## assuming quotes are in pairs, 
            ## make sure we actually were 
            ## inside a quoted substring 
            ## by making sure the rest of the string 
            ## is what we expect it to be 
            ## 
       (
        [^']*   # match any non-quoted substring 
        |    # ...or... 
        '[^']*'   # any quoted substring, including the quotes 
       )*     # any number of times 
       $     # until we run out of string :) 
      )      # end of non-matching group 
} 

#the same regular expression without the comments 
set regexCondensed {(?x)x(?=[^']*'([^']|'[^']*')*$)} 

set replRegex {P} 
set nMatches [regsub -all -- $regex $strIn $replRegex strOut] 
puts "$nMatches replacements. " 
if {$nMatches > 0} { 
    puts "Original: |$strIn|" 
    puts "Result: |$strOut|" 
} 
exit 

Esta impresora:

3 replacements. 
Original: |axbx'cxdxe'fxgh'ixj'k| 
Result: |axbx'cPdPe'fxgh'iPj'k| 
2
#!/usr/bin/perl -w 

use strict; 

# Break up the string. 
# The spliting uses quotes 
# as the delimiter. 
# Put every broken substring 
# into the @fields array. 

my @fields; 
while (<>) { 
    @fields = split /'/, $_; 
} 

# For every substring indexed with an odd 
# number, search for x and replace it 
# with P. 

my $count; 
my $end = $#fields; 
for ($count=0; $count < $end; $count++) { 
    if ($count % 2 == 1) { 
     $fields[$count] =~ s/a/P/g; 
    }  
} 

No sería este trozo hacer el trabajo?

2

Una solución más general (y más simple) que permite citas sin pares.

  1. Encuentra citado cadena
  2. Reemplazar 'x' por 'P' en la cadena de etiqueta

    #!/usr/bin/env python 
    import re 
    
    text = "axbx'cxdxe'fxgh'ixj'k" 
    
    s = re.sub("'.*?'", lambda m: re.sub("x", "P", m.group(0)), text) 
    
    print s == "axbx'cPdPe'fxgh'iPj'k", s 
    # -> True axbx'cPdPe'fxgh'iPj'k 
    
Cuestiones relacionadas