Usar funciones en la sustitución de StringRegExpReplace

Tus preguntas. Algoritmos o Grupos de Comandos formando Programas Escripts.
Responder
Avatar de Usuario
Ximorro
Profesional del Autoit
Mensajes: 1500
Registrado: 10 Jul 2009, 12:35
Ubicación: Castellón, España

Usar funciones en la sustitución de StringRegExpReplace

Mensaje por Ximorro »

Una de las principales frustraciones que he tenido usando expresiones regulares (ER) en Autoit ha sido el no poder usar funciones en StringRegExpReplace para procesar los grupos capturados en las sustituciones.

Lo que quiero decir es que a la hora de sustituir puedes usar los grupos capturados usando las referencias $1, $2, etc. (o usando "\" en vez de "$")
Por ejemplo, para intercambiar día-mes en el formato americano de fecha (mes/día/año) al nuestro (día/mes/año) se puede hacer:
StringRegExpReplace("01/20/2012", "(\d{2})/(\d{2})/(\d{4})", "$2/$1/$3")
(esto devuelve "20/01/2012)

Ahí $1 es "el primer grupo capturado, que en la fecha original corresponde al mes, y así $2 es el día y $3 el año.
Gracias a las referencias las puedo usar directamente para recolocarlas en la sustitución.

Ahora bien ¿y si no quiero usar directamente el grupo capturado sino que quiero aplicarle algún proceso antes de usarlo?

Por ejemplo supongamos que quiero hacer una sustitución que me cambie todas las vocales de una frase a mayúsculas.
Primero hago una prueba sin funciones para ver que estoy capturando lo que quiero, por ejemplo sustituyo las vocales minúsculas por un asterisco:
StringRegExpReplace("mayúsculas", "([aeiouáéíóú])", "*")
Lo pruebo y funciona, esto de me devuelve "m*y*sc*l*s"

Ahora voy a intentar usar lo capturado pasándolo antes a mayúscula con StringUpper():
StringRegExpReplace("mayúsculas", "([aeiouáéíóú])", StringUpper("$1"))
Lo pruebo ¡y no funciona!, me devuelve "mayúsculas", como si no hubiera procesado nada.

¿Qué ha ocurrido? Pues que ahí no estoy diciendo que me sustituya cada vocal con su versión StringUpper, sino que estoy diciendo que el tercer parámetro de la función StringRegExpReplace es StringUpper("$1"). AutoIt, como todos los lenguajes, calcula ese parámetro que es una expresión antes de pasarlo a la función, así que evalúa StringUpper("$1") y le da como resultado la cadena "$1" (como no hay letras se queda igual) y eso que ya es un string es lo que pasa a la función StringRegExpReplace como tercer parámetro.
Así que dentro de StringRegExpReplace no se llega a ver el StringUpper, sino su resultado, con lo que no lo aplica dentro de la ER.

Esto es importante pues forma parte del mecanismo de paso de parámetros a funciones, así que por favor si no lo habéis entendido leerlo con detenimiento dos veces más, y si seguís sin entenderlo preguntar las dudas y lo explicaré con más detalle.

Se pueden hacer otros intentos, como meter la función en la cadena que se pasa a la ER:
StringRegExpReplace("mayúsculas", "([aeiouáéíóú])", "StringUpper($1)")
Así la ER sí que recibe el StringUpper en su sustitución ¡pero no lo ejecuta! pues eso no es algo entendible por el motor de expresiones regulares, lo interpreta como que quieres poner ese texto y ya está, el resultado es:
"mStringUpper(a)yStringUpper(ú)scStringUpper(u)lStringUpper(a)s"

¿Veis que sí ha hecho la sustitución? Cada vocal la ha rodeado de la llamada a StringUpper, pero no ejecuta ese código AutoIt, lo está tomando como un texto cualquiera.

¿Hay solución? ¡¡¡SI!!!
Ver algo como el resultado anterior puede dar la idea. AutoIt puede ejecutar código generado dinámicamente: creas el código en un string y lo ejecutas con la función Execute(). Así que variando un poco el resultado anterior si conseguimos una cadena como ésta:
'"m" & StringUpper("a") & "y" & StringUpper("ú") & "sc" & StringUpper("u") & "l" & StringUpper("a") & "s"'
Al ejecutar eso con Execute nos dará lo que queremos:
Execute('"m" & StringUpper("a") & "y" & StringUpper("ú") & "sc" & StringUpper("u") & "l" & StringUpper("a") & "s"')
devuelve "mAyÚscUlAs" ¡Bieeeeeeeeeeen!

Así que la cosa es tener todo lo que no hay que sustituir entre comillas (o sea dejar el literal como está) y para los grupos a sustituir concatenar el resultado de la función, o sea '& StringUpper($1) &'
Así cada vocal se sustituye con una llamada a la función que la procesa. Además habrá que poner comillas antes y después de la vocal para entrecomillar los literales, así como poner comillas a inicio y final de cadena.

Un primer código que ya funciona sería este:

Código: Seleccionar todo

$str = "mayúsculas"
$codigoAutoIt = '"' & StringRegExpReplace($str, "([aeiouáéíóú])", '" & StringUpper("$1") & "') & '"'
$resultado = Execute($codigoAutoIt)
ConsoleWrite($resultado & @LF)
Eso asigna a $resultado el valor "mAyÚscUlAs" ¡FUNCIONA!

El principal problema es la eficiencia, esto crea cadenas muy grandes, para este caso de una simple palabra la variable $codigoAutoIt vale:
"m" & StringUpper("a") & "y" & StringUpper("ú") & "sc" & StringUpper("u") & "l" & StringUpper("a") & "s"

Imaginaros para pasar un texto de varias megas.
No he encontrado grandes soluciones a ese problema, pero lo que hago es minimizarlo quitando espacios innecesarios y remapeando la función que quiero pasar a otra personalizada cuyo nombre sólo tiene un carácter, para que ocupe lo menos posible, por ejemplo:

Código: Seleccionar todo

$str = "mayúsculas"
$codigoAutoIt = '"' & StringRegExpReplace($str, "([aeiouáéíóú])", '"&F("$1")&"') & '"'
$resultado = Execute($codigoAutoIt)
ConsoleWrite($resultado & @LF)

Func F($cad)
	Return StringUpper($cad)
EndFunc
De esta manera la variable $codigoAutoIt queda bastante más corta:
"m"&F("a")&"y"&F("ú")&"sc"&F("u")&"l"&F("a")&"s"

No es la panacea, pero así puedo ejecutar funciones en las sustituciones de las expresiones regulares, si a alguien se le ocurre algo mejor o una mejora de esto estoy deseoso de oírlo.
El uso de una función personalizada por supuesto es necesario cuando no existe una función directa en AutoIt para lo que queremos hacer (como StringUpper en este caso). Lo bueno es que en F() podemos poner un proceso tan complicado como queramos.

Nota: normalmente las cadenas AutoIt no quedan tan largas, dependerá de la sustitución, pensad que cambiar las vocales implica hacer una barbaridad de sustituciones, pero este es un caso un poco extremo. En cualquier caso si hay que procesar cadenas especialmente largas donde hay que hacer muchas sustituciones cuidado con las necesidades de memoria y velocidad.
"¿Y no será que en este mundo hay cada vez más gente y menos personas?". Mafalda (Quino)
Avatar de Usuario
Chefito
Profesional del Autoit
Mensajes: 2035
Registrado: 21 Feb 2008, 18:42
Ubicación: Albacete/Cuenca (España)

Re: Usar funciones en la sustitución de StringRegExpReplace

Mensaje por Chefito »

Hasta hoy no lo había leído porque quería hacerlo detenidamente. Muy chulo el estudio :smt023 .

TEXTO BORRADO. UN GALIMATÍAS DE LOS MÍOS.

Perdón a Ximorro por hacerle un lío.

Saludos.
Última edición por Chefito el 30 Ene 2012, 21:26, editado 1 vez en total.
Cita vista en algún lugar de la red: En este mundo hay 10 tipos de personas, los que saben binario y los que no ;).
Avatar de Usuario
Ximorro
Profesional del Autoit
Mensajes: 1500
Registrado: 10 Jul 2009, 12:35
Ubicación: Castellón, España

Re: Usar funciones en la sustitución de StringRegExpReplace

Mensaje por Ximorro »

¿Pero entonces cómo ejecutas luego la función, y sobre qué?
En este caso habría que volver a recorrer el string buscando esos caracteres, y ejecutando la función sobre el carácter siguiente.
En ese caso aún (aunque creo que no salimos ganando con otra pasada sobre la cadena), pero piensa también que podríamos no querer ejecutar directamente sobre un único carácter, sino ser algo más complejo usando las referencias de la ER.

Para casos simples como este igual es mejor, aunque programarlo es más complejo (con F() ya genero el código AutoIt a ejecutar, no hay que reanalizar el string y recomponer el resultado).
Vaya, o es que no he entendido tu sugerencia, lo metía en un F() para eso, para poder usar la función directamente sobre una expresión generada con los grupos capturados de la ER.
Explícamelo mejor que creo que no te he entendido...
"¿Y no será que en este mundo hay cada vez más gente y menos personas?". Mafalda (Quino)
Avatar de Usuario
Chefito
Profesional del Autoit
Mensajes: 2035
Registrado: 21 Feb 2008, 18:42
Ubicación: Albacete/Cuenca (España)

Re: Usar funciones en la sustitución de StringRegExpReplace

Mensaje por Chefito »

Normal que no lo hayas entendido. Lo he vuelto a leer todo y no lo entiendo ni yo :smt005 :smt005 :smt005 .

Un cruce de cables de los míos. Olvidalo. Perdona por la chorrada que puse.

El mensaje ha sido editado y el texto borrado para no liar al personal.

Saludos.
Cita vista en algún lugar de la red: En este mundo hay 10 tipos de personas, los que saben binario y los que no ;).
Avatar de Usuario
Ximorro
Profesional del Autoit
Mensajes: 1500
Registrado: 10 Jul 2009, 12:35
Ubicación: Castellón, España

Re: Usar funciones en la sustitución de StringRegExpReplace

Mensaje por Ximorro »

Eeeh, bueno, vale, quizás era una idea feliz de esas de genio que vienen medio en sueños :smt015 , y ahora no entiendes ni tu genialidad :smt005
"¿Y no será que en este mundo hay cada vez más gente y menos personas?". Mafalda (Quino)
Responder