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:
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 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.
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 CharacterDat
a 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::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.
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
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í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);
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";
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.