Saltar al contenido
Código en GitHub (se abre en otra pestaña)
Lenguaje de Marcas · Tema 04

XSD: XML Schema Definition

TeoríaEjerciciosSoluciones

Tipos, restricciones, patrones, tipos con nombre, default y fixed, ref, recursividad, ID e IDREF.

En este documento · 43 secciones
  1. 1. Por qué existe XSD
  2. 2. Estructura de un XSD
  3. ¿Dónde va xmlns:xs?
  4. Vincular el XSD al XML
  5. 3. Tipos de datos predefinidos
  6. 4. Elementos simples — xs:element
  7. Cardinalidad — minOccurs y maxOccurs
  8. 5. Atributos — xs:attribute
  9. 6. Tipos complejos — xs:complexType
  10. Composición de hijos
  11. xs:choice — ejemplo
  12. xs:all — ejemplo y comportamiento
  13. Ejemplo completo — impresora
  14. Contenido mixto
  15. 7. Restricciones — xs:simpleType y xs:restriction
  16. Estructura
  17. Facetas disponibles
  18. Ejemplos
  19. 8. Expresiones regulares — xs:pattern
  20. Sintaxis básica
  21. Ejemplos
  22. 9. Tipos vs elementos — la distinción fundamental
  23. xs:simpleType suelto vs xs:complexType suelto
  24. 10. Usar tipos personalizados en elementos y atributos
  25. 10. Esquema completo — ejemplo impresoras
  26. 11. El patrón “no muñeca rusa”
  27. El problema: tipos inline anidados
  28. La solución: tipos con nombre en el nivel raíz
  29. Reglas del patrón
  30. Por qué lo exige la profesora
  31. 12. default y fixed
  32. En atributos
  33. En elementos
  34. 13. Elemento vacío con solo atributos
  35. 14. xs:whiteSpace
  36. 15. xs:choice con maxOccurs
  37. 16. Grupos opcionales — minOccurs en el compositor
  38. 17. xs:element ref
  39. ref vs type
  40. 18. Tipos recursivos
  41. 19. xs:ID y xs:IDREF
  42. Limitaciones
  43. Diferencia con xs:string normal

1. Por qué existe XSD

Un documento XML puede estar bien formado (sintaxis correcta) pero contener datos inválidos. Por ejemplo:

XML
<impresora>
    <peso>mucho</peso>
    <tipo>cohete</tipo>
</impresora>

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.

Fichero Lleva xmlns:xs Lleva xmlns:xsi
.xsd (el esquema) Sí — obligatorio No
.xml (los datos) No Sí — para vincularlo al esquema

Vincular el XSD al XML

En el elemento raíz del XML:

XML
<impresoras
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="impresoras.xsd">
    ...
</impresoras>

xmlns:xsi es obligatorio — sin él, el prefijo xsi: no existe y no puedes usar xsi:noNamespaceSchemaLocation ni xsi:schemaLocation.

Hay dos variantes según si el XML tiene namespace o no:

Situación Atributo Valor
XML sin namespace (lo normal en los ejercicios) xsi:noNamespaceSchemaLocation "impresoras.xsd"
XML con namespace propio xsi:schemaLocation "http://miempresa.com/ns impresoras.xsd" (par: namespace + ruta)

El “no namespace” describe al XML, no al esquema: significa “este XML no tiene namespace, aquí está su esquema”.

Lectura de las dos líneas juntas:

XML
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="impresoras.xsd"
  • 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:

XML
xmlns:tin="http://miempresa.com/tintas"
xsi:schemaLocation="http://miempresa.com/tintas tintas.xsd"

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.

Con varios namespaces:

XML
xmlns:tin="http://miempresa.com/tintas"
xmlns:imp="http://miempresa.com/impresoras"
xsi:schemaLocation="http://miempresa.com/tintas     tintas.xsd
                    http://miempresa.com/impresoras impresoras.xsd"

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:

XML
<xs:simpleType name="tipoPrecio">
   <xs:restriction base="xs:decimal">
       <xs:minExclusive value="0" />
   </xs:restriction>
</xs:simpleType>

4. Elementos simples — xs:element

Un elemento simple es el que solo contiene texto (sin atributos ni elementos hijo):

XML
<xs:element name="marca" type="xs:string" />
<xs:element name="peso" type="xs:decimal" />
<xs:element name="año" type="xs:positiveInteger" />

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:

XML
<xs:attribute name="numSerie" type="xs:string" use="required" />
<xs:attribute name="tipo" type="xs:string" use="required" />
<xs:attribute name="compra" type="xs:positiveInteger" use="optional" />

El atributo use controla si es obligatorio:

use Significado
required Obligatorio
optional Opcional (valor por defecto)
prohibited No puede aparecer

6. Tipos complejos — xs:complexType

Un tipo complejo es cualquier elemento que tenga atributos o elementos hijo. Se define con xs:complexType.

Composición de hijos

Tres formas de organizar los elementos hijo:

Compositor Significado
xs:sequence Los hijos deben aparecer en ese orden
xs:choice Solo puede aparecer uno de los hijos
xs:all Todos los hijos deben aparecer, en cualquier orden

xs:choice — ejemplo

XML
<xs:element name="contacto">
    <xs:complexType>
        <xs:choice>
            <xs:element name="email" type="xs:string" />
            <xs:element name="telefono" type="xs:string" />
            <xs:element name="direccion" type="xs:string" />
        </xs:choice>
    </xs:complexType>
</xs:element>

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

xs:all — ejemplo y comportamiento

XML
<xs:element name="direccion">
    <xs:complexType>
        <xs:all>
            <xs:element name="calle" type="xs:string" />
            <xs:element name="ciudad" type="xs:string" />
            <xs:element name="cp" type="xs:string" />
        </xs:all>
    </xs:complexType>
</xs:element>

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.

Ejemplo completo — impresora

XML
<xs:element name="impresora">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="marca" type="xs:string" />
            <xs:element name="modelo" type="xs:string" />
            <xs:element name="peso" type="xs:decimal" />
            <xs:element name="tamaño" type="xs:string"
                        minOccurs="1" maxOccurs="unbounded" />
            <xs:element name="cartucho" type="xs:string" />
            <xs:element name="enred" minOccurs="0">
                <xs:complexType />
            </xs:element>
        </xs:sequence>
        <xs:attribute name="numSerie" type="xs:string" use="required" />
        <xs:attribute name="tipo" type="xs:string" use="required" />
        <xs:attribute name="compra" type="xs:positiveInteger" use="optional" />
    </xs:complexType>
</xs:element>

<xs:complexType /> sin contenido = elemento vacío (como <enred/>).

Contenido mixto

Si un elemento mezcla texto con elementos hijo, se añade mixed="true":

XML
<xs:element name="descripcion">
    <xs:complexType mixed="true">
        <xs:sequence>
            <xs:element name="destacado" type="xs:string" />
        </xs:sequence>
    </xs:complexType>
</xs:element>

7. Restricciones — xs:simpleType y xs:restriction

Cuando un tipo predefinido no es suficiente (quieres limitar los valores posibles), defines un tipo simple personalizado con xs:simpleType y xs:restriction.

Estructura

XML
<xs:simpleType name="miTipo">
    <xs:restriction base="xs:tipoPredefinido">
        <!-- facetas -->
    </xs:restriction>
</xs:simpleType>

Facetas disponibles

Faceta Se aplica a Significado
xs:enumeration Cualquier tipo Lista de valores permitidos
xs:minInclusive Números Valor mínimo (incluido)
xs:maxInclusive Números Valor máximo (incluido)
xs:minExclusive Números Valor mínimo (excluido)
xs:maxExclusive Números Valor máximo (excluido)
xs:totalDigits Decimal Número total máximo de dígitos
xs:fractionDigits Decimal Número máximo de decimales
xs:length String Longitud exacta
xs:minLength String Longitud mínima
xs:maxLength String Longitud máxima
xs:pattern Cualquier tipo Expresión regular
xs:whiteSpace String Tratamiento de espacios

Ejemplos

Enumeración — tipo solo puede valer tres cosas:

XML
<xs:simpleType name="tipoImpresora">
    <xs:restriction base="xs:string">
        <xs:enumeration value="láser" />
        <xs:enumeration value="matricial" />
        <xs:enumeration value="tinta" />
    </xs:restriction>
</xs:simpleType>

Rango numérico — peso entre 0 y 999.99 con máximo 2 decimales:

XML
<xs:simpleType name="tipoPeso">
    <xs:restriction base="xs:decimal">
        <xs:minExclusive value="0" />
        <xs:fractionDigits value="2" />
    </xs:restriction>
</xs:simpleType>

8. Expresiones regulares — xs:pattern

xs:pattern permite definir el formato exacto de una cadena mediante expresiones regulares.

Sintaxis básica

Patrón Significado
[A-Z] Una letra mayúscula
[a-z] Una letra minúscula
[0-9] Un dígito
[A-Za-z] Una letra (mayúscula o minúscula)
[^abc] Cualquier carácter excepto a, b, c
X (sin cuantificador) X aparece exactamente 1 vez — el {1} está implícito y nunca se escribe
X? X aparece 0 o 1 vez (opcional)
X+ X aparece 1 o más veces
X* X aparece 0 o más veces
X{n} X aparece exactamente n veces
X{n,m} X aparece entre n y m veces
A|B A o B
. Cualquier carácter
\d Un dígito (equivale a [0-9])
\D Un no-dígito

Ejemplos

Cartucho — C mayúscula, guión, 3 números, 1 o 2 letras mayúsculas:

XML
<xs:simpleType name="tipoCartucho">
    <xs:restriction base="xs:string">
        <xs:pattern value="C-[0-9]{3}[A-Z]{1,2}" />
    </xs:restriction>
</xs:simpleType>

DNI español — 8 dígitos seguidos de 1 letra mayúscula:

XML
<xs:simpleType name="tipoDNI">
    <xs:restriction base="xs:string">
        <xs:pattern value="[0-9]{8}[A-Z]" />
    </xs:restriction>
</xs:simpleType>

Año — 4 dígitos entre 1900 y 2099:

XML
<xs:simpleType name="tipoAño">
    <xs:restriction base="xs:string">
        <xs:pattern value="(19|20)[0-9]{2}" />
    </xs:restriction>
</xs:simpleType>

9. Tipos vs elementos — la distinción fundamental

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="".

xs:simpleType suelto xs:complexType suelto
Para qué Tipo de texto con restricciones Estructura con hijos y/o atributos
Cómo se referencia type="tipoCP" en un xs:element type="tipoDireccion" en un xs:element
Crea etiqueta en el XML No — necesita un xs:element No — necesita un xs:element
XML
<!-- simpleType reutilizado -->
<xs:simpleType name="tipoCP">
    <xs:restriction base="xs:string">
        <xs:pattern value="[0-9]{5}" />
    </xs:restriction>
</xs:simpleType>

<!-- complexType reutilizado en dos sitios distintos -->
<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:element name="direccionEnvio" type="tipoDireccion" />
<xs:element name="direccionFactura" type="tipoDireccion" />

La recursividad (§18) es solo un caso especial de esto — el tipo se referencia a sí mismo, pero el mecanismo es idéntico.

10. Usar tipos personalizados en elementos y atributos

Los xs:simpleType definidos con nombre se reutilizan referenciándolos por nombre:

XML
<xs:element name="cartucho" type="tipoCartucho" />
<xs:attribute name="tipo" type="tipoImpresora" use="required" />

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.


10. Esquema completo — ejemplo impresoras

XML
<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">

    <!-- Tipos simples reutilizables -->

    <xs:simpleType name="tipoImpresora">
        <xs:restriction base="xs:string">
            <xs:enumeration value="láser" />
            <xs:enumeration value="matricial" />
            <xs:enumeration value="tinta" />
        </xs:restriction>
    </xs:simpleType>

    <xs:simpleType name="tipoPeso">
        <xs:restriction base="xs:decimal">
            <xs:minExclusive value="0" />
            <xs:fractionDigits value="2" />
        </xs:restriction>
    </xs:simpleType>

    <xs:simpleType name="tipoCartucho">
        <xs:restriction base="xs:string">
            <xs:pattern value="C-[0-9]{3}[A-Z]{1,2}" />
        </xs:restriction>
    </xs:simpleType>

    <!-- Estructura -->

    <xs:element name="impresoras">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="impresora" maxOccurs="unbounded">
                    <xs:complexType>
                        <xs:sequence>
                            <xs:element name="marca" type="xs:string" />
                            <xs:element name="modelo" type="xs:string" />
                            <xs:element name="peso" type="tipoPeso" />
                            <xs:element name="tamaño" type="xs:string"
                                        minOccurs="1" maxOccurs="unbounded" />
                            <xs:element name="cartucho" type="tipoCartucho" />
                            <xs:element name="enred" minOccurs="0">
                                <xs:complexType />
                            </xs:element>
                        </xs:sequence>
                        <xs:attribute name="numSerie" type="xs:string" use="required" />
                        <xs:attribute name="tipo" type="tipoImpresora" use="required" />
                        <xs:attribute name="compra" type="xs:positiveInteger" use="optional" />
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

</xs:schema>

11. El patrón “no muñeca rusa”

El problema: tipos inline anidados

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

  1. Todos los xs:complexType y xs:simpleType con name="" en el nivel raíz del xs:schema
  2. Los elementos los referencian con type="nombreDelTipo" — nunca definen tipos dentro de sí mismos
  3. Los elementos raíz del documento (el que valida el XML entero) se declaran también en el nivel raíz con name=""
  4. 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.

En atributos

XML
<xs:attribute name="moneda" type="xs:string" default="EUR" />
<xs:attribute name="version" type="xs:string" fixed="2.0" />
Situación default="EUR" fixed="2.0"
El atributo no aparece en el XML El validador lo trata como EUR — válido El validador lo trata como 2.0 — válido
El atributo aparece con el mismo valor Válido Válido
El atributo aparece con otro valor Válido (el autor puede cambiarlo) Inválido — el valor debe ser exactamente 2.0

En elementos

XML
<xs:element name="cantidad" type="xs:integer" default="1" />

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:

XML
<xs:element name="cantidad" type="xs:integer" default="1" minOccurs="0" />

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:complexType sin ningún compositor (xs:sequence, xs:choice, xs:all) define un elemento que solo puede tener atributos — ningún texto ni elementos hijo:

XML
<xs:complexType name="tipoServidor">
    <xs:attribute name="host" type="xs:string" use="required" />
    <xs:attribute name="puerto" type="xs:integer" use="required" />
    <xs:attribute name="ssl" type="xs:boolean" use="required" />
</xs:complexType>

<xs:element name="servidor" type="tipoServidor" />

Esto valida:

XML
<servidor host="192.168.1.1" puerto="8080" ssl="true" />

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
XML
<xs:simpleType name="tipoCodigo">
    <xs:restriction base="xs:string">
        <xs:whiteSpace value="collapse" />
        <xs:pattern value="[A-Z]{2}[0-9]{3}" />
    </xs:restriction>
</xs:simpleType>

Con collapse:

  • " 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:

XML
<xs:element name="mensaje">
    <xs:complexType>
        <xs:choice maxOccurs="unbounded">
            <xs:element name="parrafo" type="xs:string" />
            <xs:element name="imagen" type="xs:anyURI" />
            <xs:element name="separador">
                <xs:complexType />
            </xs:element>
        </xs:choice>
    </xs:complexType>
</xs:element>

Este XML es válido porque xs:choice maxOccurs="unbounded" significa “elige uno de estos, tantas veces como quieras, en cualquier orden”:

XML
<mensaje>
    <parrafo>Hola</parrafo>
    <imagen>http://img.com/foto.jpg</imagen>
    <parrafo>Adiós</parrafo>
    <separador/>
</mensaje>

Comparación:

xs:choice sin maxOccurs xs:choice maxOccurs="unbounded"
Hijos permitidos Exactamente 1, de los declarados 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.

Diferencia clave:

XML
<!-- Esquema 1: descuento opcional individualmente, direccion siempre obligatoria -->
<xs:sequence>
    <xs:element name="cliente" type="xs:string" />
    <xs:element name="descuento" type="xs:decimal" minOccurs="0" />
    <xs:element name="direccion" type="xs:string" />   <!-- obligatoria -->
</xs:sequence>
XML
<!-- 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):

XML
<xs:element name="factura">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="numero" type="xs:string" />
            <xs:sequence minOccurs="0">
                <xs:element name="cliente" type="xs:string" />
                <xs:element name="nif" type="xs:string" />
            </xs:sequence>
            <xs:element name="total" type="xs:decimal" />
        </xs:sequence>
    </xs:complexType>
</xs:element>

17. xs:element ref

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>

Esto valida XML de cualquier profundidad:

XML
<portal nombre="Mi Web">
    <seccion titulo="Intro">
        <contenido>Texto.</contenido>
        <seccion titulo="Subsección">
            <seccion titulo="Sub-sub">
                <contenido>Anidado.</contenido>
            </seccion>
        </seccion>
    </seccion>
</portal>

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

Descargas

Teoría.md ejercicio1.xsdejercicio2.xsdejercicio3.xsdejercicio4.xsdejercicio5.xsdejercicio6.xsd

Mis soluciones: resueltas por mí y corregidas al final de cada fichero.

Mi matrícula

Marca las asignaturas de las que te has matriculado. La web y el calendario mostrarán solo esos; sin ninguno marcado se ve todo.

Primero
Segundo