29 de marzo de 2001

Búsqueda de paréntesis en la ventana activa

Problemática:

¿Quién no ha tenido alguna vez que encontrar en un programa el paréntesis cerrado asociado a un paréntesis abierto? Esta utilidad intenta dar solución a este problema.

Descripción:

Búsqueda del token asociado (que denominaremos "Pareado") al token seleccionado en la ventana activa.

Con el símil de la problemática, el token seleccionado sería el paréntesis abierto y el pareado el paréntesis cerrado.

Restricciones:

(R1) El token seleccionado sólo puede ser un carácter.
(R2) La correspondencia entre token seleccionado y su pareado está fija en el programa, restringiéndose a la siguiente:
'(' ')'
'[' ']'
(R3) El token seleccionado es siempre el carácter situado a la derecha del cursor.
(R4) Si se encuentra el Pareado, el cursor se situará a la izquierda del mismo.

Se cree conveniente esta forma porque las ejecuciones sucesivas, sin mover el cursor, siempre nos llevan a los mismos pareados.

Condicionantes:

(C1) Necesidad de enviar resultado a una ventana.
Aunque resulte curioso, si no se envía ningún resultado a otra ventana, ya sea la ventana principal de Visual FoxPro o una ventana activa definida por el usuario, el programa tarda mucho más en ejecutarse. No he logrado averiguar este extraño comportamiento. En la versión actual, por cada carácter procesado se escribe, en esa ventana, un único carácter (que se asemeja a un molino en movimiento [recuerdo de viejos tiempos ;)]) en la columna 1 y siempre en la misma línea.

(C2) Determinar condición de principio/fin de archivo por número de caracteres repetidos.
Al llegar el principio o fin del archivo en la ventana en la que hacemos la búsqueda, dado que el cursor no puede ir más allá, siemre se lee el mismo carácter (el primero o el último).
Ante la imposibilidad de determinar cuándo se ha llegado al principio o al final del archivo, se establece como heurística para que se cumpla dicha condición la lectura de un mismo carácter un número determinado de veces (véase parámetro).

(C3) Obtención de caracteres retrasada.
En cada ejecución del programa se obtiene como primer carácter el último carácter procesado en la ejecución anterior. Este extraño suceso que no me explico (¿quizá esté haciendo algo mal?) se ha solventado asignando al portapapeles (variable del sistema _CLIPTEXT) un carácter especial (CHR(255)).

Parámetros:

(P1) tnNumMaxCaracteresRepetidos (e) : Número de veces que se debe leer un mismo carácter para establecer la condición de princpio/fin de archivo. (véase (C2)).
Valor predeterminado: 100.

(P2) tlDebug (e) : Establece el programa en modo depuración para mostrar trazas.
.T. : Modo depuración.
.F. : Modo normal (Valor predeterminado).
(P3) tlCrearVentanaSalida (e) : Necesario por (C1).
.T. : Resultados a una ventana que crea el programa.
.F. : Resultados a la ventana principal de Visual FoxPro (Valor predeterminado).

Uso:

Para un uso cómodo se puede asociar a una combinación de teclas, como por ejemplo:
ON KEY LABEL ALT+8 DO "BuscarPareado"
* ON KEY LABEL ALT+8 DO "D:\JavierValero\vfp\Experimentos\Proyecto\Pruebas\progs\BuscarPareado" WITH 50,.F.,.F.

Así, para una ejecución correcta, situar el cursor a la derecha de un carácter '(' o ')' y pulsar ALT+8, y el cursor se nos situaría en su paréntesis asociado.

Bugs:

(BUG1) Por la heurística adoptada en (C2), el programa puede devolver una búsqueda insatisfactoria en ciertos casos. Por ejemplo, es muy común poner comentarios incluyendo muchos '*'. Si el par de tokens asociados se encuentra entre líneas de ese tipo y el parámetro es muy pequeño, el programa puede devolver que se ha llegado al principio/fin de archivo cuando no es cierto. Para evitar esto, habría que aumentar el valor del parámetro mencionado.

(BUG2) Por el criterio adoptado en (C3) si el carácter en la ventana de búsqueda

Mejoras:

- No limitar los token a caracteres sino a palabras, para que así se puedan encontrar los IF-ENDIF, DO WHILE-ENDDO, ...

**************************************
* BuscarPareado.prg
**************************************
* Autor: JVA
* Versión: 1.0
* Fecha creación: 27/03/2001
* Modificaciones realizadas: Ninguna
* Notas: Debido a la inexperiencia del autor,
* puede que haya conceptos mal
* interpretados de Visual FoxPro y
* además puede que parte del apartado
* de  'Condicionantes:' sea erróneo.
**************************************
LPARAMETERS tnNumMaxCaracteresRepetidos, tlDebug, tlCrearVentanaSalida

PRIVATE ALL

* Para simular un molinillo en movimiento cuando 
* el programa está en ejecución

#DEFINE MOLINILLO "|/-"
#DEFINE MOLINILLO_NUM_CARACTERES 20
#DEFINE MOLINILLO REPLICATE("|",5) + ;
  REPLICATE("/",5) + REPLICATE("-",5) + ;
  REPLICATE("",5)

* Dirección de exploración en la ventana de 
* búsqueda: hacia la derecha o la izquierda
#DEFINE DIRECCION_DERECHA "D"
#DEFINE DIRECCION_IZQUIERDA "I"

* Valores predeterminados para los parámetros
#DEFINE DEFECTO_NUM_MAX_CARACTERES_REPETIDOS 100
#DEFINE DEFECTO_DEBUG .F.
#DEFINE DEFECTO_VENTANA .F.

* Condicionante (C3): Carácter especial para el portapapeles
#DEFINE CARACTER_EN_PORTAPAPELES  CHR(255)

* Declaración de una variable como privada.
#DEFINE PRIVATE_DECL

* (C2) Número de caracteres repetidos para 
* determinar condición principio/fin de archivo
LOCAL lnNumMaxCaracteresRepetidos

* Carácter seleccionado por el usuario para 
* buscar su pareado.

* Se coge el carácter a la derecha del cursor.
LOCAL lcCaracterSeleccionado

* Carácter pareado del .
LOCAL lcCaracterPareado

* Indica si debemos ir cogiendo caracteres 
* hacia la izquierda o la derecha.
LOCAL lcDireccion

* Retorno de procedimientos
LOCAL llRetorno

* Programa en depuración
* Si .T. se muestran trazas en la ventana activa
LOCAL llDebug

* Crear ventana de salida para enviar los resultados
LOCAL llCrearVentanaSalida

* Para restaurar el antiguo valor de SET TYPEAHEAD
LOCAL lcOldTypeAhead

* Para restaurar el antiguo valor de SET STATUS BAR
LOCAL lcSetStatusBar

* Indice para mostrar el molinillo en movimiento
PRIVATE_DECL pnMolinillo = 0

lcSetStatusBar = SET("STATUS BAR")
IF lcSetStatusBar = "OFF"
   SET STATUS BAR ON
ENDIF

* Valores predeterminados
lnNumMaxCaracteresRepetidos = DEFECTO_NUM_MAX_CARACTERES_REPETIDOS
llDebug = DEFECTO_DEBUG
llCrearVentanaSalida = DEFECTO_VENTANA

*
*  Procesar parámetros
*

IF (PCOUNT() > 0)
   IF (UPPER(VARTYPE(tnNumMaxCaracteresRepetidos)) = 'N')
      lnNumMaxCaracteresRepetidos = tnNumMaxCaracteresRepetidos
   ENDIF
ENDIF

IF (PCOUNT() > 1)
   IF (UPPER(VARTYPE(tlDebug)) = 'L')
      llDebug = tlDebug
   ENDIF
ENDIF

IF (PCOUNT() > 2)
   IF (UPPER(VARTYPE(tlCrearVentanaSalida)) = 'L')
      llCrearVentanaSalida = tlCrearVentanaSalida
   ENDIF
ENDIF

IF (llDebug=.T.)
   CLEAR
ENDIF

* Si TYPEAHEAD = cero, no funciona el programa
lcOldTypeAhead = STR(SET('TYPEAHEAD'))
SET TYPEAHEAD TO 20

* Ventana donde se envían los resultados (C1)LOCAL lcNombreVentanaSalida   

* Nombre de la ventana de búsqueda
LOCAL lcVentanaBusqueda 

IF ( llCrearVentanaSalida = .T.)
   lcNombreVentanaSalida = "W_TMP" + SYS(3)
   DO WHILE (WEXIST(lcNombreVentanaSalida) = .T.)
      lcNombreVentanaSalida = "W_TMP" + SYS(3)
   ENDDO
   lcVentanaBusqueda = WTITLE()
   DEFINE WINDOW (lcNombreVentanaSalida) FROM 1,1 TO 2,2 CLOSE FLOAT
   ACTIVATE WINDOW (lcNombreVentanaSalida)
   ACTIVATE WINDOW (lcVentanaBusqueda)
ENDIF

_CLIPTEXT = CARACTER_EN_PORTAPAPELES

* Coger el carácter a la derecha del cursor (R3)
lcCaracterSeleccionado = Caracter DIRECCION_DERECHA)

* Condicionante (C3)

DO WHILE (lcCaracterSeleccionado == CARACTER_EN_PORTAPAPELES)

   lcCaracterSeleccionado = Caracter(DIRECCION_DERECHA)
ENDDO

IF (llDebug=.T.)
? "Carácter seleccionado=[" + lcCaracterSeleccionado + "]"
ENDIF

* Obtener el Pareado del carácter Seleccionado
llRetorno = CaracterPareado(lcCaracterSeleccionado, @lcCaracterPareado, @lcDireccion)

IF (llRetorno = .F.)
   SET MESSAGE TO 'Se desconoce el pareado para el carácter [' + lcCaracterSeleccionado + ']'
   RETURN .F.
ELSE
   SET MESSAGE TO "Buscando pareado " + lcCaracterPareado + " ..."
ENDIF

IF (llDebug=.T.)
   ?? "   Carácter pareado=[" + lcCaracterPareado + "]"
   ? ""
ENDIF

IF (lcDireccion = DIRECCION_IZQUIERDA)
   * Nos quedamos en la posición original
   Caracter(DIRECCION_IZQUIERDA)
ELSE  
   * Nada, o entonces volveríamos a leer el 
   * mismo carácter pareado.
ENDIF

* Número de Pareados encontrados, para tratar 
* la anidación de pareados
LOCAL lnNumPareados 

* Número de veces que se repite un mismo carácter
LOCAL lnNumCaracteresRepetidos  

* Último carácter leído
LOCAL lcCaracter 

* Para comparar si se repite un carácter
LOCAL lcOldCaracter 

lnNumPareados = 0  
lnNumCaracteresRepetidos = 0 
lcOldCaracter = CARACTER_EN_PORTAPAPELES 

* Condicionante (C2)
DO WHILE ( (lnNumCaracteresRepetidos(lnNumPareados >= 0) )
   lcCaracter = Caracter(lcDireccion)
   *
   * Tratar la anidación de los Pareados
   *
   * Anidamos pareado
   IF (lcCaracter == lcCaracterSeleccionado)
      lnNumPareados = lnNumPareados + 1
   ENDIF
   
   * Desanidamos pareado
   IF (lcCaracter == lcCaracterPareado)
      lnNumPareados = lnNumPareados - 1
   ENDIF

   IF (llDebug=.T.)
      ?? "NumPareados=" + STR(lnNumPareados) + "  "
   ENDIF

   *
   * Tratar la condición de principio/fin de archivo
   * mediante el reconocimiento de la repetición 
   * del mismo carácter.
   *
   IF (lcCaracter == lcOldCaracter)
      lnNumCaracteresRepetidos = lnNumCaracteresRepetidos + 1
   ELSE
      lnNumCaracteresRepetidos = 0
   ENDIF

   lcOldCaracter = lcCaracter
   
   IF (llDebug = .T.)
      ? lcCaracter + " ASC=" + ALLTRIM(STR(ASC(lcCaracter))) AT 1
   ENDIF
ENDDO

IF ((lnNumPareados 

   * Nos hemos pasado un carácter
   Caracter(DIRECCION_IZQUIERDA)
ELSE
   * Nada, o entonces volveríamos a leer el 
   * mismo carácter pareado
ENDIF

IF (lcCaracter == lcCaracterPareado)
   SET MESSAGE TO "Encontrado " + lcCaracterPareado + "."
ELSE
   IF (lcDireccion == DIRECCION_DERECHA)
      SET MESSAGE TO "No se encuentra " + lcCaracterPareado + ". Se llegó al final del archivo."
   ELSE
      SET MESSAGE TO "No se encuentra " + lcCaracterPareado + ". Se llegó al principio del archivo."
   ENDIF
ENDIF

IF ( llCrearVentanaSalida = .T.)
   RELEASE WINDOW (lcNombreVentanaSalida)
ENDIF

_CLIPTEXT = CARACTER_EN_PORTAPAPELES

*
* Restaurar entorno
*
SET TYPEAHEAD TO &lcOldTypeAhead
IF lcSetStatusBar = "OFF"
   SET STATUS BAR OFF
ENDIF

?? "." AT 1
* SET MESSAGE TO

RETURN .T.

****************
PROCEDURE Caracter
****************
LPARAMETERS tcDireccion
LOCAL lcC

IF (tcDireccion = DIRECCION_DERECHA)
   KEYBOARD '{SHIFT+RIGHTARROW}{CTRL+C}' PLAIN CLEAR
   KEYBOARD '{RIGHTARROW}' PLAIN
ELSE
   KEYBOARD '{SHIFT+LEFTARROW}{CTRL+C}' PLAIN CLEAR
   KEYBOARD '{LEFTARROW}' PLAIN
ENDIF

DOEVENTS

*
* Condicionante (C1)
*
?? SUBSTR(MOLINILLO, pnMolinillo+1, 1) AT 1
pnMolinillo = (pnMolinillo + 1) % 40

lcC = substr(_cliptext,1,1)
return lcC
ENDPROC  && Caracter

****************
PROCEDURE CaracterPareado
****************
LPARAMETERS tcCaracterBuscar, tcCaracterPareado, tcDireccion
* !!! Pasar los dos últimos por referencia !!!

LOCAL llRetorno, lnPosicion
LOCAL DIMENSION aPareadosBeg[2]
LOCAL DIMENSION aPareadosEnd[2]
aPareadosBeg[1] = '('
aPareadosEnd[1] = ')'
aPareadosBeg[2] = '['
aPareadosEnd[2] = ']'

llRetorno = .T.  && Suponemos que sí está
lnPosicion = ASCAN(aPareadosBeg,tcCaracterBuscar)

IF (lnPosicion != 0)
   tcCaracterPareado = aPareadosEnd[lnPosicion]
   tcDireccion = DIRECCION_DERECHA
ELSE
   lnPosicion = ASCAN(aPareadosEnd,tcCaracterBuscar)
   IF ( lnPosicion != 0)
      tcCaracterPareado = aPareadosBeg[lnPosicion]
      tcDireccion = DIRECCION_IZQUIERDA
   ELSE
      llRetorno = .F.
   ENDIF
ENDIF

RETURN llRetorno

ENDPROC  && CaracterPareado

****************
Javier Valero

22 de marzo de 2001

Crear gráficos en Excel desde VFP

Podemos crear una planilla de Excel desde Visual FoxPro. También dar formato al texto y a las celdas de una planilla de Excel desde Visual FoxPro mediante OLE.

Todo esto con el fin de brindar a nuestros usuarios una presentación mas elegante de una consulta de Visual FoxPro en una planilla de Excel.

Si queremos sorprender a nuestros usuarios: ¿Por qué no creamos también un gráfico en la planilla Excel?. De esta forma transformaremos la conocida frase: "una imagen vale mas que mil palabras", en "un gráfico vale mas que mil datos".

En este ejemplo, partimos de dos consultas: 1) Una consulta que acumula las ventas anuales, y 2) Una consulta que acumula las ventas semanales de "n" items.

Para simular los resultados de estas consultas crearemos dos tablas: 1) ANIO.DBF y 2) SEMANA.DBF

Con la primera consulta vamos a crear una planilla ANIO.XLS con un gráfico de líneas como muestra la siguiente figura:



Con la segunda consulta vamos a crear una planilla SEMANA.XLS con un bráfico de barras apiladas, como se muestra abajo:



Podemos ver que a los gráficos se le puede añadir títulos, leyendas al gráfico, a las series y a los ejes, y modificar sus atributos para que estos se vean elegantes.

¿Cómo hacer esto desde VFP?

Una vez que tenemos las tabla o cursor con los resultados de la consulta, lo copiamos a formato XLS con la sentencia COPY TO ... TYPE XL5.

Luego creamos un objeto "Excel.Application" con el cual abrimos la planilla generada y allí creamos el gráfico en una hoja nueva, y le damos el formato deseado.

Una vez finalizado, grabamos y cerramos la planilla. Si queremos que quede abierto el Excel con la planilla generada, no cerramos la planilla y la hacemos visible con la propiedad: loExcel.APPLICATION.VISIBLE = .T.

El código en VFP de este ejemplo que nos genera ambas planillas, es el siguiente y lo podemos descargar de graficos.zip
*-----------------------------
SET DATE DMY
SET SAFETY OFF

*--- Crea las tablas que simulan las consultas
DO CreaArchivos

*--- Genera gráfico anual
DO GraficoAnual

*--- Genera gráfico semanal
DO GraficoSemanal

CLOSE TABLES ALL
RETURN
*-----------------------------

*-----------------------------
* PROCEDURE CreaArchivos
*-----------------------------
* Crea y llena las tablas de 
* ejemplo y las copia
* como archivo tipo .XLS
*-----------------------------
PROCEDURE CreaArchivos
   LOCAL lnI, ldFechaIni

   *--- Inicializo la funcion RAND()
   lnI = RAND(-1)
   ldFechaIni = DATE(1998,12,27)

   *---- Creo tablas
   CREATE TABLE Semana (Dia C(10), Item01 N(10,2), ;
      Item02 N(10,2), Item03 N(10,2), Item04 N(10,2))

   CREATE TABLE Anio (Mes C(10), TOTAL N(10,2))

   *--- Cargo datos de los 7 días y los 12 meses
   FOR lnI = 1 TO 12
      IF lnI <= 7
         INSERT INTO Semana ;
            (Dia, Item01, Item02, Item03, Item04) ;
            VALUES (CDOW(ldFechaIni+lnI), RAND()*1000, ;
            RAND()*1000, RAND()*1000, RAND()*1000 )
      ENDIF
      INSERT INTO Anio (Mes, TOTAL) ;
         VALUES (CMONTH(GOMONTH(ldFechaIni,lnI)), RAND()*10000 )
   ENDFOR

   *--- Guardo como tipo XL5
   SELECT Semana
   COPY TO Semana TYPE XL5
   SELECT Anio
   COPY TO Anio TYPE XL5
   RETURN
ENDPROC

*-----------------------------
* PROCEDURE GraficoAnual
*-----------------------------
* Creo un gráfico de Lineas en Excel
*-----------------------------
PROCEDURE GraficoAnual
   LOCAL lnFil, lnCol, lcRango, lcPlanilla, lcHoja, loExcel

   lnFil = RECCOUNT("Anio") + 1
   lnCol = FCOUNT("Anio")
   lcRango = "A1:" + CHR(64+lnCol) + ALLTRIM(STR(lnFil))
   lcHoja = "Anio"
   lcPlanilla = SYS(5) + CURDIR() + "Anio.xls"

   *--- Creo objeto Excel
   loExcel=CREATEOBJECT("Excel.application")
   WITH loExcel.APPLICATION
      .VISIBLE = .F. && oculto el trabajo en la aplicacion Excel
      .workbooks.OPEN(lcPlanilla)

      *---- Añado grafico de líneas
      .Charts.ADD
      .ActiveChart.ChartType = 65 && xlLineMarkers
      .ActiveChart.SetSourceData(.Sheets(lcHoja).RANGE(lcRango), 2)
      .ActiveChart.Location(1, "Grafico")
      .ActiveChart.HasDataTable = .F.

      *--- Estilo y ancho línea
      .ActiveChart.SeriesCollection(1).SELECT
      WITH .SELECTION.BORDER
         .Weight = 4 && xlThick
         .LineStyle = 1 && xlContinuous
      ENDWITH

      *--- Titulo gráfico
      .ActiveChart.HasTitle = .T.
      .ActiveChart.ChartTitle.TEXT = "Ventas acumuladas anuales"
      .ActiveChart.ChartTitle.SELECT
      WITH .SELECTION.FONT
         .NAME = "Arial"
         .FontStyle = "Negrita"
         .SIZE = 16
      ENDWITH

      *--- Leyenda
      .ActiveChart.HasLegend = .T.
      .ActiveChart.Legend.SELECT
      .SELECTION.Position = -4160 && xlTop

      *--- Ejes
      WITH .ActiveChart
         .Axes(1, 1).HasTitle = .T.
         .Axes(1, 1).AxisTitle.TEXT = "Meses del año"
         .Axes(2, 1).HasTitle = .T.
         .Axes(2, 1).AxisTitle.TEXT = "Totales en U$S"
         .Deselect
      ENDWITH

      *--- Grabo planilla y cierro
      .VISIBLE = .F.
      .ActiveWorkbook.SAVE
      .workbooks.CLOSE
   ENDWITH
   RELE loExcel
   RETURN
ENDPROC

*-----------------------------
* PROCEDURE GraficoSemanal
*-----------------------------
* Gráfico de Barras Apiladas en Excel 
*-----------------------------
PROCEDURE GraficoSemanal
   LOCAL lnFil, lnCol, lcRango, lcPlanilla, lcHoja, loExcel

   lnFil = RECCOUNT("Semana") + 1
   lnCol = FCOUNT("Semana")
   lcRango = "A1:" + CHR(64+lnCol) + ALLTRIM(STR(lnFil))
   lcHoja = "Semana"
   lcPlanilla = SYS(5) + CURDIR() + "Semana.xls"

   *--- Creo objeto Excel
   loExcel=CREATEOBJECT("Excel.application")

   WITH loExcel.APPLICATION
      .VISIBLE = .F. && oculto el trabajo en la aplicacion Excel
      .workbooks.OPEN(lcPlanilla)

      *---- Añado grafico barras apiladas
      .Charts.ADD
      .ActiveChart.ChartType = 52 && xlColumnStacked
      .ActiveChart.SetSourceData(.Sheets(lcHoja).RANGE(lcRango), 2)
      .ActiveChart.Location(1, "Grafico")
      .ActiveChart.HasDataTable = .F.

      *--- Titulo gráfico
      .ActiveChart.HasTitle = .T.
      .ActiveChart.ChartTitle.TEXT = "Ventas semanales por items"
      .ActiveChart.ChartTitle.SELECT
      WITH .SELECTION.FONT
         .NAME = "Arial"
         .FontStyle = "Negrita"
         .SIZE = 16
      ENDWITH

      *--- Leyenda 
      .ActiveChart.HasLegend = .T.
      .ActiveChart.Legend.SELECT
      .SELECTION.Position = -4160 && xlTop

      *--- Ejes
      WITH .ActiveChart
         .Axes(1, 1).HasTitle = .T.
         .Axes(1, 1).AxisTitle.TEXT = "Días de la semana"
         .Axes(2, 1).HasTitle = .T.
         .Axes(2, 1).AxisTitle.TEXT = "Totales en U$S"
         .Deselect
      ENDWITH

      *--- Grabo planilla y cierro
      .VISIBLE = .F.
      .ActiveWorkbook.SAVE
      .workbooks.CLOSE
   ENDWITH
   RELE loExcel
   RETURN
ENDPROC
*-----------------------------
Una idea que nos ayudará a crear nuestros gráficos, es abrir la planilla con Excel, y comenzar a grabar una macro; hacemos nuestro gráfico y al terminar, finalizamos la grabación de la macro. El código que genera la macro está en Visual Basic for Applications, y es muy fácil convertirlo a código de Visual FoxPro.

Hasta la próxima,


Luis María Guayán