Filtros en QDOS

Norman Dunbar

Traducción: Afx
enero de 2009

Un tipo de programa QL que casi nunca se mencionan en revistas o en fazines publicados en disco son los programas de filtrado. No veo ninguna razón por la que este tipo de programas se descuiden tanto, y espero que el siguiente artículo ayude a promover su causa. Si no es así, bueno, espero que al menos te de una idea de las diferentes formas que hay de hacer las cosas.

Un programa de filtrado es un programa simple. Se fundamenta básicamente en tomar como entrada la salida de otro programa, generalmente para cambiar datos antes de, por ejemplo, volcarlos a disco. Actúa precisamente como un "filtro" en medio de un programa que almacena los datos en disco o en cualquier otro dispositivo.

Los programas de filtro se llama así porque se sitúan entre los flujos de datos (stream) y tienen el objetivo de filtrar el flujo de dichos datos a través de ellos - al igual que el filtro que es utilizado antes de embotellar el vino para eliminar cualquier suciedad antes de ser embasado y almacenado en estanterías-.

Por ejemplo, tú puedes tener un programa que envía a una salida líneas tras línea de texto. Esta salida no muestra cabeceras ni números de línea tampoco saltos ni números de página. Una salida por impresora de esta forma no es muy útil. Por supuesto, puedes editar el fichero en disco y añadir los números de línea, los saltos de página donde correspondan, las cabeceras en cada página, etc. Un programa de filtrado puede hacer todo esto para ti. Sigue leyendo para averiguar cómo.

En primer lugar debemos tener un comando en nuestro sistema QDOS (no puedo usar el término "QL" ya que muchos de ustedes van a utilizar QXL, QPC, Amigas o ST para hacer el trabajo del 'QL') que permita a un archivo ser ejecutados y una forma de suministrarle parámetros. En sistemas con Toolkit 2, éste será "EX" o "EXEC". En QLs normales sin Toolkit 2 -o equivalente- no tendremos este comando. Si es éste tu caso, entonces lo siento, pero este artículo no será de mucha ayuda para ti. Los filtros se mencionan en el capítulo 8.2 del manual de la Gold Card, pero creo que es está en todos los manuales del Toolkit 2, descrito en el comando EX. Los comandos EX o EXEC del Toolkit 2, el comando EXECUTE del Turbo Toolkit y el QX de QLiberator te permiten todos ellos pasar parámetros a una tarea.

Como los datos pasan de un filtro a otro, no hay cambio en los datos originales. Por ejemplo, un archivo de datos es filtrado a través de un programa que convierte todo a minúsculas y, a continuación, a través de otro que capitaliza la primera palabra en cada frase y, por último, vuelca dichos datos a la impresora.

En este ejemplo, el archivo original sigue siendo tal cual es, y un par de "pipes" transitorios se crean durante el transcurso del filtrado. Después de que los datos llegan a la impresora, los caracteres en minúscula y el inicio de frase capitalizado existe sólo en la copia en papel (no en el archivo original).

Los programas de filtrado pueden ser escritos en cualquier lenguaje, C, assembler, SuperBASIC compilado o SuperBASIC no compilado en la QXL. Hay ligeras diferenticas si tu escribes un programa en SuperBASIC compilado - Turbo usa los canalies #14 y #15 como sus canales pipes y QLiberator usa #0 y #1-. Los programas SBasic no compilados de la QXL también usan los canales #0 y #1. Los programas C usan STDIN y STDOUT que generalmente se mapean en #0 y #1.

Como se pueden insertar tantos filtros como se deseen en el flujo de datos, ninguno de ellos debe necesitar saber algo acerca de cualquier otro. Un filtro bien escrito no se preocupa por la proveniencia de los datos, simplemente aceptar los datos, los filtra de alguna manera y los escribe de nuevo. Al programa no le importa nada acerca de dónde vienen los datos o hacia donde van después.

El formato general para la ejecución de un filtro será algo como lo siguiente:

     EX programa to programa to programa

Donde cada 'programa' consta del nombre del ejecutable y ciertos parámetros que se requieren con el formato siguiente:

     nombre_programa, fichero_entrada, fichero_salida; "cadena de comandos"

Para ampliar el primer ejemplo, con los detalles expresados en el segundo, podríamos tener una línea de comandos como la siguiente:

     EX prog_1, ram1_input; "1 2 3" to prog_2

En este caso la tarea ejecutable llamada 'prog_1' lee su entrada desde el fichero 'ram1_input', requiere 3 parámetros numéricos '1', '2' y '3' y pasa como salida de datos a otro ejecutable llamado 'prog_2'.

Como puedes imaginar, la línea de comandos para ejecutar un número largo de filtros se puede complicar bastante. Nosotros, sin embargo, comenzaremos por un ejemplo muy simple. Este primer programa recibe sus datos de entrada y lo convierte en minúsculas. Está escrito en C y asumimos que se va a compilar a un programa llamado 'Tolower'.

#include <stdio_h>
#include <ctype_h>

/*------------------------------------------------------------*
 * TOLOWER - un filtro que convierte su input a minúsculas    *
 *------------------------------------------------------------*
 * Copyright Norman Dunbar 1997.                              *
 * Permiso ilimitado para uso y  abuso!                       *
 *------------------------------------------------------------*
 * EX tolower,input,output                                    *
 *   (Lee 'input' y escribe minúsculas a 'output')         O  *
 *                                                            *
 * EX tolower,input TO program,output_file                    *
 *   (Lee 'input', escribe minúsculas a entrada de 'program'  *
 *    y este escribe su salida a 'output')                 O  *
 *                                                            *
 * EX tolower,input_file TO program TO program,output_file    *
 *------------------------------------------------------------*/

main()
{
        int ch;

        while((ch = getchar()) != EOF)
                putchar(tolower(ch));

}

Como puedes ver el filtro es muy sencillo. No abre ni cierra canal alguno, de hecho no parece hacer gran cosa. Lo que ha ocurrido es que el filtro se inicia y considera que la entrada y salida de canales es asignada a sus canales ya abiertos por defecto, que son 'stdin' y 'stdout'.

Los programas C siempre tienen al menos 3 canales abiertos de forma estándar, muy similar a los canales #0, #1 y #2 de los programas interpretados SuperBASIC. En los programas C el input es siempre 'stdin', el output es siempre 'stdout' y los errores son siempre 'stderr'. Pueden haber otros para la impresora, etc..., pero no nos vamos a preocupar por eso ahora.

El filtro anterior lee desde su canal 'stdin', convierte cualquier carácter que esté en mayúsculas a minúsculas y lo escribe en su canal 'stdout'. Cualquier carácter que no sea susceptible de convertirse en minúsculas, como dígitos y signos de puntuación son simplemente escritos en la salida sin cambio alguno.

Podrías preguntarte dónde se realiza la comprobación de si un carácter está en mayúsculas, en este caso la función de la librería 'tolower' realiza esta comprobación para asegurarse que el carácter está en mayúscula, con lo cual nosotros no tenemos que comprobar nada. Como dije, un ejemplo muy simple.

Si ejecutas este programa usando la siguiente línea de comando:

     EX tolower, input_file, output_file

Entonces todos los caracteres en MAYÚSCULAS de 'input_file' son transformados a minúsculas en 'output_file'.

Podemos por supuesto pasar la entrada desde un filtro directamente a otro. El siguiente ejemplo es una variación del anterior, y convierte todos los caracteres en minúsculas a mayúsculas.

#include <stdio_h>
#include <ctype_h>

/*------------------------------------------------------------*
 * TOUPPER - a filter to convert its input to uppercase.      *
 * TOUPPER - a filtro que convierte su entrada a mayúsculas   *
 *------------------------------------------------------------*
 * Copyright Norman Dunbar 1997.                              *
 * Permiso ilimitado para uso y  abuso!                       *
 *------------------------------------------------------------*
 * EX tolower,input,output                                    *
 *   (Lee 'input' y escribe mayúsculas a 'output')         O  *
 *                                                            *
 * EX tolower,input TO program,output_file                    *
 *   (Lee 'input', escribe mayúsculas a entrada de 'program'  *
 *    y este escribe su salida a 'output')                 O  *
 *                                                            *
 * EX tolower,input_file TO program TO program,output_file    *
 *------------------------------------------------------------*/

main()
{
        int ch;

        while((ch = getchar()) != EOF)
                putchar(toupper(ch));

}

Este convierte (sólo) los caracteres en minúsculas a mayúsculas, justo lo contrario del primer ejemplo. Este programa lo compilaremos a un fichero llamado 'toupper'

Primero, ejecuta esto:

     EX toupper, input_file, output_file

Si observas el contenido de output_file verás efectivamente que todos los caracteres en minúsculas han sido convertidos a mayúsculas. Ahora necesitamos una línea de comandos ligeramente diferente para pasar la salida de un filtro a la entrada de otro. El comando es:

     EX tolower, input_file TO toupper, output_file

Esto tomará tu 'input_file' y lo pasará al programa 'tolower' donde todos sus caracteres en MAYÚSCULAS serán convertidos a minúsculas. Todos esos caracteres serán redirigidos a su vez al programa 'toupper' y convertidos de nuevo, junto con todos los caracteres en minúsculas, a MAYÚSCULAS y escritos en tu 'output_file'.

La salida del programa 'tolower' es pasada a 'toupper' como si lo estuviera escribiendo en su salida, ésta no es almacenada en la memoria del QL hasta la llegada del final del archivo, sino que simplemente pasa a través de ellos, al igual que hace un filtro.

Por supuesto que podríamos prescindir del programa 'tolower' y ejecutar directamente 'toupper' para obtener el mismo resultado, pero lo hacemos de esta manera para mostrar cómo se pueden vincular 2 filtros.

Bueno, ahora sigamos progresando. En nuestro ejemplo original queríamos colocar los números de línea entre otras cosas, concentrémonos primero en los números de línea. El filtro siguiente, de nuevo está escrito en C, lee un fichero de datos y escribe el mismo archivo con los números de línea al comienzo de cada línea.

#include <stdio_h>

/*------------------------------------------------------------*
 * LINENO - un filtro que incluye números de línea            *
 *------------------------------------------------------------*
 * Copyright Norman Dunbar 1997.                              *
 * Permiso ilimitado para uso y  abuso!                       *
 *------------------------------------------------------------*
 * EX lineno,input,output                                     *
 *   (Lee 'input' y escribe al 'output' con número de lineas) *
 *                                                            *
 * EX lineno,input TO program,output_file                     *
  *   (Lee 'input', escribe con número de lineas al input de  *
 *    'program' y este escribe su salida a  'output')     O   *
 *                                                            *
 * EX lineno,input_file TO program TO program,output_file     *
 *------------------------------------------------------------*/

#define MAX_LINE 255

main()
{
        int  line_no = 1;
        char buffer[MAX_LINE];

        while(!feof(stdin)) {
                fgets(buffer, MAX_LINE, stdin);
                printf("%.5d %s", line_no, buffer);
                line_no++;
        }
}

Éste es ligeramente más complejo que el ejemplo previo, pero aún es muy simple.

Tenemos la variable line_no que maneja el número de línea actual y un buffer que maneja cada línea que es leída por el programa. El buffer es de una longitud máxima de 255 caracteres (MAX_LINE) (incluyendo el carácter de terminación de cadena de C) el cual no se podrá superar independientemente de la longitud que tenga las líneas de entrada.

El filtro usa la función 'fgets' para leer cada línea de texto desde el canal de entrada 'stdin' del filtro y graba lo leído en 'buffer'. Entonces imprime un número de línea de 5 dígitos y el contenido almacenado en buffer al canal de salida, 'stdout'. La última línea simplemente añade uno al contador de números de línea, listo para la siguiente línea. Cuando se detecta fin de fichero, el programa simplemente termina.

En este momento tenemos un archivo de salida al que se le ha añadido los números de línea. ¿Cómo hacemos ahora para segmentar la salida diferentes páginas? En el siguiente ejemplo, troceamos el 'stream' de salida en secciones de una longitud determinada.

#include <stdio_h>

/*------------------------------------------------------------*
 * PAGE - un filtro que trocea una entrada en páginas de 65   *
 *        lineas                                              *
 *------------------------------------------------------------*
 * Copyright Norman Dunbar 1997.                              *
 * Permiso ilimitado para uso y  abuso!                       * 
 *------------------------------------------------------------*
 * EX page,input_file, output_file                         O  *
 * EX page,input_file TO prog, output_file                 O  *
 * EX page,input_file TO prog, TO prog TO prog, output_file   *
 *------------------------------------------------------------*/

#define MAX_LINE  255
#define PAGE_SIZE  65
#define FORM_FEED  12

main()
{
        int  page_no = 1,
             line_no = 99;

        char buffer[MAX_LINE];

        while(!feof(stdin)) {
                fgets(buffer, MAX_LINE, stdin);

                if (line_no > PAGE_SIZE) {
                        printf("%cPAGE %3d\n", FORM_FEED, page_no);
                        line_no = 1;
                        page_no++;
                }

                printf("%s", buffer);
                line_no++;
        }
}

En este ejemplo, el programa lleva el control de lo que le llega por su entrada y comprueba cuantas líneas se van imprimiendo por la salida para mantener un número de página. Cuando el número de líneas por página que se han escrito es mayor que el número de líneas que pueden caber en una página, PAGE_SIZE, el programa obliga a lo "impresora" a forzar un cambio de página y a imprimir el número actual de página. A medida que esto sucede se va incrementando el número de página.

Podemos tomar nuestro fichero de texto, pasarlo a través del filtro LINENO para añadir los números de línea y a continuación la salida de LINENO pasarla a través del filtro PAGE para partir la salida en páginas de 65 líneas en cada página e imprimiendo al principio de cada página el número de página. Esto se hace con la siguiente línea de comandos:

     EX lineno, input_file TO page, output_file

Y eso es casi todo lo que hay en cuanto a los filtros. Aunque no te he demostrado que puedes escribir tus programas de filtro para que el número de comienzo de línea y el valor de incremento sean pasados como parámetros,

     EX lineno, input_file, output_file; '100 10'

donde el número de línea comenzaría en 100 e iría incrementando el número de cada línea de 10 en 10. Por supuesto, el programa LINENO debe cambiarse para que capture los parámetros de entrada antes de proceder a realizar su trabajo.

Visto estos ejemplos podrías pensar que los filtros sólo se pueden escribir en C. Esto no es así. Para mostrártelo haremos un programa en SuperBASIC, reescribiremos el programa 'tolower' que hemos visto anteriormente. Éste es compatible solamente con Qliberator y lo debemos compilar sin ventanas abiertas.

Los usuarios de Turbo pueden descomentar la línea 1015 y comentar la línea 1010 para establecer los número de canales correctos. Asegúrate de configurar la opción a copia ventana 0 antes de compilar.

No olvides los puntos y coma (;) de las líneas 1045 y 1055 o si no obtendrás una nueva línea entre cada carácter pasado a través del programa.

1000 REMark TOLOWER
1005 :
1010 in = 0: out = 1: REMark QLiberator only
1015 REMark in = 14: out = 15: REMark Turbo only
1020 :
1025 REPeat lower
1030   IF EOF(#in) THEN EXIT loop: END IF
1035   a = CODE(INKEY$(#in))
1040   IF a > 64 AND a < 91 THEN
1045      PRINT #out, CHR$(a || 32);
1050   ELSE
1055      PRINT #out, CHR$(a);
1060   END IF
1065 END REPeat lower

Date cuenta, sin embargo, que debemos comprobar las mayúsculas antes de hacer la conversión. Otro problema con esta versión es que los conjuntos de caracteres extranjeros (no UK) no se convierten, cosa que si sucede (o debería) con la versión C. Una vez más, teclea simplemente:

     EX tolower_obj, input_file, output_file

para comprobar que el programa hace su trabajo.

Originalmente queríamos números de línea, las páginas y cabeceras en nuestra salida. Así como ya te han dado toda la información que necesitas para escribir un filtro, ¿por qué no cambiar el filtro PAGE para que imprima la cabecera también? El texto de la cabecera podría ser también un texto pasado como parámetro tal como muestra el siguiente comando:

     EX lineno, input_file TO page, output_file; 'header$'

Y, si has logrado que esta modificación funcione correctamente, ¿Por qué no pasar también el número de página en la que se comienza a numerar?. El ejemplo PAGE anterior, utilizaba 65 líneas por página, pero podría ser que esto no vaya bien en su impresora. La línea de comandos que quedaría sería la siguiente:

     EX lineno, input_file TO page, output_file; 'header$ page_length'

Que te diviertas y feliz filtrado.

Artículo original: http://www.dilwyn.me.uk/docs/articles/filters.zip


Sinclair QL Recursos en Castellano Alojado en / Hosted at:
Sinclair QL Recursos en Castellano
Sinclair QL Spanish Resources