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:
El soporte de XML es bastante completo: Entre otros, Xpointer, Xinclude, Xpath (1.0), xml:id, Catálogos, y validaciones contra RelaxNG y esquemas XML.
Permite procesar también documentos HTML y DTDs. Tiene asímismo un soporte parcial de SGML, y en concreto para Docbook SGML, sin validación, pero esta función ha sido declarada obsoleta.
Soporta cualquier tipo de URI en enlaces Xinclude y entidades externas. Las uris que se refieren al sistema de ficheros, las de HTML y FTP son reconocidas de forma nativa, incorporando clientes de estos protocolos para su utilización transparente; se provee además de un mecanismo para soportar cualquier otro tipo de uri.
Permite utilizar archivos incompletos o con errores.
Se puede utilizar un amplio abanico de medios de entrada: archivos, cadenas de caracteres o filehandles. Estos últimos permiten encadenarlo con otros procesos del sistema
Admite documentos comprimidos con gzip, y puede en consonancia producir en la salida textos igualmente comprimidos.
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.
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.
Procesadores DOM.
XML::LibXML realiza el procesado DOM normal para documentos XML, HTML y SGML. Tres procesadores adicionales están disponibles para documentos de definiciones de datos: XML::LibXML::Dtd, de DTDs; XML::LibXML::RelaxNG[1], para esquemas RelaxNG; XML::LibXML::Schema[2], para esquemas XML.
Procesadores SAX.
Generadores SAX.
XML::LibXML::SAX::Parser[3], generador de eventos SAX desde DOM, y XML::LibXML::SAX::Generator, otra clase similar, declarada obsoleta.
Manejadores SAX.
XML::LibXML::SAX::Builder, construye un árbol DOM desde eventos SAX.
Clases DOM.
XML::LibXML::Attr, XML::LibXML::CDATASection, XML::LibXML::Comment, XML::LibXML::Document, XML::LibXML::DocumentFragment, XML::LibXML::Element, XML::LibXML::NamedNodeMap, XML::LibXML::Node,XML::LibXML::Nodelist, XML::LibXML::PI, XML::LibXML::Text.
Extensiones a DOM.
XML::LibXML::Namespace es una clase añadida a las definidas por el estándard. El procesador XML::LibXML::Dtd también provee de una clase específica no contemplada en DOM que sustituye parcialmente al interfaz DOM DocumentType.
Tipos de datos XPath.
XML::LibXML::Boolean; XML::LibXML::Literal; XML::LibXML::Number.
Otros.
XML::LibXML::SAX::AttributeNode[4] (Obsoleta).
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.
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 | |||
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].
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.
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.
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");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);
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.
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);
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 esquema general de trabajo con el procesador DOM, XML::LibXML, podría ser éste:

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.
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.
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 7Si 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.
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);
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.
Esta opción añade mensajes adicionales de atención analizando varias cuestiones, típicamente para algunos casos de depuración:
Por defecto la opción está deshabilitada.
$parser->pedantic_parser(1);
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);
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);
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>
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.
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.
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 );
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);
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 | Sí |
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 | Sí | |
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.
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');
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");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.
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!");
}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);;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);
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 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.
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.
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 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
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.
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.

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.

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.
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"?>
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.
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::No