Lo guardamos en el directorio donde se genera nuestro ejecutable para este ejemplo. Bien como comprobamos se trata de un documento XML bien formado con sus nodos y sus atributos. Lo que vamos a hacer es un programa que extraiga esa información y la muestre en una ventana de consola. Empecemos.
Cargando el documento
Lo primero que tenemos que saber es que todos los elementos que componen la biblioteca pugiXML se encuentran dentro del namespace pugi . Así pues todas las instrucciones que han referencia a la biblioteca irán precedidas de pugi::
.
Empezamos creando un objeto pugi::xmldocument
que será el objeto que englobe nuestro árbol XML una vez lo hemos cargado. Acto seguido cargamos el documento con su método loadfile
el cual englobaremos dentro de un if
para prevenir errores de carga.
#include
#include "pugiconfig.hpp"
#include "pugixml.hpp"
int main()
{
pugi::xml_document doc;
if (!doc.load_file("mapa.tmx"))
{
std::cerr << "Error al cargar el documento XML." << std::endl;
return -1;
}
std::cin.get();
return 0;
}
Una vez cargado el archivo en el objeto doc, lo que hacemos es obtener el nodo principal de el archivo que en este caso es map .
#include
#include "pugiconfig.hpp"
#include "pugixml.hpp"
int main()
{
pugi::xml_document doc;
if (!doc.load_file("mapa.tmx"))
{
std::cerr << "Error al cargar el documento XML." << std::endl;
return -1;
}
// Creamos un objeto nodo
pugi::xml_node root_node;
// Le asignamos el nodo principal comprobando que sea correcto
if (!(root_node = doc.child("map")))
{
std::cerr << "El documento no es un mapa valido." << std::endl;
return -2;
}
std::cin.get();
return 0;
}
Como vemos solo tenemos que crear un objeto pugi::xml_node
y con el método child
extraemos nodos hijos del documento pasando el nombre del nodo como parámetro. Lo hacemos dentro de un if que comprueba que el nodo exista.
Ahora vamos a trabajar con el objeto root_node que es el que contiene toda la información ya que el resto de nodos son hijos de este. El objetivo de este ejemplo es sacar toda la información del mapa y mostrarla por pantalla. Así que comenzaremos por los atributos del nodo mapa.
Extrayendo atributos
Extrar los atributos de un nodo es tan sencillo como usar el metodo attribute
que nos devolverá un objeto pugi::xmlattribute que tiene varios métodos interesantes uno de ellos es value que nos da el valor de ese atributo como una cadena de caracteres, pero tiene otros como as int, asdouble, as float o as_uint que nos devuelve el valor en el tipo de dato especificado.
#include
#include
#include "pugiconfig.hpp"
#include "pugixml.hpp"
int main()
{
pugi::xml_document doc;
if (!doc.load_file("mapa.tmx"))
{
std::cerr << "Error al cargar el documento XML." << std::endl;
return -1;
}
// Creamos un objeto nodo
pugi::xml_node root_node;
// Le asignamos el nodo principal comprobando que sea correcto
if (!(root_node = doc.child("map")))
{
std::cerr << "El documento no es un mapa valido." << std::endl;
return -2;
}
// Declaramos las variables necesarias
std::string orientation;
float version;
unsigned int width, height, tile_width, tile_height;
// Obtenemos los datos del mapa
orientation = root_node.attribute("orientation").value();
version = root_node.attribute("version").as_float();
width = root_node.attribute("width").as_uint();
height = root_node.attribute("height").as_uint();
tile_width = root_node.attribute("tilewidth").as_uint();
tile_height = root_node.attribute("tileheight").as_uint();
// Mostramos los datos obtenidos en pantalla
std::cout << "Mapa\n------\n";
std::cout << "Version: " << version << std::endl;
std::cout << "Orientacion: " << orientation << std::endl;
std::cout << "Dimensiones: " << width << "x" << height << std::endl;
std::cout << "Dimensiones de Tiles: " << tile_width << "x" << tile_height << std::endl;
// Evitamos el cierre de la aplicacion en entornos
std::cin.get();
return 0;
}
Con este código nos debería salir en pantalla algo como lo siguiente.
Mapa
------
Version: 1
Orientacion: orthogonal
Dimensiones: 40x30
Dimensiones de Tiles: 32x32
Cabe destacar de los atributos que si lo ponemos dentro de un if igual que sucede con los nodos nos devolverá falso si el nodo no contiene el atributo. Ideal para saber si un determinado nodo tiene o no cierto atributo y actuar en consecuencia. Hasta aquí hemos visto como extrar información de los atributos de un nodo conocido, que es el principal. Ahora vamos a ver como extraer información de los nodos hijos.
Recorriendo nodos hijos
En nuestro mapa el nodo map contiene tres tipos de nodos hijos:
tileset
layer
objectgroup
Así pues sabemos que los nodos que nos vamos a encontrar son de uno de estos tres tipos. Lo que vamos a hacer a continuación es recorrer todos los nodos hijos de root_node y "preguntar" como se llaman.
#include
#include
#include "pugiconfig.hpp"
#include "pugixml.hpp"
int main()
{
pugi::xml_document doc;
if (!doc.load_file("mapa.tmx"))
{
std::cerr << "Error al cargar el documento XML." << std::endl;
return -1;
}
// Creamos un objeto nodo
pugi::xml_node root_node;
// Le asignamos el nodo principal comprobando que sea correcto
if (!(root_node = doc.child("map")))
{
std::cerr << "El documento no es un mapa valido." << std::endl;
return -2;
}
// Declaramos las variables necesarias
std::string orientation;
float version;
unsigned int width, height, tile_width, tile_height;
// Obtenemos los datos del mapa
orientation = root_node.attribute("orientation").value();
version = root_node.attribute("version").as_float();
width = root_node.attribute("width").as_uint();
height = root_node.attribute("height").as_uint();
tile_width = root_node.attribute("tilewidth").as_uint();
tile_height = root_node.attribute("tileheight").as_uint();
// Mostramos los datos obtenidos en pantalla
std::cout << "Mapa\n------\n";
std::cout << "Version: " << version << std::endl;
std::cout << "Orientacion: " << orientation << std::endl;
std::cout << "Dimensiones: " << width << "x" << height << std::endl;
std::cout << "Dimensiones de Tiles: " << tile_width << "x" << tile_height << std::endl;
// Recorremos todos los nodos hijos de root_node
for (pugi::xml_node node = root_node.first_child(); node; node = node.next_sibling())
{
std::string node_name = node.name();
}
// Evitamos el cierre de la aplicacion en entornos
std::cin.get();
return 0;
}
Lo hacemos con un bucle for que tiene como variable un pugi::xmlnode
al que en primer lugar le asignamos el primer nodo de rootnode
con el método firstchild() que poseen los objetos nodos, que como es obvio nos devuelve el primer nodo hijo. A su vez nextsibling() lo que hace es cambiar al siguiente nodo hermano.
Ya dentro del for con el método name() obtenemos el nombre del nodo actual y lo almacenamos en la variable string node_name
. AHora como sabemos que para este caso concreto el nombre del nodo solo puede ser de uno de estos tipos decidimos con unas sentencias if-else.
#include
#include
#include "pugiconfig.hpp"
#include "pugixml.hpp"
int main()
{
pugi::xml_document doc;
if (!doc.load_file("mapa.tmx"))
{
std::cerr << "Error al cargar el documento XML." << std::endl;
return -1;
}
// Creamos un objeto nodo
pugi::xml_node root_node;
// Le asignamos el nodo principal comprobando que sea correcto
if (!(root_node = doc.child("map")))
{
std::cerr << "El documento no es un mapa valido." << std::endl;
return -2;
}
// Declaramos las variables necesarias
std::string orientation;
float version;
unsigned int width, height, tile_width, tile_height;
// Obtenemos los datos del mapa
orientation = root_node.attribute("orientation").value();
version = root_node.attribute("version").as_float();
width = root_node.attribute("width").as_uint();
height = root_node.attribute("height").as_uint();
tile_width = root_node.attribute("tilewidth").as_uint();
tile_height = root_node.attribute("tileheight").as_uint();
// Mostramos los datos obtenidos en pantalla
std::cout << "Mapa\n------\n";
std::cout << "Version: " << version << std::endl;
std::cout << "Orientacion: " << orientation << std::endl;
std::cout << "Dimensiones: " << width << "x" << height << std::endl;
std::cout << "Dimensiones de Tiles: " << tile_width << "x" << tile_height << std::endl;
// Recorremos todos los nodos hijos de root_node
for (pugi::xml_node node = root_node.first_child(); node; node = node.next_sibling())
{
std::string node_name = node.name();
// Actuamos en consecuencia segun el tipo de nodo
if (node_name == "tileset")
{
}
else if (node_name == "layer")
{
}
else if (node_name == "objectgroup")
{
}
}
// Evitamos el cierre de la aplicacion en entornos
std::cin.get();
return 0;
}
Vamos a empezar extrayendo los datos de los nodos tileset. En este caso solo tenemos un nodo tileset con una serie de atributos y que contiene un nodo hijo llamado image. Vamos a extraer la información de sus atributos y de su nodo hijo.
#include
#include
#include "pugiconfig.hpp"
#include "pugixml.hpp"
int main()
{
pugi::xml_document doc;
if (!doc.load_file("mapa.tmx"))
{
std::cerr << "Error al cargar el documento XML." << std::endl;
return -1;
}
// Creamos un objeto nodo
pugi::xml_node root_node;
// Le asignamos el nodo principal comprobando que sea correcto
if (!(root_node = doc.child("map")))
{
std::cerr << "El documento no es un mapa valido." << std::endl;
return -2;
}
// Declaramos las variables necesarias
std::string orientation;
float version;
unsigned int width, height, tile_width, tile_height;
// Obtenemos los datos del mapa
orientation = root_node.attribute("orientation").value();
version = root_node.attribute("version").as_float();
width = root_node.attribute("width").as_uint();
height = root_node.attribute("height").as_uint();
tile_width = root_node.attribute("tilewidth").as_uint();
tile_height = root_node.attribute("tileheight").as_uint();
// Mostramos los datos obtenidos en pantalla
std::cout << "Mapa\n------\n";
std::cout << "Version: " << version << std::endl;
std::cout << "Orientacion: " << orientation << std::endl;
std::cout << "Dimensiones: " << width << "x" << height << std::endl;
std::cout << "Dimensiones de Tiles: " << tile_width << "x" << tile_height << std::endl;
// Recorremos todos los nodos hijos de root_node
for (pugi::xml_node node = root_node.first_child(); node; node = node.next_sibling())
{
std::string node_name = node.name();
// Actuamos en consecuencia segun el tipo de nodo
if (node_name == "tileset")
{
unsigned int firstgid, width, height;
std::string name, source;
firstgid = node.attribute("firstgid").as_uint();
name = node.attribute("name").value();
// Obtenemos el nodo hijo image si existe
if(pugi::xml_node node_image = node.child("image"))
{
source = node_image.attribute("source").value();
width = node_image.attribute("width").as_uint();
height = node_image.attribute("height").as_uint();
}
// Una vez extraida la información la mostramos en pantalla
std::cout << "--------\nTileset\n--------\n";
std::cout << "Nombre: " << name << std::endl;
std::cout << "Ruta: " << source << std::endl;
std::cout << "Primer Tile: " << firstgid << std::endl;
std::cout << "Dimensiones: " << width << "x" << height << std::endl;
}
else if (node_name == "layer")
{
}
else if (node_name == "objectgroup")
{
}
}
// Evitamos el cierre de la aplicacion en entornos
std::cin.get();
return 0;
}
Como vemos comprobamos solamente que tenga un nodo hijo image para extraerlo, en realidad habría que comprobar con un if cada atributo si no estamos seguro de que el XML lo fuera a contener.
Una vez hecho esto pasamos a extraer los datos de las capas layer.
#include
#include
#include "pugiconfig.hpp"
#include "pugixml.hpp"
int main()
{
pugi::xml_document doc;
if (!doc.load_file("mapa.tmx"))
{
std::cerr << "Error al cargar el documento XML." << std::endl;
return -1;
}
// Creamos un objeto nodo
pugi::xml_node root_node;
// Le asignamos el nodo principal comprobando que sea correcto
if (!(root_node = doc.child("map")))
{
std::cerr << "El documento no es un mapa valido." << std::endl;
return -2;
}
// Declaramos las variables necesarias
std::string orientation;
float version;
unsigned int width, height, tile_width, tile_height;
// Obtenemos los datos del mapa
orientation = root_node.attribute("orientation").value();
version = root_node.attribute("version").as_float();
width = root_node.attribute("width").as_uint();
height = root_node.attribute("height").as_uint();
tile_width = root_node.attribute("tilewidth").as_uint();
tile_height = root_node.attribute("tileheight").as_uint();
// Mostramos los datos obtenidos en pantalla
std::cout << "Mapa\n------\n";
std::cout << "Version: " << version << std::endl;
std::cout << "Orientacion: " << orientation << std::endl;
std::cout << "Dimensiones: " << width << "x" << height << std::endl;
std::cout << "Dimensiones de Tiles: " << tile_width << "x" << tile_height << std::endl;
// Recorremos todos los nodos hijos de root_node
for (pugi::xml_node node = root_node.first_child(); node; node = node.next_sibling())
{
std::string node_name = node.name();
// Actuamos en consecuencia segun el tipo de nodo
if (node_name == "tileset")
{
unsigned int firstgid, width, height;
std::string name, source;
firstgid = node.attribute("firstgid").as_uint();
name = node.attribute("name").value();
// Obtenemos el nodo hijo image si existe
if(pugi::xml_node node_image = node.child("image"))
{
source = node_image.attribute("source").value();
width = node_image.attribute("width").as_uint();
height = node_image.attribute("height").as_uint();
}
// Una vez extraida la información la mostramos en pantalla
std::cout << "--------\nTileset\n--------\n";
std::cout << "Nombre: " << name << std::endl;
std::cout << "Ruta: " << source << std::endl;
std::cout << "Primer Tile: " << firstgid << std::endl;
std::cout << "Dimensiones: " << width << "x" << height << std::endl;
}
else if (node_name == "layer")
{
std::string name, data, encoding, compression;
name = node.attribute("name").value();
if(pugi::xml_node node_data = node.child("data"))
{
encoding = node_data.attribute("encoding").value();
compression = node_data.attribute("compression").value();
data = node_data.child_value();
}
std::cout << "--------\nLayer\n--------\n";
std::cout << "Nombre: " << name << std::endl;
std::cout << "Codificacion: " << encoding << std::endl;
std::cout << "Compresion: " << compression << std::endl;
std::cout << "Cadena de datos: " << data << std::endl;
}
else if (node_name == "objectgroup")
{
}
}
// Evitamos el cierre de la aplicacion en entornos
std::cin.get();
return 0;
}
Lo mas destacable del nuevo código es el uso de el método child_value() que nos devuelve el contenido del nodo, es decir lo que está entre su etiqueta de apertura y cierre en forma de cadena de caracteres.
A estas alturas queda poco misterio de como extraer atributos de nodos y de sus nodos hijos, así que queda como ejercicio implementar el objectgroup. Si queréis ver una versión completa y con sus comprobaciones de como se haría correctamente todo este sistema y almacenando los datos en objetos podéis revisar este archivo de un proyecto personal.
Qué pasa si no sabemos que datos va a tener el XML
Puede suceder que no sepamos que datos tiene el XML pasado ni donde está lo que buscamos. Podemos hacer uso de los iteradores para recorrer todo el xml y extraer todos sus datos sin conocerlos.
#include
#include
#include "pugiconfig.hpp"
#include "pugixml.hpp"
void getNodeInfo(pugi::xml_node);
int main()
{
pugi::xml_document doc;
if (!doc.load_file("mapa.tmx"))
{
std::cerr << "Error al cargar el documento XML." << std::endl;
return -1;
}
getNodeInfo(doc);
// Evitamos el cierre de la aplicacion en entornos
std::cin.get();
return 0;
}
void getNodeInfo(pugi::xml_node node)
{
for (pugi::xml_node_iterator it = node.begin(); it != node.end(); ++it)
{
std::cout << it->name() << "\n--------\n";
for (pugi::xml_attribute_iterator ait = it->attributes_begin(); ait != it->attributes_end(); ++ait)
{
std::cout << " " << ait->name() << ": " << ait->value() << std::endl;
}
std::cout << std::endl;
for (pugi::xml_node_iterator sit = node.begin(); sit != node.end(); ++sit)
{
getNodeInfo(*sit);
}
}
}
Esto solo es una muestra de lo que se puede lograr con pugiXML, puedes aprender mucho más leyendo la documentación oficial. Se recomienda leer acerca de xPath que se ha quedado fuera de este artículo.
Sitio oficial | pugiXML