23 de junio de 2008

Será bueno seguir programando en VFP ?

Recientemente en "Mundo Visual Foxpro" acabo de ver nuevamente la pregunta de si vale la pena seguir desarrollando en VFP. En lo personal pienso que sí y me tomé la libertad de responderle afirmativa al postulante de la cuestión. Se me ha ocurrido publicar aquí, en Portal Fox, también mi opinión, con mucho respeto a los colegas que podrían opinar totalmente lo contrario.

A quien pregunta entonces si aún vale la pena desarrollar en VFP, yo le respondo rotundamente que si porque:

1- Tu piensas permanecer vivo aún mas allá del 2,017 ¿no? Entonces tus aplicaciones en VFP 9 seguirán vivas mientras tu sigas vivo. Por ejemplo, yo aún tengo aplicaciones hechas en Foxpro para DOS 2.6 funcionando "de maravilla" en Windows Vista. Son aplicaciones sólidas, bien hechas, robustas, rápidas y mis clientes no tienen intención de migrarlas. Son aplicaciones que están operando desde 1,992 (16 años...!). Otro punto importante: yo dejo que mi cliente decida cuando migrar, NO LO PRESIONO NI CHANTAJEO para que lo haga; veo en el cliente un amigo y no "una minita de oro" a quien poderle estar exprimiendo a cada rato con cada cambio de sistema operativo que hace Microsoft.

2- Varios clientes "me han cortado" porque han querido moverse a otras plataformas que "supuestamente" son mucho mejores que VFP (Entiéndase Visual Studio con SQL Server). De entre los que me "habían cortado", tres han vuelto de regreso conmigo. La razón de que hayan regresado conmigo no fue porque .NET, Visual Studio o SQL Server sean malos. Sino porque tenían que moverse a SQL Server (un chorro de plata mas en licencias), depender de los cambios de versiones de Windows Server, y lo más importante, los programadores que les hablaron maravillas de .NET eran muy buenos en eso: "hablar de las bondades de .NET", pero como programadores en sí mismos, eran sumamente inexpertos, impacientes, imprecisos, inconstantes e incumplidos. No alcanzaron a llevar a término sus nuevas aplicaciones con sus flamantes nuevos lenguajes de programación.

Mis clientes primero se frustraron y aburrieron de tanta excusa en los retrasos de entrega del proyecto, los gastos cada vez mayores en herramientas de terceros (que para reportar ahora hay que comprar Crystal Reports...), etc. etc.

Luego de tanta frustración, incumplimiento, incomodidad, gastos extras no mencionados al principio y pobres avances con lo que consideraban "la octava maravilla", resolvieron mejor volver con su viejo pero confiable programador.

Mis clientes no volvieron conmigo porque yo les programara en VFP (a estas alturas no les importa en que les programe). Ellos volvieron conmigo porque en mis aplicaciones tenían, experiencia, constancia, presición, velocidad, resolución de problemas a toda prueba y ante todo cumplimiento con los ofrecimientos cotizados.

3- No es tu herramienta la que te trae éxito, es tu actitud frente a los problemas informáticos que se dan en el camino. Cuando hay un problema "x" en la implementación de una aplicación, eres de los que esperan que otros les digan como salir adelante o eres tu de los que encuentran la solución. Eres de los que "se limpian" en Windows y dicen: "Windows es así, no hay nada que podamos hacer al respecto". Eres de los que resuelven problemas o de los que buscan quien los resuelve por ti. Eres de los que descubren el verdadero mecanismo de un problema o de los que bajan de Internet "parches" o cualquier cosa que mediocremente disimule un problema. No digo con esto que no sea bueno "documentarse" e "investigar" en la Internet o Portal Fox en busca de soluciones (es también absurdo "re-inventar" innecesariamente la rueda), pero lo haces como última instancia o es lo primero que haces frente a un problema.

4- No importa que "tan poderoso" sea el lenguaje de programación que elijas, éste jamás podrá "Pensar por Ti", "Solucionar problemas por ti", "Encontrar mejores alternativas de diseño por tí", "Buscar clientes por ti", "Administrar mejor tu tiempo por tí", "Auto-Diseñarse por tí". Cualquier lenguaje de programación, por poderoso que sea, solo es una herramienta en manos de un hábil o un torpe ingeniero, será solo como un pincel en manos de un prodigioso o un mediocre artista.

Te recuerdo la célebre anécdota del pordiosero que tocaba desafinadamente su maltrecho violín en la salida de un teatro donde justamente en ese momento el famoso Paganini daba un concierto. A la salida, luego de haber escuchado al maestro, la multitud se alejaba del desafinado pordiosero como quien le huye a la peste, hasta que el mismo Paganini, le pidió al pordiosero "su mismo violín", lo afinó y tocó allí afuera con ese mugroso violín, un fragmento de la obra que acababa de tocar dentro del teatro. La multitud lo rodeó y escuchó con admiración y asombro al genial maestro Paganini mientras el pordiosero gritaba: "Ese es mi violín..."

A lo largo de tu profesión como programador verás verdaderas "obras maestras" hechas en VFP, Delphi, XBase++, Xailer, Clippper, Visual Flagship, MySQL con PHP, .NET, Visual Studio, SQL Server, Visual C, etc. etc. Y también verás "grandísimas porquerías" hechas con los mismos lenguajes de programación ya citados. Es cosa de la actitud mental del programador, no de la herramienta que este utiliza. Recuerda: "El hábito no hace al monje..." o dicho en nuestro lenguaje: "La herramienta de programación no hace al buen programador..."

Para cuando ya no sea posible usar mas VFP 9 porque ya no pueda funcionar en ningún sistema operativo, yo ya seré un anciano que de todos modos tendré que estar jubilado de la programación y dedicarme a alguna otra cosa. Si te pones a pensar que tengo corriendo programas luego de 16 años como el caso de Foxpro para DOS, haciendo una analogía, se podría decir que 16 años luego del fin del soporte para VFP 9 (2,016), lo cual sería allá por el 2,032 yo ya tendré 64 años, por lo que esperaría estarme dedicando a alguna otra cosa menos desgastante y estresante que la programación de aplicaciones informáticas (algo así como desactivar bombas terroristas, re-hacer como doble de riesgo los documentales del "cazador de cocodrilos", servir como sujeto de prueba para desarrollo de armas bacteriológicas...)

Si eres del tipo de programador que resuelve, que se responsabiliza (en vez de culpar al hardware, a Windows, a los otros, al usuario), que investiga, que escudriña, que encuentra soluciones; tu futuro con VFP está garantizado, independientemente de cuando acaba el soporte de Microsoft para VFP. Los lenguajes de programación tipo XBASE vinieron para quedarse y quien no siga con VFP lo hará con Xailer, Visual Xbase++, Visual Flagship, Visual DBase o algún otro que surja en el camino. Y finalmente ¿Piensas vivir por siempre?, ¿Cuantos años mas piensas seguir viviendo de la programación? ¿50 años más talvez...?

Tip "existencial": Cuando un programador "habla maravillas" de su lenguaje de programación para obtener un contrato, puedes estar seguro de que no es un buen programador y que el producto terminado no será muy bueno, sencillamente porque se apoya en el trabajo de otros para valorar su trabajo propio y endosa la responsabilidad del éxito de su proyecto a su herramienta de trabajo.

Cuando un programador se apoya en las características de su programa terminado para obtener un contrato, es muy probable que sea un buen programador porque se se basa en el mérito de su propio trabajo y no el de otros. Asume la responsabilidad del éxito de su proyecto y no basa el éxito de su trabajo en lo "poderoso" de su herramienta. Como decimos en mi tierra: "El perico donde sea es verde."

Por ejemplo, tu no ves que Microsoft ande alardeando de que Windows Server 2003 esta hecho en "C", para que la gente lo compre. Mas bien, habla de las cualidades propias del software, haciendo totalmente de lado en que lenguaje de programación esta hecho. Es decir, se apoya en los méritos propios del software de Server 2003 y no en las ventajas de haberlo "programado" en lenguaje "C" en vez de JAVA, por ejemplo.

Edgar Acevedo

22 de junio de 2008

Usando la Orientación a Objetos para Reducir el Tiempo de Desarrollo

Artículo original: Using Object Orientation to Reduce Development Time
http://www.jamesbooth.com/reducedevtime.htm
Autor: J. Booth
Traductor: Sergio E. Aguirre

Introducción

Visual FoxPro es Orientado a Objetos. ¿Qué es lo que hace eso por mí? ¿Cómo la orientación de objetos puede hacer mi vida más fácil? Todos sabemos que hay una curva de aprendizaje a subir para entender qué nos han dicho sobre objetos y que hay una recompensa al subir esa curva. Este artículo investigará uno de las recompensas, una reducción para nuestro tiempo de desarrollo.

Tiempo de Desarrollo

El tiempo de desarrollo es siempre muy importante y los pasos que tomamos para reducir este tiempo son muy valiosos. La orientación a objetos nos promete que nuestro trabajo será reusable y que cada proyecto que nosotros desarrollamos tomará menos tiempo que el anterior. ¿Cómo nos cobramos esa promesa?

Como es el caso en todas las situaciones, la solución a un problema nunca se encuentra hasta que el problema sea entendido y definido claramente. El tiempo de desarrollo puesto bajo un microscopio nos muestra que está dividido en varias tareas diferentes. Las mayores son el análisis, diseño, implementación, prueba, y entrenamiento del usuario. ¿Cuáles son estas tareas y cómo influyen éstas en el tiempo global de desarrollo?

Análisis

Todo el desarrollo de la aplicación empieza con el análisis del problema comercial a ser resuelto. Un análisis completo es crítico para una solución exitosa. Nos dicen que en el desarrollo orientado a objetos, la fase de análisis ocupará un porcentaje más grande del tiempo de desarrollo total que en las prácticas de programación estructuradas del pasado. ¿Qué podemos hacer para reducir el tiempo requerido para esta fase de un proyecto?

Buscando patrones en problemas comerciales reduciremos bastante el tiempo destinado al análisis. Catalogando estos patrones cuando los identifiquemos nos permitirán descubrirlos más fácilmente en el futuro. Con un catálogo de patrones en nuestras manos podremos reconocer rápidamente similitudes a los problemas comerciales anteriores y rápidamente podremos registrar los requerimientos.

Hay un peligro en cualquier esfuerzo para reducir el tiempo de análisis es el de producir un análisis incompleto. Cuando cortamos esquinas sobre el análisis de un problema de negocio nosotros potencialmente abrimos el proyecto a una cantidad de problemas que van a consumir tiempo. Es posible que no se descubran requisitos perdidos hasta que el proyecto esté bien encaminado para su finalización y que esos requisitos no descubiertos nos obliguen a abandonar un grupo grande de trabajo debido a los cambios en el diseño.

Si el problema de negocio no es entendido claramente por los diseñadores y programadores de una aplicación, es probable que la aplicación no satisfaga las necesidades del usuario. Las aplicaciones que no reúnan los requisitos de los usuarios son abandonadas o escritas de vuelta. Es asustadiza cómo verdadera la vieja frase: "Nunca hay bastante tiempo para hacerlo correctamente, pero siempre hay bastante tiempo para hacerlo encima."

Aparte de reconocer, usar, y catalogar los patrones vistos durante el análisis, ésta es una de las dos áreas del proceso de desarrollo que no debe abreviarse.

Diseño

El diseño es la abstracción de una solución, en otras palabras es la descripción general de la solución a un problema sin los detalles. Así como pueden categorizarse problemas de negocio en patrones que se repiten, los diseños también exhiben patrones. Patrones vistos en la fase del análisis pueden ser trazados en el diseño. Catalogando y reusando patrones de diseño podemos reducir el tiempo requerido para crear el diseño de la solución.

La fase de diseño es el segundo paso de desarrollo que no debe abreviarse. Es sorprendente qué fácil y rápida pueden lograrse las metas cuando esta fase se define claramente. La fase de diseño es la planificación de la solución, sin un buen diseño podemos seguir un gran número de puntos muertos. Estos puntos muertos consumirán mucho más tiempo que el desarrollo del propio diseño en sí.

Comparando la construcción de una aplicación con una vacaciones en familia. ¿Si, en el momento de las vacaciones, usted metió a su familia dentro del automóvil y empezó a manejar para cruzar el país y usted no tenía ningún mapa y ninguna ruta planeada, piensa usted que en esas condiciones alcanzará su destino? ¿Cuántos malos giros podría tomar usted en el viaje? ¿Cuánto tiempo le tomaría estar al otro lado del país? Construir una aplicación sin un buen diseño es algo muy parecido.

Implementación

La implementación es la ejecución del plan de diseño. Esta parte es donde nosotros escribimos el código que hace realmente el trabajo. Este artículo se enfoca en técnicas para reducir el tiempo de la fase de implementación del desarrollo de una aplicación.

Empezando con un legítimo framework pueden reducir el codificado de la aplicación quitando la necesidad de tratar con las actividades rutinarias de las interacciones del componente.

Reusar código previamente exitoso ha sido una meta para programadores desde hace mucho tiempo. La orientación a objetos promete, de nuevo, permitir la reusabilidad de nuestro código, a pesar de que la orientación a objetos nos permite reusar no más que código. Nosotros podemos reusar cosas reales, widgets. Estos widgets pueden ser tan simples como un cuadro de texto que maneja entradas de fecha, o tan complejo como un formulario que maneja la administración de los datos. Un framework es un widget reusable muy complejo comprendido por muchos objetos que proporcionan un servicio a la aplicación.

Usted puede reconocer widgets útiles durante el desarrollo de un proyecto. Una vez que usted se encuentra haciendo la misma cosa por segunda vez, deténgase y pregúntese si esto podría ser manejado por un widget reusable.

11 de junio de 2008

Interfaz Visual FoxPro con (SQLServer, MySQL, PostgreSQL y Oracle)

Lanzo al mercado Clase para poder trabar con cualquier motor de Base de Datos, sin tener que cambiar el código nativo de Visual FoxPro a MySQL, SQLServer, PostgreSQL o Oracle.

Antes de comenzar a explicar de cómo funciona la clase quiero darles algunos alcances y no se dejen sorprender por otras personas por que lo único que quieren es lucrar con personas que no conocen mucho de este tema.

Somos muchos los programadores que hemos venido desarrollando aplicación con la base nativa de Visual FoxPro; particularmente nunca me gusto Visual FoxPro como motor de base de datos ya que es limitado, si uno quiere orientarse a realizar aplicación remotas o de entorno web es allí donde surgen los inconvenientes de limitación, no se si este equivocado pero eso es mi punto de vista.

La gran interrogante a todo esto es, si migro a un motor de gestor de base de datos, tendré que cambiar la forma de programar, bueno en teoría si. Aquí unos pequeños comandos y la forma como seria para programar si se cambia la base de datos, pero con la clase que desarrolle no se tiene que cambiar casi nada en absoluto.

Ahora déjenme explicarles como funciona

Se ha desarrollado dos clases, que es para mantenimientos y otra que es para transacciones.

Primero todos los programadores en Visual FoxPro, sabemos que nuestros formularios utilizamos diferentes herramientas como TextBox, LisTbox, Combobox, etc... y en su propiedad controlsource hacemos referencia a la tabla y el campo que esta enlazado a este objeto. Bien si tenemos un formulario ya desarrollado lo único que vamos a tener que hacer es copiar todos los objetos utilizados en nuestro mantenimiento a nuestro nuevo formulario que hereda de la clase (mantenimiento) y configurar las siguientes propiedades. Primero comenzamos viendo las propiedades que utilizamos para un Textbox se fijan en el controlsource, bueno esa es la tabla que esta en el motor de MySQL, SQLServer o PostgreSQL



Bueno como eso va ser copiado del formulario anterior no hay problema, ahora veremos las propiedades del formulario.



La propiedades ha utilizar son:
  • SQLTabla = la tabla con lo que queremos trabajar
  • nCeros = cuando el código será autogenerador, (00001,00002, etc.)
Con solo estas dos propiedades, tienen un mantenimiento de tablas, ahora pasemos a la pestaña Listado General.



Bien aquí utilizamos las propiedades del grilla, muchas veces queremos una búsqueda interactiva, ejemplo tengo 1000 clientes no voy a bajar uno por uno, aquí explico como crear la búsqueda.
  • GenBusqueda = “S” genera un buscador, “N” sin buscador
  • cBusCampo1 = aquí va el primer campo para la búsqueda
  • cBusCampo2 = aquí va el segundo campo para la búsqueda
  • cBusCampo3 = aquí va el tercer campo para la búsqueda
  • cTituloBus = es la presentación que aparecerá en el combobox.
Ya todo esto definido tendremos un mantenimiento diferente a lo acostumbrado.



Bueno yo lo tengo ya acoplado a mis sistemas.



La ventana de búsqueda que configuramos.



Sin programar ninguna línea de código solo haciendo la copia del formulario que ya teníamos desarrollado y configurando algunas propiedades.

Ahora vayamos a lo supuestamente más difícil, como trabajar con las transacciones que tenemos. Las conocidas tablas (cabeceas de documento y detalle de documento). Bueno se ha creado 4 propiedades fundamentales y un método que hay que tener en cuenta

Propiedades:

  • Clave primaria = llave primara para la actualización de registros
  • Validacampotablasql = Valor de .T. o .F. Compara la tabla del SQL con el cursor que se ha utilizado.
  • Validaimg = Valor de .T. o .F. Si la tabla tiene imágenes
  • Validar fecha = Valor de .T. o .F. Cuando queremos guardar fechas vacía en mysql no permite enviar error como si estuviéramos mandando valores .null. a un campo de tipo fecha, en SQLServer no hay problema.
  • sqlupdateinsert = Sus parámetros (cAccion, cTablaSqlCur, cTablaSql, CondUpdate)
  • cAccion = “N” nuevo; “A” Actualiza.
Forma de uso, no pongo la opción para eliminar porque es sumamente sencillo. El comando que se utiliza para recuperar una tabla de cualquier motor de base de datos es.

SQLExecute (Conexión,”Select * From Tabla ”,”TablaCursor”)
Yo tengo un método que es similar con la diferencia que sale el error específico si sale mal la consulta o hay algún campo mal escrito.

Thisform.Execute(”Select * from Tabla ”,”TablaCursor”)
Forma de uso: Si queremos utilizar un cursor que hemos agregado campos diferentes que no pertenecen a la tabla que vamos hacer referencia.


6 de junio de 2008

Expandiendo expresiones con TextMerge()

La consulta

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 [&nbsp;]
    lcTexto = STRTRAN(lcTexto, [ ], [&nbsp;])
  ENDIF
  RETURN lcTexto
 ENDPROC
 *--
 PROCEDURE TransformarTexto(tcTexto)
  *-- Transformo algunos caracteres "\<>&
  tcTexto = STRTRAN(tcTexto, ["], [&quot;])
  tcTexto = STRTRAN(tcTexto, [\], [&#092;])
  tcTexto = STRTRAN(tcTexto, [<], [&lt;])
  tcTexto = STRTRAN(tcTexto, [>], [&gt;])
  tcTexto = STRTRAN(tcTexto, [&], [&amp;])
  *-- Si es vacio o nulo
  IF EMPTY(tcTexto) OR ISNULL(tcTexto)
    tcTexto = [&nbsp;]
  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

4 de junio de 2008

Buscar el nombre de un Tag en un archivo de índices de una tabla

Función que busca el nombre de un Tag en un archivo de índices de una tabla seleccionada, utilizando la información de la función ATAGINFO() de VFP7 y superior.
Ej:
USE HOME(2) + "NORTHWIND\CUSTOMERS.DBF"

? BuscarTag("Customers", "City")

*---------------------------------------------------
* FUNCTION BuscarTag(tcAlias, tcTag)
*---------------------------------------------------
* Busca el nombre de un tag en el alias de 
* una tabla abierta en un area de trabajo
* RETORNO: Lógico
*   .T. = Si el tag existe en la tabla
*   .F. = Si el tag no existe o el alias no existe 
*---------------------------------------------------
FUNCTION BuscarTag(tcAlias, tcTag)
  RETURN SELECT(tcAlias) > 0 AND ;
    ATAGINFO(laTag,"",SELECT(tcAlias)) > 0 AND ;
    ASCAN(laTag,tcTag,-1,-1,1,1) > 0
ENDFUNC
*---------------------------------------------------
Luis María Guayán