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 finalEspero 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.