Apuntes DAM
Volver al inicio

Observaciones meteorológicas: XPath, XQuery, XSLT a CSV y DTD

Ejercicio de XMLDifícilUnos 80 minutos

Siete apartados sobre los datos de una red de estaciones meteorológicas en XML: búsquedas XPath con números y atributos, medias y el día más extremo con XQuery, una exportación a CSV con XSLT de salida de texto y la DTD del documento con atributos ID y obligatorios.

  • XPath con comparaciones numéricas
  • distinct-values
  • XQuery: let, order by y [1]
  • Navegar hacia el padre (..)
  • XSLT con salida de texto
  • DTD: ELEMENT y ATTLIST

Enunciado

Varios institutos tienen una estación meteorológica en el tejado y comparten sus datos en un documento XML común: una estacion por instituto, con sus atributos (identificador, nombre, provincia y altitud), y dentro un dia por cada día con la temperatura máxima, la mínima y la lluvia en milímetros.

El documento declara su DTD en el DOCTYPE, pero esa DTD todavía no existe: el último apartado es escribirla. Antes, vas a responder con XPath y XQuery a las preguntas que se hace cualquiera con estos datos (dónde ha llovido más, qué estación es más fría) y a exportarlos en CSV con XSLT para abrirlos en una hoja de cálculo.

Qué hay que hacer

  1. Resuelve cada apartado con la herramienta que indica, sobre el documento observaciones.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: observaciones.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.

observaciones.xml
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE observaciones SYSTEM "observaciones.dtd">
3<observaciones red="Estaciones de los institutos" periodo="2026-09">
4  <estacion id="E01" nombre="Madrid Retiro" provincia="Madrid" altitud="667">
5    <dia fecha="2026-09-01"><tmax>31.2</tmax><tmin>18.4</tmin><lluvia>0</lluvia></dia>
6    <dia fecha="2026-09-02"><tmax>29.8</tmax><tmin>17.9</tmin><lluvia>0</lluvia></dia>
7    <dia fecha="2026-09-03"><tmax>24.1</tmax><tmin>15.2</tmin><lluvia>12.4</lluvia></dia>
8    <dia fecha="2026-09-04"><tmax>26.5</tmax><tmin>14.8</tmin><lluvia>1.2</lluvia></dia>
9  </estacion>
10  <estacion id="E02" nombre="Navacerrada" provincia="Madrid" altitud="1894">
11    <dia fecha="2026-09-01"><tmax>19.6</tmax><tmin>9.1</tmin><lluvia>0</lluvia></dia>
12    <dia fecha="2026-09-02"><tmax>18.2</tmax><tmin>8.4</tmin><lluvia>3.5</lluvia></dia>
13    <dia fecha="2026-09-03"><tmax>12.7</tmax><tmin>5.9</tmin><lluvia>28.1</lluvia></dia>
14    <dia fecha="2026-09-04"><tmax>15.0</tmax><tmin>6.3</tmin><lluvia>6.0</lluvia></dia>
15  </estacion>
16  <estacion id="E03" nombre="Valencia Puerto" provincia="Valencia" altitud="5">
17    <dia fecha="2026-09-01"><tmax>30.1</tmax><tmin>23.5</tmin><lluvia>0</lluvia></dia>
18    <dia fecha="2026-09-02"><tmax>30.6</tmax><tmin>24.0</tmin><lluvia>0</lluvia></dia>
19    <dia fecha="2026-09-03"><tmax>28.4</tmax><tmin>22.1</tmin><lluvia>0.4</lluvia></dia>
20    <dia fecha="2026-09-04"><tmax>27.9</tmax><tmin>21.7</tmin><lluvia>15.8</lluvia></dia>
21  </estacion>
22</observaciones>

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. Números en XPath

En una comparación con un número, XPath convierte el atributo o el elemento a número: [@altitud > 500] y [lluvia > 10] funcionan sin conversiones. max, min, avg y sum también trabajan con números.

2. Subir al padre

Desde un dia, .. es su estacion: $d/../@nombre da el nombre de la estación de ese día. Es lo que permite recorrer los días y seguir sabiendo de qué estación son.

3. Quedarse con el primero en XQuery

Un FLWOR devuelve una secuencia. Para «el mayor», ordénalo de mayor a menor y toma el primero con un predicado sobre la secuencia entera entre paréntesis.

markup
(for $d in //dia
 order by $d/tmax descending
 return string($d/@fecha))[1]
4. XSLT que genera texto

Con <xsl:output method="text"/> no se generan etiquetas: solo el texto de los xsl:value-of y xsl:text. Los espacios y saltos de línea de la hoja no salen, así que los saltos hay que escribirlos con &#10;.

5. Una DTD de arriba abajo

Declara cada elemento con su contenido ((hijos), (#PCDATA)) y, a continuación, sus atributos con ATTLIST. Comprueba con los documentos del ejercicio: cada documento inválido rompe una sola regla.

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 nombre (el valor del atributo, como texto) de las estaciones situadas a más de 500 metros de altitud, en el orden del documento.

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

La temperatura máxima más alta registrada en todo el periodo (un número).

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

Las fechas (sin repetir y en el orden del documento) en las que alguna estación registró más de 10 mm de lluvia.

observaciones.xmlsolo lectura
expresión XPath
XQueryApartado 4 · XQueryDifícil

Para cada estación, en el orden del documento, una cadena nombre: media de máximas X °C, lluvia total Y mm, con la media redondeada a un decimal.

observaciones.xmlsolo lectura
consulta.xq
XQueryApartado 5 · XQueryDifícil

El día de mayor amplitud térmica (máxima menos mínima) de toda la red, como una única cadena estación (fecha): amplitud X °C con la amplitud redondeada a un decimal.

observaciones.xmlsolo lectura
consulta.xq
XSLTApartado 6 · XSLTDifícil

Exporta a CSV (salida de texto) los días con lluvia: una primera línea estacion;fecha;tmax;tmin;lluvia y una línea por cada día con más de 0 mm, con el nombre de la estación, ordenadas por nombre de estación y después por fecha. Cada línea termina con un salto de línea.

observaciones.xmlsolo lectura
transformacion.xsl
DTDApartado 7 · DTDDifícil

Escribe la DTD observaciones.dtd: observaciones con uno o más estacion y los atributos obligatorios red y periodo; estacion con uno o más dia y los atributos obligatorios id (de tipo ID, único en el documento), nombre, provincia y altitud; dia con tmax, tmin y lluvia en ese orden y el atributo obligatorio fecha; y tmax, tmin y lluvia con texto.

esquema.dtd

Documentos de prueba (pulsa para verlos):

Solución explicada

Ver las soluciones de todos los apartados

Apartado 1 · XPath

/observaciones/estacion[@altitud > 500]/@nombre/string()

Apartado 2 · XPath

max(//tmax)

Apartado 3 · XPath

distinct-values(//dia[lluvia > 10]/@fecha)

Apartado 4 · XQuery

for $e in /observaciones/estacion
return concat($e/@nombre, ': media de máximas ', round(avg($e/dia/tmax) * 10) div 10, ' °C, lluvia total ', sum($e/dia/lluvia), ' mm')

Apartado 5 · XQuery

(for $d in //dia
 let $amplitud := $d/tmax - $d/tmin
 order by $amplitud descending
 return concat($d/../@nombre, ' (', $d/@fecha, '): amplitud ', round($amplitud * 10) div 10, ' °C'))[1]

Apartado 6 · 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="text"/>
  <xsl:template match="/observaciones">
    <xsl:text>estacion;fecha;tmax;tmin;lluvia&#10;</xsl:text>
    <xsl:for-each select="estacion/dia[lluvia &gt; 0]">
      <xsl:sort select="../@nombre"/>
      <xsl:sort select="@fecha"/>
      <xsl:value-of select="concat(../@nombre, ';', @fecha, ';', tmax, ';', tmin, ';', lluvia)"/>
      <xsl:text>&#10;</xsl:text>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

Apartado 7 · DTD

markup
<!ELEMENT observaciones (estacion+)>
<!ATTLIST observaciones
  red     CDATA #REQUIRED
  periodo CDATA #REQUIRED>
<!ELEMENT estacion (dia+)>
<!ATTLIST estacion
  id        ID    #REQUIRED
  nombre    CDATA #REQUIRED
  provincia CDATA #REQUIRED
  altitud   CDATA #REQUIRED>
<!ELEMENT dia (tmax, tmin, lluvia)>
<!ATTLIST dia fecha CDATA #REQUIRED>
<!ELEMENT tmax (#PCDATA)>
<!ELEMENT tmin (#PCDATA)>
<!ELEMENT lluvia (#PCDATA)>

Las consultas XPath se apoyan en la conversión automática a número: comparar un atributo con 500 o un elemento con 10 funciona directamente, y las funciones de agregado (max, avg, sum) hacen el resto.

El eje padre (..) es lo que distingue a XPath de una búsqueda de texto: desde un día se llega a su estación sin guardar nada aparte. En XQuery, guardar la amplitud en un let permite ordenar por ella y usarla en el resultado.

La salida de texto de XSLT convierte XML en cualquier formato plano (CSV, ficheros de configuración, SQL). Ordenar por dos claves con dos xsl:sort funciona igual que un ORDER BY con dos columnas.

La DTD es más limitada que el XML Schema (no tiene tipos de datos: una altitud negativa sería válida), pero describe la estructura y garantiza identificadores únicos con ID. Por eso sigue apareciendo en documentos antiguos y en el DOCTYPE de HTML.

Para ir más allá

  • Escribe el XML Schema equivalente a la DTD, con tipos numéricos y la fecha como xs:date.
  • Genera con XSLT una página HTML con una tabla por estación y las celdas de lluvia coloreadas según la cantidad.
  • Calcula con XQuery el día más lluvioso de cada provincia agrupando con distinct-values(@provincia).

Dónde se explica