Módulos Perl para XML

Carlos Escribano


Introducción
Siguiendo los estándares
DOM y XPath
XSLT
Transformaciones no estándares
Funciones en LibXSLT
Librerías de etiquetas
XPathScript
Generación de documentos XML
SAX
Factorías, filtros y tuberías
Procesadores
Generadores
Filtros
Manejadores
Alternativas a DOM y SAX
PYX
Grove
XML::Filter::Dispatcher
XML::Twig
Definiciones de documentos
DTD
XML Schema
Conversiones
Validación
DTD
XML Schema
RelaxNG
Schematron
Colofón

Introducción

Vamos a dar un repaso a los módulos disponibles en CPAN para tratar XML. La biblioteca CPAN está continuamente actualizándose, así que muchas de las impresiones obtenidas sólo servirán durante un breve período de tiempo y enseguida quedarán obsoletas. Éste es un primer contacto, en absoluto una descripción de los módulos y sus posibilidades, pero puede servir como punto de partida para empezar a buscar cuando necesitamos XML mediante Perl.

El índice de CPAN en fecha 12-12-2004 nos ofrece algo más de 7500 módulos censados. Los relacionados con XML son bastantes; tan sólo un rastreo superficial en el el índice en busca de la cadena XML en el nombre del módulo nos ofrece 257 ocurrencias (entre los módulos principales, 1065 si contamos todos). La lista no es exhaustiva, porque un repaso así no se percata de varios proyectos importantes basados en XML, como, por ejemplo, los 38 módulos agrupados en torno a Axkit.

Si nuestra curiosidad se extendiera por los lenguajes de la familia sgml/xml la lista sería evidentemente aún mayor. El nombre del de más éxito, HTML, aparece en 221 módulos de CPAN, el de RSS en 26 y SVG en 13. Hay bastantes lenguajes XML que cuentan con algún módulo dedicado, como SMIL, XUL, XTM, MIDI XML, XMLTV, XMLRPC, WSDL, StateML, ebXML, DocBook, TEI, NewsML, AxPoint y VoiceXML.

Estamos pues ante uno de los temas relevantes de CPAN, por lo que el repaso puede ser una tarea larga. Mejor no adentrarnos en la telaraña de CPAN, y limitarnos a las herramientas que sirven para tratar XML en general, no las tecnologías que lo utilizan, por ejemplo en servicios web, o sus lenguages específicos. O sea que dejaremos para otra ocasión Mkdoc o AxKit, HTML o DocBook, y nos centraremos tan sólo en XML.

Y por descontado que no podemos repasarlos todos, aunque sí, aunque sea brevemente, los módulos más importantes.

Siguiendo los estándares

Los estándares XML preveen el tratamiento de los documentos mediante un procesado que sistematiza la información que contiene, y, si fuera necesario, un ulterior procesado que provoca una transformación en otra salida estructurada. La información puede ser utilizada mediante el modelo DOM (con la ayuda opcional del lenguaje XPath), y transformada mediante el lenguaje XSLT.

DOM y XPath

DOM modela los documentos XML en términos de objetos persistentes; Para DOM cada documento XML es un objeto con una estructura jerárquica, contenedor de otros objetos, dispuestos en forma de árbol, que pueden ser a su vez nuevos contenedores de objetos. Cada uno de esos objetos contiene propiedades (nombre, atributos, nodos hijo, ...), y nos provee de métodos para leerlas o modificarlas. El objeto que incluye todo el documento se llama DocumentElement, y cada uno de los componentes del árbol se llama Node. Cada uno de estos nodos a su vez es un objeto (ej. los objetos Element, contenedores de otros nodos, o Text, que contienen texto). Como objetos, disponemos de acceso tanto a los métodos de la clase genérica (ej. Node) como a los de las clases que se se derivan de ella (ej. Element, Text). DOM provee también del concepto de colecciones de nodos.

Los procesadores DOM leen todo el documento y crean en memoria una estructura Perl que modela el contenido, proveyéndonos de métodos mediante los que podemos interactuar con esta estructura. DOM ha tenido tres especificaciones y es difícil encontrar módulos que provean alguna de las últimas funcionalidades, por lo que debemos evaluar con cuidado hasta dónde llegan nuestras necesidades antes de elegir uno.

Veamos con un ejemplo el funcionamiento. Utilizaremos un documento sencillo, sidebar.xml, de un tipo que sirve en la web de AxKit para describir la barra lateral:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE sidebar SYSTEM "sidebar.dtd">
<sidebar><section>
   <title>Mi web</title>
        <item>
                <title>Informacion</title>
                <url>/Informacion/</url>
        </item>
   </section></sidebar>

El módulo XML::DOM nos provee acceso al árbol DOM. En este ejemplo veremos la estructura del nodo raíz:

#!/usr/bin/perl -w

use XML::DOM;
my $parser = XML::DOM::Parser->new;
my $tree = $parser->parsefile('sidebar.xml');
my $node=$tree->getDocumentElement();

print $node->getNodeName . ": " . $node->getNodeType . "\n";
foreach my $child ($node->getChildNodes()) {
	print "\t" . $child->getNodeName . ":" . $child->getNodeType . "\n";
}

El método parsefile de XML::DOM::Parser nos devuelve un objeto de la clase Document, o sea crea el árbol del documento. Una invocación al método getDocumentElement nos devuelve un objeto DocumentElement, objeto contenedor que representa al nodo raíz, en nuestro ejemplo el nodo sidebar, puesto que se trata de un documento del tipo sidebar. Los métodos getNodeName y getNodeType nos devuelven la información del nombre del nodo y su tipo. El método getChildNodes nos devuelve un objeto NodeList, que es un un arreglo de los objetos Node hijos del nodo. Una interrogación posterior a cada elemento nos devolverá también el nombre y el tipo:

sidebar: 1
        section:1

El nodo raíz sólo tiene un nodo hijo. El tipo 1 (NODE_ELEMENT) corresponde a un nodo Element, o contenedor de otros nodos. La propiedad NodeName en los nodos Element es el nombre del elemento xml, o etiqueta, en este caso sidebar y section.

El tutorial de F. Javier García Castellano Parsers XML nos muestra más ejemplos de uso de DOM en Perl.

DOM siempre utiliza el punto de vista de un nodo, y, como vemos, se mueve despacio dentro de él, puesto que provee acceso a sus componentes en un sistema simple: El conjunto de sus nodos hijo, el primero de ellos, el siguiente, el anterior, el último, el conjunto de sus nodos que están dentro de él y tienen un determinado nombre. Como el problema es llegar al nodo que deseamos, se articuló un lenguaje aparte, XPath, para proveer de un sistema de búsqueda de nodos. Pero hasta DOM3 XPath no ha estado integrado dentro del estándard DOM, por lo que una implementación estricta de las versiones anteriores puede no soportar XPath.

XPath describe la posición del nodo en términos de un sistema de ficheros. Llegar a un nodo es indicar una ruta, y para especificarla es posible utilizar algunos comodines. El módulo XML::XPath nos provee de XPath1:

#!/usr/bin/perl -w
use XML::XPath;

my $xp = XML::XPath->new( filename => 'sidebar.xml' );
my $nodeset = $xp->find('//section/title');

foreach my $node ($nodeset->get_nodelist) {
                print XML::XPath::XMLParser::as_string($node), "\n";
        }

Éste es quizá el ejemplo más simple del uso del módulo XML::XPath. Con el método new procesamos el documento externo, es decir, creamos las estructuras Perl que lo modelan, y con el método find recuperamos una lista de nodos que cumplen una condición; en ese caso comenzamos la búsqueda en cualquier posición (la doble barra inicial indica eso), y el nombre del nodo es title:

<title>Mi web</title>

El veterano XML::DOM se limita a DOM1, por lo que, entre otras cosas, no soporta espacios de nombres ni XPath, aunque si deseamos esta última posibilidad, XML::DOM::XPath la añade como extensión del módulo. Pero para un uso real de DOM seguramente nos decantaremos por otros procesadores DOM que al menos soporten espacios de nombres, o sea que como mínimo cumplan con DOM2.

Desde Perl podemos acceder a tres importantes procesadores DOM que proveen XPath, todos ellos librerías C o C++, sobre las que hay construidos interfaces:

XML::GDOME, interfaz de la librería Gdome, a su vez basada en libxml2; cumple DOM2, con las opciones de XPath de DOM3.

XML::Xerces, el wrapper de perl sobre la librería Xerces C++. Soporta DOM2 con algunas implementaciones de DOM3. Pathan-P implementa XPath1 sobre la librería Pathan, a su vez basada en Xerces-C++.

XML::LibXML, interfaz de la popular librería libxml2, que soporta DOM2 e incluye Xpath mediante extensiones al estandard. El tutorial XML::LibXML - An XML::Parser Alternative de Kip Hampton es un buen punto de partida para su uso.

XSLT

Si en lugar de la manipulación directa del árbol DOM se desea una transformación, es decir actuar como filtros que provocan una salida a partir de la entrada XML, el sistema estándard de lograr esto es XSLT, un lenguaje de transformaciones mediante la aplicación de conjuntos de reglas que se mueven en el árbol con la ayuda de XPath, llamados hojas de estilo.

Este minimalística hoja de estilo XSLT, sidebar.xsl, extraerá de los documentos de tipo sidebar una relación en texto plano de las secciones y sus títulos. No comentaremos la sintaxis de XSLT, que queda fuera del tema de esta introducción:

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:sb="http://axkit.org/NS/sidebar-1.0" >

	<xsl:output method="text"/>

	<xsl:template match="/">
		<xsl:for-each select="sidebar/section">
			<xsl:text>Section: </xsl:text>
			<xsl:value-of select="title"/>
		</xsl:for-each>
	</xsl:template>

</xsl:stylesheet>

Hay varias utilidades que nos permiten generar la transformación directamente desde la línea de comandos, como ésta xsltroc, de libxslt:

xsltproc sidebar.xsl sidebar.xml
Section: Mi web

Perl nos provee de diversas herramientas si deseamos un control más detallado que la simple línea de comandos. En este ejemplo obtenemos con un XML::XSLT un resultado similar:

#!/usr/bin/perl -w
use XML::XSLT;
my $xslt = XML::XSLT->new ("sidebar.xsl");
$xslt->transform ("sidebar.xml");
print $xslt->toString;

XML::XSLT obtiene los árboles DOM, tanto el del documento XML como el de la hoja de estilos, de XML::DOM::Parser, y por tanto la entrada puede personalizarse pasándole los argumentos adecuados a este módulo. También es posible pasar argumentos (variables) a la hoja de estilos, mediante el método process. El tutorial Introducción al módulo XML::XSLT para perl es un buen punto de partida para utilizarlo.

El veterano XML::XSLT no es el único procesador de XSLT existente en Perl. Al contrario, contamos otros más eficientes y potentes. Los más importantes son:

Transformaciones no estándares

Hay en Perl varias alternativas al estándard XSLT para transformar documentos. Las más importantes son las extensiones de funciones en LibXSLT, las librerías de etiquetas y XPathScript.

Funciones en LibXSLT

XML::LibXSLT provee de una extensión, el método register_function, para injectar funciones de Perl en las hojas de estilo XSLT. En este ejemplo incluiremos la fecha y hora del sistema en nuestro proceso de la barra lateral. El método requiere como parámetros la definición de un espacio de nombres para la función o funciones(aquí sera urn:funciones, el nombre local del elemento XML que invocará a la función (timestamp), y una referencia a la función (hora).

#!/usr/bin/perl -w
use XML::LibXML;
use XML::LibXSLT;
XML::LibXSLT->register_function("urn:funciones", "timestamp", \&hora);
my $parser = XML::LibXML->new();
my $xslt = XML::LibXSLT->new();
my $stylesheet = $xslt->parse_stylesheet_file("sidebar2.xsl");
my $source_doc = $parser->parse_file("sidebar.xml");
my $result = $stylesheet->transform($source_doc);
print $stylesheet->output_string($result);
sub hora {
	return scalar localtime
}

La rutina de nuestro ejemplo se limita a retornar el valor de la función interna localtime, pero las posibilidades están abiertas a todo CPAN, como es obvio. La hoja de estilos ligeramente modificada para incluir este nuevo elemento y su espacio de nombres queda así:

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:sb="http://axkit.org/NS/sidebar-1.0"
  xmlns:funciones="urn:funciones" >
	<xsl:output method="text"/>
	<xsl:template match="/">
		<xsl:text>Mis secciones hoy </xsl:text>
		<xsl:value-of select="funciones:timestamp()"/>
		<xsl:text>:&#xa;</xsl:text>
		<xsl:for-each select="sidebar/section">
			<xsl:value-of select="title"/>
		</xsl:for-each>
	</xsl:template>
</xsl:stylesheet>

y el resultado:

Mis secciones hoy Fri Jan 28 10:51:05 2005:
Mi web

Librerías de etiquetas

El siguiente paso es agrupar unas cuantas funciones en un módulo externo, que se reserva el espacio de nombres común a todas ellas. Estos módulos se conocen como librerías de etiquetas (taglibs). En Perl sólo es capaz de utilizarlas el procesador Apache::AxKit::Language::XSP, integrado en AxKit, y puesto que AxKit es a su vez un módulo de Apache, sólo pueden usarse en entornos web.

Para hacerlo basta con registrar el espacio de nombres y su módulo asociado mediante directivas del archivo de configuración de Apache:

AxAddXSPTaglib My::TagLibPersonalizado

Ese procesador de AxKit, en compatibilidad con Cocoon, provee de una clase genérica de librerías de etiquetas por defecto que permiten inyectar código de Perl, a la que se han añadido unas cuantas extensiones para tareas habituales, algunas también compatibles con las equivalentes de Cocoon: AxKit::XSP::WebUtils, para diversas tareas del entorno web, AxKit::XSP::PerForm, para formularios de datos, AxKit::XSP::Auth para gestión de accesos, AxKit::XSP::BasicSession, AxKit::XSP::Session y AxKit::XSP::Minisession para gestión de sesiones web, AxKit::XSP::Cookie para cookies, AxKit::XSP::CharsetConv para conversión de caracteres entre códigos, AxKit::XSP::QueryParam, para utilización de las funciones de Apache request, AxKit::XSP::Exception, para gestión de exceptciones, AxKit::XSP::Sendmail, para incluir funciones de correo, AxKit::XSP::MD5, para incluir funciones de resúmenes MD5, AxKit::XSP::ESQL para proveer de consultas SQL, AxKit::XSP::LDAP, para acceso a datos de directorio LDAP. También hay algunas para tareas más concretas, como AxKit::XSP::Handel::Cart para mantener cartas de precios, AxKit::XSP::Blog para mantener bases de datos de Blogs, y AxKit::XSP::Wiki, para levantar un wiki.

Existen cuatro esqueletos para contruir nuestras propias librerías de etiquetas, Apache::AxKit::XSP::Language::SimpleTaglib, Apache::AxKit::Language::XSP::TaglibHelper, GCT::XSP::ActionTaglib, y Apache::AxKit::Language::XSP::ObjectTaglib, con diversas diferencias en el enfoque. Dos estupendos tutoriales de Barrie Slaymaker, XSP, Taglibs and Pipelines y Taglib TMTOWTDI nos guirán en el proceso si utilizamos cualquiera de los dos primeros métodos.

XPathScript

XPathscript es un lenguaje para transformaciones de documentos XML, que no utiliza DOM para procesar el documento XML, sino un subconjunto de XPath, lo que le hace potente y rápido. Las hojas de estilo XpathScript son plantillas del documento final que incluyen intercaladas instrucciones de procesamiento con funciones de XPathScript, aunque el procesador también soporta insertar código Perl normal. Aunque su uso habitual será con el procesador Apache::AxKit::Language::XPathScript, de AxKit, también puede utilizarse fuera de este entorno, con XML::XPathScript.

En este ejemplo creamos un raquítico documento html con el título de la primera seccción de nuestro documento habitual, sidebar.xml:

#!/usr/bin/perl -w
use XML::XPathScript;
open(IN, "sidebar.xml");
my $xml = join '', <IN>;
close(IN);
my $stylesheet = q|
<html><body><p>
<%= findvalue("/sidebar/section/title/text()") %>
</p></body></html>
|;
my $xps = XML::XPathScript->new(xml => $xml, stylesheet => $stylesheet);
$xps->process;

El módulo exige que la hoja de estilos y el archivo de entrada le sean entregados mediante un escalar, por lo que hemos debido importar nuestro archivo antes. La hoja la hemos incluido tal cual:

<html><body><p>
Mi web
</p></body></html>

La guía de Matt Sergeant XPathScript - A Viable Alternative to XSLT? es el punto de partida obligado para conocer este lenguaje.

Generación de documentos XML

Las transformaciones suelen conllevar la conversión de un documento XML de un tipo en otro. Esa es la tarea principal de XSLT. Si lo que deseamos es generar un documento XML como parte del tratamiento de uno ya existente, los procesadores importantes de Perl proveen todos de métodos para salida XML; algunos de ellos van apareciendo en el código de los ejemplos de este trabajo.

En muchas ocasiones sin embargo es necesario crear documentos XML completamente nuevos; un uso típico es la utilización en nuestro programa de una fuente no XML que deseamos convertir a este formato.

XML::Writer es el más popular de los módulos de generación de documentos XML. Su sencilla API nos permite ir creando elementos entendidos como etiquetas de inicio y fin y bloques de caracteres, en la forma más simple de describir cómo se construye el texto:

#!/usr/bin/perl -w
use XML::Writer;
my $writer = XML::Writer->new();
$writer->xmlDecl('ISO-8859-1');
$writer->doctype('sidebar','' ,'sidebar.dtd');
$writer->startTag('sidebar');
$writer->startTag('title',
	"tipo" => "simple");
$writer->characters('Mi web');
$writer->endTag('title');
$writer->endTag('sidebar');
$writer->end();

Con lo que obtendremos un particular (y no válido) texto XML del tipo sidebar:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE sidebar SYSTEM "sidebar.dtd">
<sidebar><title tipo="simple">Mi seccion</title></sidebar>

Si queremos otro enfoque, podemos usar XML::Generator, donde el entramado XML se construye directamente con estructuras Perl:

#!/usr/bin/perl -w
require XML::Generator;
my $gen = XML::Generator->new();
print $gen->xmldtd([ 'sidebar', 'SYSTEM', '', 'sidebar.dtd' ]);
print $gen->sidebar($gen->title({ tipo => 'simple' }, 'Mi web'));

Hay otros módulos que provocan salidas XML de tipos específicos. XML::Edifact es un módulo conversor a XML de documentos en formato UN/EDIFACT y viceversa. XML::CSV convierte documentos de texto CSV en un formato XML. DBIx::XML_RDB obtiene un texto XML a partir de datos de la información de cualquier base de datos que pueda utilizar la interfaz DBI.

Hay también unos cuantos manejadores SAX que crean código XML a partir de eventos SAX, y otros generadores SAX que producen eventos de material no XML, con lo que el abanico se amplía considerablemente.

Véase el tutorial de Kip Hampton Generating Dynamic SVG From Non-XML Data para un ejemplo más extenso del uso de XML::Writer. El tutorial Manipulating Documents: The Document Object Model (DOM) nos presenta un ejemplo con XML::Xerces::DOMParse.

SAX

Los tratamientos de flujo aportaron desde fecha temprana otro modelo de datos de los documentos XML. Ven los documentos como series ordenadas de elementos formales simples: Una etiqueta, un atributo, una cadena de caracteres, ... De esta manera pueden tratarse una sóla vez, y puesto que cada elemento de la serie es un fragmento pequeño del documento original, el proceso requiere pocos recursos y se convierte en una herramienta rápida y eficiente. Su similitud con los componentes de las tuberías de UNIX facilita el uso de varios tratamientos sobre una sola entrada.

SAX es un sistema de tratamiento de flujo de documentos XML creado desde xml-dev. No sólo define el modelo de datos, es decir los elementos que constituirán la serie que describe el documento XML; también define cómo han de ser procesados. El API SAX en Perl está definido por el grupo Perl SAX. SAX ha tenido dos implementaciones mayores, y no todos los módulos de CPAN soportan SAX2.

PerlSAX está bien documentado, y podemos acudir a la excelente descripción de Ken MacLeod Perl SAX 2.0 Binding, para obtener una visión general de su uso.

El modelo de proceso está basado en eventos que se activan al producirse un cambio de elemento en la serie: inicio de elemento XML, encontrada una instrucción de procesamiento, fin de una sección CDATA, etc. Los sistemas SAX más simples constan de un procesador (parser), que es el que lleva a cabo el tratamiento propiamente dicho, generando los eventos, y un manejador (handler), que los recibe y actúa en consecuencia. Esta arquitectura básica puede tener importantes adiciones: Pueden establecerse varios procesadores; en lugar del procesador también puede utilizarse un driver o generador, que es un procesador de documentos no XML que ofrece como salida un flujo XML; por último, los manejadores pueden procesarse en tubería, por lo que se convierten en filtros. Esta arquitectura es muy flexible, y facilita la construcción de aplicaciones a medida.

El manejador XML::Handler::PrintEvents, creado para depuración de procesadores SAX, como su nombre indica vuelca a la salida estandard cierta información de los eventos que recibe, lo que nos permite de paso ver cómo trabaja el procesador SAX.

El procesador XML::SAX::Expat es un procesador SAX2 que se asienta sobre XML::Parser y hace uso de la librería C Expat.

#!/usr/bin/perl -w
use XML::SAX::Expat;
use XML::Handler::PrintEvents;
my $p = XML::SAX::Expat->new(Handler => XML::Handler::PrintEvents->new);
$p->parse_uri("sidebar.xml");

Esta és la manera más simple de trabajar con SAX: se crea una nueva instancia del procesador, enviándole una instancia del manejador como parámetro, y ya se pueden invocar los métodos del procesador, que admite tratar el texto XML contenido en cadenas o archivos.

Vemos que el procesador SAX, a medida que procesa el flujo de datos, va generando los eventos SAX, start_document, start_element, characters, comment, processing_instruction, end_element y end_document , y en cada caso va pasando al manejador diversa información sobre el evento. Véase en la referencia de Perl SAX una información detallada de qué es lo que entrega el procesador:

start_document         Version => [1.0], Encoding => [ISO-8859-1], Standalone => []
start_element          Prefix => [], LocalName => [sidebar], Attributes => [HASH(0x83ca318)], Name => [sidebar], NamespaceURI => []
characters             Data => [
]
characters             Data => [   ]
start_element          Prefix => [], LocalName => [section], Attributes => [HASH(0x83f3614)], Name => [section], NamespaceURI => []
characters             Data => [
]
...

Con cada evento, se llama a un método del manejador. El lado del manejador se limita por tanto a proveer de métodos adecuados a los eventos que se van a producir. Ésta es una versión muy simplificada de lo que conseguíamos con XML::Handler::PrintEvents, en este caso un manejador que simplemente imprime una indicación a medida que recibe eventos de inicio o fin de elementos. El ejemplo procede de la documentación de libxml2, Using PerlSAX:

package MyHandler;

sub new {
        my ($type) = @_;
        return bless {}, $type;
    }

sub start_element {
        my ($self, $element) = @_;
        print "Start element: $element->{Name}\n";
    }

sub end_element {
        my ($self, $element) = @_;
        print "End element: $element->{Name}\n";
    }

    1;

Factorías, filtros y tuberías

SAX2 introduce nuevos componentes en la estructura, las factorías y los filtros. En Perl disponemos de XML::SAX, un módulo base sobre el que se pueden construir procesadores y drivers, que soporta el uso de factorías SAX.

La factoría es un método que gestiona procesadores SAX. En ocasiones es interesante utilizar varios procesadores, poque cada uno está adaptado a determinadas tareas. El módulo factoría nos entrega el procesador deseado, y hace las gestiones adecuadas para dar de alta o buscar un procesador.

El código anterior puede verse entonces así:

#!/usr/bin/perl -w
use XML::SAX::ParserFactory;
use XML::Handler::PrintEvents;
$XML::SAX::ParserPackage = "XML::SAX::Expat";
my $parser = XML::SAX::ParserFactory->parser(
        Handler => XML::Handler::PrintEvents->new
  );
$parser->parse_uri("sidebar.xml");

Como vemos la factoría ha sustituido al procesador, porque de hecho nos provee de él. Si no hemos especificado ninguno se utilizará el último, o XML::SAX::PurePerl por defecto. También podemos registrar otro procesador, y pedir una lista de los existentes, y sus características, gracias al método parsers:

#!/usr/bin/perl -w
use XML::SAX;

XML::SAX->add_parser(q(XML::SAX::Expat))->save_parsers();
my @parsers = @{XML::SAX->parsers( )};

foreach my $p ( @parsers ) {
    print "\n", $p->{ Name }, "\n";
    foreach my $f ( sort keys %{$p->{ Features }} ) {
        print "$f => ", $p->{ Features }->{ $f }, "\n";
    }
}
XML::SAX::PurePerl
http://xml.org/sax/features/namespaces => 1

XML::SAX::Expat
http://xml.org/sax/features/external-general-entities => 1
http://xml.org/sax/features/external-parameter-entities => 1
http://xml.org/sax/features/namespaces => 1

Vemos que el ambos soportan espacios de nombres, pero sólo el segundo entidades externas. Mediante la variable $XML::SAX::ParserPackage estableceremos el que deseamos utilizar, si no queremos usar el último.

La estructura SAX puede complicarse en la medida que deseemos, gracias a la aparición de los filtros. Éstos son similares a un generador, que produce eventos SAX pero no a partir de un documento externo, sino de los eventos SAX que ha recibido.

Las primeras estructuras que podemos construir con filtros son aquellas en que varios procesados SAX acaban en un manejador. Una estructura así es la utilizada por el filtro XML::Filter::XInclude, que provee de soporte XInclude en SAX (aunque no soporta Xpointer). Otra, la que provee el filtro XML::Filter::Merger, que deriva a un manejador los eventos procedentes de varios flujos SAX.

Veamos ahora la posibilidad de utilizar varios manejadores. Una primera forma de abordar el problema es la de replicar los eventos a varios manejadores a la vez. Es lo que hacen XML::Filter::SAXT y XML::Filter::Distributor, utilizando técnicas de multiplexado diferentes.

Sin embargo el uso más habitual de los filtros son las tuberías, donde la salida de uno de ellos sea entrada del siguiente, provocando sucesivas transformaciones.

XML::SAX permite montar una tubería basada en filtros. Veamos un ejemplo de su uso. Aquí nuestro archivo sidebar.xml no contiene ningún indentado:

<?xml version="1.0" encoding="ISO-8859-1"?>
<sidebar><section><title>Mi web</title><item><title>Informacion</title><url>/Informacion/</url></item></section></sidebar>

Pero queremos hacer una impresión con un aspecto más legible. Utilizaremos la cadena SAX pasando el texto a través de un procesador que generará eventos tras la recepción del texto, y un manejador, XML:SAX:Writer, que realizará el proceso contrario, generando texto XML a partir de los eventos recibidos. Entre ambos colocaremos un filtro que modifique los eventos del procesador y devuelva los elementos indentados, XML::Filter::Reindent. La tubería se arma al revés de como se va a utilizar:

#!/usr/bin/perl -w

use XML::SAX::ParserFactory;
use XML::Filter::Reindent;
use XML::SAX::Writer;

my $output_string;
my $handler1 = XML::SAX::Writer->new();
my $handler2 = XML::Filter::Reindent->new(Handler => $handler1);
my $parser = XML::SAX::ParserFactory->parser(Handler => $handler2);
$parser->parse_uri("sidebar.xml");
<sidebar>
 <section>
  <title>Mi web</title>
  <item>
   <title>Informacion</title>
   <url>/Informacion/</url>
  </item>
 </section>
</sidebar>

Si lo queremos más fácil, aquí está XML::SAX::Machines, donde hay poco que hacer, aparte de definir la tubería:

#!/usr/bin/perl -w
use XML::SAX::Machines qw(Pipeline);
my $parser = Pipeline( XML::Filter::Reindent => XML::SAX::Writer => \*STDOUT );
$parser->parse_uri("sidebar.xml");

Véase la estupenda documentación de XML::SAX::Machines y de XML::SAX::Pipeline, además de Introducing XML::SAX::Machines, Part One, de Kip Hampton, un tutorial con ejemplos de las utilidades de este módulo. A partir de XML::SAX::Machines se puede construir un controlador SAX, un sistema que encapsule todas las operaciones SAX volviéndolas transparentes a una aplicación, como se explica en Introducing XML::SAX::Machines, Part Two.

Procesadores

El ya mencionado XML::SAX::Expat está disponible también con una extensión, XML::SAX::ExpatXS, que le añade algunas caracerísticas.

También disponemos de otros procesadores SAX. El más veterano es XML::Parser::PerlSAX, también basado en XML::Parser. Con él XML::ESISParser un procesador capaz también de utilizar archivos SGML.

XML::LibXML::SAX nos provee de un interfaz de los métodos SAX de libxml2, que son limitados.

XML::SAX::PurePerl, es un procesador que, como su nombre indica, está construido enteramente en Perl y por tanto es más lento que los que operan sobre librerías C, aunque más portable.

XML::Xerces::SAXParser, interfaz de las funciones SAX de Xerces C++.

Si deseamos implementar la factoría de procesadores, podemos acudir a XML::SAX o a XML::Xerces.

En ocasiones puede ser de interés construir un sencillo procesador para necesidades concretas. En la documentación de XML::SAX puede verse un pequeño tutorial al respecto

Generadores

Los drivers o generadores son capaces de generar eventos SAX de fuentes que no son documentos XML. Los eventos pueden ser utilizados entonces con cualquier manejador SAX.

Muchos generadores SAX son la base para conversiones de estos documentos a otros formatos. En CPAN disponemos de Pod::SAX, que recibe de entrada archivos Pod (o Perl que contienen textos Pod), generando eventos SAX; Text::WikiFormat::SAX es un generador para documentos de Twiki; WAP::SAXDriver::wbxml trata documentos wbxml; XML::SAXDriver::vCard documentos vCard; XML::SAXDriver::Excel, los de esta popular hoja de cálculo; XML::SAX::RTF, los de este formato de texto; XML::SAXDriver::CSV, archivos CVS.

Íntimamente ligados a este planteamiento son los generadores que utilizan como fuente salidas de aplicaciones, que tratan como si fuera un documento. Así, Class::DBI::ToSax, genera los eventos a partir de objetos de bases de datos DBI; XML::Directory::SAX utiliza como entrada un directorio del sistema de ficheros.

Hay también unos cuantos módulos relacionados con código, como Perl::SAX, que genera eventos a partir de código Perl. Devel::TraceSAX sigue una orientación parecida. Para código de Python contamos con Python::Bytecode::SAX.

XML::GDOME::SAX::Parser, basado en GDOME, es capaz de generar eventos SAX desde un árbol DOM. Un resultado similar lo consigue XML::LibXML::SAX::Generator, a partir de libxml2. XML::XPath::PerlSAX genera eventos a partir de nodos XPath, eventos que pueden pasarse a un manejador que soporte XML::PerlSAX. Obviamente está limitado a SAX1.

XML::SAX::Base nos ofrece un punto de partida para contruir un generador. Contamos con excelentes tutoriales para construir generadores en Perl, como éste de Kip Hampton.

Filtros

Los filtros suelen realizar tareas específicas en necesidades concretas, pero hay en CPAN unos cuantos filtros genéricos, diseñados para suplir deficiencias o ampliar las posibilidades del procesador o del manejador que está antes del filtro:

XML::Filter::CharacterChunk por ejemplo permite tratar correctamente determinados elementos, que el procesador puede ver de manera errónea.

XML::Filter::BufferText une elementos de texto consecutivos, que se producen en ocasiones como resultado de una errónea apreciación del procesador, que detecta como varios textos lo que en realidad es uno.

XML::Filter::Cache añade una caché al procesador, y de allí extrae los eventos el manejador.

XML::Filter::DetectWS intercepta los espacios en blanco que pueden ser ignorados.

XML::Filter::Reindent reformatea el documento XML con indentado, como hemos visto en ejemplos anteriores.

XML::Filter::SAX1toSAX2 permite utilizar procesadores SAX1 con manejadores o filtros SAX2.

XML::Perl2SAX es un filtro SAX que convierte los métodos SAX al estilo Perl en los correspondientes del estilo de Java/CORBA. Lo contrario puede obtenerse de otro filtro, XML::SAX2Perl.

XML::Filter::ExceptionLocator añade números de línea y columna a los errors generados por los manejadores.

XML::Filter::Sort ordena fragmentos del documento XML como si fueran registros. Incluye una utilidad, xmlsort, que puede ser utilizada desde la linea de comandos.

XML::SAX::Base nos provee de un punto de partida para construir filtros personalizados. El tutorial de Kip Hampton Transforming XML With SAX Filters y el tutorial del Calendario de Adviento de Perl de 2002, XML::SAX son una magnífica guía.

Manejadores

Los manejadores son el último eslabón de la cadana SAX. Reciben los eventos de un procesador, de un generador o de un filtro y ejecutan las medidas oportunas. Suele ser la tarea más personalizada del ensamblaje SAX, y es fácil construirse uno dedicado a nuestros propósitos. Para esta tarea quizá nos auxiliaremos con Test::XML::SAX, dedicado a la depuración de manejadores. Contamos con un esqueleto ya preparado, XML::Handler::Sample, sobre el que escribir uno propio es muy sencillo.

A pesar de ello, un uso típico de los manejadores es ejecutar tareas intermedias en un proceso que abarca más que la cadena SAX, permitiendo después el uso de otros módulos. Por ejemplo, los que construyen árboles DOM desde eventos SAX, especialmente indicados para tratar posteriormente la salida con XSLT. Si se combinan con generadores de eventos que tienen como fuente documentos no XML sirven como conversores de formatos. Para ser utilizado con Sablotron está XML::Sablotron::SAXBuilder. El módulo XML::Handler::BuildDOM tiene un propósito similar.

Existen en CPAN unos cuantos manejadores SAX que realizan tareas específicas. Así, SVG::Parser::SAX:Handler obtiene una salida SVG a partir de los eventos SAX generados utilizando de entrada un documento XML. Data::Stag::SAX2Stag recibe eventos SAX y devuelve eventos Stag. Bio::OntologyIO::Handlers::BaseSAXHandler de Bioperl es sólo un esqueleto, que proporciona los métodos básicos que la aplicación final debe terminar. XML::Handler::AxPoint convierte documentos AxPoint en PDF. De los manejadores específicos para documentación de DTDs, XML::Handler::Dtd2DocBook y XML::Handler::Dtd2Html hablamos en otro apartado de este texto. XML::Handler::Pdb crea una base de datos Palm desde un documento XML. XML::Handler::HTMLWriter obtiene documentos HTML 4.0 desde entradas XHTML.

Un grupo de manejadores crean documentos XML; su uso habitual es para depuración o visualización rápida del contenido de los documentos, o volcar el resultado obtenido por un generador trantando documentos no XML. El veterano XML::Handler::YAWriter se limita a SAX1, y si deseamos SAX2 debemos acudir a XML::SAX::Writer. Otras alternativas son XML::Handler::Composer, XML::Handler::CanonXMLWriter y XML::Handler::XMLWriter.

XML::Checker puede ser utilizado entre otras maneras como manejador SAX. Con él podemos validar documentos contra DTDs.

Alternativas a DOM y SAX

El veterano XML::Parser ha sido durante mucho tiempo la referencia en los tratamientos de flujo en el mundo Perl-XML. Es un módulo de muy bajo nivel, y en la actualidad se desaconseja su uso, en favor de los más flexibles procesadores SAX. Véanse las explicaciones de Matt Sergeant al respecto, entre las que destaca el hecho de que su API no está entandarizada, lo que hace muy difícil utilizar otros módulos. En CPAN hay otros procesadores de flujo con una API similar, como PXR::Parser y XML::Parser::Lite a los que se les pueden hacer las mismas objeciones.

PYX

PYX es seguramente el más simple de los modelos que utilizan el paradigma de la serialización de los documentos XML. Los describe mediante un subconjunto del formato SGML ESIS como lineas de texto precedidas por una indicación sobre su tipo. Los posibles tipos de elementos de la serie son tan sólo cinco:
  • ( start-tag

  • ) end-tag

  • A attribute

  • - character data

  • ? processing instruction

Una vez obtenida la conversión, las expresiones regulares pueden ser utilizadas para filtrarla, incluso desde la línea de comandos, gracias a las utilidades que vienen con el módulo XML::PYX. El filtro puede actuar en ambos sentidos, siendo trivial construir archivos XML desde la notación PYX.

Este ejemplo cuenta los elementos title de nuestro archivo sidebar.xml, es decir las líneas que comienzan con el carácter ( seguido del texto title:

pyx sidebar.xml | grep "(title" | wc -l
2

Otro módulo que implementa PYX es XML::TiePYX.

Su significativa simplificación lo hace muy eficiente en documentos no complejos, aunque no le permite ser utilizado como herramienta genérica.

Pero simplicidad no es enemiga de potencia. Los trabajos de Michel Rodriguez Simple XML Transformation with Perl y Ways to Rome: Processing XML with Perl muestran ejemplos de transformaciones de documentos con esta herramienta.

Grove

Grove, descrito en la especificación SGML HyTime, es otro modelo que intenta representar los documentos XML. Éstos son vistos como un conjunto de nodos, que son conjuntos de propiedades, que pueden ser valores atómicos (cadenas, enteros, booleanos), listas de nodos o referencias a otros nodos.

libxml-perl construye una implementación de ese modelo, proveyendo de varios módulos base y de un manejador SAX, XML::Grove, como herramienta final.

En este ejemplo presentamos dos propiedades del nodo raíz: El nombre y sus atributos:

use XML::Parser::PerlSAX;
use XML::Grove::Builder;

my $builder = XML::Grove::Builder->new;
my $parser = XML::Parser::PerlSAX->new(Handler => $builder);
my $grove = $parser->parse (Source => { SystemId => 'sidebar.xml'});
my $root = $grove->{Contents}[0];
print "Nombre: " . $root->{Name};
print "\nAtributos:\n";
$attrs = $root->{Attributes};       # name the root's attributs
foreach $attr (keys %$attrs) {
	print "\t$attr\t", %$attrs->{$attr} . "\n"
};

Véase el trabajo de Paul Prescod Addressing the Enterprise: Why the Web needs Groves para una explicación de las cualidades de este modelo de datos.

XML::Filter::Dispatcher

En SAX la aplicación tiene un papel pasivo, a la espera de los eventos que reciba del procesador, y por tanto no mantiene información del estado del proceso del documento. A los procesadores SAX se les llama por ello push parsers.

La programación SAX se torna muy compleja cuando se tratan documentos de los que no se sabe exactamente antes su contenido. Observemos nuestro ejemplo: El elemento title puede aparecer como hijo de un elemento section y como hijo de un elemento item, y quizá deseamos un tratamiento diferente en cada caso. En SAX no tenemos un API que distinga ambas situaciones, el procesador sólo es capaz de producir un evento cuando se inicia o termina un elemento de nombre title, por lo que la rutina de tratamiento se hace más compleja. Con XPath esto es más sencillo, pues disponemos de todo el árbol antes de preguntar, y no habrá problemas en identificar los nodos adecuados.

Algunos módulos Perl intentan conjugar lo mejor de ambas orientaciones, la sobriedad de recursos y rapidez del proceso de flujo y la potencia del modelo de datos del árbol DOM. Se habla entonces de procesos push/pull, porque la apliación mantiene el control del documento y no es un simple receptor de eventos, y puede tener una visión conjunta del documento y pedir eventos, al estilo de lo que hace XPath al poner el foco en informaciones muy concretas, y sólo en esas.

XML::Filter::Dispatcher nos ofrece acceso XPath a un flujo SAX. En realidad es una adaptación de XPath, denominada EvenPath. El módulo recibe varios parámetros para regular su conducta; el más importante es Rules, un hash de reglas que aplicar. Cada regla es a su vez un hash donde la clave es la regla EvenPath y el valor a su vez un arreglo de hashes con subrutinas donde se ejecuta el código Perl adecuado.

En este ejemplo imprimiremos los elementos title, indicando si son hijos de section o de item:

#!/usr/bin/perl -w
use XML::SAX::Machines qw( Pipeline );
use XML::Filter::Dispatcher qw ( :all );;

my $out='';
my $xml= new XML::Filter::Dispatcher->new(
	Rules => [
		"/sidebar/section/title" =>  [ 'string()' =>
                                   sub { $out .= "Section: ". xvalue() . "\n"; }
				],
		"/sidebar/section/item/title" =>  [ 'string()' =>
                                   sub { $out .= "Item: ". xvalue() . "\n"; }
				],
	],
);

Pipeline($xml)->parse_uri("sidebar.xml");

print $out;

Sólo hemos indicado dos reglas, una para cada uno de los casos:

Section: Mi web
Item: Informacion

XML::Twig

XML::Twig es una herramienta más madura y con un buen abanico de posibilidades. La idea inicial es también la de procesar tan sólo ramas (twigs) del árbol, a las que aplica un modelo de datos basado en DOM, pero descartando el resto. A partir de aquí es posible procesar esas ramas, suprimir o añadir elementos, o modificar su contenido.

El siguiente ejemplo es muy similar al anterior. Del documento de entrada obtendremos un nuevo documento XML con los títulos e información de su nodo padre, éste como atributo:

#!/usr/bin/perl -w

use XML::Twig;
my $twig_handlers = {'sidebar' =>  \&root,
			'section/title' =>  \&section_title,
                     'item/title'   =>  \&item_title};
my $twig= new XML::Twig(TwigRoots => {title => 1},
                        TwigHandlers => $twig_handlers);

$twig->parsefile("sidebar.xml");
$twig->print;

sub root{
    	my ($twig, $title) = @_;
	$title->set_gi( 'titles');
}
sub section_title{
    	my ($twig, $title) = @_;
	$title->set_att('DescendsFrom', 'section');
}

sub item_title {
    	my ($twig, $title) = @_;
	$title->set_att('DescendsFrom', 'item');
}

Vemos que el módulo recibe como parámetro un hash de manejadores, hashes indicando la regla XPath en la clave y una referencia a la rutina Perl a ejecutar en el valor. En la salida el tipo de documento ha sido cambiado de sidebar a titles y hemos creado un elemento por cada titulo encontrado:

<?xml version="1.0" encoding="ISO-8859-1"?>
<titles><title DescendsFrom="section">Mi web</title><title DescendsFrom="item">Informacion</title></titles>
XML::Twig es un módulo bien documentado. El estupendo tutorial de su autor, Michel Rodriguez, Processing XML efficiently with Perl and XML::Twig cubre en detalle sus posibilidades. Una introducción más liviana la ha hecho Kip Hampton con Using XML::Twig.

Definiciones de documentos

DTD

Los DTD fueron el sistema original de definir lenguages XML, y por tanto están regulados en la recomendación de XML (hay traducción castellana, aunque no actualizada).

Los documentos de nuestro ejemplo no tienen una definición formal, pero está implícita en el archivo sidebar.xml que se incluye en la distribución 1.6.2 de AxKit. En nuestro caso el elemento raíz, sidebar, se compone de un conjunto de nodos section identificados por un elemento title; estos nodos section a su vez incluyen nodos simples, item, o compuestos, subsection, que abren conjuntos de nodos item. Vemos que los nodos compuestos pueden aparecer expandidos (por defecto no), y que de cada item nos interesa el título y la URL. Los elementos section/subsection no crean una estructura recursiva, tan sólo una jerarquía de nodos en dos niveles. El archivo lo llamaremos sidebar.dtd.

<?xml version="1.0" encoding="ISO-8859-1"?>
<!ELEMENT sidebar (section+)>
<!ELEMENT section (title, (subsection|item)+ )>
<!ELEMENT subsection (title, url, item+)>
<!ATTLIST subsection expand ( yes | no ) "no">
<!ELEMENT item (title, url)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT url (#PCDATA)>

Si lo que deseamos es simplemente conocer la estructura de un DTD existente podemos usar el módulo XML::DTDParser, un procesador simple que nos permite un volcado a estructuras de Perl. La única variación posible es utilizar un escalar como único parámetro, en lugar de un archivo que contenga el DTD. El módulo retorna un hash de hashes con información del DTD; no existen métodos de interrogación a la estructura, sino que hemos de hacerlo todo nosotros. En nuestro ejemplo podemos visualizar la descripción que hace con Data::Dumper:

#!/usr/bin/perl
use XML::DTDParser qw(ParseDTD ParseDTDFile);
use Data::Dumper;
my $DTD = ParseDTDFile( "sidebar.dtd");
print Dumper $DTD;

Si reproducimos una parte de la salida, la del elemento compuesto item nos hacemos una idea de cuál es la información que procesa el módulo, sistematizando el DTD. Se trata de una información específica del módulo, puesto que las estructuras Perl para los DTDs no han sido objeto de especificaciones:

          'item' => {
                      'childrenARR' => [
                                         'title',
                                         'url'
                                       ],
                      'parent' => [
                                    'subsection',
                                    'section'
                                  ],
                      'childrenX' => {
                                       'url' => '1',
                                       'title' => '1'
                                     },
                      'childrenSTR' => '(title, url)',
                      'option' => '*',
                      'children' => {
                                      'url' => '!',
                                      'title' => '!'
                                    }
                    },

El módulo XML::ParseDTD es más completo, y nos provee de métodos para interrogar al dtd, averiguando así qué elementos contiene, cuáles son definidos como vacíos, qué atributos están permitidos en un elemento, cuáles son los valores permitidos para un atributo, qué atributos son obligatorios, cuáles son los atributos por defecto y qué atributos tienen valor constante. En todo caso es limitado: No se pueden utilizar entidades en los DTDs.

En este sencillo ejemplo mostramos qué elementos contiene el DTD y sus atributos, utilizando los métodos del módulo get_document_tags y get_attributes:

#!/usr/bin/perl
use XML::ParseDTD;
$dtd = XML::ParseDTD->new('sidebar.dtd');
@tags = $dtd->get_document_tags();
foreach $tag (@tags) {
        @attributes = $dtd->get_attributes($tag);
        print "$tag - @attributes \n";
}
sidebar -
subsection - expand
item -
url -
section -
title -

El más completo de este grupo es XML::Smart::DTD, basado en XML::DTDParser, que ofrece 29 métodos de acceso al DTD completo o sus partes. Por ejemplo, este método is_elem_pcdata:

#!/usr/bin/perl
  use XML::Smart::DTD ;

    my $dtd = XML::Smart::DTD->new('sidebar.dtd') ;

   if ( $dtd->is_elem_pcdata ('title'))  {
          print "title es PCDATA\n";
   }
title es PCDATA

Otra aproximación nos la ofrece XML::Stream::Parser::DTD. Utilizado como los procesadores anteriores nos ofrece información sistematizada del DTD:

#!/usr/bin/perl
use XML::Stream::Parser::DTD;
use Data::Dumper;
my $parser = new XML::Stream::Parser::DTD(uri=>'sidebar.dtd');
print Dumper $parser;

Su propósito no obstante es disponer de un procesador de flujo de DTDs, y nos provee de métodos para transformar la estructura devuelta, en concreto la posibilidad de añadir o borrar nodos y atributos. Como tantos otros módulos, ha sido construido como auxiliar en otras tareas, y está incompleto, por lo que aún no podemos implementar los eventos típicos de SAX.

La especificación Perl SAX sí define eventos de DTD, opcionales para los procesadores, pero sólo XML::SAX::Expat contiene la totalidad de los eventos para DTD en la especificación incluido el soporte de entidades externas, mientras XML::Parser::PerlSAX contiene un subconjunto significativo. No hay en CPAN manejadores que se dediquen completamente a DTDs, pero muchos de los existentes incluyen alguno de estos eventos para tareas específicas. En el apartado de SAX hay algunos enlaces con ayuda para construir manejadores y filtros.

El objetivo de estos módulos suele ser utilizar la información del DTD para otros propósitos, como índices o tesauros. Otros módulos sin embargo están orientados a generar documentación a partir de los DTDs, útiles sobre todo a organizaciones con una cierta complejidad de tipos de documento. Ése es el propósito de XML::Handler::Dtd2Html. Se dispone también de un programa, dtd2html.pl, que envuelve las clases y métodos del módulo en un ejecutable desde la línea de comandos.

Su uso es algo complejo; estamos ante herramientas para los mantenedores de los DTDs, y eso ha de tenerse en cuenta. Deben definirse unas plantillas de descripción del tipo de documento (hay algunas de ejemplo en el tarball); es un filtro SAX2, lo que exige que la fuente sea un documento XML, y los DTD no lo son, o sea que se han de utilizar o bien integrándolos en uno de aquéllos, o bien invocándolos como entidad externa. Por último como la finalidad es documentar el DTD, se requiere que vayamos colocando comentarios explicativos antes de cada entrada de elemento o atributo. Lo de siempre con los programas de autodocumentación: No encontraremos en el resultado nada que no haya sido escrito antes.

Un fragmento de nuestro DTD de ejemplo sería:

<!-- @BRIEF : root element of sidebar doctype -->
 <!-- It's the root element of a sidebar document type,
 used in webs for quick and easy access -->
 <!-- HISTORY : v0.1 - Initial release -->
<!ELEMENT sidebar (section+)>
 <!-- First element in sidebar hierarchy,
  usually is a web first index link  -->
<!ELEMENT section (title, (subsection|item)+ )>
...

Necesitariamos un documento XML real que invocara el DTD para ser procesado. En él incluiremos la llamada al DTD externo:

<!DOCTYPE sidebar SYSTEM "sidebar.dtd">

Si a este último documento lo hemos llamado sidebar.xml, y deseamos que la salida sea descripcion.html, el uso más simple sería:

dtd2html.pl -o descripcion sidebar.xml

El documento resultante puede ser largo, sobre todo si hemos elaborado una plantilla extensa, y contendrá enlaces a cada uno de los campos descritos. Así el elemento title que preparamos más arriba podría formatearse (con la plantilla por defecto, claro):

<a id='elt_section' name='elt_section'/>
<h3>section</h3>
<p>&lt;<span class='keyword1'>!ELEMENT</span> section ( <span class='keyword1'>#PCDATA</span>  ) &gt;</p>
<p class='comment'> First element in sidebar hierarchy, usually is a web first index link .</p>

Si deseamos otras salidas, el mismo autor nos facilita otro módulo, XML::Handler::Dtd2DocBook, que formatea esta información en DocBook. Esta vez se pueden utilizar las facilidades de sus hojas de estilo, y por tanto generar un documento PDF con la información, otro en formato chm o un grupo navegable de archivos html, uno por cada parte relevante del dtd original.

Herramientas que construyan información automáticamente a partir de un DTD, sin preparación previa como el caso anterior, hay bastantes; en Perl contamos con el estupendo DTDParse.

XML Schema

El Esquema XML es el segundo de los sistemas de descripción de los documentos XML, y pretende resolver las limitaciones de los DTD con los espacios de nombres, tipos de elementos o restricciones en tipos y atributos.

El tipo sidebar que nos sirve de ejemplo podría ser representado así:

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns='http://www.w3.org/2001/XMLSchema'>

  <element name='sidebar'>
    <complexType>
      <choice minOccurs='0' maxOccurs='unbounded'>
        <element ref='section'/>
      </choice>
    </complexType>
  </element>

  <element name='subsection'>
    <complexType>
      <choice minOccurs='0' maxOccurs='unbounded'>
        <element ref='item'/>
        <element ref='url'/>
        <element ref='title'/>
      </choice>
      <attribute name='expand' type='string'/>
    </complexType>
  </element>

  <element name='url'  type='string'>
    <complexType>
    </complexType>
  </element>

  <element name='item'>
    <complexType>
      <choice minOccurs='0' maxOccurs='unbounded'>
        <element ref='url'/>
        <element ref='title'/>
      </choice>
    </complexType>
  </element>

  <element name='title'  type='string'>
    <complexType>
    </complexType>
  </element>

  <element name='section'>
    <complexType>
      <choice minOccurs='0' maxOccurs='unbounded'>
        <element ref='subsection'/>
        <element ref='item'/>
        <element ref='title'/>
      </choice>
    </complexType>
  </element>

</schema>

El módulo XML::Schema provee de métodos con los que construir un esquema XML y validar un documento contra él. No puede utilizar esquemas directamente, sino que debemos contruir la estructura perl previamente, y después validar.

Así, el fragmento de nuestra definición de tipo de documento sidebar que describe la estructura item/title/url podría ser utilizado por XML::Schema como:

use XML::Schema;

# create a schema
my $schema = XML::Schema->new();

# define simple type for url
my $urlType = $schema->simpleType( name => 'url', base => 'string' );

# define simple type for title
my $titleTipe = $schema->simpleType( name => 'title', base => 'string' );

# define a 'item' complex type
my $item = $schema->complexType( name => 'itemType' );

# define 'item' content model
$item->content(
    sequence => [
    {   element => $item->element( name => 'title', type => 'titleType' ),
        min => 1,
        max => 1,
    },
    {   element => $item->element( name => 'url', type => 'urlType' ),
        min => 1,
        max => 1,
    }
],
);

# add item element to schema
$schema->element( name => 'item', type => 'itemType' )
    || die $schema->error();

WSDL::Generator::Schema es capaz de generar esquemas XML a partir de documentos WSDL.

Conversiones

Para convertir un DTD a XML Schema podemos utilizar dtd2xsd.pl.

Para extraer esquemas de documentos IDL disponemos de idl2xsd. También podemos hacer que la salida sea un esquema de Relax NG con idl2rng.

XML::RDB provee de métodos para extraer de un documento XML un esquema RDB, o convertir un esquema XML en un esquema RDB.

Validación

La mayoría de los procesadores XML son no validadores, o tienen deshabilitada esta opción por defecto; nos informan sólo de errores si el documento no es bien formado. Ello es porque la validación es un proceso normalmente ligado a la construcción del documento XML, y después se hace un lastre en términos de tiempo y recursos. En el caso de los documentos construidos por aplicaciones intermedias, la validación es innecesaria. En numerosas ocasiones sin embargo necesitamos comprobar que los documentos siguen las reglas previstas, por lo que necesitaremos un procesador validador.

Hay herramientas en Perl para validar documentos XML con los estándares DTD y esquemas XML, con Schematron y Relax NG.

Para este apartado utilizaremos un documento no válido del tipo sidebar (sidebar.xml), que incluye un elemento subsection como hijo de un nodo item, lo que no está permitido:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE sidebar SYSTEM "sidebar.dtd">
<sidebar>
	<section>
	<title>Mi web</title>
	<item>
		<title>Informacion</title>
		<url>/Informacion/</url>
		<subsection>Informacion</subsection>
	</item>
	</section>
</sidebar>

DTD

XML::Checker::Parser provee validación sobre el procesador XML::Parser:

#!/usr/bin/perl
use XML::Checker::Parser;

my $xp = new XML::Checker::Parser (
    Handlers => { },
    ParseParamEnt => 1,
    SkipExternalDTD  => 1,
  );
eval {
  $xp->parsefile("sidebar.xml");
};
if ($@) {
  # ... your error handling code here ...
  print "$xml_file failed validation!\n";
  die "$@";
}

Por defecto el módulo espera que el DTD sea interno, y nuestro ejemplo lo declaraba como entidad externa, por lo que hay que habilitar la opción de XML::Parser ParseParamEnt y la de XML::Checker::Parser SkipExternalDTD.

XML::Checker ERROR-157: unexpected Element [subsection]
        Context: ChildElementIndex 2, line 9, column 2, byte 238
XML::Checker ERROR-149: Element should only contain sub elements, found text [Informacion]
        Context: line 9, column 14, byte 250
XML::Checker ERROR-170: Element can't be empty Found=[] RE=[((title)(url)(item)+)]
        Context: line 9, column 25, byte 261

Otra opción sería la de utilizar XML::Checker con el procesador XML::DOM::Parser por debajo, en lugar de XML::Parser. Es lo que hace XML::DOM::Valparser.

Otro procesador con validación contra DTDs es XML::LibXML, un wrapper de la librería libxml2:

#!/usr/bin/perl
use XML::LibXML;
$parser = XML::LibXML->new();
$parser->validation(1);
$doc = $parser->parse_file("sidebar.xml");

Por defecto este procesador no valida, debe activarse la función con el método validation:

sidebar.xml:9: element subsection: validity error : Element subsection content does not follow the DTD
                <subsection>Informacion</subsection>
                                                    ^
sidebar.xml:10: element item: validity error : Element item content does not follow the DTD
        </item>
               ^
 at prueba.pl line 7
Por último, podemos recurrir a XML::Xerces, el interfaz de perl sobre la librería C++ de Xerces.

XML Schema

La validación contra esquemas XML está cambiando rápidamente, fruto de la cada vez mayor importancia de esta técnica de descripción de tipos de documento XML, y próximamente serán más los procesadores capaces de soportarlos.

  • Con XML::Validator::Schema

    El módulo XML::Validator::Schema nos permite llevar a cabo esta validación. Es un filtro SAX, por lo que tendremos que incluirlo en algún sistema que los admita. En este caso utilizaremos un simple procesador para generar los eventos, XML::SAX::PurePerl. El código de un sencillo test podría ser:

    #!/usr/bin/perl -w
    use XML::SAX::PurePerl;
    use XML::Validator::Schema;
    $validator = XML::Validator::Schema->new(file => 'sidebar.xsd');
    $parser = XML::SAX::PurePerl->parser(Handler => $validator);
    eval { $parser->parse_uri('sidebar.xml') };
      die "File failed validation: $@" if $@;
    

    que nos demostrará que efectivamente nuestro documento no es válido:

    File failed validation: Found unexpected <subsection> inside <item>.  This is not a valid child element.
  • Con XML::Xerces

    Otra opción es recurrir a XML::Xerces, el wrapper de perl sobre la librería C++ de Xerces, que también permite validar contra esquemas. En esta ocasión deberemos incluir la referencia al esquema externo en nuestro documento XML, en la misma etiqueta inicial del elemento raíz, sidebar:

    <sidebar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:noNamespaceSchemaLocation='sidebar.xsd'>

    Nuestro ejemplo ha de establecer una serie de propiedades de XML:Xerces::XMLUni, en especial activar fgXercesSchema, que nos permitirá validar contra esquemas XML:

    use strict;
    use XML::Xerces;
    
    my $parser = XML::Xerces::XercesDOMParser->new();
    $parser->setValidationScheme($XML::Xerces::AbstractDOMParser::Val_Always);
    $parser->setDoNamespaces(1);
    $parser->setDoSchema (1);
    $parser->setErrorHandler(XML::Xerces::PerlErrorHandler->new);
    
    $parser->parse (XML::Xerces::LocalFileInputSource->new("sidebar.xml"));

    Nuestro procesador nos informa enseguida del error, aunque el mensaje no es tan claro como los anteriores:

    ERROR:
    FILE:    /var/tmp/cpan-xml/mas/sidebar.xsd
    LINE:    23
    COLUMN:  38
    MESSAGE: Element 'url' cannot have both a type attribute and a simpleType/complexType type child
     at prueba5.pl line 10
  • Con XML::LibXML

    XML::LibXML también permite validar esquemas XML, con una clase específica, XML::LibXML::Schema:

    use XML::LibXML;
    my $parser = XML::LibXML-> new();
    my $validator = XML::LibXML::Schema->new(location => "sidebar.xsd");
    my $doc = $parser-> parse_file("sidebar.xml");
    eval { $validator-> validate($doc); };

RelaxNG

XML::LibXML permite procesar esquemas RelaxNG, también utilizando un procesador específico, XML::LibXML::RelaxNG.

use XML::LibXML;
my $parser = XML::LibXML-> new();
my $validator = XML::LibXML::RelaxNG->new(location => "sidebar.rng");
my $doc = $parser-> parse_file("sidebar.xml");
eval { $validator-> validate($doc); };

Schematron

Schematron es otro sistema de establecer tipos de documentos XML. No busca una definición formal, sino enumerar qué reglas deben cumplirse para validar el documento. Un esquema Schematron es básicamente una lista de las reglas XPath usadas en la validación. En CPAN disponemos de XML::Schematron, un módulo que utiliza los servicios de un procesador XPath que realiza el chequeo de las reglas. Están disponibles sendas versiones sobre Sablotron (XML::Schematron::Sablotron), sobre LibXSLT (XML::Schematron::LibXSLT) y sobre XML::XPath (XML::Schematron::XPath).

En este tutorial de Kip Hampton podemos ver algunos ejemplos de su uso.

Colofón

Perl siempre ha destacado en el tratamiento de la información textual, y XML no es la excepción. Hay muchas iniciativas diferentes abordando el tema desde cualquier ángulo, ateniéndose a los estándares o sacrificándolos en aras de la eficiencia o de un modelo de datos más acorde a cada problema.

Pero hay muchos módulos XML en CPAN que ni siquiera hemos mencionado. Vé al buscador de CPAN y entra en su telaraña.