Thinking on hiring me?

Please read

Fernando Guillén

a Freelance Web Developer

cabecera decorativa

software development as an artistic expression

Ruby, sustituyendo matches de una regex en un String con matches de la misma regex

Madre mía que título bueno me ha quedado :)

Esto es una nota mental y puede que si no sabes de que hablo no te interese y si sabes de que hablo ya lo sepas.

Intento sustituir una parte de un String por otra parte que se encuentra en el mismo String.

Es decir, tengo esto “me gusta el heavy y no me gusta el country” y quiero obtener esto otro “me gusta el country y no me gusta el heavy”.

Se puede hacer así:

>> "me gusta el heavy y no me gusta el country".gsub( /me gusta (.*) y no me gusta el (.*)/, 'me gusta \2 y no me gusta el \1' )
=> "me gusta country y no me gusta el el heavy"

Donde \1 y \2 son las ocurrencias de los (.*).

El ejemplo puede parecer un poco tonto, pero esta utilidad me ha venido muy bien para quedarme con el contenido de una etiqueta html:

>> "<body>contenido</body>".gsub( /.*<body[^>]*>(.*)<\/body>.*/mix, '\1' ).strip
=> "contenido"

Que se puede solucionar de muchas otras maneras pero esta me ha parecido la más sencilla.

Escribo esta nota mental por lo poco intuitivo que es el uso de ‘\1′ como cadena sustitutiva pues se supone que las cadenas entre comillas simples no se interpretan…

Otra cosa es que google siempre me llevaba a soluciones como esta:

>> "me gusta el heavy y no me gusta el country".gsub( /me gusta (.*) y no me gusta el (.*)/, "me gusta #{$2} y no me gusta el #{$1}" )
=> "me gusta country y no me gusta el el heavy"

Y aunque parece que funciona no es así porque los contenidos de $1 y $2 los ha cogido del gsub anterior y no de éste:

>> "me gusta el musical y no me gusta el flamenco".gsub( /me gusta (.*) y no me gusta el (.*)/, "me gusta #{$2} y no me gusta el #{$1}" )
=> "me gusta country y no me gusta el el heavy"
>> "me gusta el musical y no me gusta el flamenco".gsub( /me gusta (.*) y no me gusta el (.*)/, "me gusta #{$2} y no me gusta el #{$1}" )
=> "me gusta flamenco y no me gusta el el musical"
10 Comments to “Ruby, sustituyendo matches de una regex en un String con matches de la misma regex”
  1. Balint Erdi Says:

    Hola Fernando, estoy disfrutando de tus posts sobre Ruby y aprendiendo contigo. Mi dos cientos de euro (un anglisismo?) es que quizas sea mas intuitivo usar scan or =~ cuando quieres sacar algo del string i no sustituir.

    Asi tu ejemplo seria:

    "<body>contenido</body>".scan( /.*<body[^>]*>(.*)<\/body>.*/mix) { |m| (... hacer algo con 'contenido' ... }

    o bien:

    "<body>contenido</body>".scan( /.*<body[^>]*>(.*)<\/body>.*/mix).first.first.strip #ya, es un poco fea, parece que scan en este forma sea un overkill)

    Con =~ :

    "<body>contenido</body>" =~ /.*<body[^>]*>(.*)<\/body>.*/mix

    y tienes ‘contenido’ en $1.

    Keep your Ruby posts coming!
    Balint

  2. Aitor Says:

    IMHO hay formas más directas para el caso de obtener un substring:

    <body>contenido</body>"[/REGEXP/, 1]
    /REGEXP/.match( "<body>contenido</body>")[1]

    Los backreferences (\1, \2…) son más útiles y están pensados para cuando se usan dentro de la propia REGEXP:

    "abbc"[/(.)\1/] -> "bb" #matchea dos caracteres iguales seguidos
  3. fguillen Says:

    Balint: Mi dos cientos de euro => Mis dos céntimos de euro :)

  4. fguillen Says:

    Balint, Aitor: Os he reparado los comentarios pues el cabroncete del wordpress se come muchos caracteres importantes. Espero no haber metido la pata :)

  5. fguillen Says:

    Balint, tienes toda la razón de que el gsub no era buena solución para mi intento de extraer el contenido de una tag xml, además de lioso no respondía bien pues en caso de no encontrar ningún match con la regex devolvía todo el string y no es correcto.

    Si no hay match no se debería devolver nada.

    De hecho, al final mi metodo extractor de bodys queda así:

    def get_body_content string
      string.match( /.*<\s*body[^>]*>(.*)<\/body\s*>.*/mi )
      $1.nil? ? nil : $1.strip
    end
  6. fguillen Says:

    Aitor, increíblemente instructivos tus ejemplos de malabarismos con regexs, no se me habrían ocurrido :)

  7. Balint Erdi Says:

    Ya pensaba que hay solucion mas elegante que la mia, sino no seria ruby :) (Gracias, Aitor)

  8. Balint Erdi Says:

    Porque dos centimos? Dos cientos son mejor, no? ;)

  9. fguillen Says:

    En realidad la solución buena es usar el ‘xxx \1′ con comillas simples:

    >> “me gusta el musical y no me gusta el flamenco”.gsub( /me gusta (.*) y no me gusta el (.*)/, ‘me gusta \2 y no me gusta el \1′ )
    => “me gusta flamenco y no me gusta el el musical”

  10. el verdor Says:

    Hola, y como lo harías si tienes dos select en un string, y quieres sacar primero un contenido y luego el otro

Leave a comment

a Freelance Web Developer is proudly powered by WordPress
Entries (RSS) and Comments (RSS).

Creative Commons License
Fernando Guillen's blog by Fernando Guillen is licensed under a Creative Commons Attribution-NoDerivs 3.0 Unported License.