Apuntes DAM
Volver al inicio

Catálogo de una editorial: XPath, XQuery, XSLT y XSD

Ejercicio de XMLDifícilUnos 90 minutos

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

  1. Resuelve cada apartado con la herramienta que indica, sobre el documento catalogo.xml.
  2. 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.
  3. Cuando un apartado pide «el texto» de algo, la consulta debe devolver cadenas (con text() o string()), 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.

catalogo.xml
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.

markup
/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.

markup
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.

XPathApartado 1 · XPathDifícil

El texto de los títulos de las novelas, en el orden del documento.

catalogo.xmlsolo lectura
expresión XPath
XPathApartado 2 · XPathDifícil

El texto del título del libro más caro.

catalogo.xmlsolo lectura
expresión XPath
XPathApartado 3 · XPathDifícil

Cuántos libros no tienen ningún ejemplar en stock.

catalogo.xmlsolo lectura
expresión XPath
XPathApartado 4 · XPathDifícil

El texto de los títulos de los libros escritos por más de un autor, en el orden del documento.

catalogo.xmlsolo lectura
expresión XPath
XQueryApartado 5 · XQueryDifícil

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).

catalogo.xmlsolo lectura
consulta.xq
XQueryApartado 6 · XQueryDifícil

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.

catalogo.xmlsolo lectura
consulta.xq
XSLTApartado 7 · XSLTDifícil

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.

catalogo.xmlsolo lectura
transformacion.xsl
XSDApartado 8 · XML SchemaDifícil

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).

esquema.xsd

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

markup
<?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

markup
<?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.

Dónde se explica