IniParser: una lib simple para parsing de archivos INI en C

Publicado por Diego Córdoba en

Hoy vamos a introducir un ejemplo práctico de parsing de archivos INI en un código C con IniParser, que puede sernos muy útil si estamos programando una aplicación parametrizable.


Muchas veces programamos en C aplicaciones que parametrizamos, por ejemplo, utilizando macros #define… pero esto tiene un problema: para cambiar un parámetro necesitamos re-compilar toda la aplicación.

iniparser config iniSi, por ejemplo, queremos que ciertos parámetros, como datos de conexión a una base de datos, o nombres de archivos para logging, no estén hardcodeados en el código, una opción sería la de pasarle parámetros y utilizar getopt() para capturarlos e interpretarlos.

Esta opción es interesante cuando son pocas opciones, y fáciles de recordar, pero si queremos que queden estáticas almacenadas en un archivo de texto, y luego que el programa las interprete, será necesario escribir nuestro propio archivo de configuración .ini.

¿Qué es un archivo INI?

Es un archivo que tiene cierta estructura, y generalmente se divide en secciones y contenido por sección en el formato «clave = valor«.

Veamos un ejemplo:

[SECCION_1]
clave_string = "valor1"
clave_num = 12

[SECCION_2]
clave_num1 = 111
clave_num2 = 222
clave_num3 = 333

En este archivo tenemos dos secciones, y algunas claves dentro de cada sección, tanto numéricas como de string.

¿Qué es iniparser?

Existen muchas bibliotecas en C para poder parsear archivos INI, entre ellas MinIni e IniParser.

En este caso vamos a ver un ejemplo sencillo de uso de IniParser.

Si tenemos el código bien estructurado en directorios como lib/, src/, bin/, y obj/, vamos a descargar primero la biblioteca de iniparser.

Para ello vamos al directorio lib/ de nuestra aplicación y clonamos el repositorio GIT oficial:

cd lib/
git clone https://github.com/ndevilla/iniparser

Con esto ya tendremos nuestro código de IniParser descargado.

Ahora, vamos a escribir nuestra aplicación C, por ejemplo, src/parser.c.

Lo primero que tenemos que hacer es definir dos variables, una de tipo char* que va a almacenar el nombre del archivo a parsear, y otra de tipo dictionary* que contendrá archivo cargado en memoria.

dictionary * ini = NULL;
char       * ini_name = NULL;

Luego, con la función iniparser_load() cargamos en el diccionario ini el archivo de configuración.

A continuación, podremos ejecutar cualquiera de las funciones de iniparser para leer datos particulares y metainformación del archivo de configuración INI.

Entre ellas tenemos:

  • iniparser_getstring(): permite leer un valor de cadena determinado en base a una estructura «Seccion:clave».
  • iniparser_getint(): idem, pero para leer valores enteros.
  • iniparser_dump(): permite leer todo el diccionario y enviarlo a un stream de salida determinado (en el ejemplo, stdout).
  • iniparser_getnsec(): permite leer la cantidad de secciones del archivo INI.
  • iniparser_getsecnkeys(): idem, para leer la cantidad de claves dentro de una sección determinada.
  • iniparser_freedict(): libera la memoria asignada al diccionario que usamos.

Veamos un ejemplo completo del uso de estas funciones:

#include<stdio.h>
#include<iniparser.h>

int main(int argc, char** argv){
    dictionary * ini = NULL;              // Diccionario
    char       * ini_name = *(argv+1);    // Archivo .ini

    // cargamos archivo de configuracion .ini en el diccionario
    ini = iniparser_load(ini_name);

    printf("Valor leido: %s\n", iniparser_getstring(ini,"SECCION_1:clave_string",NULL));
    printf("Valor leido: %d\n", iniparser_getint(ini,"SECCION_2:clave_num2",0));

    // Escribimos por pantalla el contenido completo del diccionario
    iniparser_dump(ini, stdout);

    printf("Cantidad de secciones: %d\n",iniparser_getnsec(ini));
    printf("Cantidad de en la SECCION_2: %d\n",iniparser_getsecnkeys(ini,"SECCION_2"));

    // Liberamos memoria
    iniparser_freedict(ini);

    return 0;
}

Los prototipos de estas funciones y su documentación pueden encontrarse en el archivo iniparser/src/iniparser.h, ahí tendremos la info para saber qué parámetros pasar y los valores de retorno, y por supuesto, algunas otras funciones no mencionadas en este artículo.

A compilar…

Para poder compilarlo incluida la biblioteca, nada mejor que escribir nuestro archivo Makefile. En este caso lo cargué dentro del directorio src/ de la aplicación, y le puse el siguiente contenido (en otra oportunidad hablaremos de Makefile:

##########################################
# Makefile
#
# @Author: @Ing. Diego Cordoba - @d1cor - www.juncotic.com
# @Date: 18 Jan 2017
##########################################

BIN := parser
OBJDIR := ../obj
SRCDIR := .
LIBDIR := ../lib

CFLAGS := -g -Wall $(CFLAGS) $(DEFINE) -I../lib/iniparser/src/
LDFLAGS := $(LDFLAGS) -L../lib/iniparser/ -liniparser

OBJECTS :=  $(addprefix $(OBJDIR)/, \
                        parser.o \
                        $(BIN).o)

TARGET := ../bin/$(BIN)

all: $(TARGET)

$(TARGET): $(OBJECTS)
    @echo "compiling libiniparser"
    @make libiniparser
    @echo "Generating binary ...."
    @$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)

$(OBJDIR)/%.o: $(SRCDIR)/%.c
    @echo "Compiling $<"
    @$(CC) $(CFLAGS) $< -c -o $@

$(OBJDIR)/iniparser.o: $(LIBDIR)/iniparser/iniparser.c
    @echo "Compiling $<"
    @$(CC) $(CFLAGS) $< -c -o $@

libiniparser:
    @echo "Compiling iniparser"
    @$(MAKE) --no-print-directory -C ../lib/iniparser/

clean:
    @rm -f $(TARGET) $(OBJECTS)
    @cd ../lib/iniparser/ && make clean

Con este Makefile dentro de nuestro directorio src/, no tendremos mas que compilar:

iniparser

y ejecutar!

iniparser

Trabajo para el hogar 😛

El código se encuentra publicado en nuestro repositorio de GitHub, y puede descargarse en ZIP o clonarse directamente:

Clonamos el repositorio en algún directorio local:

git clone https://github.com/JuncoTIC/Parser_ini_c.git 

Entramos al directorio de fuentes:

cd parser/src/

Compilamos:

make

Y ejecutamos la prueba:
../bin/parser config.ini

Espero resulte interesante!

Cualquier aporte o corrección no tienen mas que comentarlo en esta entrada en el blog.

¡Gracias!


Diego Córdoba

- Ingeniero en Informática - Mg. Teleinformática - Tesis pendiente - Docente universitario - Investigador