XML::LibXML

Carlos Escribano


XML::LibXML es uno de los mejores procesadores XML disponibles en Perl. Su potencia y rapidez se deben a que es un interfaz de la librería XML de Gnome, escrita en C, libxml2. Seguramente es el camino más rápido y eficiente de tratar documentos XML en Perl al modo DOM, sobre todo si se trata de documentos extensos y complejos.

Constituye uno de los tres procesadores XML mayores de Perl, los que proveen de los conjuntos de funcionalidades más completos, junto con XML::GDOME, interfaz de la librería Gdome, a su vez basada en libxml2 y XML::Xerces, que da acceso desde Perl a la librería Xerces C++.

Entre sus muchas ventajas podemos destacar:

XML::LibXML es la base de un buen número de módulos de CPAN, que lo utilizan para extraer y manipular datos concretos. Además, sobre XML::LibXML se han establecido algunos módulos de uso de DOM de manera no estándard, buscando facilidades de uso, como XML::LibXML::Tools y XML::SimpleObject::LibXML. Un módulo adicional, Template::Plugin::XML::LibXML permite la creación de plantillas para el uso de XML::LibXML.

Las clases de XML::LibXML

La distribución de XML::LibXML (1.58) provee de 24 clases, para el procesamiento (DOM y SAX) y la utilización del árbol DOM.

Hay en CPAN otros módulos que añaden clases nuevas o subclasean las existentes:

  • XML::LibXML::Fixup, extiende el sistema de recuperación de errores de LibXML::XML permitiendo ligar acciones a estos errors, típicamente para permitir la continuación del procesado.

  • XML::LibXML::Iterator, extensión a DOM, estructura los elementos linealmente, al estilo de la serialización de SAX, permitiendo su recorrido secuencial. En versiones anteriores de la distribución, iterator era un método de XML::LibXML::Node.

  • XML::LibXML::NodeList::Iterator, subclase de XML::LibXML::NodeList que extiende DOM para permitir manejar los objetos NodeList como una lista, pudiendo moverse hacia arriba y abajo.

  • XML::LibXML::XPathContext, extensión a la capa XPath de LibXML::XML para utilizar contextos XPath.

Procesamiento

El uso habitual de la distribución es el procesado DOM. El procesador del módulo principal, XML::LibXML, genera objetos XML::LibXML::Document y XML::LibXML::DocumentFragment, a partir de textos XML, HTML y DocBook SGML (esto último desaconsejado). Los procesadores para las definiciones de documentos devuelven objetos de clases especiales, XML::LibXML::Dtd, XML::LibXML::RelaxNG y XML::LibXML::Schema.

El procesador XML::LibXML::SAX y los generadores XML::LibXML::SAX::Parser y XML::LibXML::SAX::Generator (obsoleto), devuelven eventos SAX, que pueden ser utilizados con cualquier sistema SAX2. La diferencia entre ellos es que XML::LibXML::SAX es un procesador SAX nativo, mientras los generadores son una capa añadida al procesador DOM, que atraviesa el árbol ya creado y provee de eventos desde él.

Estos procesadores son muy flexibles a la hora de recibir datos. Le pueden llegar desde archivos o cadenas (escalares) que contengan los documentos. En el caso de cadenas, éstas pueden ser sólo fragmentos balanceados, no necesariamente bien formados. También pueden ser su fuente Perl filehandles, y flujos de caracteres, quizá discontinuos y no necesariamente agrupados en cadenas con texto XML bien formado.

Para cada uno de las diversas entradas de datos nos provee de un método distinto de invocarlo. Los métodos DOM a su vez se desdoblan según el texto de entrada sea XML, HTML o DocBook SGML.

No todas las modalidades están disponibles para todos los procesadores. Esta tabla resume las posibilidades:

Table 1. Tipos de procesamiento

Procesador

Textos

Bien Formados

Balanceados

Archivos

Cadenas

Filehandles

Flujos

Cadenas

XML::LibXML

XML

parse_file

parse_string

parse_fh

parse_chunk,start_push, push,finish_push

parse_balanced_chunk

HTML

parse_html_file

parse_html_string

parse_html_fh

  

DocBook SGML

parse_sgml_file

parse_sgm_string

parse_sgml_fh

  

XML::LibXML::Dtd

DTD

new

parse_string

   

XML::LibXML::RelaxNG

RelaxNG

new

new

   

XML::LibXML::Schema

esquemas XML

new

new

   

XML::LibXML::SAX

XML

parse_uri,parse_file

parse_string

parse_chunk

 

XML::LibXML::SAX::Parser

XML

parse_uri,parse_file

parse_string

   

Textos HTML y DocBook SGML

XML::LibXML permite procesar algunos archivos SGML, y en concreto HTML y DocBook SGML. El soporte no es completo, sin embargo, y he tenido diferentes éxitos en su uso.

El soporte de HTML 4.01 (sin validación) es bastante bueno. La documentación del módulo recomienda evitar el carácter &, típicamente usado en enlaces a webs basadas en cgi, y que se cierren los elementos con la etiqueta final en los casos en que sea optativo en HTML.

El uso parece satisfactorio para los tipos de documento transitional y strict y he podido procesar con sólo un error los test del W3C[5].

El tipo de documento frameset en cambio no se procesa correctamente.

Aunque libxml2 todavía incluye el uso de funciones para el procesado de DocBook SGML, esta capa está declarada obsoleta. En consonancia, los documentos que he utilizado como prueba de procesamiento han dado errores; en general recomendaremos que NO se utilicen las funciones correspondientes a este tipo de documento.

Pese al nombre de las funciones y a que la documentación de XML::LibXML no es suficiente explícita en todos los casos, ha de remarcarse que el soporte de SGML es muy parcial, y los métodos parse_sgml_file y parse_sgml_string, no son para el procesado genérico de documentos SGML, sino para DocBook SGML, ya que usan respectivamente las funciones de libxml2 docbParseFile[6] y docbParseDoc[7].

Procesamiento de archivos

El camino más habitual de obtener la fuente XML para el procesamiento será un archivo. Para ellos podemos utilizar el el método parse_file de XML::LibXML:

#!/usr/bin/perl -w
use XML::LibXML;
my $parser = XML::LibXML->new();
my $doc =$parser->parse_file('hola.xml');

Pese a que su nombre pueda reflejar ambigüedad, las funciones del procesador DOM que se refieren a archivos admiten indistintamente referencias al sistema de ficheros local y URIs, cuyos enlaces pueden ser externos.

Tratamiento de URIs

libxml2 implementa internamente clientes ftp y http, por lo que podemos utilizar de manera transparente URIs de estos protocolos, tanto al indicarle al procesador que se desea tratar un archivo, como a la hora de colocar en el texto referencias a entidades externas o enlaces Xinclude.

En ambos casos el procesador XML::LibXML los trata como si de archivos locales se tratara. En consonancia con su uso transparente, el módulo ha previsto sólamente un diálogo simple con el servidor, por ejemplo el uso de ftp anónimo, sin implementar una capa compleja que lidie con el protocolo, por ejemplo ante el uso de proxies o de autentificación.

Si este uso simple no fuera suficiente, si deseáramos cambiar las URIs existentes en los documentos por otras, o habilitar el acceso a URIs de protocolos diferentes, tenemos la posibilidad de hacerlo, aunque en este caso nos queda a nosotros el implementar el diálogo con el servidor.

En este caso disponemos de métodos adicionales para dar instrucciones concretas al procesador para unas URIs determinadas.

El método match_callback nos permite indicar qué patrón debe buscarse en la URI. Nos remite a una función (una referencia a ella en realidad), que hará la tarea:

$parser->match_callback($subref);

Si la rutina indicada devuelve 1, es decir se cumple ese patrón, el procesador ejecutará las funciones que hemos indicado para él. La búsqueda tiene prioridad ante otros patrones internos de libxml2, por lo que si se activa no buscará en otros tipos de URIs; en caso de que no se siga el patrón, el procesador seguirá su curso normal.

Podemos crear funciones específicas para tres acciones que toma el procesador al tratar la URI: open_callback , al abrirla, read_callback, al leer datos del archivo referido por la URI, y close_callback, al cerrarlo.

En este ejemplo implementaremos unas funcionas básicas para gestionar URIs del protocolo tftp:

#!/usr/bin/perl -w
use XML::LibXML;
use Net::TFTP;

my $parser = XML::LibXML->new();
$parser->match_callback( \&match_uri );
$parser->read_callback( \&read_uri );
$parser->open_callback( \&open_uri );
$parser->close_callback( \&close_uri );

$doc=$parser->parse_file('tftp://localhost/sidebar.xml');
print $doc->toString();

sub match_uri {
    my $uri = shift;
    return $uri =~ /^tftp:\/\// ? 1 : 0; 
}
sub open_uri {
	my $uri = shift;
	$uri =~ /^tftp:\/\/([^\/]*)\/(.*)/;
	my $server=$1;
	my $file=$2;
	my $tftp = Net::TFTP->new($server, BlockSize => 1024);
	$tftp->binary;
	$tftp->get($file, "$file"); # /tmp/$file
	my $handler = new IO::File "$file"; # /tmp/$file
	return $handler;
}
sub read_uri {
    my $handler = shift;
    my $length  = shift;
    my $buffer = undef;
    if ( $handler ) {
        $handler->read( $buffer, $length );
    }
    return $buffer;
}
sub close_uri {
    my $handler = shift;
    if ( $handler ) {
        $handler->close();
    }
    return 1;
}

En nuestro ejemplo, la URI del archivo a procesar por el método parse_file es tftp://localhost/sidebar.xml.

La función match_uri sevuelve 1 si la URI comienza por la cadena "tftp://", e inicia el procesado alternativo.

El grueso del trabajo lo realizará la función open_uri. Ésta debe devolver un manejador de archivo, y tenemos varios métodos de implementar esto. Aquí utilizamos el más sencillo, guardar el archivo remoto en un directorio temporal, tras traerlo con Net::TFTP, y abrirlo entonces mediante IO::File, que nos proporciona el manejador necesario.

Compresión

XML::LibXML permite utilizar archivos locales comprimidos con gzip. Esta funcionalidad no está disponible con la gestión de URIs http o ftp, aunque lógicamente se podría implementar una similar mediante médodos de gestión propios para determinadas URIs, como hemos visto.

Simplemente se ha de utilizar un método de procesamiento de archivos, por ejemplo parse_file:

my $doc = $parser->parse_file('sidebar.xml.gz');

En el proceso contrario, si se vuelca un objeto XML::LibXML::Document a un archivo, com el método toFile es posible obtener una salida comprimida con zlib ( gzip). Ello dependerá obviamente de que libxml2 esté compilada con la opción de utilizar zlib.

Esta funcionalidad se controla mediante una variable interna, que podemos consultar con el método compression, o modificar mediante el método setCompression de un objeto XML::LibXML::Document.

En ausencia de compresión, la variable toma el valor -1, y si es positivo, es decir, está habilitada la compresión, guarda la ratio de compresión, que para gzip puede ser de 1 a 9.

Así, para volcar el árbol DOM del documento completo a un archivo, con la máxima compresión:

$doc->setCompression(9);
print my $state=$doc->toFile("/tmp/sidebar.xml.gz");

Cadenas

Hemos visto que varios de los procesadores de XML::LibXML nos permiten utilizar las típicas cadenas de texto XML construidas previamente dentro de nuestra aplicación Perl, guardadas en un escalar, por ejemplo XML::LibXML->parse_string.

use XML::LibXML;
my $parser = XML::LibXML->new();
my $doc = $parser->parse_string(<<'FIN');
<html><body><p>hola mundo</p></body></html>
FIN
print $doc->toString();

El texto procesado por parse_string ha de ser un texto bien formado, lo que conlleva una serie de restricciones al texto, sus atributos o el tratamiento de las entidades.

Esto es adecuado para textos XML completos, pero cuando procesamos fragmentos de texto XML es posible que no se cumplan todos los requisitos. Para estos casos contamos con otro método de procesamiento alternativo menos estricto, parse_balanced_chunk, que permite procesar cadenas de texto XML con la única condición de que sea balanceado, es decir que debe existir una etiqueta final por cada etiqueta inicial.

En este ejemplo el fragmento no es un elemento completo, es decir no contiene un elemento raíz, sino dos elementos, el primero un texto (un elemento CharData), pero el procesador puede seguir su tarea:

#!/usr/bin/perl -w
use XML::LibXML;
my $parser = XML::LibXML->new();
my $doc = $parser->parse_balanced_chunk(<<'FIN');
hola <i>mundo</i>
FIN
print $doc->toString();

Aunque el texto entrante sólo debe ser balanceado, los nodos Element que contenga sí deben ser bien formados. Esto es especialmente importante si se usan entidades: el fragmento debe contener también las indicaciones para resolverlas, o el procesador dará errores.

El procesador DOM XML::LibXML devuelve objetos XML::LibXML::DocumentFragment al invocar el método parse_balanced_chunk.

Cuando tratamos con cadenas de texto XML hay algunas limitaciones al uso del procesador. Una de ellas es que si usamos URIs relativas en los documentos el procesador no puede convertirlas en reales, porque no sabe la URI del texto inicial, con lo que otras muchas opciones como usar Xinclude, DTDs o entidades externas quedan comprometidas. En esos caso podemos añadir un segundo parámetro al método de procesamiento parse_string, con la URI base del flujo:

my $doc = $parser->parse_string( $xmlstring, $baseuri);

Flujos y fragmentos

Cuando el proceso va a trabajar en ráfagas, en vez de recibir todos los datos de una sola vez, podemos utilizar el modo de procesamiento que la documentación denomina push. En este caso el procesador es capaz de manejar trozos del texto XML, e irlos procesando a medida que llegan, guardando el estado de lo que lleva recolectado.

Este sistema es bastante diferente del también llamado procesado push que llevan a cabo los módulos XML::Filter::Dispatcher y XML::Twig.

Se parece a ellos en que efectivamente el procesador está pasivo a la espera de ls actuaciones de la aplicación en la fase de recepción de datos. Pero en estos últimos la aplicación solicita al procesador determinada información, lo contrario a SAX, donde el procesador va generando información (eventos) y la aplicación es quien la selecciona. En XML::LibXML::SAX el procesador va acumulando toda la información mientras está a la espera, y al final dispondrá de la representación completa del texto XML, a la que podremos tratar posteriormente.

El modo push del procesador puede ser utilizado en XML::LibXML con filehandles y con cadenas de texto, y en XML::LibXML::SAX con cadenas. En el primer caso, aún se dispone de una capa de bajo nivel si se desea un mayor control.

FileHandle

El primer método que podemos usar para una recepción a ráfagas está basado en el tratamiento de un flujo de datos. La llamada al procesador parse_fh nos permite utilizar para el flujo cualquier manejador de entrada (handler) que pueda gestionarse desde Perl. En este caso se le indica al procesador una referencia al manejador o un objeto IO::Handle.

Así podemos modificar el código incial para que obtenga el texto XML de la entrada estándard:

#!/usr/bin/perl -w
use XML::LibXML;
my $parser = XML::LibXML->new();
$ioref     = *STDIN{IO};
$doc = $parser->parse_fh( $ioref );
print $doc->toString();

Y ya podemos usarla en una tubería:

cat prueba.xml | prueba.pl;

En este método el procesado continúa hasta que se detecta el fin de la entrada, por ejemplo en un archivo hasta que llega al final del mismo.

El método parse_fh admite también un segundo parámetro, para indicar la URI base, que se usa igual que lo hacíamos con parse_string:

$doc = $parser->parse_fh( $ioref, $baseuri);

Fragmentos como cadenas

Otro método, parse_chunk, nos permite utilizar las ráfagas en cadenas de texto.

use XML::LibXML;
  my $parser = XML::LibXML->new;
  for my $string ( "<", "hola a todos", "/>") {
       $parser->parse_chunk( $string );
  }
  my $doc = $parser->parse_chunk("", 1); # terminate the parsing
print $doc->toString;

En este caso el procesador no sabe cuándo ha llegado la última cadena, y hemos de indicársela expresamente, con un segundo parámetro que activa el flag de fin de la entrada.

Aquí no dispondremos de la posibilidad de indicar una URI base para textos que incluyan rutas relativas.

Si los métodos anteriores no son suficientes para nuestro entorno, por ejemplo, porque la fuente de datos produce errores, aún podemos utilizar el procesador push a bajo nivel. En este caso, obviamente, armar el proceso es más parecido al trabajo real de los datos con libxml2, no como en el caso anterior en que LibXML::XML hacía todo el trabajo por nosotros. Debemos inicializar la matriz de cadenas con el método start_push, llenarla con push y declararla con el método finish_push.

A cambio, aquí es posible utilizar las facilidades de recuperación de errores del procesador, como hacemos con del método recover del modo pull; en caso de error en el procesado de una de las secuencias aún es posible continuar. Para activarlas enviamos un 1 como parámetro al método finish_push.

use XML::LibXML;
my $parser = XML::LibXML->new;
eval {
	for my $string ( "<hola>", "a" , "<todos/>") {
       		$parser->push( $string);
  	}
	$doc = $parser->finish_push(1);
};
print $doc->toString();

En esta variante de los ejemplos anteriores el elemento hola está roto, y el procesador lo advierte, pero aún así continúa procesando el texto e intenta reparar el error:

Entity: line 1: parser error : Extra content at the end of the document
<hola>a<todos/>
               ^
<?xml version="1.0"?>
<hola>a<todos/></hola>

El procesador DOM

El esquema general de trabajo con el procesador DOM, XML::LibXML, podría ser éste:

Esquema general del procesamiento con XML::LibXML

Esquema general del procesamiento con XML::LibXML

La inicialización crea la instancia del módulo e instruye al procesador de las opciones que deseemos para modificar su conducta.

Tras ello pasaremos a invocar a el procesador, eligiendo alguna de las múltiples alternativas existentes, entre procesado DOM, pull o push, y SAX. Es posible que el documento sea reprocesado una segunda vez, si se necesitara completar con datos externos, por el uso de XInclude. Al final de esta etapa contamos con una estructura de datos que representa la información XML contenida en el texto de entrada.

En la última fase haremos algo con esos datos.

En realidad esto puede complicarse sensiblemente, por ejemplo puede colocarse una capa de validación después del procesamiento, o este tratamiento se puede ensartar en otros procesos que también tratan XML, quizá al utilizarse como procesador un manejador SAX que recontruye el árbol DOM desde eventos SAX (XML::LibXML::SAX::Builder), y así múltiples variantes. Pero el esquema general nos sirve de guía.

Obsérvese que el procesador puede ser utilizado para el tratamiento de varios textos XML, pero es único. Esto tiene consecuencias importantes en trabajos en cadena: Si se rompe el proceso por error en el tratamiento de un archivo, LibXML::XML pierde la conexión con libxml2 y la cadena se interrumpe, el resto no será tenido en cuenta, aunque contuviera datos sin errores. Veremos luego que LibXML::XML provee de algún mecanismo para tratar textos conflictivos.

La segunda consecuencia del esquema es que las opciones son globales, afectan a todos los textos. Es posible ir cambiando el comportamiento del procesador para cada texto en concreto, pero estos cambios han de ser explícitamente declarados y revocados más tarde, pues sus efectos son permanentes.

La fase de inicialización parte del método new que crea la instancia del objeto procesador, y antes de llamarlo podemos modificar algunas opciones.

my $parser = XML::LibXML->new();;

El objeto procesador que hemos obtenido al inicializar el módulo nos prevee de varios métodos para cambiar las condiciones de trabajo. En su mayor parte son flags, y reciben como parámetro un 1 para habilitar la opción o un 0 para deshabilitarla.

  • validation

    Si se activa esta opción obliga al procesador a validar el documento contra un DTD. Por defecto está deshabilitado.

    $parser->validation(1);

    Si el documento no es válido, el procesador provocará un error que interrumpirá el proceso.

    La validación contra un DTD puede realizarse no obstante después del procesamiento, llamando al método >is_valid.

  • recover

    Por defecto el procesador falla si el documento no está bien formado. Por ejemplo aquí nos hemos olvidado de cerrar el elemento title:

    <sidebar><section>
       <title>Mi web
            <item>
                    <title>Informacion</title>
                    <url>/Informacion/</url>
            </item>
       </section></sidebar>

    Lo que hace que el proceso no termine:

    sidebar.xml:9: parser error : Opening and ending tag mismatch: title line 4 and section
       </section></sidebar>
                 ^
    sidebar.xml:9: parser error : Opening and ending tag mismatch: section line 3 and sidebar
       </section></sidebar>
                           ^
    sidebar.xml:10: parser error : Premature end of data in tag sidebar line 3
    ^
     at sidebar.pl line 7

    Si embargo si habilitamos la opción recover

    $parser->recover(1);
    my $source = $parser->parse_file('sidebar.xml');
    print $source->toString;

    el procesador seguirá ofreciendo los errores anteriores, pero intentará en la medida de lo posible restaurar las etiquetas perdidas hasta completar los elementos, dando una salida bien formada, esperando que sea también válida:

    <sidebar><section>
       <title>Mi web
            <item>
                    <title>Informacion</title>
                    <url>/Informacion/</url>
            </item>
    </title></section></sidebar>

    En este caso vemos que ha tenido un éxito limitado al restaurar el original, pero el proceso ha podido seguir y los daños se han minimizado.

    Esta opción es útil cuando procesamos documentos no seguros, por ejemplo los generados en tiempo real por formularios web, o documentos HTML o SGML en previsión de que el procesador no sea capaz de balancear elementos u otros errores.

    Para un mayor control de los errores podemos acudir a la extensión XML::LibXML::Fixup, que los intercepta y permite efectuar acciones, quizá reparadoras, y continuar el procesamiento.

  • expand_entities

    Por defecto las entidades externas se expanden, pero puede cambiarse el comportamiento, deshabilitando la opción. Es casi seguro que si el documento tiene entidades externas querremos que se tengan en cuenta, por lo que quizá esto tan sólo es útil en algunos contextos de depuración.

    $parser->expand_entities(0);
  • keep_blanks

    Si se mantienen los elementos constituidos en exclusiva por espacios en blanco. Estos elementos se incluyen sobre todo para una lectura más fácil del texto XML, por ejemplo indentándolo.

    $parser->keep_blanks(0);

    Por defecto la opción es afirmativa. Si se anula el texto XML se compacta, y un volcado de los datos procesados ofrecerá un documento de una sola línea.

  • pedantic_parser

    Esta opción añade mensajes adicionales de atención analizando varias cuestiones, típicamente para algunos casos de depuración:

    • los valores xml:lang

    • redeclaración de entidades

    • nombres de los espacios de nombres en conformidad con el RFC 2396 o si no son URIs absolutas

    Por defecto la opción está deshabilitada.

    $parser->pedantic_parser(1);
  • line_numbers

    Esta opción hace que el procesador guarde el número de línea de un nodo, habitualmente para depuración. Por defecto está deshabilitado.

    $parser->line_numbers(1);
  • load_ext_dtd

    Si expande el DTD externo cuando no hay validación. No tiene efecto en la validación. Si no se especifica valor, está deshabilitado.

    $parser->load_ext_dtd(1);
  • complete_attributes

    Esta opción completa el documento original con los atributos por defecto que estuvieran incluidos en la declaración de tipo de documento. Para ello se carga el DTD aunque no se especifique explícitamente con otras opciones.

    $parser->complete_attributes(1);

    Así, si en el DTD un elemento subsection contiene un atributo expand de valor no,

    <!ATTLIST subsection expand ( yes | no ) "no">

    la habilitación de esta opción en un texto que incluya el elmento sin atributos,

    <subsection> ...</subsection>

    provocará que el original quede modificado, y un volcado del elemento mostrará el atributo con su valor por defecto:

    <subsection expand="no">...</subsection>
  • expand_xinclude

    Expande los elementos XInclude al tiempo del procesamiento.

    $parser->expand_xinclude(1);

    Es posible procesar los elementos de Xinclude en una segunda pasada del procesador, mediante el método process_xincludes.

  • load_catalog

    Esta opción permite indicar un catálogo a utilizar en el procesamiento:

    $parser->load_catalog( $catalog_file );

    Los catálogos son tablas que mapean uris externas de acceso a recursos XML (DTD, esquemas, hojas de estilo,... ) con archivos situados en el sistema de ficheros local. Típicamente permiten usar copias locales de las definiciones de documento y hojas de estilo estándares, para facilitar la rapidez del proceso.

    La implementación de LibXML no permite utilizar esta opción juntamente con la carga de entidades externas.

  • base_uri

    Permite indicar una uri base que puedan utilizar los documentos XML o XSL que contengan enlaces relativos, por ejemplo de DTDs, entidades externas o documentos enlazados con XInclude.:

    $parser->base_uri( $base_uri );
  • gdome_dom

    XML::LibXML puede ser utilizado para procesar datos que después sean utilizados con XML::GDOME, un interfaz Perl a la librería gdome, a su vez una versión de libxml2 más respetuosa con los estándares DOM. Esta opción, calificada como experimental, permite configurar a LibXML::XML para que el resultado del procesamiento fuerce esta interoperatividad.

    $parser->gdome_dom(1);
  • clean_namespaces

    Esta opción permite eliminar declaraciones redundanes de espacios de nombres en el árbol DOM. Por defecto está deshabilitada.

    $parser->clean_namespaces(1);

La mayoría de las opciones de LibXML::XML afectan a valores de la estructura de libxml2 xmlGlobalState. Éste es el mapa, tal como lo podemos extraer de las funciones que modifican el hash %options[8], y la que modifica xmlGlobalState,LibXML_init_parser[9].

Table 2. Correspondecia de opciones LibXML::XML y libxml2

método

clave de %options

xmlGlobalState

Defecto

validation

XML_LIBXML_VALIDATION

xmlDoValidityCheckingDefaultValue

No

xmlLoadExtDtdDefaultValue

expand_entities

XML_LIBXML_EXPAND_ENTITIES

xmlSubstituteEntitiesDefaultValue

No

xmlLoadExtDtdDefaultValue

keep_blanks

XML_LIBXML_KEEP_BLANKS

xmlKeepBlanksDefaultValue

pedantic_parser

XML_LIBXML_PEDANTIC

xmlPedanticParserDefaultValue

No

line_numbers

XML_LIBXML_LINENUMBERS

xmlLineNumbersDefaultValue

No

load_ext_dtd

XML_LIBXML_EXT_DTD

xmlLoadExtDtdDefaultValue

No

complete_attributes

XML_LIBXML_COMPLETE_ATTR

xmlLoadExtDtdDefaultValue

No

recover

XML_LIBXML_RECOVER

No

expand_xinclude

XML_LIBXML_EXPAND_XINCLUDE

base_uri

XML_LIBXML_BASE_URI

  

gdome_dom

XML_LIBXML_GDOME

No

Observemos que algunas de las opciones automáticamente activan otras. Si activamos validation, es decir decimos a LibXML::XML que deseamos que valide el documento, automáticamente se activa el indicador de que se carge el DTD, o sea load_ext_dtd.

SAX

Procesador SAX

XML::LibXML::SAX provee un procesador PerlSAX. Su uso más simple, como es habitual en SAX, encadena un procesador a un manejador, al que pasa los eventos.

Así, en este ejemplo, utilizamos el manejador XML::Handler::PrintEvents, que imprime un informe de cada evento que recibe:

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

Este procesador es puro SAX, no construye el árbo DOM de los datos con que va trabajando; en su lugar va generando los eventos.

A los métodos de SAX2 se le añade uno nuevo, parse_chunk, que permite utilizar las facilidades del libxml2 con textos incompletos, aqunque balanceados, como hace el método parse_balanced_chunk del procesador DOM.

$p->parse_chunk($cadena');

SAX desde DOM

XML::LibXML::SAX::Parser provee de un generador PerlSAX a partir de un árbol DOM. No es un procesador de flujo, simplemente produce una lista de eventos que se pueden utilizar en la tubería SAX:

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

DOM

Validación

El procesador DOM de LibXML::XML puede validar documentos XML contra un DTD en tiempo de procesamiento. Por defecto hemos visto que no lo hace, pero podemos cambiar esta conducta habilitando la validación con el método validation.

Es posible no obstante validar el documento después de procesado. Este sistema se aplica tanto a DTDs como a RelaxNG y esquemas XML.

DTDs

Estos procediemtos se aplican a los objetos del tipo XML::LibXML::Document, es decir al árbol DOM completo obtenido tras el procesamiento con alguno de los métodos adecuados DOM, por ejemplo parse_file.

  • Con el método validate

    En este caso debemos procesar adicionalmente el DTD. El método new de XML::LibXML::Dtd nos devuelve un objeto DOM de la clase XML::LibXML::Dtd, un tipo especial de XML::LibXML::Node. El utilizar un módulo aparte para el procesamiento de los DTDs es necesario, porque recordemos que los DTDs no son documentos válidos XML.

    Y después llamamos al método validate del objeto Document que representa el documento XML:

    use XML::LibXML;
    my $parser = XML::LibXML-> new();
    my $doc = $parser->parse_file("sidebar.xml");
    
    my $dtd = XML::LibXML::Dtd->new(
                          "-//AxKit//DTD Sidebar XML V1.0//EN",
                          "sidebar.dtd"
                                    );
    $doc->validate($dtd);

    El método new de XML::LibXML::Dtd recibe como parámetros el identificador público del DTD y el nombre del archivo que lo contiene. Si el DTD lo guardamos como una cadena de texto, en un escalar, utilizaremos el método alternativo parse_string:

    my $dtd = XML::LibXML::Dtd->parse_string($mydtd);
  • Con el método is_valid

    Desde el objeto XML::LibXML::Document se puede utilizar otro método alternativo, is_valid, que devuelve un valor booleano con el resultado. Si no hay otro camino anterior para que el procesador sepa del archivo que contiene el DTD, se lo podemos inclir como parámetro:

    my $doc = $parser->parse_file("sidebar.xml");
    if (!$doc->is_valid("sidebar.dtd")) {
           warn("document is not valid!");
       }

RelaxNG

El procedimiento de validación de un documento contra un esquema RelaxNG difiere en algo de la validación contra DTDs, que son el procesamiento por defecto en XML::LibXML, y que son los definidos en el estandard.

El paso inicial del procesamiento del documento XML es igual que en caso anterior. El procesamiento del esquema RelaxNG lo lleva a cabo el módulo XML::LibXML::RelaxNG, y el método validate que lleva a cabo la acción final de validación en esta ocasión lo provee este últmo módulo.

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

Podemos probar este código con los ejemplos que nos ofrece el tutorial de RelaxNG que nos proporcionan sus desarrolladores.

El método new el validador recibe como parámetro un par clave-valor que indica el tipo de datos en que se guarda el esquema RelaxNG y dónde ha de irlo a buscar el programa. Las opciones posibles en los tipos de datos son:

  • location, un archivo físico

  • string, un escalar con el texto XML correspondiente

  • DOM, un objeto del tipo XML::LibXML::Document que representa el esquema RelaxNG, generado previamente por el procesador

Así, la creación de la instancia del validador con string podría ser:

my $validator = XML::LibXML::RelaxNG->new(string =>
<<'FIN');
<element name="addressBook" xmlns="http://relaxng.org/ns/structure/1.0">
  <zeroOrMore>
    <element name="card">
      <element name="name">
        <text/>
      </element>
      <element name="email">
        <text/>
      </element>
    </element>
  </zeroOrMore>
</element>
FIN;

Y con DOM debemos procesar previamente el esquema RelaxNG, que es después de todo un documento XML:

my $rng=$parser-> parse_file("prueba.rng");
my $validator = XML::LibXML::RelaxNG->new(DOM => $rng);;

Esquemas XML

El tratamiento para esquemas XML es similar al de RelaxNG, haciendo uso de un validador de la clase XML::LibXML::Schema. Esta vez sólo disponemos de dos opciones para entregar el código del esquema, la de un archivo y un escalar:

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

Podemos comprobar este código con los ejemplos de un tutorial de xml.com.

Si se tratara de un escalar, el comportamiento es igual al que vimos más arriba:

my $validator = XML::LibXML::Schema->new(string =>$myXMLschema);

Clases

DOM incluye ahora varios conjuntos de interfaces. LibXML::XML implementa tan sólo un subconjunto de los interfaces del Core de DOM. En lo que respecta a los que regulan la integración con XPath, la creación de documentos XML, y la validación, libxml2 había implementado soluciones específicas bastante antes de que se formalizaran estos estándares, que tienen algo más de un año de antigüedad, por lo que LibXML::XML ofrece soluciones bien diferentes. Libxml2 también provee desde hace tiempo de algunas extensiones de salida, mientras ahora las operaciones de entrada y salida se inscriben en el estándard en la especificación Load and Save, con un planteamiento bien diferente.

En lo que respecta a la especificación Core de DOM, LibXML::XML no implementa todos los interfaces; en especial faltan todos los añadidos en el nivel 3, aunque los que sí tienen cabida en libxml2 han sido actualizados en muchos de sus métodos a los niveles 2 y 3 de DOM.

En general, LibXML::XML tiende a cubrir los interfaces DOM que forman la representación en árbol. DOM representa a XML como un conjunto jerárquico de objetos Node. De esta clase básica se derivan las subclases Document, DocumentFragment, DocumentType, EntityReference, Element , Attr, ProcessingInstruction, Comment, Text, CDATASection, Entity y Notation.

XML::LibXML provee también clases correspondientes a los interfaces DOM que son colecciones de sus objetos: NodeList y NamedNodeMap.

La siguiente tabla muestra la correspondencia entre los interfaces DOM y las clases correspondientes de XML::LibXML.

Table 3. Implementación de los interfaces DOM y extensiones

Interfaz DOM

Nivel

Clase XML::LibXML

Attr

1

XML::LibXML::Attr

CDATASection

1

XML::LibXML::CDATASection

CharacterData

1

 

Comment

1

XML::LibXML::Comment

Document

1

XML::LibXML::Document

DocumentFragment

1

XML::LibXML::DocumentFragment

DocumentType

1

XML::LibXML::Dtd

DOMConfiguration

3

 

DOMError

3

 

DOMErrorHandler

3

 

DOMImplementation

1

 

DOMImplementationList

3

 

DOMImplementationSource

3

 

DOMLocator

3

 

DOMStringList

3

 

Element

1

XML::LibXML::Element

Entity

1

 

EntityReference

1

 

NamedNodeMap

1

XML::LibXML::NamedNodeMap

NameList

3

 

Node

1

XML::LibXML::Node

NodeList

1

XML::LibXML::NodeList

Notation

1

 

ProcessingInstruction

1

XML::LibXML::PI

Text

1

XML::LibXML::Text

TypeInfo

3

 

UserDataHandler

3

 
  

XML::LibXML::Namespace

En XML::LibXML no poseemos acceso directo a las propiedades de los objetos DOM. En su lugar contamos con métodos de lectura o escritura de estas propiedades.

La referencia al nivel DOM en la tabla anterior indica el que describió por primera vez el interfaz, aunque haya sido modificado por versiones posteriores. Para los interfaces implementados ya indicaremos el nivel de DOM utilizado en cada atributo y operación.

Destacan algunas peculiaridades de la implementación DOM de XML::LibXML:

  • No hay una implementación para el interfaz DomLocator, que permite establecer con precisión referencias de localización textual (fila, columna,...), típicamente usadas en descripción de errores de las tareas de procesado o validación. En LibXML::XML contamos tan sólo con un método line_number de la clase LibXML::XML::Node, que indica el número de línea en que se sitúa el nodo. El subsistema de errores de libxml2, accesible desde los métodos tradicionales de captura de error de Perl, ofrece información adicional sobre la posición, así como la naturaleza del error. En caso de error no se siguen las directrices de los interfaces DomError y DomErrorHandler.

  • XML::LibXML no tiene un interfaz CharacterData como base para los interfaces de texto,y en su lugar los modela desde la clase XML::LibXML::Text.

  • No hay en LibXML::XML una clase correspondiente con los interfaces DOM Entity, EntityReference o Notation: Para tratarlos se utilizan directamente elementos XML::LibXML::Node. Por tanto no se posee como en los otros casos un método new para su creación, y debe acudirse alternativamente a los métodos correspondientes de la clase del nodo superior, XML::LibXML::Document. Internamente las operaciones reconocen el tipo de nodo.

  • La clase XML::LibXML::NameSpace no forma parte de DOM, es una extensión al modelo efectuada por libxml2.

  • XML::LibXML no implementa una clase para el interfaz DOM DocumentType. En su lugar ofrece una clase más compleja, XML::LibXML::Dtd, que puede albergar como hijos todos los elementos del DTD completo, no limitándose a la declaración.

  • No hay una implementación para el interfaz DOM DOMImplementation, pero los métodos new y createDocument de LibXML::XML proveen de la operación de aquél, CreateDocument.

Las discrepancias entre DOM y LibXML::XML pueden entenderse mejor si observamos el modelo desde la perspectiva de libxml2.

DOM piensa el documento XML como un conjunto de nodos, de los que describe 12 tipos distintos, tal como vemos en la propiedad nodeType del interfaz Node. Estos nodos son los que constituyen el árbol DOM en sí.

Pero la implementación de libxml2 en cambio ve 21 tipos de nodos diferentes:

Table 4. Tipos de Nodos en DOM y en libxml2

Nodos DOM

Nodos libxml2

NodeType

Node

XML_ELEMENT_NODE

1

Attribute

XML_ATTRIBUTE_NODE

2

Text

XML_TEXT_NODE

3

CDATASection

XML_CDATA_SECTION_NODE

4

EntityReference

XML_ENTITY_REF_NODE

5

Entity

XML_ENTITY_NODE

6

ProcessingInstruction

XML_PI_NODE

7

Comment

XML_COMMENT_NODE

8

Document

XML_DOCUMENT_NODE

9

DocumentType

XML_DOCUMENT_TYPE_NODE

10

DocumentFragment

XML_DOCUMENT_FRAG_NODE

11

Notation

XML_NOTATION_NODE

12

XML_HTML_DOCUMENT_NODE

13

XML_DTD_NODE

14

XML_ELEMENT_DECL

15

XML_ATTRIBUTE_DECL

16

XML_ENTITY_DECL

17

XML_NAMESPACE_DECL

18

XML_XINCLUDE_START

19

XML_XINCLUDE_END

20

XML_DOCB_DOCUMENT_NODE

21

Dos de los añadidos señalan los nodos Document en textos no XML que puede tratar libxml2, la raíz de los documentos HTML y Docbook SGML. Otros dos indican los puntos de anclaje de los elementos Xinclude.

El nodo DocumentType es obsoleto, y se mantiene por compatibilidad con DOM, pero de hecho está sustituido por el nodo Dtd. Para los componentes del DTD se han añadido los 5 tipos restantes.

La mayoría de estos tipos de nodos han sido pasados a clases por XML::LibXML, pero no en todos los casos. Si no se ha establecido una clase, libxml2 los reconocerá, pero los trataremos desde XML::LibXML::Node, o desde los métodos que les correspondan en los nodos raíz o ramas de DOM, Document o Element.

Node

Node es el interfaz base del Core de DOM. DOM modela los documentos XML como un conjunto de nodos. Veamos la corespondencia entre el interfaz y su implementación con XML::LibXML::Node.

Table 5. Interfaz DOM Node y clase XML::LibXML::Node

Tipo

Nombre

RW

Nivel

Método

attr

attributes

R

1

attributes

attr

baseURI

R

3

 

attr

childNodes

R

1

childNodes

attr

firstChild

R

1

firstChild

attr

lastChild

R

1

lastChild

attr

localName

R

2

localname

attr

namespaceURI

R

2

namespaceURI

attr

nextSibling

R

1

nextSibling

attr

nodeName

R

1

nodeName - setNodeName

attr

nodeType

R

1

nodeType

attr

nodeValue

RW

1

nodeValue

attr

ownerDocument

R

1-2

ownerDocument - setOwnerDocument

attr

parent

R

1

parentNode

attr

prefix

RW

2

prefix

attr

previousSibling

R

1

previousSibling

attr

textContent

RW

3

textContent

oper

appendChild

-3

appendChild

oper

clone

1

cloneNode

oper

compareDocumentPosition

3

 

oper

getFeature

3

 

oper

getUserData

3

 

oper

hasAttributes

2

hasAttributes

oper

hasChildNodes

1

hasChildNodes

oper

insertBefore

1-3

insertBefore

oper

isDefaultNamespace

3

 

oper

isEqual

3

isEqual

oper

isSame

3

isSameNode

oper

isSupported

2

 

oper

lookupNamespaceURI

3

lookupNamespaceURI

oper

lookupPrefix

3

 

oper

normalize

2-3

normalize

oper

removeChild

1-3

removeChild

oper

replaceChild

1-3

replaceChild

oper

setUserData

3

 

addChild

addNewChild

addSibling

getNamespaces

getOwner

insertAfter

removeChildNodes

replaceNode

unbindNode

Para su uso típico en escritura de documentos, LibXML::XML provee de un método de escritura para el atributo NodeName del estándard DOM, que no contempla este caso, setNodeName. Tengamos en cuenta que DOM sigue caminos diferentes en la creación de documentos XML.

Otra diferencia con el estándard es el atributo baseURI, que DOM modela dentro de Node. Hemos visto que es un parámetro global en XML::LibXML, pasado al procesador, por lo que ni siquiera está a la altura del nodo raíz del documento XML, sino afectado a todos los documentos pasados al procesador.

El interfaz Node del estandard DOM provee de una propiedad de sólo lectura, ownerDocument, que indica el nodo de tipo Document que ocupa el lugar más alto de la estructura arbórea XML. Tal valor será nulo cuando se crea un nodo por primera vez. LibXML::XML, por contra, provee de dos métodos para esta propiedad, uno de lectura, ownerDocument, y otro de escritura, setOwnerDocument, lo que de hecho nos permite sacar un nodo de un documento e incluirlo en otro.

Esta operación ya la permite DOM 3, pero efectuada desde el nodo superior, Document, mediante la operación adoptNode. Lo que hace LibXML::XML es permitir también la operación desde el nodo inferior. Las dos últimas líneas del siguiente código son similares:

my $doc = XML::LibXML::Document->new;
my $xnode = XML::LibXML::Element->new("test");

my $node = $doc->adoptNode( $xnode );
$xnode->setOwnerDocument( $doc );

Adicionalmente, LibXML::XML permite adscribir nodos no sólo a documentos, sino también a fragmentos -nodos DocumentFragment-. De ahí que provee de un método para averiguar directamente el nodo superior, getOwner, sin hacer referencia explícita al nodo XML::LibXML::Document. Su efecto no obstante es similar al método ownerDocument si efectivametne el nodo está adscrito a un documento.

XML::LibXML extiende las operaciones del estándard DOM en la manipulación de nodos con métodos de adición (addChild, addNewChild, addSibling, insertAfter), substracción (removeChildNodes) o sustitución (replaceNode).

En lo que se refiere a los enlaces directos de los nodos, LibXML::XML provee de un método, unbindNode, que desliga el nodo de sus superiores y hermanos; la adscripción al nodo documento sin embargo no se pierde, y deberemos acudir a setOwnerDocument si deseamos cambiarlo de documento. Los nodos desligados son implícitamente hijos de un nodo XML::LibXML::DocumentFragment.

Otra extensión a DOM es el método getNamespaces, que devuelve los espacios de nombres declarados explícitamente en ese nodo (no los heredados desde los nodos superiores). Este método devuelve un arreglo de objetos XML::LibXML::Namespace.

XML::LibXML::Node ofrece métodos adicionales para búsquedas XPath y salida, a los que nos referiremos más tarde.

NodeList

DOM define el interfaz Nodelist como una simple lista ordenada de nodos, sin entrar a detallar su implementación. Tan sólo indica dos propiedades que describen el número de items (length) y los nodos componentes (item).

XML::LibXML permite utilizar las lista de nodos como arreglos de Perl, con cualquiera de sus procedimientos habituales. El siguiente ejemplo nos devolverá por este camino el número de elementos de la lista, que presentaremos seguidamente como cadenas de texto:

use XML::LibXML;

my $string = "<A><A><B/></A><A><B/></A></A>";
my $parser = XML::LibXML->new();
my $document = $parser->parse_string($string);
my @array = $document->getElementsByTagName("A");
print scalar( @array ), "\n";
foreach $node (@array) {
	print $node->toString, "\n";
}

Lo que devolverá el número de elementos, y cada uno de los nodos, como texto:

3
<A><A><B/></A><A><B/></A></A>
<A><B/></A>
<A><B/></A>

XML::LibXML provee también de una implementación específica del interfaz Nodelist, XML::LibXML::Nodelist. Esta clase funciona de manera bastante similar a un arreglo Perl, con métodos para crear un objeto (new), introducir elementos en él (push como en una pila, unshift como en colas), extraerlos (pop como en pilas, shift como en colas) y para conocer el número de elementos (size, equivalente a la propiedad lenght del interfaz).

Si utilizamos la clase XML::LibXML::Nodelist::Iterator, externa a la distribución, dispondremos de un método adicional, iterator, que nos permite crear subconjuntos de XML::LibXML::Nodelist que cumplan determinada condición XPath y tratarlos como una lista, con movimientos lineales hacia arriba y hacia abajo.

Los métodos de la clase XML::LibXML::Document que devuelven listas de nodos si se utilizan en contexto de arreglos, como en el ejemplo anterior, devuelven arreglos Perl, pero si se utilizan en contexto escalar devuelven un objeto XML::LibXML::Nodelist.

Podemos modificar ligeramente el ejemplo anterior, para conocer desde un objeto XML::LibXML::Nodelist su número de elementos, y que nos devuelva el último:

use XML::LibXML;

my $string = "<A><A><B/></A><A><B/></A></A>";
my $parser = XML::LibXML->new();
my $document = $parser->parse_string($string);
my $nodelist = $document->getElementsByTagName("A");
print $nodelist->size, "\n";
my $node=$nodelist->pop;
print $node->toString;

Obtendremos:

3
<A><B/></A>

Los métodos prepend y append nos permiten añadir un objeto XML::LibXML::Nodelist a otro ya existente, colocándolo al principio o al final respectivamente.

Modificamos nuestro ejemplo para que añada un nuevo Nodelist al que hemos creado inicialmente, al final del conjunto existente:

my $nodelist2 = $document->getElementsByTagName("B");
$nodelist->append($nodelist2);

y el resultado mostrará los cambios introducidos tras la adición del nuevo objeto XML::LibXML::Nodelist:

5
<B/>

El método get_nodelist devuelve un arreglo Perl, con lo que podremos revertir al modelo de gestión de listas de nodos que veíamos arriba.

Añadiendo estas lineas al ejemplo anterior:

my @array=$nodelist->get_nodelist;
print scalar( @array ), "\n";

nos devolverá el número de elementos del arreglo que hemos creado con los nodos que contiene el objeto XML::LibXML::Nodelist.

NamedNodeMap

La interfaz NamedNodeMap permite acceder a colecciones de nodos, pero a diferencia de Nodelist los nodos están agrupados por su nombre, y no están ordenados.

Table 6. Interfaz DOM NamedNodeMap y clase XML::LibXML::NamedNodeMap

Tipo

Nombre

RW

Nivel

Método

attr

length

R

1

length

oper

getNamedItem

 

1

getNamedItem

oper

getNamedItemNS

 

2

getNamedItemNS

oper

item

 

1

item

oper

removeNamedItem

 

1

removeNamedItem

oper

removeNamedItemNS

 

2

removeNamedItemNS

oper

setNamedItem

 

1

setNamedItem

oper

setNamedItemNS

 

2

setNamedItemNS

    

new

    

nodes

El método attributes de XML::LibXML::Node devuelve en contexto escalar un nodo XML::LibXML::NamedNodeMap (en contexto de arreglo devuelve un arreglo Perl de objetos XML::LibXML::Attr):

use XML::LibXML;
my $string = '<a n="1" b="c"><b/></a>';
my $parser = XML::LibXML->new();
my $doc= $parser->parse_string($string);

my $nnm=$doc->firstChild->attributes;
print $nnm->length;

El método length devuelve el número de nodos que componen la colección. El método new que introduce XML::LibXML::NamedNodeMap permite crear un objeto nuevo, a partir de un arreglo de nodos:

use XML::LibXML;
my $string = '<a><b/><c/><d/></a>';
my $parser = XML::LibXML->new();
my $doc= $parser->parse_string($string);
my @array=($doc->firstChild->firstChild, $doc->firstChild->lastChild);
my $nnm=XML::LibXML::NamedNodeMap->new(@array);
print $nnm->getNamedItem("d")->toString;

El uso habitual de esta colección de nodos es a través de su nombre. En este ejemplo, el método getNamedItem nos devuelve el nodo correspondiente a un nombre que conocemos previamente.

El método nodes, también extensión de XML::LibXML::NamedNodeMap a DOM, permite realizar la operación contraria a new, o sea obtener un arreglo de nodos a partir de un objeto XML::LibXML::NamedNodeMap. nodes en realidad devuelve una referencia a un arreglo. Así, si el objeto XML::LibXML::NamedNodeMap anterior se convierte en una colección ordenada, podemos acceder a ella secuencialmente:

my $new_array= $nnm->nodes;
foreach $node ( @{$new_array} ) {   
    print $node->toString , "\n";
}

Document

Document y DocumentFragment son los nodos más altos en la jerarquía que establece el modelo DOM para representar los textos XML. Todos los demás nodos serán hijos de estos nodos. En consecuencia, el procesador DOM de LibXML::XML devuelve objetos de estos tipos tras tratar un texto de entrada.

Table 7. Interfaz DOM Document y clase XML::LibXML::Document

Tipo

Nombre

RW

Nivel

Método

attr

doctype

R

1

 

attr

documentElement

R

1

documentElement - setDocumentElement

attr

documentURI

RW

3

 

attr

domConfig

R

3

 

attr

implementation

R

1

 

attr

inputEncoding

R

3

 

attr

strictErrorChecking

RW

3

 

attr

xmlEncoding

R

3

encoding - setEncoding

attr

xmlStandalone

RW

3

standalone - setStandalone

attr

xmlVersion

RW

3

version

oper

adoptNode

3

adoptNode

oper

createAttribute

1

createAttribute

oper

createAttributeNS

2

createAttributeNS

oper

createCDATASection

1

create

oper

createComment

1

createComment

oper

createDocumentFragment

1

createDocumentFragment

oper

createElement

1

createElement

oper

createElementNS

2

createElementNS

oper

createEntityReference

1

createEntityReference

oper

createProcessingInstruction

1

createProcessingInstruction

oper

createTextNode

1

createTextNode

oper

getElementById

2

 

oper

getElementsByTagName

1

getElementsByTagName

oper

getElementsByTagNameNS

2

getElementsByTagName

oper

importNode

2

importNode

oper

normalize

3

 

oper

renameNode

3

 

compression

createDocument | new

createExternalSubset

createInternalSubset

externalSubset

getElementsById

getElementsByLocalName

indexElements

insertProcessingInstruction

internalSubset

removeExternalSubset

removeInternalSubset

setCompression

setExternalSubset

setInternalSubset

Si no hemos utilizado el procesador, y deseamos crear un objeto XML::LibXML::Document nuevo, debemos partir del método createDocument o su alias new.

XML::LibXML añade dos extensiones adicionales a las operaciones que permiten obtener listas de nodos desde el nodo documento: getElementsById y getElementsByLocalName.

XML::LibXML permite compresión, y por ello añade los métodos compression y setCompression a los nodos Document; se trata de la manipulación de una propiedad, y obviamente no afecta al árbol DOM, sino a las operaciones de escritura que se se realicen posteriormente con él.

DOM establece una única propiedad, doctype, de sólo lectura, para indicar el tipo de documento (interfaz DocType). LibXML::XML en cambio utiliza la clase XML::LibXML::Dtd para representar el tipo de documento.

libxml2 distingue entre nodos Dtd internos y externos: los internos son procesados como paso previo a su utilización; de ahí que los métodos de adscripción genéricos de Document (adoptNode, importNode), no son suficientes para este tipo de nodos. En su lugar se ofrece un grupo extenso de métodos para actuar sobre este tipo de nodos desde el nivel superior del árbol (XML::LibXML::Document), además de los que provee la clase XML::LibXML::Dtd en el nivel inferior. Se proveen para crear (createDTD, createExternalSubset, createInternalSubset), indicar cuál se aplica (setExternalSubset, setInternalSubset) o simplemente leer el valor (externalSubset, internalSubset).

El siguiente ejemplo establece el tipo de un documento:

use XML::LibXML;
my $doc = XML::LibXML::Document->new;
my $dtd = $doc->createExternalSubset( "html",
                                          "-//W3C//DTD XHTML 1.0 Transitional//EN",
                                          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
                                        );
print $dtd->toString;

El resultado:

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

El método createDTD crea un nodo XML::LibXML::Dtd sin prejuzgar si es interno o externo. El uso posterior de uno de los métodos setExternalSubset o setInternalSubset nos permite precisarlo posteriormente, sustituyendo a adoptNode o importNode:

my $dtd = $doc->createDTD( "html",
                                "-//W3C//DTD XHTML 1.0 Transitional//EN",
                                "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
                              );
$doc->setInternalSubset( $dtd);

Al método insertProcessingInstruction, alternativa a createProcessingInstruction nos referiremos al tratar las instrucciones de procesamiento

Element

Los nodos Element son contenedores de otros nodos. En el modelo DOM son los encargados de crear las ramas del árbol como posición intermedia entre los nodos raíz (Document o DocumentFragment) y los nodos con información final.

Table 8. Interfaz DOM Element y clase XML::LibXML::Element

Tipo

Nombre

RW

Nivel

Método

attr

schemaTypeInfo

R

3

 

attr

tagName

R

1

 

oper

getAttribute

1

getAttribute

oper

getAttributeNode

1

getAttributeNode

oper

getAttributeNodeNS

2

getAttributeNodeNS

oper

getAttributeNS

2

getAttributeNS

oper

getElementsByTagName

1

getElementsByTagName

oper

getElementsByTagNameNS

2

getElementsByTagNameNS

oper

hasAttribute

2

hasAttribute

oper

hasAttributeNS

2

hasAttributeNS

oper

removeAttribute

1

removeAttribute

oper

removeAttributeNode

1

 

oper

removeAttributeNS

2

removeAttributeNS

oper

setAttribute

1

setAttribute

oper

setAttributeNode

1

 

oper

setAttributeNodeNS

2

 

oper

setAttributeNS

2

setAttributeNS

oper

setIdAttribute

3

 

oper

setIdAttributeNode

3

 

oper

setIdAttributeNS

3

 

appendText | appendTextNode

appendWellBalancedChunk | appendChild

appendTextChild

getChildrenByTagName

getChildrenByTagNameNS

getElementsByLocalName

new

setNamespace

DOM incluye en el interfaz la propiedad tagName, que no está implementada en XML::LibXML::Element. En su lugar acudiremos a los métodos nodeName, setNodeName o localname que heredamos de la clase padre, XML::LibXML::Node.

XML::LibXML incluye extensiones al estandard para añadir nodos hijo a un objeto Element. El método appendWellBalancedChunk -o su alias appendChild- permite incluir un fragmento balanceado, es decir una sucesión cualquiera de nodos hijo, sin encuadrarse en un elemento. Puesto que el procesador puede tratar también textos balanceados, podemos obtener esos fragmentos directamente, y añadirlos a un nodo Element, de esta manera:

my $element = XML::LibXML::Element->new( $name )
my $fragment = $parser->parse_xml_chunk( $chunk );
$element->appendChild( $fragment );

El método appendText - o su alias appendTextNode - añade un nodo texto a un elemento, que puede contener otros nodos hijos. El método appendTextChild en cambio crea un elemento con un único hijo, un nodo texto.

Otros métodos extienden la posibilidad de buscar conjuntos de nodos dentro del elemento, que en DOM queda reducida a la búsqueda de nodos con un determinado nombre, ya sea simple (getElementsByTagName) o con espacio de nombres (getElementsByTagNameNS). El método getElementsByLocalName permite buscar elementos que contengan un determinado nombre local, no sólo el conjunto nombre local + espacio de nombres que permite getElementsByTagNameNS. Por último, los métodos getChildrenByTagName y getChildrenByTagNameNS listan los nodos hijo con igual nombre de etiqueta.

El método setNameSpace permite añadir espacios de nombres al nodo Element. Según los parámetros recibidos, se instalará un espacio de nombres adicional, a añadir a la lista de los ya existentes, o será el espacio de nombres por defecto del elemento.

DOM prevee la creación de nodos Element desde Document. LibXML::XML en cambio nos permite crear nodos LibXML::XML::Element independientes, gracias al método new. Estos nodos están desligados mientras no los insertemos en un árbol, y por defecto quedan adscritos a un nodo LibXML::XML::DocumentFragment implícito.

Interfaces de texto

DOM modela los interfaces de texto desde un interfaz común, CharacterData, del que heredan los interfaces Text y Comment. A su vez el interfaz Text sirve de base para CDATASection.

Los interfaces de texto de DOM

XML::LibXML prescinde del interfaz CharacterData y concentra todos los métodos en LibXML::XML::Text. Las clases LibXML::XML::Comment y LibXML::XML::CDATASection heredan los métodos de LibXML::XML::Text y no poseen métodos propios.

Las clases de texto de XML::LibXML

Por lo demás, LibXML::XML::Text implementa prácticamente todo el interfaz CharacterData. Pero como las operaciones de subcadenas son limitadas en DOM, añade extensiones para ellas.

Las operaciones DOM replaceData y deleteData (y sus correspondientes métodos XML::LibXML::Text) cambian o borran subcadenas, indicando la posición de inicio y la longitud. Los métodos replaceDataString y deleteDataString parten de subcadenas conocidas, sustituyéndolas o eliminándolas.

$text->deleteDataString($remstring, $flag);
$text->replaceDataString($old, $new, $flag);

El parámetro final es un indicador de si la operación se realizará una sólo vez (con valor 0) o en todas las ocurrencias (con valor 1).

Por último, el método replaceDataRegEx permite la sustitución de una cadena mediante un patrón de expresiones regulares.

Como en otros nodos intermedios o finales, LibXML::XML permite extender las posibilidades de DOM creando nodos LibXML::XML::Text invocando la clase directamente con el método new, sin necesidad de hacerlo desde LibXML::XML::Document.

PI (ProcessingInstruction)

DOM define este interfaz para instrucciones de procesamiento con dos únicas propiedades, para indicar la marca o identificador de enlace (la primera palabra) y un texto que le sigue.

Table 9. Interfaz DOM ProcessingInstruction y clase XML::LibXML::PI

Tipo

Nombre

RW

Nivel

Método

attr

data

RW

1

setData

attr

target

R

1

 

El texto puede ser escrito mediante el método setData, mientras que para la lectura utilizaremos el método getData que provee la clase superior, XML::LibXML::Node. La marca es accesible mediante los métodos nodeName y setNodeName de XML::LibXML::Node o al tiempo de creación del nodo.

La creación de una instrucción de procesamiento, siguiendo los métodos DOM, implica tres pasos: Crearla, escribir el texto que contiene y añadirla al árbol:

my $pi = $dom->createProcessingInstruction("abc");
$pi->setData(foo=>'bar', foobar=>'foobar');
$dom->appendChild( $pi );

XML::LibXML::Document nos provee del método alternativo insertProcessingInstruction para hacerlo todo en un paso:

$dom->insertProcessingInstruction("abc",'foo="bar" foobar="foobar"');

que dará el mismo resultado que el código de arriba:

<?abc foo="bar" foobar="foobar"?>

Attr (Attribute)

XML::LibXML::Attr implementa el interfaz Attribute de DOM.

Table 10. Interfaz DOM Attribute y clase XML::LibXML::Attr

Tipo

Nombre

RW

Nivel

Método

attr

isId

R

3

 

attr

name

R

1

 

attr

ownerElement

R

2

getOwnerElement

attr

schemaTypeInfo

R

3

 

attr

specified

R

1

 

attr

value

RW

1

value |getValue - setValue

new

setNamespace

En esta clase tampoco hay una implementación de la propiedad Name de DOM, y acudiremos a los métodos nodeName, setNodeName o localname que heredamos de XML::LibXML::Node.

El método setNameSpace extiende el interfaz DOM para permitir colocar el atributo en un espacio de nombres.

Dtd

La clase XML::LibXML::Dtd incluye un procesador para documentos DTD, como la clase LibXML::XML ofrece un procesador para documentos XML. El procesamiento con esta clase devuelve un objeto especial, de esta clase.

Los nodos Dtd se corresponden -más bien sustituyen- al interfaz DOM DocumentType, que es hijo (en una sola ocurrencia) de un nodo Document. XML::LibXML::Dtd no obstante no implementa los métodos de DocumentType. Tan sólo posee los métodos que hereda de XML::LibXML::Node, aunque dada la naturaleza de los DTDs no todos los métodos están disponibles.

Puesto que los DTDs no soportan espacios de nombres, los métodos de XML::LibXML::Node que se refieren a ellos no están soportados en este tipo de nodo.

Sí en cambio soportan todos los métodos de XML::LibXML::Node referentes a los nodos que se encuentran incluidos en él, que son objetos Node de los tipos 15 a 18 en la tabla de NodeType de libxml2 que veíamos arriba, como las operaciones de inserción, eliminación o búsqueda.

Como hemos visto, la clase XML::LibXML::Document también manipula objetos XML::LibXML::Dtd, pero los creados mediante los métodos new o parse_string de XML::LibXML::Dtd son procesados y expandidos y se puede acceder a sus nodos componentes; los creados mediante createDTD o createInternalSubset de XML::LibXML::Document son también procesados, y pueden utilizarse para validación, pero sus componentes no son accesibles desde los métodos de XML::LibXML::Node; los creados mediante el método createExternalSubset de XML::LibXML::Document no son procesados, simplemente se crea el nodo y se incluye la uri en el mismo.

Namespace

XML::LibXML altera el esquema DOM introduciendo una irregularidad, una clase adicional, XML::LibXML::Namespace, independiente, que no hereda de XML::LibXML::Node. Desde un punto de vista general no tiene sentido explicar el documento XML como un conjunto de nodos y objetos Namespace, desligados; por tanto ha de verse en realidad como herramienta práctica para utilizar los espacios de nombres.

XML::LibXML concibe los objetos Namespace de manera similar al de los nodos XML::LibXML::Attribute: Un nodo, en especial un nodo XML::LibXML::Element, puede contener nodos XML::LibXML::Attribute y objetos Namespace.

XML::LibXML modela los espacios de nombres como una clase con dos propiedades: el identificador, y la uri correspondiente.

El identificador a su vez se compone de la marca de los espacios de nombres -la cadena constante xmlns- y un prefijo. Para acceder al primero se provee del método prefix, y al segundo, el método name o sus alias getName y getLocalName. Al identificador completo podemos acceder mediante el método nodeName o su alias getNodeName.

A la uri se accede mediante el método value, o su alias getData.

Para crear los objetos contamos con el método new. También obtenemos objetos XML::LibXML::Namespace, en este caso arreglos de objetos, con los métodos findnodes, de XML::LibXML::Element y getNamespaces, de XML::LibXML::Node.

Este código de ejemplo sirve de test básico para los métodos de la clase:

use XML::LibXML;
my $string = '<a xmlns:b="http://myNamespace.com"><b:c/></a>';
my $parser = XML::LibXML->new();
my $document = $parser->parse_string($string);
my @ns=$document->firstChild->getNamespaces();
foreach (@ns){
	print $_->prefix, "\n";
	print $_->nodeName, "\n";
	print $_->getLocalName, "\n";
	print $_->value, "\n";
}

El arreglo obviamente sólo contiene un espacio de nombres:

xmlns
xmlns:b
b
http://myNamespace.com

Salida

La clase XML::LibXML::Node incorpora varios métodos para la salida de datos. El más importante es toString, que heredarán todas las otras clases DOM. Se puede invocar con un alias, serialize.

Este método genera en la salida estandard un texto XML correspondiente a un objeto DOM. En principio tiene sentido para los nodos Document, pero se provee de una mayor precisión, y al hallarse en este nivel podemos volcar nodos concretos de todo tipo.

Un parámetro (opcional) pasa un indicador acerca de si el documento debe ser formateado con identado y saltos de línea (un número mayor de 1), con identado (1) o con los elementos originales (0, por defecto);

Un segundo parámetro (también opcional) hace que el texto XML se genere en la codificación original del documento, y no en UTF-8, que es el formato interno que utiliza Perl para los datos. Esto es válido a partir de Perl 5.006. Obviamente invocar esta opción ha de estar en consonancia con el uso posterior de los datos, porque Perl seguirá empeñado en utilizar la cadena resultante como UTF-8, salvo que le digamos lo contrario.

las clases XML::LibXML::Document y XML::LibXML::DocumentFragment realizan sus propias variantes del método; en ellas no está disponible este segundo parámetro de respeto de la codificación original.

La salida que ofrece el método toString depende también de algunas variables globales del procesador:

  • skipXMLDeclaration, si está activada hace que no se vuelque la declaración XML.

  • skipDTD, si está activada hace que no se vuelquen las definiciones internas de tipos.

  • setTagCompression, si está activada los elementos nulos serán expandidos con etiqueta inicial y final.

No hay métodos para ellas, o sea que las debemos de utilizar directamente:

local $XML::LibXML::skipXMLDeclaration=1;

Y se trata de variables globales, que afectan al procesador DOM y a todos los documentos que utilicen este objeto.

Otro método de XML::LibXML::Node para la salida es string_value. En este caso se vuelcan tan sólo los elementos textuales del nodo.

XML::LibXML::Document posee un método de salida con identado automático, pensado para los documentos HTML, toStringHTML, al que también podemos invocar por su alias serialize_html.

XML::LibXML::Node provee también de un método para un volcado canónico del nodo, toStringC14N, con su alias serialize_c14n. La canonización, definida en el RFC 3076 es el formateado del texto XML siguiendo unas reglas concretas (remover la declaración del DTD, valores normalizados en los atributos, eliminación de espacios en blanco extras, etc..).

El método propuesto por XML::LibXML::Node para la canononización de nodos XML permite dos parámetros para alterar su conducta. El primero es una marca indicadora de si se vuelcan los comentarios o no. Como parámetro adicional podemos indicar una expresión XPath que limite el alcance del volcado.

Este ejemplo nos permitirá un primer contacto con las diversas posibilidades de la salida de un nodo XML::LibXML::Document:

use XML::LibXML;
my $parser = XML::LibXML->new();
my $doc = $parser->parse_string('<html><body><p>hola</p><p><!-- típico ejemplo -->mundo</p><p/></body></html>');
print "--Salida de toString (0)--:\n" . $doc->toString(0);
print "--Salida de toString (1)--:\n" . $doc->toString(1);
print "--Salida de toString (2)--:\n" . $doc->toString(2);
print "--Salida de string_value:--\n" . $doc->string_value() . "\n";
print "--Salida de toStringHTML():--\n" . $doc->toStringHTML();
print "--Salida de toStringC14N():--\n" . $doc->toStringC14N(). "\n";
print "--Salida de toStringC14N(1):--\n" . $doc->toStringC14N(1). "\n";
print "--Salida de toStringC14N(1,'//p'):--\n" . $doc->toStringC14N(1,"//p"). "\n";
local $XML::LibXML::skipXMLDeclaration=1;
$doc = $parser->parse_string('<html><body><p>hola</p><p><!-- típico ejemplo -->mundo</p><p/></body></html>');
print "--Salida de toString (0) con skipXMLDeclaration--:\n" . $doc->toString . "\n";
local $XML::LibXML::setTagCompression = 1;
$doc = $parser->parse_string('<html><body><p>hola</p><p><!-- típico ejemplo -->mundo</p><p/></body></html>');
print "--Salida de toString (0) con setTagCompression--:\n" . $doc->toString . "\n";

Obsérvese que algunos de los formateos no incluyen un salto de línea adicional al final, y hemos tenido que colocarlas nosotros para mayor claridad.

--Salida de toString (0)--:
<?xml version="1.0"?>
<html><body><p>hola</p><p><!-- típico ejemplo -->mundo</p><p/></body></html>
--Salida de toString (1)--:
<?xml version="1.0"?>
<html>
  <body>
    <p>hola</p>
    <p><!-- típico ejemplo -->mundo</p>
    <p/>
  </body>
</html>
--Salida de toString (2)--:
<?xml version="1.0"?>
<html>
  <body>
    <p>
hola
    </p>
    <p>
<!-- típico ejemplo -->
mundo
    </p>
    <p/>
  </body>
</html>
--Salida de string_value:--
holamundo
--Salida de toStringHTML():--
<html><body>
<p>hola</p>
<p><!-- t&iacute;pico ejemplo -->mundo</p>
<p></p>
</body></html>
--Salida de toStringC14N():--
<html><body><p>hola</p><p>mundo</p><p></p></body></html>
--Salida de toStringC14N(1):--
<html><body><p>hola</p><p><!-- típico ejemplo -->mundo</p><p></p></body></html>--Salida de toStringC14N(1,'//p'):--
<p></p><p></p><p></p>
--Salida de toString (0) con skipXMLDeclaration--:
<html><body><p>hola</p><p><!-- típico ejemplo -->mundo</p><p/></body></html>
--Salida de toString (0) con setTagCompression--:
<html><body><p>hola</p><p><!-- típico ejemplo -->mundo</p><p></p></body></html>

Los métodos anteriores devuelven cadenas de caracteres en escalares de Perl. Es posible también volcar la salida directamente a un archivo, con el método toFile de XML::LibXML::Document. Éste presenta prestaciones similares al método de la misma clase, toString, y admite también una marca de formateado con valores similares.

Es posible también volcar los resultados a un filehandle, y con ello encadenarlo a otros procesos del sistema, con el método toFH, con el mismo sistema de formateado.

Por ejemplo, para emular la orden print de Perl, con una salida formatetada de tipo 2 (con salto de línea entre nodos):

$outref=*STDOUT{IO};
$doc->toFH($outref,2);

XPath

Las funciones XPath residen en la clase XML::LibXML::Node, es decir, están disponibles para todo el árbol DOM.

La implementación de XPath en XML::LibXML es muy simple de usar, y básicamente es accesible desde los métodos findnodes y find.

El primero admite expresiones XPath que representan objetos DOM, y en contexto de arreglo devuelve una matriz con los nodos que cumplen la condición expresada; en contexto escalar devuelve un objeto XML::LibXML::NodeList.

  my @nodes = $node->findnodes( "//p");
  my $nodelist = $node->findnodes( "//p");

El segundo admite cualquier expresión XPath (y por tanto cualquier función XPath). En consonancia con una búsqueda más abierta, tiene una salida variada, devolviendo colecciones de nodos, objetos DOM, u objetos de las clases XML::LibXML::Boolean, XML::LibXML::Literal o XML::LibXML::Number.

En una búsqueda de nodos findnodes y find son equivalentes:

my $nodelist = $doc->findnodes( "//b");
my $nodelist = $doc->find( "//b");

Pero si la expresión XPath es una función numérica devuelve objetos XML::LibXML::Number:

my $total = $doc->find( "count(//p)" );

Con funciones de cadena devuelve objetos XML::LibXML::Literal

my $literal = $doc->find( "string(//b)");

Y con funciones booleanas devuelve objetos XML::LibXML::Boolean

my $esta = $doc->find( "contains(//b, 'U')");

Una tercera función XPath en XML::LibXML::Node, findvalue retorna el valor literal del resultado. Es el equivalente del método to_literal aplicado al resultado de find.

Las clases XPath disponibles son:

  • XML::LibXML::Number

    La clase sólo admite dos métodos, new y value; el primero crea un objeto de esta clase y le asigna un valor y el segundo devuelve ese valor:

    my $num=XML::LibXML::Number->new(9);
    print $num->value;
  • XML::LibXML::Boolean

    Los métodos True y False crean los objetos de esta clase. El método value devuelve el valor booleano del objeto, mientras un método adicional, to_literal devuelve la cadena true o false correspondiente a este valor.

    my $bool=XML::LibXML::Boolean->True;
    if ($bool->value) {
    	print $bool->to_literal;
    }
  • XML::LibXML::Literal

    La clase admite como las anteriores métodos de creación y lectura del valor, new, value:

    my $literal=XML::LibXML::Literal>new("hello world");
    print $literal>value;

    Y un método adicional, cmp, que permite comparar el valor almacenado con un escalar Perl o una cadena de caracteres:

    my $literal=XML::LibXML::Literal->new("hello world");
    print $literal->cmp("hello world");

    Equivale al operador Perl cmp comparando el resultado de XML::LibXML::Literal->value con la cadena:

    print $literal->value cmp "hello world";

Cómo trabaja

Los diversos módulos Perl que se incluyen en la distribución de XML::LibXML implementan algunos métodos propios para organizar el trabajo, sobre todo en lo referente a la entrada y la salida, pero la tarea más pesada la realiza la librería libxml2, que se encarga del procesamiento en si. El paso de información entre el los módulos Perl y la librería la realiza el archivo LibXML.xs, un código escrito en xs, que es básicamente un lenguaje de macros para invocar el API C de Perl. Algunas funciones auxiliares, por ejemplo las de conversión de tipos, se encuentran en otro archivo, perl-libxml-mm.c.

Veamos un ejemplo, en este caso quizá el más reducido de los posibles usos de XML::LibXML:

#!/usr/bin/perl
use XML::LibXML;
my $parser = XML::LibXML->new();
my $source = $parser->parse_file('hola.xml');
print $source->toString;

El código anterior imprimirá casi exactamente el mismo texto de la entrada. De hecho también podemos ver en acción a libxml2 de manera similar si utilizamos la utilidad xmllint, que se incluye en la distribución de libxml2:

xmllint hola.xml

En nuestro ejemplo Perl, el método new crea una instancia del procesador; el método parse_file lleva a cabo el procesado del archivo de entrada, es decir la transformación del texto XML a una estructura de datos que podrá manipular el procesador; por último, el método toString realiza la tarea contraria, es decir construye un texto XML a partir de la estructura de datos, y la vuelca a la salida estándard. Antes podremos modificar el comportamiento del procesador, y, una vez obtenida, podremos utilizar la información haciendo selecciones o transformaciones, pero el esqueleto del trabajo del módulo ya está presente en este sencillo ejemplo.

  • El método new

    El método new[10] se encarga de crear la instancia de la clase. Éste devuelve un objeto XML::LibXML, que en esta fase temprana consta tan sólo de una referencia a un hash que incluirá las preferencias de procesado y un escalar con el nombre de la clase:

    119    my $self = bless \%options, $class;
    124    return $self;

    Los posteriores métodos que habilitan o deshabilitan opciones irán llenando este hash %options con pares clave-valor que representan las diversas opciones de procesado. Por defecto el método new[11] ya nos habilita una opción, XML_LIBXML_KEEP_BLANKS.

    Así si volcamos el objeto XML::LibXML:

    print Dumper $parser;

    obtenemos la configuración del procesador:

    $VAR1 = bless( {
                     'XML_LIBXML_KEEP_BLANKS' => 1,
                   }, 'XML::LibXML' );

    Anotemos que el método new en realidad es más complejo, porque aunque se utiliza sin argumentos, y las opciones se especifican mediante los métodos correspondientes, puede recibir como parámetros las opciones directamente, como pares clave valor (posibilidad no indicada en la documentación); es por ello que el método new ha de hacer algunas previsiones adicionales, puesto que no todas las opciones son simplemente flags; así, la opción catalog, correspondiene al método load_catalog ha de invocar directamente una función de libxml2 para que proceda a la carga del catálogo indicado.

  • El método parse_file

    El método parse_file[12], se encarga de dircernir entre los dos métodos de procesamiento de libxml2, el método DOM, por defecto, o su alternativa SAX. Si no hemos indicado nada se utiliza el primero, y el tratamiento básico es éste:

    435	eval { $result = $self->_parse_file(@_); };
    442	$result = $self->_auto_expand( $result );
    445	return $result;

    Es decir, llama bajo eval, para evitar que el programa aborte por un fallo en el procesado, a la función _parse_file, al que le pasa todos los argumentos que recibe, y posteriormente a la función _auto_expand.

    El flujo principal de la función _parse_file[13], es como sigue:

    1232 HV * real_obj;
    1235 xmlDocPtr real_doc;
    
    1249 xmlParserCtxtPtr ctxt = xmlCreateFileParserCtxt(filename);
    1267 xmlParseDocument(ctxt);
    1273 real_doc = ctxt->myDoc;
    1285 RETVAL = LibXML_NodeToSv( real_obj, (xmlNodePtr) real_doc );

    Es decir, que el núcleo de un programa C equivalente, para nuestro ejemplo, sería:

    xmlDocPtr real_doc;
    xmlParserCtxtPtr ctxt = xmlCreateFileParserCtxt(filename);
    xmlParseDocument(ctxt);
    real_doc = ctxt->myDoc;

    La función xmlCreateFileParserCtxt crea el contexto del procesador para el archivo filename, o sea que devuelve un puntero, ctxt, del tipo xmlParserCtxtPtr, a una estructura xmlParserCtxt que contiene ese contexto. El procesado en sí lo efectúa la función xmlParseDocument, que recibe como argumento el puntero anterior, y que poblará la estructura xmlParserCtxt con el resultado; más concretamente nos interesa una parte de éste, la estructura, del tipo xmlDoc, que recoge los datos del árbol DOM del documento de entrada, a la que accederemos desde el puntero ctxt->myDoc, del tipo xmlDocPtr.

  • El método toString

    El método toString[14] es el responsable de realizar el trabajo contrario al procesado, es decir, producir un texto XML a partir de la estructura de datos que utiliza libxml2.

    El código relevante de la función:

    695 $retval =  $self->_toString($flag);
    invoca a la función xmlCreateFileParserCtxt[15], que es la que hace en realidad todo el trabajo:
    2206 xmlDocPtr self
    2209 xmlChar *result=NULL;
    2232 xmlDocDumpMemory(self, &result, &len);
    2260 RETVAL = C2Sv( result, self->encoding );

    La función xmlDocDumpMemory vuelca el contenido de la estructura xmlDoc señalada por el puntero de tipo xmlDocPtr self a result, un flujo de caracteres (en realidad de caracteres xmlChar, que son del tipo C unsigned char). El resultado posteriormente se convierte al tipo de Perl SV o sea un escalar , gracias a la función C2Sv[16]; con esto ya podemos más tarde imprimir el resultado de nuestro ejemplo.

    Si se ha decidido que el resultado sea indentado, se utiliza una función alternativa, xmlDocDumpFormatMemory:

    2236 xmlDocDumpFormatMemory( self, &result, &len, format );


[1] Líneas 1181-1201 de LibXML.pm (v. 1.102).

[2] Líneas 1203-1220 de LibXML.pm (v. 1.102).

[3] Contenido en lib/XML/libXML/SAX/Parser.pm en la distribución.

[4] Líneas 98-113 de lib/XML/libXML/SAX/Generator.pm (v. 1.4) en la distribución.

[5] 143 test, 142 con éxito.

El error se produjo en el test 17_7-BF-02 donde dos formularios tienen sendos elemento textarea de idéntico nombre. El procesador de libxml2 falla al creer que el nombre debe ser único.

[6] Línea 1688 LibXML.xs v 1.178.

[7] Línea 1531 LibXML.xs v1.178.

[8] lineas 222-292 de LibXML.pm.

[9] lineas 619-771 en LibXML.xs v 1.178.

[10] lineas 107-125 de LibXML.pm,v 1.102.

[11] linea 111 de LibXML.pm.

[12] lineas 420-446 de LibXML.pm.

[13] lineas 1225-1295 en LibXML.xs v 1.178.

[14] líneas 679-699 de LibXML.pm.

[15] líneas 2203-2264 de LibXML.xs.

[16] lineas 879-911 de perl-libxml-mm.c, v 1.41.