Hace unas semanas me consultaron de como poder combinar un archivo de plantilla con los datos de una tabla de VFP, para armar el texto personalizado de un correo electrónico. Mi respuesta rápida fue: "... con la función TextMerge() ...", y brindé el enlace a un artículo escrito por el amigo Esparta Palma sobre el tema:
-- Evolución del tratamiento de cadenas con TEXTMERGE --
http://comunidadvfp.blogspot.com/2014/10/evolucion-del-tratamiento-de-cadenas.html
La función
TextMerge()
, incorporada a partir de Visual FoxPro 7.0, y como bien lo indica Esparta en su artículo, tiene similares funcionalidades que los comandos SET TEXTMERGE
y TEXT ... TEXTMERGE ... ENDTEXT
, pero ... ¡¡¡ con anabólicos !!!, ya que TextMerge()
permite la expansión de expresiones en forma anidada, y también permite recursividad si se le indica en su último parámetro.La necesidad
La verdadera necesidad de quien me consultó, aparte de combinar simplemente los campos de la tabla donde estaban los datos de los destinatarios de los correos electrónicos, era incluir una tabla con datos de otra tabla, que obviamente era diferente por cada destinatario. Gracias a los anabólicos de la función
TextMerge()
, esto no sería inconveniente, ya que dentro de la expresión que se permite para la expansión, tranquilamente se puede incluir una función definida por el usuario. Como veremos en el ejemplo completo, se incluyen tres funciones definidad por el usuario que nos retornan la lista en cuestión, y resultados de cuentas y sumatorias.La cosmética
Una vez logrado esto, también se deseaba que el correo electrónico enviado tenga formato HTML para una mejor apariencia. Entonces partiendo de un archivo de plantilla que contiene código HTML con las expresiones a expandir encerradas entre los delimitadores (por defecto "<<" para el inicio y ">>" para el final). Para el caso de la función que debia generar una lista, esta lo obtendiamos con una "tabla formateada" con código HTML. Para generar esta tabla utlizaríamos el código del artículo: "Convertir tablas a formato HTML".
El correo electrónico
Una vez obtenido el código HTML para cada correo electrónico, configuramos todas las propiedades del objeto que utilizamos para el envío de correos con sus respectivos valores. Generalmente la propiedad que se configura para darle formato HTML al cuerpo de un correo electónico es
HtmlBody
. Para mis pruebas de envío de correo electrónico utilicé CDO (Collaboration Data Objects) y el código del artículo "Mas sobre el envio de mensajes de correo electrónico desde Visual FoxPro" de este Blog.La siguiente imagen nos muestra la apariencia del correo electrónico.
El código de ejemplo
A continuación se muestra un programa de ejemplo, en donde bien podemos generar el código HTML en tiempo de ejecución, o mejor aun, tomar ya un archivo HTML generado con algún editor que nos permitirá un mayor manejo en el formato de texto.
*-- Creo una Plantilla con código HTML SET TEXTMERGE OFF TEXT TO lcFile NOSHOW <p><i><<ALLTRIM(Employees.TitleOfCourtesy)>><br> <b><<ALLTRIM(Employees.FirstName)>> <<ALLTRIM(Employees.LastName)>></b><br> <<ALLTRIM(Employees.Address)>><br> <<ALLTRIM(Employees.City)>><br> <<ALLTRIM(Employees.Country)>></i></p> <hr> <p>Tengo el agrado de hacerle llegar el listado del total de sus ordenes del año 1997 agrupadas por mes:</p> <<AgregarListaResumen(Employees.EmployeeId)>> <p>La cantidad total de sus ordenes son <b><<TotalOrdenesLista(Employees.EmployeeId)>></b> y suman un total de <b><<TotalFletesLista(Employees.EmployeeId)>></b> en concepto de fletes.</p> <p>Lo saluda atentamente</p> <p><u><i>El Gerente</i></u></p> ENDTEXT STRTOFILE(lcFile, FULLPATH("Template.htm")) *-- Tomo la plantilla recien creada o bien una plantilla *-- creada con alguna herramienta HTML lcFile = FILETOSTR(FULLPATH("Template.htm")) OPEN DATABASE (HOME(2) + "Northwind\Northwind.dbc") SELECT 0 USE Employees SCAN ALL *-- Por cada Empleado genero el Mail con sus respectivas propiedades *-- ... *-- ... *-- Y genero el codigo HTML que ira en la propiedad BodyHtml del Mail lcBodyHtml = TEXTMERGE(lcFile) *-- Solo para este ejemplo guardo el archivo HTML para visualizarlo STRTOFILE(lcBodyHtml, FULLPATH(FORCEEXT("BodyHtml" + TRANSFORM(Employees.EmployeeId), "htm"))) ENDSCAN USE IN SELECT("Employees") PROCEDURE AgregarListaResumen(tnId) LOCAL lcHtml, loHtml *-- Listado de ordenes por Empleado SELECT MONTH(OrderDate) AS mes, COUNT(OrderId) AS Cant_Ordenes, SUM(Freight) AS Importe_Flete ; FROM Orders ; WHERE EmployeeId = tnId AND YEAR(OrderDate) = 1997 ; GROUP BY Mes ; ORDER BY Mes ; INTO CURSOR Lista loHtml = CREATEOBJECT("ClaseHtml") lcHtml = loHtml.AgregarTabla("Lista") + CHR(13) + CHR(10) RELEASE loHtml USE IN SELECT("Lista") RETURN lcHtml ENDPROC PROCEDURE TotalOrdenesLista(tnId) LOCAL lcHtml *-- Total de Ordenes por Empleado SELECT COUNT(OrderId) AS Tot_Ordenes ; FROM Orders ; WHERE EmployeeId = tnId AND YEAR(OrderDate) = 1997 ; INTO CURSOR TOTAL lcHtml = TRANSFORM(TOTAL.Tot_Ordenes) USE IN SELECT("Total") RETURN lcHtml ENDPROC PROCEDURE TotalFletesLista(tnId) LOCAL lcHtml *-- Total de Fletes por Empleado SELECT SUM(Freight) AS Tot_Flete ; FROM Orders ; WHERE EmployeeId = tnId AND YEAR(OrderDate) = 1997 ; INTO CURSOR TOTAL lcHtml = TRANSFORM(TOTAL.Tot_Flete) USE IN SELECT("Total") RETURN lcHtml ENDPROC *-------------------------------- DEFINE CLASS ClaseHtml AS CUSTOM *-- *-- Clase para convertir una Tabla a formato HTML *-- Código original del artículo: *-- "Convertir tablas a formato HTML" *-- http://comunidadvfp.blogspot.com/2005/10/convertir-tablas-formato-html.html *-- #DEFINE CR CHR(13) #DEFINE LF CHR(10) #DEFINE CR_LF CR + LF *-- Propiedades DIMENSION aCampos(1,1) nCampos = 0 nRegistros = 0 cNombreAlias = "" cArchivoHtml = "" cHtml = "" cColorBody = "#DDDDDD" cColorTitle = "#FFFFDD" cColorTable = "#FFFFFF" *-- PROCEDURE AgregarTags(tcTexto, tcTag, tcOpciones) RETURN [<] + LOWER(tcTag) + IIF(EMPTY(tcOpciones),[],[ ] + tcOpciones) + [>] + ; ALLTRIM(tcTexto) + [</] + LOWER(tcTag) + [>] ENDPROC *-- PROCEDURE IniciarTag(tcTag, tcOpciones) RETURN [<] + LOWER(tcTag) + ; IIF(EMPTY(tcOpciones),[],[ ] + tcOpciones) + [>] ENDPROC *-- PROCEDURE TerminarTag(tcTag) RETURN [</] + LOWER(tcTag) + [>] ENDPROC *-- PROCEDURE AgregarTabla(tcNombreAlias) THIS.cNombreAlias = tcNombreAlias THIS.nCampos = AFIELDS(THIS.aCampos,THIS.cNombreAlias) LOCAL lcTexto, ln lcTexto = THIS.IniciarTag([table],[border="1" cellpadding="2" cellspacing="0"] + ; IIF(EMPTY(THIS.cColorTable),[],[ bgcolor="] + THIS.cColorTable + ["])) + CR_LF *-- Fila cabecera lcTexto = lcTexto + THIS.IniciarTag([tr], ; IIF(EMPTY(THIS.cColorTitle),[],[bgcolor="] + THIS.cColorTitle + ["])) + CR_LF *-- Recorro campos FOR ln = 1 TO THIS.nCampos lcTexto = lcTexto + THIS.AgregarTags(THIS.aCampos(ln,1),[th]) + CR_LF ENDFOR lcTexto = lcTexto + THIS.TerminarTag([tr]) + CR_LF *-- Filas de registros SELECT (THIS.cNombreAlias) SCAN ALL THIS.nRegistros = THIS.nRegistros + 1 *-- Inicio fila lcTexto = lcTexto + THIS.IniciarTag([tr]) + CR_LF *-- Recorro los campos FOR ln = 1 TO THIS.nCampos lcTexto = lcTexto + THIS.AgregarTags(THIS.TomarValorCampo(ln),[td], ; THIS.AlinearSoloNumeros(ln)) + CR_LF ENDFOR *-- Fin fila lcTexto = lcTexto + THIS.TerminarTag([tr]) + CR_LF ENDSCAN lcTexto = lcTexto + THIS.TerminarTag([table]) RETURN lcTexto ENDPROC *-- PROCEDURE TomarValorCampo(tn) LOCAL lcTexto, lcTipo *-- Tipo de campo lcTipo = THIS.aCampos(tn,2) DO CASE CASE INLIST(lcTipo,"C","M") && Caracter y Memo lcTexto = ALLTRIM(EVALUATE(THIS.aCampos(tn,1))) CASE INLIST(lcTipo,"D","T","I","L") lcTexto = TRANSFORM(EVALUATE(THIS.aCampos(tn,1))) CASE INLIST(lcTipo,"Y") && Monetario *-- Quitar esta opción si no se desar el símbolo monetario *-- Este tipo también está contemplado en la opcion siguiente lcTexto = TRANSFORM(EVALUATE(THIS.aCampos(tn,1))) CASE INLIST(lcTipo,"N","F","B","Y") IF THIS.aCampos(tn,4) = 0 lcTexto = ALLTRIM(STR(EVALUATE(THIS.aCampos(tn,1)))) ELSE lcTexto = ALLTRIM(STR(EVALUATE(THIS.aCampos(tn,1)), ; THIS.aCampos(tn,3) + THIS.aCampos(tn,4) + 1, THIS.aCampos(tn,4))) ENDIF CASE INLIST(lcTipo,"G") && General lcTexto = [gen] CASE INLIST(lcTipo,"Q","V","W") && Nuevos tipos de VFP 9.0 lcTexto = TRANSFORM(EVALUATE(THIS.aCampos(tn,1))) OTHERWISE lcTexto = "" ENDCASE lcTexto = THIS.TransformarTexto(lcTexto) IF lcTipo = "M" && Si es campo Memo *-- Transformo retornos de carro y saltos de lineas lcTexto = STRTRAN(lcTexto, CR_LF, [<br>]) lcTexto = STRTRAN(lcTexto, CR, [<br>]) lcTexto = STRTRAN(lcTexto, LF, [<br>]) ELSE && Si no es campo Memo *-- Transformo espacios [ ] por [ ] lcTexto = STRTRAN(lcTexto, [ ], [ ]) ENDIF RETURN lcTexto ENDPROC *-- PROCEDURE TransformarTexto(tcTexto) *-- Transformo algunos caracteres "\<>& tcTexto = STRTRAN(tcTexto, ["], ["]) tcTexto = STRTRAN(tcTexto, [\], [\]) tcTexto = STRTRAN(tcTexto, [<], [<]) tcTexto = STRTRAN(tcTexto, [>], [>]) tcTexto = STRTRAN(tcTexto, [&], [&]) *-- Si es vacio o nulo IF EMPTY(tcTexto) OR ISNULL(tcTexto) tcTexto = [ ] ENDIF RETURN tcTexto ENDPROC *-- PROCEDURE AlinearSoloNumeros(tn) LOCAL lcTexto, lcTipo lcTipo = THIS.aCampos(tn,2) DO CASE CASE INLIST(lcTipo,"N","F","B","Y","I") *-- Alinea a la derecha solo campos numéricos lcTexto = [align="right"] OTHERWISE lcTexto = [] ENDCASE RETURN lcTexto ENDPROC ENDDEFINE *--------------------------------El final
Espero que esto los ayude a encontrar diversos usos que se le puede dar a la función
TextMerge()
. Lo positivo de este artículo y esta solución, es que para toda ella utilizamos código publicado en este Blog.A raíz de otras consultas recibidas, próximamente ampliaré sobre el tema del envio de correos electrónicos con CDO, con la inclusión de formato HTML, archivos adjuntos, y de imágenes y sonidos embebidos.
Hasta la próxima.
Luis María Guayán
No hay comentarios. :
Publicar un comentario
Los comentarios son moderados, por lo que pueden demorar varias horas para su publicación.