Esto es XML bien formado — pero peso debería ser un número decimal, y tipo solo puede ser “láser”, “matricial” o “tinta”. XML por sí solo no puede verificar nada de esto.
Para resolver ese problema existen los esquemas de validación. Dos opciones:
DTD
XSD
Sintaxis
Propia (no es XML)
Es XML
Tipos de datos
Solo texto
Muchos: enteros, decimales, fechas, booleanos…
Restricciones
Muy limitadas
Completas: rangos, patrones, longitudes…
Integridad referencial
ID/IDREF (básico)
xs:key/xs:keyref (robusto)
Namespaces
No soporta
Soporta
Uso actual
Legacy
Estándar actual
XSD es el estándar actual. DTD sigue existiendo en sistemas legados y en la Tarea 4.
2. Estructura de un XSD
Un XSD es un documento XML normal. Su elemento raíz es xs:schema:
XML
<?xml version="1.0" encoding="UTF-8" ?><xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> <!-- aquí van las declaraciones --></xs:schema>
El atributo xmlns:xs="http://www.w3.org/2001/XMLSchema" es obligatorio — declara el namespace de XSD. El prefijo xs: se puede cambiar (también se ve xsd:), pero xs: es el más común.
Nota: el prefijo xs: se usa en la propia etiqueta donde se declara (<xs:schema xmlns:xs="...">). Esto es válido porque en XML una declaración xmlns: aplica al elemento en el que está escrita, incluido él mismo. El parser procesa el prefijo y su declaración como parte del mismo token de apertura — no hay “antes” ni “después” dentro de una misma etiqueta.
¿Dónde va xmlns:xs?
xmlns:xs="http://www.w3.org/2001/XMLSchema" solo va en el fichero .xsd — el esquema necesita declarar ese namespace para poder usar xs:element, xs:complexType, etc.
En el fichero .xml de datos no hay ningún xs:. Solo hay tus propios elementos (<impresoras>, <marca>…) y el xmlns:xsi para apuntar al esquema. No importas XSD para nada porque no estás escribiendo reglas — estás escribiendo datos.
Primera línea: el prefijo xsi: pertenece al namespace W3C de XMLSchema-instance — permite usar sus atributos especiales.
Segunda línea: los elementos de este documento (los que no tienen prefijo) se validan contra las reglas definidas en impresoras.xsd.
La primera dice de dónde viene xsi:. La segunda usa xsi: para apuntar al fichero con las reglas de validación. No es “importar datos de impresoras.xsd” — es “validar contra las reglas de impresoras.xsd”.
¿Se pueden mezclar schemaLocation y noNamespaceSchemaLocation?
Sí. Puedes usar los dos a la vez si el documento mezcla elementos con namespace y elementos sin él:
XML
<impresoras xmlns:tin="http://miempresa.com/tintas" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://miempresa.com/tintas tintas.xsd" xsi:noNamespaceSchemaLocation="impresoras.xsd"> <marca>Epson</marca> <!-- sin namespace → validado por impresoras.xsd --> <tin:color>negro</tin:color> <!-- con namespace → validado por tintas.xsd --></impresoras>
Lo que no puedes es tener dos bloques ambos sin namespace con esquemas distintos — el validador no podría distinguirlos. Si necesitas dos esquemas para elementos sin namespace, la solución es darle namespace a uno de los bloques.
¿Cómo sabe el validador qué esquema corresponde a qué namespace?
schemaLocation no referencia el prefijo (tin:), referencia el URI completo. El validador cruza el URI de xmlns: con el URI del par en schemaLocation:
Lee: “el namespace http://miempresa.com/tintas está definido en tintas.xsd”. Como tin: tiene ese URI, todo elemento tin: se valida contra tintas.xsd. El prefijo es el alias — la identidad real es el URI.
El validador distingue tin: de imp: por sus URIs, no por sus prefijos. Podrías renombrar los prefijos y seguiría funcionando igual.
3. Tipos de datos predefinidos
XSD incluye tipos de datos listos para usar. Los más importantes:
Tipo
Qué representa
Ejemplo
xs:string
Cadena de texto
"Epson"
xs:boolean
Verdadero/falso
true, false
xs:integer
Entero (positivo, negativo o cero)
42, -3
xs:positiveInteger
Entero estrictamente positivo (> 0)
1, 99
xs:negativeInteger
Entero estrictamente negativo (< 0)
-1, -99
xs:nonNegativeInteger
Entero mayor o igual a 0 (≥ 0)
0, 1, 99
xs:nonPositiveInteger
Entero menor o igual a 0 (≤ 0)
0, -1, -99
xs:decimal
Número con decimales (positivo, negativo o cero)
4.52, -3.2, 0
xs:date
Fecha ISO
2024-05-06
xs:dateTime
Fecha y hora
2024-05-06T09:00:00
xs:anyURI
URI/URL
http://ejemplo.com
xs:ID
Identificador único (herencia DTD)
"i245"
xs:IDREF
Referencia a un ID (herencia DTD)
"i245"
No existe xs:positiveDecimal ni variantes predefinidas para decimales. Para restringir decimales a positivos hay que usar xs:restriction con xs:minExclusive value="0" manualmente:
El atributo name es literalmente el nombre de la etiqueta que aparecerá en el XML:
XML
<!-- XSD --><xs:element name="nombre" type="xs:string" /><!-- XML resultante --><nombre>Juan</nombre>
Cardinalidad — minOccurs y maxOccurs
Controlan cuántas veces puede aparecer un elemento:
Atributo
Valor por defecto
Significado
minOccurs
1
Mínimo de apariciones
maxOccurs
1
Máximo de apariciones
Valores especiales:
minOccurs="0" → el elemento es opcional
maxOccurs="unbounded" → puede repetirse sin límite
XML
<!-- Obligatorio, exactamente una vez (por defecto) --><xs:element name="marca" type="xs:string" /><!-- Opcional --><xs:element name="enred" minOccurs="0" maxOccurs="1" /><!-- Puede repetirse: uno o más tamaños --><xs:element name="tamaño" type="xs:string" minOccurs="1" maxOccurs="unbounded" />
5. Atributos — xs:attribute
Los atributos se declaran dentro de un xs:complexType (ver §6), después de los elementos hijo:
<contacto> solo puede tener uno de los tres — o email, o teléfono, o dirección. Nunca dos a la vez. Si el XML tiene <email> y <telefono> a la vez, falla.
Los tres hijos son obligatorios por defecto (minOccurs="1") y pueden aparecer en cualquier orden. Para hacer uno opcional: minOccurs="0" en ese elemento concreto.
Limitación importante: xs:all no permite maxOccurs > 1 en sus hijos — cada elemento puede aparecer como máximo una vez. No se pueden anidar compositors dentro de xs:all.
Cuando un tipo predefinido no es suficiente (quieres limitar los valores posibles), defines un tipo simple personalizado con xs:simpleType y xs:restriction.
Tipos y elementos son cosas distintas. Un tipo define una estructura o restricción. Un elemento declara una etiqueta que aparece en el XML. Un tipo solo existe en el XML si un elemento lo referencia.
XML
<!-- Esto NO crea ninguna etiqueta <empleado> en el XML --><xs:complexType name="tipoEmpleado"> <xs:sequence> <xs:element name="nombre" type="xs:string" /> </xs:sequence></xs:complexType><!-- Esto SÍ crea la etiqueta <empleado> --><xs:element name="empleado" type="tipoEmpleado" />
Convención de nombres: los tipos llevan el prefijo tipo (tipoEmpleado, tipoDireccion) para distinguirlos de los elementos (empleado, direccion).
xs:simpleType suelto vs xs:complexType suelto
Ambos sirven para lo mismo: definir una vez y reutilizar en muchos sitios con type="".
Esto es la base del principio “no muñeca rusa” — definir los tipos con nombre arriba y referenciarlos abajo, en lugar de anidarlos inline. Se verá en detalle en XSD 2.
Cuando defines tipos directamente dentro de elementos, el esquema se convierte en una estructura de cajas dentro de cajas — como muñecas rusas:
XML
<!-- Muñeca rusa — los tipos están atrapados dentro de otros tipos --><xs:element name="agenda"> <xs:complexType> <!-- tipo inline de agenda --> <xs:sequence> <xs:element name="contacto" maxOccurs="unbounded"> <xs:complexType> <!-- tipo inline de contacto --> <xs:sequence> <xs:element name="nombre" type="xs:string" /> <xs:element name="direccion"> <xs:complexType> <!-- tipo inline de direccion --> <xs:sequence> <xs:element name="calle" type="xs:string" /> <xs:element name="ciudad" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType></xs:element>
Problema: tipoDireccion está enterrado dentro de tipoContacto que está enterrado dentro de tipoAgenda. Si otro elemento necesita el mismo tipo de dirección, hay que repetir todo el bloque.
La solución: tipos con nombre en el nivel raíz
XML
<!-- Nivel raíz del xs:schema — todos los tipos definidos aquí --><xs:complexType name="tipoDireccion"> <xs:sequence> <xs:element name="calle" type="xs:string" /> <xs:element name="ciudad" type="xs:string" /> </xs:sequence></xs:complexType><xs:complexType name="tipoContacto"> <xs:sequence> <xs:element name="nombre" type="xs:string" /> <xs:element name="direccion" type="tipoDireccion" /> <!-- referencia por nombre --> </xs:sequence> <xs:attribute name="id" type="xs:string" use="required" /></xs:complexType><xs:complexType name="tipoAgenda"> <xs:sequence> <xs:element name="contacto" type="tipoContacto" maxOccurs="unbounded" /> </xs:sequence></xs:complexType><xs:element name="agenda" type="tipoAgenda" /> <!-- elemento raíz declarado en raíz -->
Reglas del patrón
Todos los xs:complexType y xs:simpleType con name="" en el nivel raíz del xs:schema
Los elementos los referencian con type="nombreDelTipo" — nunca definen tipos dentro de sí mismos
Los elementos raíz del documento (el que valida el XML entero) se declaran también en el nivel raíz con name=""
Excepción permitida: elementos completamente simples (type="xs:string", type="tipoCP", etc.) no necesitan un complexType con nombre — solo los complejos
Por qué lo exige la profesora
Es más legible (lees el nivel raíz y ves todos los tipos), los tipos son reutilizables, y el esquema es mantenible. El enunciado de la Tarea 4 lo llama explícitamente “flexible y mantenible”.
12. default y fixed
Ambos se pueden poner en xs:element y en xs:attribute.
default en elementos no hace el elemento opcional — el elemento sigue siendo obligatorio (minOccurs="1" por defecto). Solo actúa si el elemento aparece vacío en el XML:
XML
<cantidad/> → el validador lo lee como <cantidad>1</cantidad><cantidad>5</cantidad> → válido, usa el valor del autor
Para hacer un elemento opcional con valor por defecto hay que combinar los dos:
fixed en elementos funciona igual que en atributos: el elemento puede aparecer vacío (toma el valor fijo) o con exactamente ese valor. Cualquier otro valor es inválido.
13. Elemento vacío con solo atributos
Un xs:complexTypesin ningún compositor (xs:sequence, xs:choice, xs:all) define un elemento que solo puede tener atributos — ningún texto ni elementos hijo:
Regla: si un xs:complexType no tiene compositor, el elemento validado no puede tener contenido de ningún tipo. Si el XML intenta poner texto o elementos hijo dentro, falla la validación.
14. xs:whiteSpace
Controla cómo el validador trata los espacios en blanco (espacios, tabulaciones \t, saltos de línea \n, retornos de carro \r) antes de comparar con el tipo o el patrón.
Valor
Qué hace
preserve
No modifica nada — conserva los espacios tal cual
replace
Sustituye \t, \n y \r por espacios normales
collapse
Aplica replace + elimina espacios al inicio y al final + colapsa secuencias de espacios internos en un único espacio
" AB123 " → se normaliza a "AB123" → el patrón lo acepta → válido
"AB 123" → se normaliza a "AB 123" (el espacio interno persiste, solo se colapsan múltiples) → el patrón no lo acepta → inválido
Nota:xs:string tiene preserve por defecto. xs:token es xs:string con collapse ya incorporado — si usas xs:token como base, no necesitas declarar xs:whiteSpace.
15. xs:choice con maxOccurs
Por defecto xs:choice permite exactamente uno de los hijos declarados. Con maxOccurs se permite que aparezcan varios hijos, de cualquiera de las opciones, en cualquier orden:
Cualquier número, de cualquiera de los declarados, en cualquier orden
<mensaje/> vacío
Inválido (minOccurs=1 por defecto)
Inválido (mismo motivo)
Para permitir cero hijos: <xs:choice minOccurs="0" maxOccurs="unbounded">.
16. Grupos opcionales — minOccurs en el compositor
minOccurs y maxOccurs se pueden poner no solo en xs:element sino también en el propio compositor (xs:sequence, xs:choice, xs:all). Esto convierte todo el grupo en opcional o repetible.
<!-- Esquema 2: el par (descuento + direccion) va junto o no va --><xs:sequence> <xs:element name="cliente" type="xs:string" /> <xs:sequence minOccurs="0"> <!-- el grupo es opcional --> <xs:element name="descuento" type="xs:decimal" /> <xs:element name="direccion" type="xs:string" /> </xs:sequence></xs:sequence>
En el esquema 1: <cliente> + <direccion> siempre, <descuento> puede no estar.
En el esquema 2: o van <descuento> y <direccion> juntos, o no va ninguno de los dos. Si hay <descuento> sin <direccion>, falla.
Caso de uso típico: datos que deben ir en pareja (cliente + NIF, fecha inicio + fecha fin):
Un elemento declarado en el nivel raíz del esquema es global y puede reutilizarse en otro lugar con ref=:
XML
<!-- Declaración global en el nivel raíz --><xs:element name="autor"> <xs:complexType> <xs:sequence> <xs:element name="nombre" type="xs:string" /> <xs:element name="nacionalidad" type="xs:string" /> </xs:sequence> <xs:attribute name="id" type="xs:string" use="required" /> </xs:complexType></xs:element><!-- Referencia desde otro elemento --><xs:element name="libro"> <xs:complexType> <xs:sequence> <xs:element name="titulo" type="xs:string" /> <xs:element ref="autor" maxOccurs="unbounded" /> <!-- ref, no name ni type --> </xs:sequence> </xs:complexType></xs:element>
<xs:element ref="autor" /> reutiliza la declaración completa del elemento <autor> — nombre, tipo, atributos, todo incluido.
ref vs type
type="tipoAutor"
ref="autor"
Reutiliza
Solo el tipo (la estructura interna)
El elemento completo (nombre + tipo + atributos)
Nombre del elemento en el XML
Lo decides tú
Queda fijo al del elemento global
Puedes cambiar minOccurs/maxOccurs
Sí
Sí
Puedes cambiar atributos
Sí
No
Cuándo usar ref: cuando el mismo elemento (<autor>, <direccion>…) debe aparecer en varios sitios del documento con exactamente la misma estructura y nombre. Si solo quieres reutilizar la estructura pero dar nombre distinto, usa type.
18. Tipos recursivos
Un xs:complexType puede referenciarse a sí mismo para modelar estructuras de profundidad arbitraria: secciones dentro de secciones, categorías con subcategorías, comentarios con respuestas…
Requisito imprescindible: el tipo debe tener nombre. Un tipo inline no puede referenciarse a sí mismo porque para escribir type="tipoSeccion" necesita ese nombre.
XML
<xs:complexType name="tipoSeccion"> <xs:sequence> <xs:element name="contenido" type="xs:string" minOccurs="0" /> <xs:element name="seccion" type="tipoSeccion" <!-- se referencia a sí mismo --> minOccurs="0" maxOccurs="unbounded" /> </xs:sequence> <xs:attribute name="titulo" type="xs:string" use="required" /></xs:complexType><xs:element name="portal"> <xs:complexType> <xs:sequence> <xs:element name="seccion" type="tipoSeccion" minOccurs="0" maxOccurs="unbounded" /> </xs:sequence> <xs:attribute name="nombre" type="xs:string" use="required" /> </xs:complexType></xs:element>
Por qué minOccurs="0" en las subsecciones: si fuera minOccurs="1" la recursión no podría terminar — siempre habría que tener al menos una subsección dentro de cada sección, al infinito. Con minOccurs="0" una sección puede ser hoja del árbol (sin hijos) y la recursión termina.
19. xs:ID y xs:IDREF
XSD incluye tipos especiales para referencias cruzadas dentro del mismo documento:
Tipo
Uso
xs:ID
Identificador único — su valor debe ser único en todo el documento
xs:IDREF
Referencia a un ID existente — su valor debe coincidir con algún xs:ID del documento
xs:IDREFS
Lista de IDREFs separados por espacio
XML
<!-- En el XSD --><xs:complexType name="tipoPropietario"> <xs:sequence>...</xs:sequence> <xs:attribute name="id" type="xs:ID" use="required" /> <!-- define el ID --></xs:complexType><xs:complexType name="tipoAnimal"> <xs:sequence> ... <xs:element name="propietarioRef" type="xs:IDREF" /> <!-- referencia al ID --> </xs:sequence> <xs:attribute name="id" type="xs:ID" use="required" /></xs:complexType>
XML
<!-- En el XML --><propietario id="CLI0001">...</propietario> <!-- id es xs:ID → debe ser único --><animal id="AN0001"> <propietarioRef>CLI0001</propietarioRef> <!-- debe existir un xs:ID con ese valor --></animal>
Limitaciones
El valor de un xs:ID debe ser un nombre XML válido — no puede empezar por número ni contener espacios. Válido: AN0001, CLI-01. Inválido: 001, mi id.
El validador comprueba que el IDREF apunta a algún ID existente, pero no que apunte al tipo correcto. propietarioRef podría valer AN0001 (un id de animal) y el validador no se quejaría.
Para referencias con validación de tipo existe xs:key/xs:keyref — más potente pero fuera del alcance de XSD 1.
Diferencia con xs:string normal
Un atributo type="xs:string" con valor "CLI0001" es texto libre — el validador no hace nada especial con él. Un atributo type="xs:ID" con valor "CLI0001" le dice al validador “este valor debe ser único en el documento”. Un atributo type="xs:IDREF" le dice “este valor debe coincidir con algún xs:ID”.