Apuntes DAM
Volver al inicio

El canal RSS de las noticias de un instituto: XPath, XQuery, XSLT y DTD

Ejercicio de XMLDifícilUnos 90 minutos

Ocho apartados sobre un canal RSS 2.0 real, con categorías y podcasts: consultas XPath, un recuento por categorías y la conversión a entradas Atom (con el cambio de formato de las fechas) en XQuery, la lista HTML de las últimas noticias con XSLT y la DTD que valida el canal.

  • Sindicación: RSS 2.0 y Atom
  • XPath: predicados, count y sum
  • XQuery: distinct-values, where y order by
  • Fechas RFC 822 a ISO 8601
  • XSLT: for-each, if y atributos
  • DTD: #FIXED, EMPTY y valores enumerados

Enunciado

La sindicación de contenidos permite seguir una web sin visitarla: la web publica un canal (feed) en XML con sus últimas noticias y los lectores de noticias, las aplicaciones de podcasts o los agregadores lo descargan cada cierto tiempo. Los dos formatos son RSS 2.0 y Atom, y los dos son XML: lo que se ha aprendido de XPath, XQuery, XSLT y validación sirve tal cual.

El documento noticias.xml es el canal RSS de la web de un instituto: un channel con su título, enlace, descripción e idioma y un item por noticia, de la más nueva a la más antigua. Cada noticia tiene título, enlace, descripción, fecha de publicación en el formato de RSS (RFC 822: Mon, 05 Oct 2026 09:30:00 +0200), una o varias categorías, un identificador guid y, si es un podcast o un vídeo, un enclosure con la dirección, el tamaño en bytes y el tipo MIME del fichero.

Qué hay que hacer

  1. Resuelve cada apartado con la herramienta que indica, sobre el documento noticias.xml.
  2. Cada apartado se corrige por separado. Las consultas XPath y XQuery se comparan con el resultado de la solución (importan los valores y su orden); la transformación XSLT, con la salida esperada (sin contar espacios y saltos de línea); y la DTD, con documentos que debe aceptar y otros que debe rechazar.
  3. Cuando un apartado pide «el texto» de algo, la consulta debe devolver cadenas (text() o string()), no los elementos.

El documento: noticias.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.

noticias.xml
1<?xml version="1.0" encoding="UTF-8"?>
2<rss version="2.0">
3  <channel>
4    <title>IES Ejemplo: noticias</title>
5    <link>https://instituto.example.org/noticias</link>
6    <description>Lo que pasa en el instituto: FP, actividades y avisos</description>
7    <language>es-es</language>
8    <item>
9      <title>Abierto el plazo para elegir empresa de FCT</title>
10      <link>https://instituto.example.org/noticias/plazo-fct</link>
11      <description>Los alumnos de segundo de DAM y DAW pueden elegir empresa hasta el 20 de octubre.</description>
12      <pubDate>Mon, 05 Oct 2026 09:30:00 +0200</pubDate>
13      <category>FP</category>
14      <category>Avisos</category>
15      <guid>https://instituto.example.org/noticias/plazo-fct</guid>
16    </item>
17    <item>
18      <title>Podcast: entrevistamos a una antigua alumna de DAM</title>
19      <link>https://instituto.example.org/noticias/podcast-antigua-alumna</link>
20      <description>Hoy trabaja como desarrolladora Android y nos cuenta cómo fueron sus prácticas.</description>
21      <pubDate>Thu, 01 Oct 2026 17:00:00 +0200</pubDate>
22      <category>FP</category>
23      <category>Podcast</category>
24      <enclosure url="https://instituto.example.org/audio/antigua-alumna.mp3" length="18874368" type="audio/mpeg"/>
25      <guid isPermaLink="false">podcast-12</guid>
26    </item>
27    <item>
28      <title>Hackathon de DAW: 24 horas para crear una web</title>
29      <link>https://instituto.example.org/noticias/hackathon-daw</link>
30      <description>Equipos de tres personas, un reto sorpresa y premios para las tres mejores webs.</description>
31      <pubDate>Fri, 25 Sep 2026 12:00:00 +0200</pubDate>
32      <category>FP</category>
33      <guid>https://instituto.example.org/noticias/hackathon-daw</guid>
34    </item>
35    <item>
36      <title>Torneo de baloncesto entre cursos</title>
37      <link>https://instituto.example.org/noticias/torneo-baloncesto</link>
38      <description>Las inscripciones se hacen en el departamento de Educación Física hasta el viernes.</description>
39      <pubDate>Fri, 18 Sep 2026 10:15:00 +0200</pubDate>
40      <category>Actividades</category>
41      <category>Deportes</category>
42      <guid>https://instituto.example.org/noticias/torneo-baloncesto</guid>
43    </item>
44    <item>
45      <title>Podcast: cómo preparar los exámenes de recuperación</title>
46      <link>https://instituto.example.org/noticias/podcast-recuperacion</link>
47      <description>Consejos de los profesores para organizar el estudio en dos semanas.</description>
48      <pubDate>Thu, 10 Sep 2026 18:00:00 +0200</pubDate>
49      <category>Podcast</category>
50      <enclosure url="https://instituto.example.org/audio/recuperacion.mp3" length="25165824" type="audio/mpeg"/>
51      <guid isPermaLink="false">podcast-11</guid>
52    </item>
53    <item>
54      <title>Nuevo horario de la biblioteca</title>
55      <link>https://instituto.example.org/noticias/horario-biblioteca</link>
56      <description>Desde el lunes, la biblioteca abre también por la tarde de 16:00 a 19:00.</description>
57      <pubDate>Tue, 08 Sep 2026 08:00:00 +0200</pubDate>
58      <category>Avisos</category>
59      <guid>https://instituto.example.org/noticias/horario-biblioteca</guid>
60    </item>
61    <item>
62      <title>Vídeo de la jornada de puertas abiertas</title>
63      <link>https://instituto.example.org/noticias/puertas-abiertas</link>
64      <description>Un recorrido por los talleres y las aulas de informática.</description>
65      <pubDate>Thu, 03 Sep 2026 13:45:00 +0200</pubDate>
66      <category>Actividades</category>
67      <enclosure url="https://instituto.example.org/video/puertas-abiertas.mp4" length="52428800" type="video/mp4"/>
68      <guid>https://instituto.example.org/noticias/puertas-abiertas</guid>
69    </item>
70  </channel>
71</rss>

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. Mira el canal

Antes de escribir una sola consulta, recorre noticias.xml y localiza qué es elemento y qué atributo: las categorías son elementos repetidos; el tamaño y el tipo del adjunto, atributos de enclosure.

2. Predicados sobre elementos repetidos

Con varias category, la comparación category = 'FP' se cumple si alguna coincide (comparación existencial). Para contar o exigir varias, usa count().

markup
/rss/channel/item[category = 'FP']/title/text()
/rss/channel/item[count(category) > 1]/title/text()
3. Cambiar de formato

RSS y Atom guardan lo mismo con otros nombres y formatos (pubDate en RFC 822, updated en ISO 8601). Convertir de uno a otro es exactamente para lo que sirven XQuery y XSLT.

4. Validar el canal

La DTD describe el orden y la cantidad de hijos de cada elemento y los atributos con sus valores permitidos. Pruébala con los documentos del apartado: los que deben fallar te dicen qué regla falta.

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 noticias de la categoría FP, en el orden del canal.

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

El texto de los títulos de las noticias con más de una categoría, en el orden del canal.

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

El tamaño total de los ficheros adjuntos en MB (1 MB = 1.048.576 bytes), como número.

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

La dirección (el texto del atributo url) de los adjuntos de audio, en el orden del canal.

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

Un recuento por categorías: una cadena categoría: N por cada categoría con al menos dos noticias, en orden alfabético.

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

Convierte las noticias de FP en entradas de Atom: <entry><title>…</title><link href="…"/><updated>AAAA-MM-DD</updated></entry>, con la fecha de publicación pasada del formato de RSS (Mon, 05 Oct 2026 …) al de Atom (2026-10-05), en el orden del canal.

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

Genera el bloque HTML de «Últimas noticias» con las cinco más recientes (las cinco primeras del canal): <section> con un <h2> con el título del canal y una <ul> con un <li> por noticia que contenga el enlace <a href="enlace">título</a>, el texto · y las categorías separadas por coma y espacio; si la noticia tiene un adjunto de audio, al final del <li> va <audio controls="controls" src="url"></audio>.

noticias.xmlsolo lectura
transformacion.xsl
DTDApartado 8 · DTDDifícil

Escribe la DTD rss.dtd para los canales del instituto: rss con un channel y el atributo version fijo a 2.0; channel con title, link, description, un language opcional y una o más item, en ese orden; item con title, link, description, pubDate, cero o más category, un enclosure opcional y guid, en ese orden; enclosure vacío con los atributos obligatorios url, length y type; guid con texto y el atributo isPermaLink, que solo puede valer true o false y vale true si no se pone; y el resto de elementos con texto.

esquema.dtd

Documentos de prueba (pulsa para verlos):

Solución explicada

Ver las soluciones de todos los apartados

Apartado 1 · XPath

/rss/channel/item[category = 'FP']/title/text()

Apartado 2 · XPath

/rss/channel/item[count(category) > 1]/title/text()

Apartado 3 · XPath

sum(//enclosure/@length) div 1048576

Apartado 4 · XPath

//enclosure[starts-with(@type, 'audio/')]/@url/string()

Apartado 5 · XQuery

for $c in distinct-values(//item/category)
let $n := count(//item[category = $c])
where $n >= 2
order by $c
return concat($c, ': ', $n)

Apartado 6 · XQuery

for $i in /rss/channel/item[category = 'FP']
let $d := string($i/pubDate)
let $mes := index-of(('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'), substring($d, 9, 3))
return <entry><title>{string($i/title)}</title><link href="{$i/link}"/><updated>{concat(substring($d, 13, 4), '-', if ($mes < 10) then '0' else '', $mes, '-', substring($d, 6, 2))}</updated></entry>

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="/">
    <section>
      <h2><xsl:value-of select="rss/channel/title"/></h2>
      <ul>
        <xsl:for-each select="rss/channel/item[position() &lt;= 5]">
          <li>
            <a href="{link}"><xsl:value-of select="title"/></a>
            <xsl:text> · </xsl:text>
            <xsl:for-each select="category">
              <xsl:value-of select="."/>
              <xsl:if test="position() != last()">, </xsl:if>
            </xsl:for-each>
            <xsl:if test="starts-with(enclosure/@type, 'audio/')">
              <audio controls="controls" src="{enclosure/@url}"></audio>
            </xsl:if>
          </li>
        </xsl:for-each>
      </ul>
    </section>
  </xsl:template>
</xsl:stylesheet>

Apartado 8 · DTD

markup
<!ELEMENT rss (channel)>
<!ATTLIST rss version CDATA #FIXED "2.0">
<!ELEMENT channel (title, link, description, language?, item+)>
<!ELEMENT item (title, link, description, pubDate, category*, enclosure?, guid)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT link (#PCDATA)>
<!ELEMENT description (#PCDATA)>
<!ELEMENT language (#PCDATA)>
<!ELEMENT pubDate (#PCDATA)>
<!ELEMENT category (#PCDATA)>
<!ELEMENT enclosure EMPTY>
<!ATTLIST enclosure
  url    CDATA #REQUIRED
  length CDATA #REQUIRED
  type   CDATA #REQUIRED>
<!ELEMENT guid (#PCDATA)>
<!ATTLIST guid isPermaLink (true | false) "true">

RSS y Atom son XML con un vocabulario fijo: cualquier lector de noticias entiende item, title o enclosure porque el formato está publicado. Es lo que da sentido a la sindicación: un solo formato, muchas aplicaciones que lo leen.

El enclosure es lo que convirtió RSS en el formato de los podcasts: la aplicación descarga el audio de la dirección url, sabe cuánto ocupa por length y cómo reproducirlo por type.

El guid identifica cada noticia para que el lector no la muestre dos veces aunque cambie su título; isPermaLink="false" avisa de que no es una dirección web, sino un identificador sin más.

La DTD de este ejercicio es más estricta que el estándar (RSS 2.0 permite los hijos en cualquier orden y muchos elementos más): validar un formato real se hace con su XML Schema o con validadores como el del W3C.

Para ir más allá

  • Escribe la hoja XSLT que convierte el canal completo en un documento Atom válido, con su feed, id y updated.
  • Con XQuery, agrupa las noticias por mes de publicación.
  • Escribe el XML Schema del canal con un patrón para la fecha RFC 822.

Dónde se explica