Introducción

Logo libxml2

XML es un lenguaje de marcado de propósito general. Se usa para crear documentos destinados al intercambio de datos entre diferentes sistemas.

Es decir, normaliza el modo en que se representan los datos de modo que diferentes sistemas puedan acceder a ellos, leerlos, modificarlos, procesarlos o representarlos.

Por ejemplo, XHTML es el formato XML para contener documentos HTML, que se usan para representar páginas web. Otro ejemplo es XLSX, que es el formato para hojas de cálculo, que usa Excel, y que usa internamente documentos XML.

Se trata de documentos de texto, con un formato estándar que incluye marcas y datos.

Cada elemento está compuesto por dos etiquetas, una de inicio y otra de final. Cada etiqueta se escribe entre '<>', y contienen un identificador. El identificador de la etiqueta de final empieza con '/'. Cada uno de los elementos puede contener otros elementos, o datos en formato de texto.

Por ejemplo:

<?xml version="1.0" encoding="UTF-8"?>
<cursos>
<!-- Cursos con Clase -->
  <curso lang="es">
   <titulo>Curso C++</titulo>
    <capitulo orden="1">
      <numero>0</numero>
      <nombre>Toma de conctacto</nombre>
    </capitulo>
    <capitulo orden="2">
      <numero>1</numero>
      <nombre>Variables I</nombre>
    </capitulo>
  </curso>
  <curso lang="es">
    <titulo>Curso MySQL en C++</titulo>
    <capitulo orden="1">
      <numero>0</numero>
      <nombre>Prólogo</nombre>
    </capitulo>
    <capitulo orden="2">
      <numero>1</numero>
      <nombre>Conectar/desconectar</nombre>
    </capitulo>
  </curso>
</cursos>

Cabecera

La primera línea, que a veces se omite, indica que se trata de un documento xml, y la versión de xml que se usa. También indica la codificación de caracteres usada, por ejemplo UTF-8. Es una línea especial, y no forma parte de los datos, sino que describe ciertas características del propio documento. Es por eso que no tiene un cierre de etiqueta, y añade los caracteres '?'.

DTD

A continuación puede aparecer un DTD, que es una descripción de la estructura del documento. Esto es opcional, y en el ejemplo anterior no se incluye.

Un DTD empieza con "<!DOCTYPE" y a continuación una descripción de los elementos:

<!DOCTYPE nombre SYSTEM|PUBLIC "fpi" "uri" [
  declaraciones
]>

Por ejemplo, para XHTML, el DTD puede tener este aspecto:

 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

Que nos dice que se trata de un tipo de documento llamado 'html'.

A continuación puede aparecer la palabra 'PUBLIC' para indicar que se trata de un documento público, o 'SYSTEM' si es privado. También puede omitirse.

La siguiente cadena, que es opcional, es un Identificador Público Formal, FPI, que es una cadena pequeña de texto especialmente formateado que puede ser usado para identificar de forma única un documento.

Después estará la propia DTD, que es la descripción del documento. Puede tratarse de una URL interna o externa. En el ejemplo es externa. También puede ser la propia descripción, en cuyo caso estará entre corchetes '[]', o puede ser una combinación de ambas.

La descripción contiene declaraciones de entidades, notaciones, elementos y atributos. Si sientes curiosidad, puedes ver la descripción de XHTML en DTD XHTML.

Puedes consultar con más detalle la sintaxis de la descripción en mclibre.org.

El DTD se puede usar para comprobar la sintaxis del documentos XML, describe cada elemento, el tipo de valores que puede tomar, sus posibles atributos, etc.

Árbol

Estructura Cursos
Estructura Cursos

El resto es el propio documento, que tiene una estructura en árbol. El primer elemento es el raíz. En nuestro ejemplo, indicado con la etiqueta "cursos". Dentro de él encontraremos uno o varios elementos, en nuestro ejemplo se indican con la etiqueta "curso", aunque no tienen por qué ser todos iguales. Cada elemento puede tener otros elementos anidados.

Como todas las estructuras en árbol, los elementos se relacionan por parentesco padre-hijo. En nuestro ejemplo, "cursos" es padre de "curso", o "numero" hijo de "capitulo".

Es importante que los elementos no se superpongan, es decir, antes de cerrar cualquier elemento, deberemos cerrar cualquier elemento anidado en su interior. El siguiente ejemplo no está permitido:

<tag1><tag2></tag1></tag2>

Además, el contenido de un elemento estará incluido entre las etiquetas de inicio y final, pero se pueden incluir atributos para un elemento dentro de la etiqueta de inicio. En nuestro ejemplo, "curso" tiene un atributo de lenguaje, y "capitulo" tiene un atributo de orden. Los valores de los atributos siempre se ponen entre comillas.

Si una etiqueta no tiene definida una etiqueta de cierre, el carácter '>' final debe ir precedido de un '/'. Por ejemplo, en XHTML, los elementos "meta" o "br" no tienen las etiquetas de cierre </meta> y </br>, en su lugar deben expresarse así:

<meta charset="UTF-8" />
<br />

Rutas

Carpetas
Carpetas

La estructura en árbol de un documento XML también puede representarse como la de un sistema de archivos, donde las entidades son carpetas.

Veremos que esto nos proporciona una manera muy sencilla y potente de buscar y obtener datos desde un fichero XML, usando rutas (paths) similares a las rutas para localizar ficheros en un sistema de archivos.

Por ejemplo: /cursos/curso[2]/capitulo[1]/nombre.

Pero las rutas XML son mucho más potentes que esto, y permiten seleccionar nodos o grupos de nodos filtrando valores de atributos o de elementos.

Entidades

Las entidades codifican caracteres especiales que no se pueden utilizar en documentos XML. Empiezan con el carácter '&' y terminan con el carácter ';'.

El carácter '<' es ilegal en los valores de elementos y atributos, y debe ser sustituido por la entidad "&lt;" (lt son las iniciales de less than, menor que). Dado que el carácter & se usa para codificar entidades, también es ilegal, y debe sustituirse por &amp; (amp es la abreviatura de ampersand, en español et). También es recomendable, aunque no siempre obligatorio hacer lo mismo con los caracteres '>', ''' y '"':

CarácterEntidad
<&lt;
>&gt;
&&amp;
'&apos;
"&quot;

Nota: como curiosidad, el símbolo '&' es la forma abreviada de la palabra 'et', los dos caracteres E y t unidos, que equivale a la conjunción 'y', en latín.

Comentarios

También se pueden incluir comentarios, que empiezan con "<!--" y terminan con "-->". Por ejemplo:

<!-- Comentario jocoso -->

Secciones CDATA

A veces no queremos que ciertos datos sean analizados por el parser xml. Por ejemplo, un dato puede contener caracteres como <, & o >, y necesitamos que no se interpreten como etiquetas, es decir, que el analizador no los procese.

En esos casos podemos usar una sección CDATA. Esas secciones empiezan con la cadena <![CDATA[, y terminan con la cadena ]]>. Todos los caracteres en el interior de una de estas secciones serán ignoradas por el analizador, y podrán recuperarse literalmente.

Por ejemplo:

<seccion_cdata>
<![CDATA[
#include <iostream>
#include <libxml/xpath.h>

int main() {
    xmlDocPtr doc;

    LIBXML_TEST_VERSION;

    // Crear el documento:
    doc = xmlNewDoc(version);
...
]]>
</seccion_cdata>

Por supuesto, hay algunas reglas. La cadena no puede contener ]]>, esto implica que no se pueden anidar secciones CDATA. Tampoco se pueden añadir espacios en las cadenas de marca de inicio y final de secciones CDATA.

Reglas para nombres

Los identificadores XML deben cumplir las siguientes reglas:

  • Los nombres de elementos XML distinguen mayúsculas de minúsculas.
  • Deben empezar con una letra o un '_'.
  • No pueden empezar con las letras xml (mayúsculas o minúsculas).
  • Pueden contener letras, dígitos, guiones, guiones bajos y puntos, pero no espacios.

Espacios con nombre

Como sucede en C++ y en otros lenguajes actuales, cuando se usan librerías es frecuente que se produzcan conflictos de nombres, es decir, que distintos tipos de documentos XML tengan identificadores de elementos iguales.

Para evitar estos conflictos XML también dispone de espacios con nombre (namespaces).

Los espacios con nombre se definen mediante un atributo 'xmlns', añadiendo un ":" y el identificador del espacio con nombre, al que se le asigna la uri en la que se definen los elementos que pertenecen a ese espacio con nombre. Este es un ejemplo extraído del formato EPUB:

<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
  <dc:identifier id="BookId" opf:scheme="UUID">urn:uuid:"a549e605-a4bf-4165-b1a1-f85312321237"</dc:identifier>
  <dc:language>es-ES</dc:language>
...
</metadata>

En este ejemplo se definen dos espacios con nombre: 'dc' y 'opf'. Dentro del elemento "metadata" podremos usar las etiquetas de DublinCore, con el identificador "dc" y las de W3C, con el identificador opf.

Las etiquetas de cada espacio con nombre irán precedidas con el identificador del espacio y ':'. Por ejemplo, dc:identifier, dc:language o opf:scheme.

Ejemplos

En el artículo sobre formato epub vimos que se usan varios tipos de documentos XML internamente.

También son comunes como respuestas en APIs web, en configuración de aplicaciones y juegos, en datos de aplicaciones móviles, en documentos de contabilidad y facturación, etc.

Ya hemos mencionado que se usan en ficheros de hojas de cálculo, como "xlsx", o en documentos de texto con formato, como "html" o "docx", etc.

Para mayor información sobre XML puedes consultar XML Tutorial.

Debido a que el uso de documentos XML está muy extendido, es muy interesante conocer una librería que nos permita leer, escribir y procesar este tipo de documentos desde nuestras aplicaciones. Hay varias de esas librerías. En éste curso se trata sobre una de esas librerías: libxml2.

Descargar las librerías de desarrollo

Para usar la librería libxml2 desde nuestros programas C/C++ necesitaremos la versión de desarrollo, que contiene:

  • La librería de enlazado dinámico "libxml2-2.dll".
  • Otras dependencias, como "liblzma-5.dll", "zlib1.dll" o "libiconv-2.dll".
  • Las librerías de enlazado estáticas para generar ejecutables que no usen DLLs. Más grandes, pero autónomos.
  • Las librerías de enlazado estáticas para generar ejecutables que usen DLLs. Más pequeños, pero con dependencias.
  • Los ficheros include, necesario para crear el código fuente.

La forma más sencilla de obtener todos estos ficheros es usar MSYS. Disponemos de un artículo que explica cómo instalar MSYS y algunos comandos básicos.

Desde la consola de MSYS bastará con ejecutar el comando:

pacman -S mingw-w64-x86_64-libxml2

Configurar Code::Blocks

Para que Code::Blocks pueda encontrar los ficheros necesarios para compilar programas que usen libxml2 tendremos que configurar algunas variables globales. Asumiendo que hemos instalado MSYS en C:\msys64, tendremos que añadir las siguientes variables globales desde "Settings->Global variables":

  • iconv
    • base: C:\msys64\mingw64
    • include: C:\msys64\mingw64\include
    • lib: C:\msys64\mingw64\lib
    • bin: C:\msys64\mingw64\bin
  • libxml
    • base: C:\msys64\mingw64
    • include: C:\msys64\mingw64\include\libxml2
    • lib: C:\msys64\mingw64\lib
    • bin: C:\msys64\mingw64\bin
  • zlib
    • base: C:\msys64\mingw64
    • include: C:\msys64\mingw64\include
    • lib: C:\msys64\mingw64\lib
    • bin: C:\msys64\mingw64\bin

Además, en cada proyecto que necesitemos usar estas librerías deberemos añadir las siguientes opciones en "Build options":

  • En "Linker settings: xml2.dll o libxml2.dll.a para usar la librería DLL, o bien "xml2" o "libxml2.a" para usar la librería estática (?).
  • En "Search directories":
    • Compiler: añadir "$(#libxml.include)" y "$(#iconv.include)".
    • Linker: añadir "$(#libxml.lib)" y "$(#iconv.lib)".

Nota: aún no se cómo usar librerías estáticas.

Códigos de página

Es habitual que los documentos XML usen la codificación de caracteres UTF-8, por lo que nuestras aplicaciones de consola mostrarán incorrectamente algunos caracteres, como las eñes o los acentos.

Para evitar esto podemos añadir a nuestros fuentes el fichero de cabecera "windows.h", que nos permitirá usar la función SetConsoleOutputCP y usar la codificación UTF-8 para las salidas por pantalla:

#include <windows.h>
...
    SetConsoleOutputCP(CP_UTF8);