El siguiente es un ejemplo de documento XML, que podría describir las novedades del mes en una hipotética tienda web de discos y libros:
<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?> <?xml-stylesheet type="text/css" href="novedades.css" ?> <novedades> <fecha> Diciembre 2002 </fecha> <disco genero="pop"> <titulo>O sea</titulo> <interprete>Enrique Iglesias</interprete> <direccion>http://www.enriqueiglesias.com</direccion> </disco> <libro> <titulo>Programación de aplicaciones web</titulo> <autor>John McCarra</autor> <autor>Frank Webini</autor> <direccion>http://www.paw.org</direccion> </libro> <libro> <titulo>Manolito Gafotas</titulo> <autor>Elvira Lindo</autor> <direccion>http://www.manolitogafotas.com</direccion> </libro> </novedades>
Como puede observarse, el aspecto general del fichero es vagamente similar al de un texto HTML. La diferencia fundamental es que las etiquetas no son estándar, sino que han sido definidas por el usuario.
En general, un documento XML bien formado tiene tres partes:
Se recomienda que todos los documentos XML comiencen con esta declaración, que los identifica como tales. En caso de estar presente, debe ser la primera línea del documento. Esta declaración proporciona información general: la versión de XML, el juego de caracteres y si hace referencia a entidades externas. Por ejemplo:
<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
El atributo version
indica la versión de XML empleada (la
actual y única existente hasta la fecha es la 1.0). El atributo opcional
encoding
especifica el juego de caracteres empleado (en este caso
el europeo occidental y latinoamericano, si no se especifica por defecto se
supone el inglés). Finalmente, el atributo opcional standalone
indica si se hace referencia a entidades externas al documento (en ese caso,
el documento no es "independiente" o standalone).
La declaración de documento XML es un caso particular de las llamadas instrucciones de procesamiento.
Son las instrucciones que comienzan con los símbolos <?
y terminan con ?>
. Estas instrucciones van destinadas al software
que analice o procese el documento XML, y por tanto son dependientes del mismo.
Por ejemplo, la instrucción <?xml-stylesheet?>
le
indica a los procesadores que sean capaces de reconocerlo que el estilo visual
asociado al documento está almacenado en una hoja de estilo determinada.
La parte principal de un documento XML son las etiquetas de marcado (que,
al estilo de HTML, van entre símbolos <
y >
).
Un par de etiquetas de marcado, una de apertura y una de cierre, con un texto
en medio, constituyen lo que se denomina un elemento, por ejemplo
<interprete> Enrique Iglesias </interprete>
En XML, cada etiqueta de apertura debe tener su correspondiente de cierre (esto
no se cumple en HTML con etiquetas como <br>
o <hr>
).
No obstante, se pueden crear etiquetas vacías, poniendo una barra al
final del nombre. Por ejemplo, la etiqueta <br>
de HTML,
expresada en sintaxis válida de XML sería <br/>
.
XML diferencia entre mayúsculas y minúsculas, por lo que </INTERPRETE>
no se consideraría como etiqueta de cierre válida en el ejemplo
anterior.
En cada fichero XML debe haber uno y solo un elemento raíz, que contiene
al resto de elementos del documento. En el documento del ejemplo, el elemento
raíz sería <novedades>
.
Las etiquetas pueden contener atributos, pero a diferencia de HTML, se exige que estén entre comillas:
<disco genero="pop">
Algunos caracteres, como <
y >
tienen un significado
especial en XML y por tanto no pueden emplearse en el texto que va entre las
etiquetas. Para incluir estos y otros símbolos se emplean unas secuencias
de escape, al estilo de las de HTML. La siguiente tabla recoge la equivalencia
entre símbolos y secuencias de escape
Símbolo | Secuencia |
---|---|
& | & |
< | < |
> | > |
' (apóstrofe) | ' |
" (comillas dobles) | " |
A estas secuencias de escape se las suele denominar entidades predefinidas. Aunque son las únicas que incorpora el estándar, el usuario puede definir sus propias entidades.
Si en un documento se usan muchas entidades predefinidas (como puede ser el
caso de un XML que contenga JavaScript, donde los operadores < y > son
comunes), el texto se hace farragoso y difícil de leer. Para evitar esto,
se puede incluir en una sección CDATA, que indica al procesador que esté
analizando el XML que debe dejar esa sección sin analizar y tratarla
como texto puro. Una sección CDATA comienza con los símbolos <![CDATA[
y termina con ]]>
.
Cuando un documento XML tiene la estructura descrita en el apartado anterior, se dice que está bien formado. No obstante, si varias personas van a escribir documentos XML del mismo tipo sería conveniente que pudieran especificar qué etiquetas son válidas y en qué orden deben figurar en el documento. Esto sería el equivalente a definir una gramática para cierto tipo de documento XML. En la actualidad existen dos formas de definir gramáticas para XML (dos lenguajes de definición):
Un documento bien formado que además cumple la gramática definida por el usuario se dice que es un documento válido.
Al definir cada usuario su propio conjunto de etiquetas, podría darse
el caso de que dos usuarios definieran la misma etiqueta con fines diferentes.
Por ejemplo, en un documento XML que almacenara un pedido, podría haber
una etiqueta <direccion>
, que se refiriera a la dirección de envío
del pedido (y no a una URL como en el caso del ejemplo). Esto no es un problema
en tanto en cuanto los documentos se mantengan separados, pero sí lo
sería si se fusionan (por ejemplo, podría ser interesante enviarle
al usuario una lista de discos recomendados, del mismo estilo que el que ha
pedido). Para evitar problemas se introduce el concepto de espacio de nombres, que permite
tener dos etiquetas distintas con el mismo nombre, siempre que se sitúen
en distinto "espacio".
Cada espacio de nombres se define mediante un nombre corto y un nombre largo.
Estos nombres se especifican en el atributo xmlns
de la etiqueta raíz
(normalmente). Es decir:
<etiqueta-raiz xmlns:nombre-corto=nombre-largo>
El nombre corto lo define el usuario. Si el espacio de nombres es propio, el nombre largo también lo define él. El único requisito que debe cumplir el nombre largo es ser una URI, un identificador único. Normalmente se suelen usar direcciones web pertenecientes al usuario, ya que es una forma sencilla de asegurar que es un identificador único. Por ejemplo, supongamos que se ha generado un fichero recomendaciones.xml, que contiene los datos del usuario (tomados de pedido.xml) junto con datos de discos y libros recomendados (tomados de novedades.xml). Para definir los dos espacios de nombres:
<recomendaciones xmlns:pedido="http://www.discolibro.com/pedidos" xmlns:novedad="http://www.discolibro.com/novedades">
Es importante tener en cuenta que las URL elegidas en realidad no tienen por qué existir, ni se accede a ellas durante el procesamiento del documento XML. Se tratan simplemente como identificadores.
Si se ha definido el atributo xmlns
en una etiqueta del documento, dentro de ella podemos especificar que una subetiqueta pertenece a un espacio de nombres simplemente precediéndola de su nombre corto. Por ejemplo, si queremos diferenciar entre una <direccion>
de pedido y una que se refiera a una URL, haríamos:
<datospedido> <pedido:direccion>C/ Cisne, 27 </pedido:direccion> <cliente> Pedro Pérez </cliente> </datospedido> ... <disco> <titulo> Supernatural </titulo> <interprete> Santana </interprete> <novedad:direccion>http://www.carlossantana.com</novedad:direccion> </disco>
XPath es un lenguaje que permite acceder a una parte concreta de un documento XML. Podemos utilizarlo para buscar información en un documento (por ejemplo, mostrar el título de todos los discos del documento de novedades). Su sintaxis es similar a la que se usa en sistemas de ficheros estilo UNIX para expresar rutas de directorios (de ahí el nombre de Path). El problema de especificar cómo acceder a determinadas partes de un documento XML se puede ver como una versión más compleja de las rutas de directorios, ya que podemos modelar un documento XML como un árbol cuyos nodos son las etiquetas.
La parte más importante del estándar XPath es la definición de trayectorias (location paths). En principio, las trayectorias parten por defecto del denominado nodo de contexto (que sería algo así como el equivalente al "directorio actual" en UNIX). Si el primer símbolo de la misma es una barra, entonces se considera que la trayectoria parte del nodo raíz del documento. Por ejemplo, la expresión XPath
/novedades/libro
Se refiere a las etiquetas
<libro> <titulo>Programación de aplicaciones web</titulo> <autor>John McCarra</autor> <autor>Frank Webini</autor> <direccion>http://www.paw.org</direccion> </libro> <libro> <titulo>Manolito Gafotas</titulo> <autor>Elvira Lindo</autor> <direccion>http://www.manolitogafotas.com</direccion> </libro>
Como puede verse, una expresión XPath puede referirse a un conjunto de nodos (a diferencia de una ruta de directorios, que siempre se refiere solo a uno).
En general, una trayectoria está compuesta por un conjunto de pasos separados por un símbolo /. El conjunto de nodos seleccionados en un paso es la base para el paso siguiente. Además, en cada paso se puede aplicar opcionalmente un predicado (función booleana entre corchetes) que restringe el conjunto de nodos seleccionados. Así, el formato general de una trayectoria es
paso1[predicado1]/paso2[predicado2]...
Por ejemplo, para seleccionar los autores del primer libro del documento se haría
/novedades/libro[position()=1]/autor
Donde la función position()
devuelve el orden que ocupa
un nodo en un conjunto de nodos seleccionados.
En la siguiente tabla se resumen algunos de los operadores que permiten expresar caminos.
Operador | Significado | Ejemplo | Resultado |
---|---|---|---|
// |
nodo hijo o descendiente | /novedades//titulo (todos los títulos dentro de la
etiqueta novedades - a cualquier nivel del árbol) |
|
* |
cualquier nodo | /novedades/*/titulo |
|
@ |
atributo | /novedades/disco/@genero |
|
. | el propio nodo | ||
.. | nodo padre | /novedades/disco/titulo/.. |
|
text() |
nodo de texto | /novedades/disco/titulo/text() |
|
processing-instruction() |
instrucción de procesamiento | /processing-instruction() |
|
comment() |
comentario |
Se utilizan para restringir el conjunto de nodos seleccionados. Se seleccionarán finalmente aquellos que cumplan el predicado, que no es más que una expresión entre corchetes que devuelve un resultado booleano. XPath dispone de un conjunto de operadores y funciones bastante extenso que podemos emplear a la hora de definir predicados.
En XPath hay operadores de distintos tipos:
div
(en lugar de /)not()
, and
, or
XPath tiene un gran número de funciones, algunas de las cuales se resumen en la tabla siguiente. En las siguientes funciones, el nodo de contexto es el nodo que se está evaluando en este momento, y la lista de nodos de contexto es el conjunto de nodos seleccionados.
Función | Tipo de retorno | Descripción |
---|---|---|
position() |
número | devuelve la posición del nodo de contexto en la lista de nodos de contexto. Las posiciones empiezan por el 1. |
last() |
número | devuelve el número de nodos en la lista de contexto |
count(conj-nodos) |
número | devuelve el número de nodos en conj-nodos |
local-name(conj-nodos) |
cadena | devuelve la parte local (lo que viene detrás del prefijo de espacio de nombres) del primer nodo del conj-nodos. Si se llama sin argumentos opera sobre el nodo de contexto |
generate-id(conj-nodos) |
cadena | devuelve un identificador único para el primer nodo de conj-nodos. Si se usa sin argumento, lo devuelve para el nodo de contexto. |
Podemos utilizar operadores y funciones para definir predicados. Por ejemplo
Expresión | Significado |
---|---|
|
todos los discos de género pop |
|
el intérprete del disco titulado 'O sea' |
|
el último autor del primer libro |
Las expresiones XPath vistas hasta el momento utilizan lo que se conoce como sintaxis abreviada. XPath tiene operadores más complejos, que permiten seleccionar cualquier conjunto de nodos relativo a una posición en el árbol. Estos operadores son los ejes, que se resumen en la tabla siguiente.
Eje | Descripción |
---|---|
ancestor |
antepasado del nodo actual (padre, "abuelo, etc) |
ancestor-or-self |
los anteriores contando también al propio nodo |
attribute |
los atributos del nodo actual |
child |
los hijos del nodo actual |
descendant |
hijos, "nietos", etc, del nodo actual |
descendant-or-self |
los anteriores contando también al propio nodo |
following |
nodos que comienzan después que el nodo actual en el orden del documento (no se cuentan atributos ni declaraciones de espacios de nombres) |
following-sibling |
nodos que comienzan después que el actual en el orden del documento y además tienen el mismo padre ("hermanos siguientes") |
namespace |
espacio de nombres del nodo actual |
parent |
nodo padre del nodo actual |
preceding |
nodos que comienzan antes que el nodo actual en el orden del documento (no se cuentan atributos ni declaraciones de espacios de nombres) |
preceding-sibling |
nodos que comienzan antes que el actual en el orden del documento y además tienen el mismo padre |
self |
el nodo actual |
La forma de utilizar los ejes es poner su nombre seguido de los caracteres
::
y una expresión que restrinja los nodos seleccionados.
Por ejemplo:
/novedades/disco/titulo/following-sibling::*
Seleccionaría todos (símbolo *
) los "siguientes
hermanos" (following-sibling
) del título de los discos.
En este caso, al mismo nivel que título vienen interprete y dirección,
con lo que el resultado sería
<interprete> Enrique Iglesias </interprete> <direccion>http://www.enriqueiglesias.com</direccion>
En la notación abreviada que hemos visto hasta ahora hay formas de expresar más brevemente algunos de los ejes. No obstante, otros ejes no tienen "traducción abreviada"
Notación abreviada | Notación con ejes |
---|---|
/novedades/disco |
/child::novedades/child::disco |
/novedades//titulo |
/novedades/descendant::titulo |
El intercambio de documentos XML a veces requiere además cambiar su formato. Por ejemplo, aunque dos organizaciones con distinto software utilicen XML para almacenar los mismos datos, pueden emplear etiquetas con distinto nombre o en distinto orden. O en otros casos, puede ser necesario generar documentos (como en el ejemplo del pedido en el apartado 1.3) que fusionen distintos documentos. XSLT es un lenguaje específicamente diseñado para transformar documentos XML, cambiando su estructura, fusionando documentos, o filtrando datos para generar nuevos documentos. Estos nuevos documentos pueden ser XML, pero también pueden emplear cualquier lenguaje de marcado (por ejemplo, HTML) o incluso pueden ser simplemente texto.
Podemos considerar un documento XSLT como un programa, escrito con sintaxis XML, que especifica cómo procesar un documento XML para transformarlo en otro. El XSLT toma como entrada el documento a transformar en forma de árbol, y empieza a procesarlo por la raíz, descendiendo sucesivamente por los nodos. Un documento XSLT no es más que un conjunto de plantillas (templates) que especifican qué salida generar cuando el procesamiento alcanza un nodo determinado.
Por ejemplo, supongamos que se desea obtener un documento XML únicamente
con los libros del ejemplo anterior. Además, de cada libro solo nos interesa
el primer autor, que debe venir expresado con la etiqueta <autorPrinc>
en lugar de <autor>
. El siguiente es un documento XSLT que
realiza la tarea:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!--seguir procesando desde la raíz del árbol--> <xsl:template match="/">
<xsl:apply-templates/>
</xsl:template><!-- ignorar la fecha --> <xsl:template match="fecha">
<!--ignorar los discos --> <xsl:template match="disco">
</xsl:template>
</xsl:template> <!-- de los libros, procesar únicamente el título y el primer autor --><xsl:template match="libro">
<!-- en los títulos, generar la misma etiqueta --> <xsl:template match="titulo">
<libro>
<xsl:apply-templates select="titulo"/>
<xsl:apply-templates select="autor[position()=1]"/>
</libro>
</xsl:template>
<titulo>
<xsl:value-of select="."/>
</titulo>
</xsl:template><!-- en los autores, generar la etiqueta autorPrinc --> <xsl:template match="autor">
<autorPrinc>
<xsl:value-of select="."/>
</autorPrinc>
</xsl:template></xsl:stylesheet>
Como puede verse, el documento sigue la sintaxis XML. Las etiquetas que son
instrucciones propias del lenguaje XSLT vienen precedidas del prefijo de espacio
de nombres xsl
(este prefijo es arbitrario, y lo que importa es
que en la raíz del documento esté asociado con el espacio de nombres
"oficial" de XSLT).
El procesamiento se realiza de la siguiente manera: el "intérprete"
XSLT tiene en cada momento un nodo actual en el árbol del documento
XML a transformar. Además hay una salida a donde se irá
enviando el resultado del procesamiento. Este comienza por la raíz, buscando
algún template
cuyo match
coincida
con el nodo actual. En ese caso, se ejecutan las instrucciones de dentro del
template, que básicamente pueden ser dos (aunque hay muchas más
en el lenguaje):
<xsl:apply-templates>
: significa que el intérprete
XSLT debe descender un nivel más en el árbol, procesando por
defecto todos los nodos hijos del actual. Mediante el atributo select
se puede especificar si deseamos procesar un conjunto distinto de nodos.<xsl:value-of>
: significa que el intérprete
XSLT debe enviar a la salida el valor del nodo especificado mediante el atributo
select
.El valor que puede tener un atributo match
es más
o menos el de una expresión XPath con notación abreviada
(por ejemplo, no se pueden utilizar ejes). En los atributos select
se puede colocar cualquier expresión XPath válida.
Cuando dentro de un template se pone texto o etiquetas que no pertenecen al espacio de nombres XSLT (en el ejemplo, no llevan el prefijo xsl), el intérprete xslt las copia en la salida.
En XSLT no es necesario escribir templates para procesar todos los nodos del documento XML original, ya que el estándar exige que el intérprete XSLT aplique una serie de templates por defecto para algunos casos (si el usuario no define templates para ellos). Estos casos son:
<xsl:value-of
select="."/>
Como se ha comentado, cualquier etiqueta o texto que no pertenezca al espacio de nombres de XSLT, se envía directamente a la salida. No obstante, si queremos generar etiquetas con atributos que tomen valor del documento XML original, podemos tener problemas. Por ejemplo, el siguiente template (que es incorrecto) intenta transformar la etiqueta "disco" en una etiqueta que contiene como atributo el título.
<xsl:template match="disco"> <disco titulo="<xsl:value-of select="titulo">/> </xsl:template>
El problema con el template anterior es que no es XML bien formado, ya que una etiqueta no puede ir "incrustada" dentro de otra. Por ello, hay una forma alternativa de expresar el select para estos casos que consiste en ponerlo entre llaves.
<xsl:template match="disco"> <disco titulo="{titulo}"/> </xsl:template>