ARCHIVOS: FORMATOS DE TEXTO



Variantes del formato texto.
El formato de texto se presta a una utilización arbitraria por parte del usuario, ya que la única restricción que se impone es la relativa a su composición. Un archivo con formato de texto puede contener líneas de distintas longitudes, y la distribución del contenido de la línea no está sometida a regla alguna. En otras palabras, se admite cualquier forma de escritura... pero esto puede hacer casi imposible la lectura. Si el archivo no sigue unas pautas regulares, y más aún unas pautas implementables con relativa facilidad, el problema de la lectura puede volverse casi insoluble. Para evitar esta dificultad, se suelen utilizar formatos de archivo como los descritos a continuación, que hacen posible interpretar el contenido del archivo sin un esfuerzo excesivo.
Formato libre
Mencionamos en primer lugar un formato "sin formato", que suele hallarse en archivos de texto que contienen, por ejemplo, libros o fragmentos de libros. En muchos caso, estos archivos se tratan para extraer léxicos, tecnicismos banales, concordancias y otros datos de interés filológico. No hay unas reglas generales para el tratamiento de archivos de este tipo, más allá de los procedimientos universales de lectura y escritura de archivos. Formato encolumnado.
Este tipo de archivos suele proceder de programas de cálculo numérico o de instrumentos de medición. Un posible ejemplo es el que se ve a continuación:
1271972SEAT1100.0
1241968SEAT 855.0
8501966SEAT 670.0
15001963SEAT 900.0

En este archivo hay cuatro registros, uno en cada línea, y cuatro campos en cada registro. Los tres primeros campos tienen cuatro columnas de anchura, y el cuarto tiene seis, luego las líneas son de longitud constante. No hay separador entre campos consecutivos, y es evidente la necesidad de conocer el tamaño y tipo de los datos para después efectuar una lectura correcta de los mismos. En este caso, hemos empleado el formato de escritura %4s%4d%4s%6.1f\n ; el programa utilizado puede verse más abajo .
Formato delimitado .
Se caracteriza por la inserción de un carácter delimitador entre campos consecutivos del registro. En el ejemplo siguiente,
127*1972*SEAT*1100.000000
124*1968*SEAT*855.000000
850*1966*SEAT*670.000000
1500*1963*SEAT*900.000000


Se observan los mismos campos que en el caso anterior, pero han desaparecido las columnas. Los campos tienen distintas longitudes ("124" es más corto que "1500") y las líneas, por tanto, no son de igual longitud. Además, ha aparecido un asterisco (el delimitador) entre campos consecutivos, aunque no al final de la línea. En este bloque de información hay cuatro registros, uno en cada línea, al igual que en el caso anterior (encolumnado). El formato empleado para crear este archivo es %s*%d*%s*%f\n . El programa utilizado puede verse más abajo . Formato de un campo por línea.
Como su propio nombre indica, este formato consiste en escribir un campo en cada línea del archivo. La información contenida en los dos ejemplos anteriores tiene el aspecto siguiente cuando se escribe con formato de un campo por línea:
127
1972
SEAT
1100.000000
124
1968
SEAT
855.000000
850
1966
SEAT
670.000000
1500
1963
SEAT
900.000000

En cuanto la especificación de formato empleada para crear este resultado, se trata de %s\n%d\n%s\n%f\n , como cabía esperar. La idea es bien sencilla: se inserta un '\n' detrás de cada campo, sin especificar nada más. El programa utilizado puede verse más abajo . Adición de información en un archivo ya existente.
Este proceso es tan sencillo como emplear la opción "a" en el modo de escritura. Si se ha empleado un programa para crear el archivo al que se quiere añadir información con formato de texto, todo debería ir bien, porque las reglas de formación de los tres formatos estudiados (encolumnado, delimitado y de un campo por línea) exigen que haya un carácter de fin de línea al final de todas las líneas, y en particular al final de la última línea (registro) del archivo. Si se omite este marcador de fin de línea, la próxima línea que se añada quedará unida al último campo, y se habrá roto la regularidad del archivo, con lo cual el proceso de lectura (que supone que todas las líneas son de un mismmo formato) sufrirá un error. Por tanto, procede comprobar la existencia de este último marcado de fin de línea antes de realizar la adición de información. Téngase en cuenta que omitir el fin de línea nno es un error y no será detectado cuando se efectúe la escritura. Ejemplo .- Construir un programa que escriba en disco todos los registros de una colección, empleando primero el formato encolumnado, después el delimitado y finalmente el de un campo por línea.
#include<stdio.h>

#define NUM_REGISTROS 4
struct Registro {
 char modelo[10];
 int fecha;
 char fabricante[20];
 float peso;
};

struct Registro r[NUM_REGISTROS] = {
       { "127", 1972, "SEAT", 1100.0 },
       { "124", 1968, "SEAT", 855.0 },
       { "850", 1966, "SEAT", 670.0 },
       { "1500", 1963, "SEAT", 900.0 },

      };
int main(int argc, char * argv[])
 {
  FILE *  fp;
  char nombre_por_defecto[] = "formatos.txt";
  char * p = argc>1 ? argv[1] : nombre_por_defecto;
  int i;
  fp = fopen( p, "w");
  if (fp == NULL)
   {
    printf("\n\nImposible abrir %s - Saliendo.\n\n", p);
    exit(1);
   }
  else
   {
    /* Escritura con formato encolumnado               */
    /* Los tres primeros campos tienen cuatro columnas */
    /* de anchura, y el tercero tiene seis, con un     */
    /* dígito decimal.                                 */
    for(i=0;i<NUM_REGISTROS;i++)
     {
      fprintf(fp,"%4s%4d%4s%6.1f\n", r[i].modelo,
              r[i].fecha,
              r[i].fabricante,
              r[i].peso);
     }
    /* Escritura con formato delimitado por asteriscos */
    /* Se inserta un "*" entre campos consecutivos     */
    /* pero no al final del registro.                  */
    for(i=0;i<NUM_REGISTROS;i++)
     {
      fprintf(fp,"%s*%d*%s*%f\n", r[i].modelo,
              r[i].fecha,
              r[i].fabricante,
              r[i].peso);
     }
    /* Escritura con formato de un campo por línea.   */
    for(i=0;i<NUM_REGISTROS;i++)
     {
      fprintf(fp,"%s\n%d\n%s\n%f\n", r[i].modelo,
              r[i].fecha,
              r[i].fabricante,
              r[i].peso);
     }
    fclose(fp);
   }
  return 0;
 }

 
El resultado de ejecutar este programa es el siguiente:
 1271972SEAT1100.0
 1241968SEAT 855.0
 8501966SEAT 670.0
15001963SEAT 900.0
127*1972*SEAT*1100.000000
124*1968*SEAT*855.000000
850*1966*SEAT*670.000000
1500*1963*SEAT*900.000000
127
1972
SEAT
1100.000000
124
1968
SEAT
855.000000
850
1966
SEAT
670.000000
1500
1963
SEAT
900.000000

Comentarios .- Como puede apreciarse, ha bastado ir modificando la cadena de especificación de formato empleada, para así obtener una salida encolumnada, delimitada (por asteriscos en este caso) o de un campo por línea. El formato encolumnado requiere únicamente especificar el número total de columnas deseado. El formato delimitado requiere especificar el carácter empleado como separador, que es un asterisco ("*") en este caso. El formato de un campo por línea, último de los utilizados y el más sencillo de construir, produce resultados de interpretación menos clara que los anteriores (para un lector humano).
Bien, ya se sabemos cómo se escribe. Leer es ligeramente más complicado, pero no reviste especial dificultad. Véase a continuación un programa que lee lo que se había escrito en el ejercicio anterior. Ejemplo .- Construir un programa adecuado para leer registros de un archivo de texto. Los cuatro primeros registros tienen formato encolumnado, los cuatro siguientes tienen formato delimitado (por asteriscos) y los cuatro últimos tienen formato de un campo por línea.
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#define NUM_REGISTROS 4
struct Registro {
 char modelo[10];
 int fecha;
 char fabricante[20];
 float peso;
};

int main(int argc, char * argv[])
 {
struct Registro  enc[NUM_REGISTROS] = {
       { "----", 0, "----", 0.0 },
       { "----", 0, "----", 0.0 },
       { "----", 0, "----", 0.0 },
       { "----", 0, "----", 0.0 },

      },
     del[NUM_REGISTROS] = {
       { "----", 0, "----", 0.0 },
       { "----", 0, "----", 0.0 },
       { "----", 0, "----", 0.0 },
       { "----", 0, "----", 0.0 },

      },
     uncampo[NUM_REGISTROS] = {
       { "----", 0, "----", 0.0 },
       { "----", 0, "----", 0.0 },
       { "----", 0, "----", 0.0 },
       { "----", 0, "----", 0.0 },

      };
  FILE * fp;
  char nombre_por_defecto[] = "formatos.txt";
  char * p = argc>1 ? argv[1] : nombre_por_defecto;
  int i;
    char t1[20], t2[20], t3[20], t4[20];
  fp = fopen( p, "r");
  if (fp == NULL)
   {
    printf("\n\nImposible abrir %s - Saliendo.\n\n", p);
    exit(1);
   }
  else
   {
    /* Lectura de encolumnados */
    for(i=0;i<NUM_REGISTROS;i++)
     {
      fscanf(fp,"%4c%4d%4c%6f%*c", enc[i].modelo,
             &enc[i].fecha,
             enc[i].fabricante,
             &enc[i].peso);
     }
    printf("\nEncolumnados leídos:\n\n");

    for(i=0;i<NUM_REGISTROS;i++)
     {
      printf("%s----%d----%s----%f\n",enc[i].modelo,
              enc[i].fecha,
              enc[i].fabricante,
              enc[i].peso);
     }

    /* Lectura de delimitados */
    for(i=0;i<NUM_REGISTROS;i++)
     {
      fscanf(fp,"%[^*]%*c%[^*]%*c%[^*]%*c%[^*\n]\n",t1, t2, t3, t4);
      /*
       Se puede comprobar el contenido de las cadenas leídas.
       printf("-->%s....%s....%s....%s<--\n", t1, t2, t3, t4);

       Es necesario efectuar una transformación al formato
       numérico deseado. Obsérvese que se han descartado
       los espacios iniciales.
      */
      strcpy(del[i].modelo,t1);
      del[i].fecha = strtol(t2, NULL, 10);
      strcpy(del[i].fabricante, t3);
      del[i].peso = strtof(t4, NULL);
     }
    printf("\nDelimitados leídos:\n\n");

    for(i=0;i<NUM_REGISTROS;i++)
     {
      printf("%s----%d----%s----%f\n",del[i].modelo,
      del[i].fecha,
      del[i].fabricante,
      del[i].peso);
     }
    /* Lectura de un campo por línea */
    for(i=0;i<NUM_REGISTROS;i++)
     {
      fscanf(fp,"%s\n%d\n%s\n%f\n", uncampo[i].modelo,
              &uncampo[i].fecha,
              uncampo[i].fabricante,
              &uncampo[i].peso);
     }
    printf("\nUn campo por línea leídos:\n\n");
    for(i=0;i<NUM_REGISTROS;i++)
     {
      printf("%s\n%d\n%s\n%f\n",  uncampo[i].modelo,
              uncampo[i].fecha,
              uncampo[i].fabricante,
              uncampo[i].peso);
     }
   }
  return 0;
 }


Comentarios .- El resultado de ejecutar este programa, que sirve como comprobación de su correcto funcionamiento, es como sigue:
/*
Encolumnados leídos:

 127----1972----SEAT----1100.000000
 124----1968----SEAT----855.000000
 850----1966----SEAT----670.000000
1500----1963----SEAT----900.000000

Delimitados leídos:

127----1977----SEAT----1100.000000
124----1968----SEAT----855.000000
850----1966----SEAT----670.000000
1500----1963----SEAT----900.000000

Un campo por línea leídos:

127
1972
SEAT
1100.000000
124
1968
SEAT
855.000000
850
1966
SEAT
670.000000
1500
1963
SEAT
900.000000

*/


Comentarios .- La forma de leer archivos encolumnados es muy simple: basta especificar, en el formato de lectura, el número de caracteres que debe admitirse en cada conversión. Nosotros habíamos empleado cuatro columnas para todos los campos salvo el de tipo real, situado en último lugar. Por tanto, especificamos 4 columnas después del signo de % en las tres primeras conversiones; la última, correspondiente a un campo float, se detiene automáticamente al llegar al fin de línea y no precisaría más información, pero hemos añadido la especificación 6 (en %6f ) para uniformizar la forma de las especificaciones de acceso. Obsérvese la necesidad de leer el '\n' final, mediante la especificación %*c , que sirve precisamente para descartar un carácter.
La forma de leer archivos delimitados (una de las muchas posibles) es ligeramente distinta. En este caso, hemos empleados las cadenas temporales t1 , t2 , t3 y t4 con objeto de almacenar en ellas los resultados de las lecturas, para después copiar estos valores (previa conversión a un formato binario cuando se precisa) en los campos correspondientes de la lista de estructuras. El método de lectura pasa por utilizar la especificación de formato [] , ya descrita , que permite leer un carácter tras otro, almacenándolos en la cadena correspondiente, y se detiene cuando encuentra un asterisco. También podría emplearse la función strtok() .
En cuanto a los archivos de un campo por línea , no hay dificultad: basta emplear en lectura el mismo formato empleado en escritura, teniendo en cuenta el retorno de carro.
Lo expuesto servirá como base para la lectura y escritura de los formatos de texto habituales. Considérense también los siguientes

Ejercicios propuestos

Sea una estructura de la forma siguiente:

struct Registro {
	char modelo[10];
	int fecha;
	char fabricante[20];
	float peso;
};


Esta estructura sirve como base para todos los ejercicios expuestos a continuación.
  1. Ejercicio 1003r01.- Se dispone de una lista de estructuras como la indicada. Se pide construir un programa que vuelque a disco el contenido de la lista, empleando el formato de un campo por línea . Se recomienda escribir el número de registros volcados en la primera línea del archivo.
  2. Ejercicio 1003r02.- Se dispone de una colección de estructuras como la anterior, volcadas a disco con formato de un campo por línea. Se pide construir un programa que cargue estos datos en memoria. Suponer primero que la primera línea del archivo contiene el número de registros volcados. Suponer después que la primera línea del archivo no contiene el número de registros volcados.
  3. Ejercicio 1003r03.- Se dispone de una lista de estructuras como la indicada. Se pide construir un programa que vuelque a disco el contenido de la lista, empleando el formato de encolumnado . Se recomienda escribir el número de registros volcados en la primera línea del archivo.
  4. Ejercicio 1003r04.- Se dispone de una colección de estructuras como la anterior, volcadas a disco con formato encolumnado. Se pide construir un programa que cargue estos datos en memoria. Suponer primero que la primera línea del archivo contiene el número de registros volcados. Suponer después que la primera línea del archivo no contiene el número de registros volcados.
  5. Ejercicio 1003r05.- Se dispone de una lista de estructuras como la indicada. Se pide construir un programa que vuelque a disco el contenido de la lista, empleando el formato delimitado por tabuladores . Se recomienda escribir el número de registros volcados en la primera línea del archivo.
  6. Ejercicio 1003r06.- Se dispone de una colección de estructuras como la anterior, volcadas a disco con formato delimitado por tabuladores. Se pide construir un programa que cargue estos datos en memoria. Suponer primero que la primera línea del archivo contiene el número de registros volcados. Suponer después que la primera línea del archivo no contiene el número de registros volcados.
  7. Ejercicio 1003r07.- Considérese una estructura genérica formada por campos de los tipos int , float , double y/o cadenas . Diseñar una convención que permita especificar (en la primera línea de un archivo) el formato encolumnado utilizado para escribir el archivo. Contruir un programa adecuado para escribir y leer archivos dotados de esta línea de especificación en el encabezado
  8. Ejercicio 1003r08.- Considérese una estructura genérica formada por campos de los tipos int , float , double y/o cadenas . Diseñar una convención que permita especificar (en la primera línea de un archivo) el formato delimitado utilizado para escribir el archivo. Contruir un programa adecuado para escribir y leer archivos dotados de esta línea de especificación en el encabezado. Recuérdese que la línea debe contener el carácter delimitador empleado, que podrá variar para distintos archivos o plataformas.
  9. Ejercicio 1003r09.- Se dispone de un archivo histórico, formado por registros del tipo ya conocido. Se pide construir un programa capaz de añadir al final de este archivo la información contenida en otros dos archivos de nombre conocido e igual formato.
Nota
Los signos diacríticos no tienen los mismos códigos ASCII en distintas plataformas, dando lugar a "destrozos" cuando se efectúan intercambios de información. Una solución rápida y eficiente consiste en codificar los archivos de texto (delimitados o encolumnados) como si se tratase de código HTML. Como es sabido, todo signo diacrítico posee un equivalente en código ASCII (por ejemplo, la letra "á" tiene el equivalente &aacute; en HTML). Es sencillo traducir de texto a HTML en la plataforma de origen, y de HTML a texto en la plataforma de destino. Una vez efectuada la traducción en destino, se dispondrá de los signos diacríticos originales.