IniParser: una lib simple para parsing de archivos INI en C
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.
Si, 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:
y ejecutar!
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!