Fichero de configuración

by

Uno de los siguientes paso que quería dar en el proyecto es añadir un fichero de configuración. Las características eran muy sencillas:

  • La posibilidad de comentarios que empezarían con # y acabarían en final de línea.
  • Sentencias de tipo: clave = valor
  • Tanto la clave como el valor estarán compuestos por una sola palabra, ya sean números, letras o un conjunto de ellos sin espacios entre ellos.
  • Permitir líneas vacías.
  • Poder añadir espacios o tabulaciones en cualquier parte del fichero.
  • Detectar cualquier línea que no cumpla con lo anterior, o sea, erróneas y omitirlas.

Un primer código que realizado para parsear el fichero es el que viene a continuación. Obviamente hay que adaptarlo al código de Meshias, pero para empezar a hacer pruebas viene perfecto y puede ser ejecutado directamente.

#include <stdio.h>
#include <string.h>

#define COMMENT '#'
int main()
{
    FILE* file;

    file = fopen("meshias.cfg", "r");

    if(!file)
    {
        perror("File couldn't not be opened");
        return -1;
    }

    char key[64], value[256];
    char line[512];

    int bytes = 0;
    int n_line = 0;
    char *pos = NULL;

    while(fgets(line, 511, file))
    {
        n_line++;
        printf("%d: ", n_line);

        // Eliminar el comentario
        if (pos = strchr(line, COMMENT))
        {
            *pos = '\0';
            printf("Hay un comentario. ");
        }

        if (strlen(line) == 0)
        {
            puts("Linea vacia.");
        }
        else if (strlen(line) == 1 && strncmp(line, "\n", 1) == 0)
        {
            puts("Linea vacia.");
        }
        else if (sscanf(line, " %63s = %255s \n", key, value) == 2)
        {
            printf("Clave: %s, Valor: %s\n", key, value);
        }
        else if (sscanf(line, " \n%n", &bytes) == 0 && bytes)
        {
            puts("Linea con espacios.");
        }
        else
        {
            puts("Linea incorrecta.");
        }
    }

    if(!feof(file))
    {
        perror("Error leyendo el fichero");
        return -1;
    }

    puts("El programa acabo satisfactoriamente.");

    fclose(file);

    return 0;
}

Este va a ser el fichero de configuración, con todos los casos posibles que se me han ocurrido y que nos van a servir de guía para testear el código:

# a
          a
nada =
todo

   numero=8 # hola    y adios

   # color = verde

un coche = ruedas motor chasis
cadena = pepito grillo
una cadena = manzana

# Bien hecho
 bien = hecho

Primeramente pensé en realizar el parseo sin usar fgets, usando simplemente fscanf. El problema es que la familia scanf tiene algunas carencias difíciles de solucionar. Por ejemplo un espacio dentro de la cadena de scanf representa cualquier conjunto de cero o más espacios, tabulaciones y saltos de líneas. Vamos que representa a la expresión regular [' ''\t''\v''\n']*; lo cual está muy bien la mayoría de las veces y lo desconocía al principio. Sin embargo, un caso como el que se presenta en las líneas tres y cuatro del fichero de configuración lo daría como válido cuando no queremos que no sea así. Conseguí que permitiera cualquier combinación de uno ó más de espacios y tabulaciones, pero no de cero o más: “%*[ \t\v]“. Con esa expresión puesta en la cadena de scanf obviamos los espacios y tabulaciones. El uso del asterisco hace que la expresión se omita a la hora de ser guardada en una variable. Desde luego scanf tiene algunos casos particulares como este muy curiosos. Aun queda alguno por explicar.

Para evitarme dolores de cabeza preferí usar fgets ya que me ahorraba algunos problemas. fgets va cogiendo cada línea y la mete en un buffer. Luego el parseo es sencillo.

  1. Primero eliminamos el comentario. La forma más simple que se me ocurrió fue poner el carácter de fin de cadena en la posición del #; de esta manera todas las funciones pensarán que la línea acaba ahí, que es lo que realmente queremos.
  2. Luego si la cadena es de tamaño cero es porque el comentario está al principio de la línea (línea 36).
  3. Si solamente tiene el carácter de salto de linea es una linea vacía también (línea 40).
  4. Ahora vemos si cumple el caso de que haya una sentencia con clave y valor (línea 44). Para evitar desbordamientos de buffer ponemos la longitud máxima que pueden tener las cadenas. Además añadimos un salto de línea al final que acabe así… de poca utilidad como veremos más adelante.
  5. Por último vemos si la línea es una combinación de espacios y tabulaciones (línea 48). Aquí hay un pequeño truco de scanf que consiste en usar %n. Esto devuelve el numero de bytes leídos, y se puede colocar en cualquier parte de la cadena. Eso sí, hay que respetar el orden con otros posibles argumentos de la cadena, en este caso no existe el problema porque no guardamos ningún dato de la línea. Si es mayor de uno los bytes leido es que  ha ido bien la expresión.
  6. Si ningún caso anterior se cumple es que es una linea incorrecta.

Cuando ya creí dar con el código perfecto me topé con la línea dos y once del fichero de configuración. La línea dos tiene espacios y al final un carácter. Aunque ponga un salto de línea al final de la expresión de la línea 48, no tengo forma de saber si eso se cumple. Lo mismo pasa en la línea once: hay un valor, hay un igual y hay un valor… y varios más. La función scanf devuelve el número de elementos guardados a medida que va leyendo la cadena. Si se detiene por no coincidir la entrada con la cadena, devuelve el número que haya guardado hasta ese momento. En este caso, guarda la segunda palabra y ya no guardo más, por lo que no tengo forma de comprobar que lo que venga después sean sólo espacios, tabulaciones y al final el salto de línea, ya que, se cumpla o no, la función devolverá 2.

Finalmente lo he arreglado, pero con una función que poco tiene que ver con scanf. Pero lo dejo para el próximo post que esto ha quedado un poco largo… De mientras pueden sugerir soluciones, o algún truquito para hacer con scanf.

Advertisement

Deja un comentario

Fill in your details below or click an icon to log in:

Logo de WordPress.com

You are commenting using your WordPress.com account. Log Out / Cambiar )

Twitter picture

You are commenting using your Twitter account. Log Out / Cambiar )

Facebook photo

You are commenting using your Facebook account. Log Out / Cambiar )

Connecting to %s


Seguir

Get every new post delivered to your Inbox.