Catálogo de una editorial: XPath, XQuery, XSLT y XSD
Ocho apartados sobre el catálogo en XML de una editorial: consultas XPath con predicados y funciones, informes y nuevos elementos con XQuery (FLWOR), una tabla HTML ordenada con XSLT y el XML Schema que valida el documento con tipos, patrones y enumeraciones.
- XPath: rutas y predicados
- count, max y avg
- XQuery: FLWOR y order by
- Constructores de elementos
- XSLT: for-each, sort e if
- XSD: tipos simples, patrones y enumeraciones
Enunciado
Una editorial pequeña publica su catálogo en XML para que las librerías lo carguen en sus programas. El documento tiene un elemento libro por título, con el código, el género y los ejemplares en stock como atributos, y el título, los autores (uno o varios), el año, el precio y las páginas como elementos.
Con ese único documento vas a hacer lo que se hace en el día a día con XML: buscar datos con XPath, sacar informes y construir XML nuevo con XQuery, presentarlo como página web con XSLT y, por último, escribir el XML Schema que garantiza que el documento que reciben las librerías es correcto.
Los apartados se pueden hacer en cualquier orden, pero conviene seguirlo: cada herramienta se apoya en la anterior (XQuery y XSLT usan expresiones XPath, y el XSD formaliza lo que has aprendido de la estructura).
Qué hay que hacer
- Resuelve cada apartado con la herramienta que indica, sobre el documento
catalogo.xml. - Cada apartado se corrige por separado. Las consultas XPath y XQuery se comparan con el resultado de la solución sobre el documento (importan los valores y su orden); las transformaciones XSLT, con la salida esperada (sin contar espacios y saltos de línea); y los esquemas, con documentos que deben aceptar y otros que deben rechazar.
- Cuando un apartado pide «el texto» de algo, la consulta debe devolver cadenas (con
text()ostring()), no elementos.
El documento: catalogo.xml
Todos los apartados trabajan sobre este documento. Léelo entero antes de empezar: la estructura (qué es elemento y qué es atributo) decide cómo se escribe cada expresión.
1<?xml version="1.0" encoding="UTF-8"?>
2<catalogo editorial="Ediciones del Sur" actualizado="2026-09-30">
3 <libro codigo="L001" genero="novela" stock="12">
4 <titulo>La casa de las palabras</titulo>
5 <autor>Marta Ruiz</autor>
6 <anio>2019</anio>
7 <precio>18.90</precio>
8 <paginas>320</paginas>
9 </libro>
10 <libro codigo="L002" genero="poesia" stock="0">
11 <titulo>Mar adentro, versos</titulo>
12 <autor>Julio Peña</autor>
13 <anio>2021</anio>
14 <precio>12.50</precio>
15 <paginas>96</paginas>
16 </libro>
17 <libro codigo="L003" genero="ensayo" stock="5">
18 <titulo>Breve historia de la imprenta</titulo>
19 <autor>Ana Gil</autor>
20 <autor>Luis Mora</autor>
21 <anio>2017</anio>
22 <precio>24.00</precio>
23 <paginas>410</paginas>
24 </libro>
25 <libro codigo="L004" genero="novela" stock="3">
26 <titulo>El último faro</titulo>
27 <autor>Marta Ruiz</autor>
28 <anio>2023</anio>
29 <precio>21.50</precio>
30 <paginas>288</paginas>
31 </libro>
32 <libro codigo="L005" genero="infantil" stock="25">
33 <titulo>Lola y el dragón azul</titulo>
34 <autor>Pablo Sanz</autor>
35 <anio>2022</anio>
36 <precio>9.95</precio>
37 <paginas>48</paginas>
38 </libro>
39 <libro codigo="L006" genero="novela" stock="0">
40 <titulo>Ciudades de niebla</titulo>
41 <autor>Elena Ríos</autor>
42 <anio>2015</anio>
43 <precio>19.90</precio>
44 <paginas>352</paginas>
45 </libro>
46 <libro codigo="L007" genero="ensayo" stock="8">
47 <titulo>Pensar con datos</titulo>
48 <autor>Luis Mora</autor>
49 <anio>2024</anio>
50 <precio>27.50</precio>
51 <paginas>264</paginas>
52 </libro>
53 <libro codigo="L008" genero="infantil" stock="14">
54 <titulo>El bosque que cantaba</titulo>
55 <autor>Sara Molina</autor>
56 <autor>Pablo Sanz</autor>
57 <anio>2020</anio>
58 <precio>11.00</precio>
59 <paginas>40</paginas>
60 </libro>
61</catalogo>Guía paso a paso
Intenta resolverlo por tu cuenta y abre un paso solo cuando te atasques: cada uno te acerca a la solución sin dártela entera.
1. Lee la estructura antes de escribir
Haz una lista de qué es elemento y qué es atributo: codigo, genero y stock son atributos (se escriben con @); titulo, autor, anio, precio y paginas son elementos hijos de libro. La mitad de los errores en XPath vienen de confundirlos.
2. XPath: ruta, predicado, resultado
Una expresión XPath es una ruta (/catalogo/libro) que se filtra con predicados entre corchetes ([@genero = 'novela']) y termina en lo que quieres (/titulo/text()). Los predicados admiten funciones y comparaciones.
/catalogo/libro[precio > 20 and @stock > 0]/titulo/text()3. XQuery: FLWOR
for recorre, let guarda valores, where filtra, order by ordena y return construye el resultado. El return puede devolver cadenas o XML nuevo con partes calculadas entre llaves.
for $l in /catalogo/libro
where $l/@stock > 10
order by $l/titulo
return <libro>{string($l/titulo)}</libro>4. XSLT: plantillas e instrucciones
La plantilla de /catalogo escribe la tabla; dentro, xsl:for-each recorre los libros, xsl:sort los ordena, xsl:value-of escribe valores y xsl:if añade lo condicional. Todo lo que no empieza por xsl: se copia tal cual a la salida.
5. XSD: de dentro afuera
Empieza por los tipos simples con restricciones (el año, el precio, el código), sigue con el tipo complejo del libro (secuencia de elementos y atributos) y termina con el elemento raíz. Prueba el esquema con los documentos del ejercicio: debe aceptar los válidos y rechazar los demás.
Resuélvelo aquí
Cada apartado se corrige por separado: las consultas se comparan con el resultado esperado sobre el documento de arriba, las transformaciones con la salida esperada y los esquemas con documentos que deben aceptar o rechazar.
El texto de los títulos de las novelas, en el orden del documento.
El texto del título del libro más caro.
Cuántos libros no tienen ningún ejemplar en stock.
El texto de los títulos de los libros escritos por más de un autor, en el orden del documento.
Un informe por género, en orden alfabético: una cadena por género con el formato género (N): precio medio X €, donde N es el número de libros y X el precio medio redondeado a dos decimales (sin ceros sobrantes: 25.75, 20.1).
La oferta del mes: para cada libro con más de 10 ejemplares, construye <oferta codigo="…"><titulo>…</titulo><precio>…</precio></oferta> con su código, su título y su precio rebajado un 20 % redondeado a dos decimales, ordenadas de menor a mayor precio rebajado.
Transforma el catálogo en una tabla HTML: <table> con la fila de cabecera <tr><th>Título</th><th>Autores</th><th>Precio</th></tr> y una fila por libro, ordenadas por título, con los autores separados por coma y espacio, el precio con dos decimales seguido de un espacio y € (18.90 €), y el atributo class="agotado" en las filas de los libros sin stock.
Escribe el XML Schema del catálogo: catalogo con los atributos obligatorios editorial (texto) y actualizado (fecha) y uno o más libro; cada libro con los atributos obligatorios codigo (una L y tres cifras), genero (novela, poesia, ensayo o infantil) y stock (entero no negativo), y los elementos, en este orden, titulo, uno o más autor, anio (entero de 1450 a 2026), precio (decimal no negativo con dos decimales como mucho) y paginas (entero positivo).
Documentos de prueba (pulsa para verlos):
Solución explicada
Ver las soluciones de todos los apartados
Apartado 1 · XPath
/catalogo/libro[@genero = 'novela']/titulo/text()Apartado 2 · XPath
/catalogo/libro[precio = max(/catalogo/libro/precio)]/titulo/text()Apartado 3 · XPath
count(/catalogo/libro[@stock = 0])Apartado 4 · XPath
/catalogo/libro[count(autor) > 1]/titulo/text()Apartado 5 · XQuery
for $g in distinct-values(/catalogo/libro/@genero)
let $libros := /catalogo/libro[@genero = $g]
order by $g
return concat($g, ' (', count($libros), '): precio medio ', round(avg($libros/precio) * 100) div 100, ' €')Apartado 6 · XQuery
for $l in /catalogo/libro[@stock > 10]
let $precio := round($l/precio * 0.8 * 100) div 100
order by $precio
return <oferta codigo="{$l/@codigo}"><titulo>{string($l/titulo)}</titulo><precio>{$precio}</precio></oferta>Apartado 7 · XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/catalogo">
<table>
<tr><th>Título</th><th>Autores</th><th>Precio</th></tr>
<xsl:for-each select="libro">
<xsl:sort select="titulo"/>
<tr>
<xsl:if test="@stock = 0">
<xsl:attribute name="class">agotado</xsl:attribute>
</xsl:if>
<td><xsl:value-of select="titulo"/></td>
<td>
<xsl:for-each select="autor">
<xsl:value-of select="."/>
<xsl:if test="position() != last()">, </xsl:if>
</xsl:for-each>
</td>
<td><xsl:value-of select="format-number(precio, '0.00')"/> €</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>Apartado 8 · XML Schema
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="catalogo">
<xs:complexType>
<xs:sequence>
<xs:element name="libro" type="tipoLibro" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="editorial" type="xs:string" use="required"/>
<xs:attribute name="actualizado" type="xs:date" use="required"/>
</xs:complexType>
</xs:element>
<xs:complexType name="tipoLibro">
<xs:sequence>
<xs:element name="titulo" type="xs:string"/>
<xs:element name="autor" type="xs:string" maxOccurs="unbounded"/>
<xs:element name="anio">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="1450"/>
<xs:maxInclusive value="2026"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="precio">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:minInclusive value="0"/>
<xs:fractionDigits value="2"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="paginas" type="xs:positiveInteger"/>
</xs:sequence>
<xs:attribute name="codigo" use="required">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="L[0-9]{3}"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="genero" use="required">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="novela"/>
<xs:enumeration value="poesia"/>
<xs:enumeration value="ensayo"/>
<xs:enumeration value="infantil"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="stock" type="xs:nonNegativeInteger" use="required"/>
</xs:complexType>
</xs:schema>Las cuatro tecnologías comparten XPath: es el lenguaje para señalar partes de un documento, y aparece dentro de los for de XQuery, en los select y test de XSLT y en las restricciones de identidad de XSD. Dominar rutas y predicados es la mitad del trabajo con XML.
XQuery es a XML lo que SQL a las bases de datos relacionales: el FLWOR recuerda a SELECT … FROM … WHERE … ORDER BY, con la ventaja de que el resultado puede ser XML nuevo. Eso lo hace útil para transformar datos entre sistemas.
XSLT resuelve el mismo problema desde otro ángulo: describe cómo debe ser la salida (aquí, HTML) a partir de plantillas. Por eso es habitual en la publicación de documentos y en la integración entre aplicaciones.
El XML Schema es el contrato del documento: con tipos y restricciones, cualquier librería puede validar el catálogo antes de cargarlo y rechazar un precio negativo o un género desconocido sin escribir código. Los documentos inválidos del ejercicio son justo los errores que el esquema tiene que atrapar.
Para ir más allá
- Añade al XSD una clave (
xs:key) que impida dos libros con el mismo código. - Haz con XSLT una página por género usando
xsl:key(agrupación de Muench en XSLT 1.0). - Carga el catálogo en BaseX y resuelve las consultas XQuery de este ejercicio contra la base de datos.