Continuamos con la serie de cómo se hace en C++. En la primera entrega recuerda que vimos cómo manejar ficheros XML con pugiXML. En esta segunda entrega vamos a ver como parsear y editar otro tipo de archivos comunes para almacenar datos. Se trata de los archivos de configuración que son con conocidos como archivos ini o archivos cfg.
Estructura de un archivo de configuración
Como ya hemos dicho los archivos de configuración suelen tener la extensión .ini ó .cfg y lo que se almacena en ellos son parejas de clave - valor. Además vienen divididos en varias secciones que agrupa un grupo de parámetros, lo mejor es verlo con un ejemplo.
[window]
width=640
height=480
bpp=32
fullscreen=1
"#" Esto es un comentario, ignorado por el parseador.
[player]
posx=14.6
posy=45.8
lives=5
name=Alex
Como vemos en este archivo tendríamos dos secciones una llamada window y otra llamada player y ambas contienen un grupo de parámetros separados por línea con un igual entre la clave y el valor. Los valores pueden ser cadenas de caracteres, enteros, números reales o variables booleanas.
Además podemos añadir comentarios a nuestro archivo para hacerlo más compresivo anteponiendo un ;
o #
. Estos comentarios son ignorados por el parseador.
Leyendo ficheros con inih
El proyecto inih es una pequeña biblioteca para parsear estos archivos ini y extraer la información. Es muy pequeña y rápida. Cuenta con una versión C y también con un wrapper para C++. Al ser una biblioteca tan pequeña lo más factible es integrar los propios archivos de cabecera e implementaciones en nuestro proyecto más que añadirlo como una biblioteca separada.
Su uso es tán sencillo como lo siguiente:
#include <iostream> | |
#include <string> | |
#include "INIReader.h" | |
int main() | |
{ | |
INIReader file("prueba.ini"); | |
if (file.ParseError() < 0) { | |
std::cout << "No se puede cargar prueba.ini" << std::endl; | |
return -1; | |
} | |
std::cout << file.GetInteger("window", "width", -1) << std::endl; | |
std::cout << file.GetInteger("window", "height", -1) << std::endl; | |
std::cout << file.GetInteger("window", "noExiste", -1) << std::endl; | |
if (file.GetBoolean("window", "fullscreen", false)) | |
{ | |
std::cout << "Pantalla Completa" << std::endl; | |
} | |
else | |
{ | |
std::cout << "Modo ventana" << std::endl; | |
} | |
std::string nombre = file.Get("player", "name", "unknow"); | |
std::cout << "Hola, " << nombre << std::endl; | |
float x = file.GetReal("player", "posx", 0); | |
float y = file.GetReal("player", "posy", 0); | |
std::cout << "Coordenadas: (" << x << ", " << y << ")" << std::endl; | |
system("pause"); | |
return 0; | |
} |
Como vemos después de importar los headers necesarios lo único que debemos hacer es crear un objeto del tipo INIReader y pasarle la ruta de nuestro fichero de configuración. El método ParseError nos devuelve un código menor que cero si no ha sido posible cargar el fichero.
Cuenta con solo 4 métodos más que son: Get, GetInteger, GetReal y GetBoolean. Estos cuatro métodos sirven para extraer los valores como cadena de texto (std::string), entero (long int), real (double) o Booleano (bool). Como vemos son siempre los tipos mayores de los diferentes tipos de datos para no perder información, si luego necesitamos menos siempre podemos hacer casting a int, float, etc.
Los métodos reciben 3 parámetros: El nombre de la sección, el nombre de la clave y el valor por defecto. El valor por defecto será el que se devuelva en caso de que no se encuentre al par clave-valor solicitado o que este no tenga un valor asignado.
Crear ficheros ini
Lamentablemente inih solo es un parseador de ficheros ini, si lo que queremos es modificarlo o crear uno debemos usar otras herramientas. Generar un archivo de configuración es tan trivial como crear un fichero de texto, así que podríamos hacer una clase pequeña para ello:
Archivo de cabecera
class ConfigCreate | |
{ | |
public: | |
ConfigCreate(); | |
~ConfigCreate(); | |
void Open(const std::string& filename); | |
void Close(); | |
void PutSection(const std::string& section); | |
void PutValue(const std::string& key, const std::string& value); | |
void PutComment(const std::string& comment); | |
void PutBlankLine(); | |
private: | |
std::ofstream file; | |
}; // class ConfigCreate |
Implementación
ConfigCreate::ConfigCreate() | |
{ | |
} | |
ConfigCreate::~ConfigCreate() | |
{ | |
} | |
void ConfigCreate::Open(const std::string& filename) | |
{ | |
file.open(filename); | |
file.clear(); | |
} | |
void ConfigCreate::Close() | |
{ | |
file.close(); | |
} | |
void ConfigCreate::PutSection(const std::string& section) | |
{ | |
file << "[" << section << "]" << std::endl; | |
} | |
void ConfigCreate::PutValue(const std::string& key, const std::string& value) | |
{ | |
file << key << "=" << value << std::endl; | |
} | |
void ConfigCreate::PutComment(const std::string& comment) | |
{ | |
file << "# " << comment << std::endl; | |
} | |
void ConfigCreate::PutBlankLine() | |
{ | |
file << std::endl; | |
} |
Cuidado al crear un fichero sobreescribe otro preexistente, si lo que se quiere es modificar un archivo ini, primero usa un parser para obtener le contenido del fichero y luego generarlo de nuevo a partir de los valores obtenidos y los nuevos y/o modificados.
Su utilización sería de la siguiente manera.
ConfigCreate c; | |
c.Open("prueba.cfg"); | |
c.PutSection("window"); | |
c.PutValue("width", "640"); | |
c.PutValue("height", "480"); | |
c.PutComment("Activa la pantalla completa"); | |
c.PutValue("fullscreen", "0"); | |
c.PutBlankLine(); | |
c.PutSection("Otra"); | |
c.PutComment("lalalalala"); | |
c.PutValue("djd", "edjie"); | |
c.Close(); |
Esto generaría el siguiente fichero llamado prueba.cfg.
[window]
width=640
height=480
"#" Activa la pantalla completa
fullscreen=0
[Otra]
"#" lalalalala
djd=edjie
Más información | inih