EZINE HISPABYTE

Parseando XML con Qt

QXML es un módulo de la librería de Trolltech Qt (sí sí, la misma que el otro doc :-) que sirve para analizar sintácticamente documentos en XML. El módulo XML está disponible en la versión Enterprise y GPL de Qt. Debo decir que esto está totalmente orientado a Qt3, para Qt4 han cambiado la librería. Ya hay la documentación en internet, pero he pensado que es mejor tener algo que podemos usar que algo que podremos usar.
Para hacer este documento asumiré que sabéis C++ (más o menos ;-) y que conocéis las normas de XML, no es complicado pero es necesario.

¿Para qué XML?

Bueno, esto está muy explicado en internet. Básicamente porque es simple y potente. Simple porque no requiere un estudio muy importante, por lo menos por lo que refiere a XML en sí, las tecnologías que lo rodean sí que dan para más aprendizaje :). Potente porque da muchas posibilidades.

Para dar una idea de lo que representa, daré unos cuantos ejemplos:

  • XHTML, que es una versión más moderna de HTML ceñida a los parámetros de XML orientada a estandarizar el formato de este y así ahorrar problemas a los navegadores por malas artes de diseñadores.
  • XSLT, que está pensado para representar XML.
  • MathML, que está pensado para representar lógica y gráficamente funciones matemáticas.
  • SVG, formato de gráficos vectoriales.
  • y un largo etcétera.

Para comprobar la sintaxis de estos tenemos los ficheros DTD que, mediante un parser, nos comprueba si un archivo XML se ciñe a lo que marca el DTD, generalmente la especificación estándar.

¿Como funciona esto?

Bueno, hasta aquí es todo bonito. Tenemos unos textos puestos expresamente para que nosotros los leamos, parece fácil...
Tenemos a nuestra disposición 2 formas de parsearlo. Tenemos SAX y tenemos DOM. La diferencia básica entre ambos es que DOM da una idea global del documento mientras que SAX hace el parse por trozos. Eso significa que SAX es un poco más ligero en memoria pero es más limitado.

Para explicaros como funciona, haremos un programa que nos gestione haremos una lista de la compra. Es demasiado freak ir al super con una lista XML, no lo probéis, es sólo un ejemplo, la gente normal no lo entenderían y se asustarían.

El programa tendrá esta forma:

	<!DOCTYPE ListaDeLaCompraML>
	<lista>
		<producto cantidad="5" tipo="agua" />
		<producto cantidad="1" tipo="leche" />
		<producto cantidad="3" tipo="tomates" />
	</lista>

La lista tiene tags a un nivel sólo, podéis poner los que queráis, es para hacerlo lo más comprensible para vosotros.

Los ejemplos los he hecho para consola. Habría podido usar interfaz gráfica, pero pensé que como menos líneas tuviera el programa más claro sería y no es necesária una interfaz gráfica.

Usando DOM con Qt

Qt nos ofrece muchas clases para DOM y es posible que nos perdamos entre tantas.
La clase principal es QDomNode. TODA la resta son hijas de esta.

De las hijas, tenemos principalmente:

  • QDomDocument, que representa un documento entero.
  • QDomElement, que representa un elemento. El elemento principal siempre es el hijo del documento.
  • QDomAttr, que representa un atributo

Hay más, pero esas son las más utilizadas. Para entender un poco la nomenclatura, el esquema sería este:

	<documento>
		<elemento atributo="valor">
			texto
			<!-- comentario -->
		</elemento>
	</documento>

Escribiendo un documento XML con DOM

Para crear el documento pasaremos por 3 pasos:

1. Crear un documento y un hijo para este.

	QDomDocument doc("ListaDeLaCompraML"); 
	QDomElement root = doc.createElement("lista"); 
	doc.appendChild(root); 

La primera linea crea un documento, ListaDeLaCompraML es el DOCTYPE. La segunda crea un elemento para el documento y la tercera lo añade al árbol de doc como hijo de éste.

2. Crear hijos para root, para dar contenido al fichero

	hijo = doc.createElement("producto"); 
	hijo.setAttribute("tipo","leche"); 
	hijo.setAttribute("cantidad","1");  
	root.appendChild(hijo);

La primera línea crea un elemento para doc, como en la primera parte del programa. Las 2 siguientes atributos para el elemento hijo que acabamos de crear y después lo ponemos como hijo de root. Podéis observar que esta acción es la misma que la primera vez pero en vez de crear un nodo hijo del documento, lo es del nodo raiz.

3. Sacarlo todo por stdout. Podía escribirlo en un archivo también, o donde hubiese querido, el método .toString() nos devuelve un QString, así que tenemos toda libertad. :-)

	cout << doc.toString() << endl;

Leiendo un documento XML con DOM

Esto lo haremos en x pasos:

1. Cargar el archivo en el Documento

	QFile nuestralista(argv[1]);
	if (!nuestralista.open(IO_ReadOnly))
		...
	if (!doc.setContent(&nuestralista)) 
		...
	nuestralista.close();

En la primera línea pasamos argv[1] para que lo abra (es el primer parámetro), después lo abrimos, lo asignamos como contenido a doc y lo cerramos. En los puntos suspensivos tendríamos el código en caso que hubiése algun error.

2. Preparamos el Documento para leerlo.

	docElem = doc.documentElement();
	if(docElem.tagName() != "lista"){
		...
	elem = docElem.firstChild();

Asignamos el documento a un elemento raiz, comprobamos que el tag del elemento raiz sea lista y ponemos el primer hijo de la raiz en elem, que es un elemento.

3. Mediante un bucle pasamos por todos los hijos de la raiz. Como es un documento a 2 niveles, no tendremos que descender más ni usar recursiones.

	while(!elem.isNull()) {
		e = elem.toElement();
		cant = e.attribute("cantidad");
		tipo = e.attribute("tipo");
		cout << "\t- " << cant << " de " << tipo << endl;
		elem = elem.nextSibling();
	}

Mientras elem no sea un nulo, pasaremos de elem que es un QDomNode a e que es QDomElement y de este sacaremos los atributos cantidad y tipo y los pasaremos por stdout.

Leiendo un documento XML con SAX

Para parsear SAX, Qt nos ofrece QXmlReader y QXmlDefaultHandler. Además, tenemos una abstracción de QXmlReader llamada QXmlSimpleReader que hace lo mismo que QXmlReader pero con menos opciones y de forma mas simple :).
Para parsear un archivo con SAX haremos una clase hija de QXmlDefaultHandler añadiendo como método las funciones que queramos. Para hacer este ejemplo sólo he necesitado startDocument y startElement. Existen ambos con end (endDocument y startElement) entre otros. Podéis encontrar una lista completa en la documentación oficial de Qt.

Este ejemplo se compone de 2 partes principalmente. La clase y el main.

La clase sería asi:

	class ClaseLista : public QXmlDefaultHandler {
	public:
		bool startDocument(){
			cout << "Tienes k comprar: " << endl;
			return true;
		}
		
		bool startElement( const QString&, const QString&,
                         const QString &name, const QXmlAttributes &attrs ) {
			if(name == "producto" ){
				QString cant, tipo;
				for( int i=0; i<attrs.count(); i++ ) {
					if( attrs.localName( i ) == "cantidad" )
						cant = attrs.value( i );
					else if( attrs.localName( i ) == "tipo" )
						tipo = attrs.value( i );
				}
				cout << "\t- " << cant << " de " << tipo << endl;
			}
			
			return true;
		}
	};

En startDocument() ponemos lo que queremos que haga cuando empieza el documento y startElement() cuando empieza un Elemento. El parámetro name nos dice el tagName. El bucle de dentro de la condición navega por los atributos, creo que no tiene ninguna dificultad.
El main queda así:

	int main(int argc, char *argv[]){
		if(argc!=2)
			...
		
		ClaseLista control;
		QFile nuestralista(argv[1]);
		QXmlInputSource documento(nuestralista);
		QXmlSimpleReader lector;
		
		lector.setContentHandler(&control);
		lector.parse(documento);
		return EXIT_SUCCESS;
	}

Esto lo que hace es comprobar que haya los parámetros correctos, asignar el primer parámetro a nuestralista (como antes, en DOM). Declaramos y definimos un documento de entrada, un lector, pasamos la clase que hemos hecho para que nos avise y lo ponemos a parsear (analizar el documento).

Archivo adjunto: qxml.tar.bz2 (se explica luego)

Ya acabo

Pues esto ha sido todo, creo que no es muy complicado. Si tenéis alguna duda ya sabéis, podéis contactar conmigo o postear en el foro. He adjuntado un ejemplo para cada uno de los apartados que he tratado más una aplicación gráfica que lo hace más o menos todo, por si tenéis curiosidad o algo :-P. Si habéis tenido algun problema con alguna explicación, los ejemplos también están explicados, quizá os ayude en algo esto.

Un saludo a todos y hasta la próxima :-).


Boooring ( boooring@gmail.com ) - http://proli.net/