4 Crear XML

Es probable que en algún momento queramos crear nuestros propios documentos XML para guardar o compartir información de nuestras aplicaciones.

Por ejemplo, Code::Blocks guarda la estructura de un proyecto en un fichero XML, o podemos generar documentos EPUB, que contienen internamente varios documentos XML.

El formato XML puede ser muy útil para guardar configuraciones de opciones de usuario, bases de datos, historiales de valores de entrada, etc.

Crear un documento XML

Lo primero que debemos hacer es crear un documento XML nuevo. Para ello disponemos de la función xmlNewDoc, que solo requiere un parámetro con la cadena de versión de XML a usar (en el momento de escribir esto solo existe la versión "1.0"), y nos devuelve un documento nuevo vacío.

libxml2 tiene su propio tipo para manipular caracteres, xmlChar, lo que puede resultar un engorro a la hora de usar cadenas C, o strings de C++.

Podemos hacer cast en cada caso, reinterpret_cast<const xmlChar*>("1.0"), o sencillamente usar la macro que nos proporciona la librería, BAD_CAST.

    xmlDocPtr doc;
    const xmlChar *version = BAD_CAST "1.0";

    doc = xmlNewDoc(version);

Insertar el nodo raíz

Un documento XML sin elementos no nos servirá de mucho, y un documeto xml bien formado debe tener un único nodo raíz. Así que veamos cómo añadir un nodo a un documento.

Lo primero será crear un nuevo nodo. Para ello podemos usar la función xmlNewDocNode, indicando en el primer parámetro el documento. En el segundo indicaremos el espacio con nombre, o NULL si no se necesita. El tercero es el nombre del nodo. El cuarto parámetro es el contenido del nodo.

Si no se especifica un contenido, y el nodo no contiene nodos hijo, se creará una etiqueta vacía. Como añadiremos los nodos hijos más tarde, de momento usaremos el valor NULL para el cuarto parámetro.

    xmlChar documento[] = "aplicacion";
...
    xmlNodePtr nodo_raiz = xmlNewDocNode(doc, NULL, documento, NULL);
    xmlDocSetRootElement(doc, nodo_raiz);

Insertar nodos hijo

El resto de los nodos no los añadiremos de este modo. Para añadir un nodo hijo a un nodo existente usaremos la función xmlNewChild, indicando como primer parámetro el nodo padre, en el segundo el espacio con nombre, o NULL si no se necesita, en el tercero la etiqueta o nombre del nodo hijo y en el cuarto el contenido del nodo, o NULL para no asignar contenido.

Así, para añadir un nodo hijo al nodo raíz usaremos una sentencia de este tipo:

    xmlNodePtr nodoNombres = xmlNewChild(nodo_raiz, NULL, BAD_CAST "nombres", NULL);

Podemos usar el valor de retorno para añadir atributos o nodos hijo a este nuevo nodo.

Por ejemplo:

    xmlNodePtr nodo = xmlNewChild(nodoNombres, NULL, BAD_CAST "valor", BAD_CAST "Eusebio");

La función xmlNewTextChild insertará un nodo hijo, pero en el texto se sustituirán los caracteres especiales por sus entidades escapadas, como & por &amp; y < por %amp;lt;, etc.

Por ejemplo:

    xmlNewTextChild(nodo_raiz, NULL, BAD_CAST "texto", BAD_CAST "Uno nodo que contiene <texto>");

Insertará un nodo con la etiqueta "texto", pero el texto se sustituirá por "Uno nodo que contiene &lt;texto&gt;".

Insertar atributos

Los nodos de atributo se añaden de una manera parecida. Para añadir un atributo a un nodo usaremos la función xmlNewProp, indicando como primer parámetro el nodo al que añadiremos el atributo, en el segundo el nombre del atributo y en el tercero el contenido.

    xmlNewProp(nodo, BAD_CAST "pordefecto", BAD_CAST "Si");

Si lo que queremos es añadir un atributo a un nodo usaremos la función xmlNewDocProp. Esta función crea un nodo de tipo atributo y usa los mismos parámetros que xmlNewDocNode, menos el segundo, correspondiente al espacio con nombre.

Después de creado hay que añadirlo al nodo correspondiente. Para hacerlo usaremos la función xmlAddChild, para añadirlo como hijo, indicando en el primer parámetro el nodo de atributo y en el segundo el nodo en el que hay que insertarlo.

    xmlNodePtr n = xmlNewDocNode(doc, NULL, BAD_CAST "prueba", BAD_CAST "Un valor X");
    xmlAddChild(nodo_raiz, n);

Insertar nodos hermanos

También podemos usar la función xmlAddSibling para añadir hermanos a un nodo determinado, o si es necesario especificar un orden, disponemos de las funciones xmlAddNextSibling y xmlAddPrevSibling para añadirlos a continuación o antes de un nodo determinado.

    n = xmlNewDocNode(doc, NULL, BAD_CAST "Valor2", BAD_CAST "Un valor Y");
    xmlAddSibling(m, n);
    n = xmlNewDocNode(doc, NULL, BAD_CAST "Valor0", BAD_CAST "Un valor Z");
    xmlAddPrevSibling(m, n);

Variantes

Por otra parte, disponemos de la función xmlNewDocNodeEatName que funciona igual que xmlNewDocNode con la diferencia de que la cadena apuntada por el parámetro name con el nombre del nodo se usa directamente, en lugar de hacer una copia.

Esto puede resultar útil en el caso de que usemos muchos nodos con el mismo nombre, o cuando los nombres estén almacenados en un array.

    char name[30] = "numero";
    xmlNodePtr nodoName = xmlNewDocNodeEatName(doc, NULL, BAD_CAST name, BAD_CAST "29");
    xmlAddChild(nodo, nodoName);

Insertar comentarios

Un comentario se crea mediante la función xmlNewDocComment y requiere como primer argumento el documento, y como segundo el texto del comentario.

El valor devuelto es un puntero a un nodo, y se inserta del mismo modo que cualquier otro nodo.

    // Insertar un comentario para el documento:
    comentario = xmlNewDocComment(doc, BAD_CAST "Historial de valores");
    xmlAddChild(xmlNodePtr(doc), comentario);

Insertar nodos de texto

Para insertar texto dentro de un elemento podemos usar las funciones xmlNewDocText, indicando en el primer parámetro el documento y en el segundo la cadena de texto y xmlNewDocTextLen, que permite un tercer parámetro con la longitud de la cadena, de modo que no tiene por qué estar terminada con cero.

    char cadena[128];
    std::strcpy(reinterpret_cast<char*>(cadena), "Cadena mas larga de lo necesario que no se usara completa.");
    xmlNodePtr nodo7 = xmlNewDocTextLen(doc, reinterpret_cast<xmlChar*>(cadena), 17);
    xmlAddChild(nodo, nodo7);

Hay que tener en cuenta que los ficheros xml no necesitan estar formateados, es decir, no hay necesidad de que cada nodo esté en una línea diferente ni que estén tabulados. Los ficheros xml generados usando las funciones expuestas aquí tendrán los nodos en una misma línea.

Existen programas para crear versiones de ficheros xml más amigables y fáciles de leer, y si los abrimos en un navegador los podremos leer fácilmente.

En cualquier caso, si solo vamos a usar los ficheros xml para intercambio de datos entre aplicaciones, que cada nodo esté o no en líneas separadas es indiferente,

Liberar recursos

Hemos estado creando estructuras en memoria dinámica: el documento, nodos, atributos, etc. Por lo tanto deberemos liberar esa memoria cuando ya no sea necesario usar esas estructuras.

Cada vez que se añade un nodo a un documento es insertado en el árbol del documento, por lo que no tendremos que liberar ambos.

Las funciones para liberar nodos y documentos son recursivas, es decir, su liberamos un nodo se liberarán todos sus nodos hijos y sus atributos, y lo mismo sucede si se libera el documento.

Para liberar un documento usaremos la función xmlFreeDoc, para liberar un nodo xmlFreeNode y para liberar un atributo xmlFreeProp.

Los nodos o atributos que se hayan añadido a un documento serán liberados al liberar el documento. Solo será necesario liberar nodos o atributos que no estén ligados a ningún documento. Para el ejemplo anterior, entonces, bastará con liberar los siguientes recursos:

    xmlFreeDoc(doc);

Estas funciones también sirven para liberar listas de nodos y atributos.

Guardar documento

Para guardar un documento XML que se almacena en memoria usaremos la función xmlSaveFile, indicando el nombre del fichero y el documento a almacenar.

También podemos usar la función xmlSaveFileEnc, que admite un tercer parámetro para indicar la codificación de caracteres a utilizar.

    xmlSaveFileEnc("opciones.xml", doc, "UTF8");

Si para el nombre de fichero se especifica "-", en lugar de guardar el fichero se enviará a la salida estándar.

Si queremos tener algo más de control sobre el fichero de salida podemos usar las funciones xmlDocDump indicando en el primer parámetro un puntero FILE abierto para escritura, y en el segundo el documento a volcar.

También podemos volcar el documento a un buffer en memoria mediante xmlDocDumpMemory o xmlDocDumpMemoryEnc. Para la primera hay que indicar como parámetros el documento, un puntero a un array de xmlChar y un puntero a un int en el que se nos devolverá el tamaño del buffer creado. En el caso de la segunda función, un cuarto parámetro indica la codificación de caracteres:

    xmlChar *mem;
    int tam;
    xmlDocDumpMemoryEnc(doc, &mem, &tam, "UTF8");
    std::cout << mem << std::endl;
    xmlFree(mem);

Corre de nuestra cuenta liberar el buffer mediante la función xmlFree.

Ejemplo 7

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 7 xml004.zip 2024-02-13 1708 bytes 56