Validador XSD
Valida contra un XSD. Los dos archivos se quedan en tu navegador.
Todo se ejecuta en esta pestaña. Nada de lo que pegues se sube, se registra ni se envía a ningún sitio. Abre el panel de red y compruébalo.
Pega el documento a la izquierda, su XSD a la derecha y pulsa Validar contra el esquema. libxml2, compilado a WebAssembly, ejecuta la comprobación en un Web Worker de esta pestaña, y cada infracción se lista con línea, columna y un marcador que puedes pulsar para saltar hasta ella.
Normalmente llegas aquí después de que otra cosa haya rechazado el documento: un socio devuelve cvc-complex-type.2.4.a, un paso del build falla con un archivo de configuración, una pasarela dice que el payload no cumple el esquema y ahí se queda. Tienes el XSD y quieres el elemento que falla, no una cadena de herramientas Java.
La diferencia está en dónde se ejecuta. Los navegadores no traen validador de esquemas, así que las herramientas gratuitas que ofrecen validación XSD envían ambos archivos a un servidor; la más conocida te obliga a subir primero el XML y el esquema en un segundo formulario. Aquí ningún archivo sale de esta pestaña, y el motor, unos 480 KB comprimidos, se descarga al pulsar el botón y no al cargar la página.
Primero bien formado, luego válido
Son dos comprobaciones, no una. La buena formación es sintaxis: etiquetas cerradas, anidamiento correcto, una raíz, ampersands escapados. Solo necesita el documento, así que se ejecuta mientras escribes. La validez es buena formación más conformidad con un esquema, lo que exige un segundo archivo y por eso es una acción deliberada.
El motor analiza el XSD, lo compila, analiza el documento y después valida, y cada paso falla de forma distinta. Un documento mal formado nunca se comprueba contra un esquema, porque no hay nada coherente que comprobar. Cada diagnóstico se etiqueta como Problema en el esquema o Error de validez, y una posición dentro del esquema deliberadamente no es pulsable, porque ese número de línea pertenece al otro panel.
Solo XSD 1.0, y qué queda fuera
libxml2 implementa XSD 1.0. XSD 1.1 pasó a ser Recomendación del W3C el 5 de abril de 2012 y está implementado por Apache Xerces-J, por Saxon-EE y por el paquete xmlschema de Python, pero no por libxml2, ni por el XmlSchemaSet de .NET, ni por lxml, que envuelve libxml2. Un esquema que use funciones de 1.1 no compilará aquí, y el panel señala 1.1 como la causa probable en vez de dar un error estructural desconcertante.
- xs:assert y la faceta xs:assertion: restricciones de coocurrencia, donde la validez de un campo depende de otro. XSD 1.0 no puede expresarlas en absoluto.
- xs:alternative, la asignación condicional de tipo, donde el tipo de un elemento se elige mediante una prueba XPath sobre sus propios atributos.
- xs:openContent, xs:defaultOpenContent y xs:override, más los tipos de dato de 1.1 xs:dateTimeStamp, xs:dayTimeDuration y xs:yearMonthDuration. xs:precisionDecimal, que aparece en muchísimos artículos, se descartó antes de la Recomendación final.
- Dos límites más de libxml2: xs:redefine lleva mucho tiempo cayendo en una ruta no implementada, y xs:import no puede resolver un schemaLocation aquí, porque nada en esta compilación puede leer un archivo. Aplana antes un conjunto de esquemas repartido en varios documentos.
Los fallos que vas a ver de verdad
El orden causa la mayoría. xs:sequence significa que los hijos deben aparecer en el orden declarado, y ese orden es parte del contrato y no una preferencia de formato, así que la solución suele ser mover el elemento. XSD 1.0 permite xs:all, la alternativa sin orden, solo en la cima de un modelo de contenido y con cada elemento como mucho una vez, y por eso casi todos los esquemas reales usan una secuencia.
Los espacios de nombres causan el resto. Si el esquema declara targetNamespace="urn:example:orders", cada elemento que gobierna debe estar en ese espacio de nombres; si no declara ninguno, deben estar sin espacio de nombres, y añadir un xmlns por defecto lo rompe. La trampa relacionada es elementFormDefault: si falta, significa unqualified, así que la raíz va cualificada y los hijos no deben ir.
- cvc-complex-type.2.4.a: apareció un elemento donde el esquema no lo permitía. Los nombres entre llaves son los que habrían sido legales, así que "One of {qty} is expected" significa que tocaba qty.
- cvc-complex-type.2.4.b: no se satisfizo el modelo de contenido, normalmente un hijo obligatorio que falta al final.
- cvc-datatype-valid.1.2.1: el texto no es léxicamente válido para su tipo. Un elemento vacío declarado xs:int, un dateTime sin segundos, un decimal con separador de miles.
- cvc-elt.1: no se encontró declaración para el elemento raíz. Casi siempre es un desajuste de targetNamespace y no una declaración que falte.
- cvc-complex-type.3.2.2: un atributo no está permitido, a menudo xsi:type o xsi:nil sin declarar el espacio de nombres XMLSchema-instance.
Lo que nunca hace, y lo que cuesta
Nunca descarga xsi:schemaLocation. La parte 1 de XSD llama a ese atributo una pista "sobre la ubicación física de los documentos de esquema" y dice que un procesador "debería intentar dereferenciarlo" "salvo que se le indique lo contrario, por ejemplo por la aplicación invocante". Negarse es conforme, y habitual: SQL Server lo ignora en columnas de tipo xml.
Aunque quisiera, no podría descargar nada. Cada análisis usa XML_PARSE_NO_XXE, NONET y NO_SYS_CATALOG, y esta compilación no registra ningún cargador de recursos, así que las entidades externas, un subconjunto DTD externo y una URL de schemaLocation se resuelven todos en nada. XML_PARSE_HUGE nunca se activa, lo que mantiene los topes de libxml2: profundidad de elementos 256, anidamiento de entidades 20 y un límite de amplificación.
Una debilidad que conviene admitir: la documentación de libxml2 llama a su módulo de esquemas una implementación parcial de la parte 1 de XML Schema, y a menudo informa de menos diagnósticos que Xerces con las mismas entradas. Lee la lista como "al menos estos" y vuelve a ejecutar tras cada arreglo.
Validar contra un XSD desde código
La misma comprobación en los lenguajes que consumen XML. Todos los ejemplos desactivan el acceso externo, porque una referencia a un esquema es una instrucción de descarga y la mayoría de estas bibliotecas la siguen por defecto.
// npm i libxml2-wasm, the same engine this page runs.
import { XmlDocument, XsdValidator, ParseOption, XmlValidateError } from 'libxml2-wasm';
// NO_XXE blocks external DTDs and entities. Leaving HUGE unset is what keeps
// libxml2's depth, entity-nesting and amplification limits switched on.
const SAFE = ParseOption.XML_PARSE_NO_XXE | ParseOption.XML_PARSE_NONET;
export function validateXsd(xmlText, xsdText) {
let xsd = null;
let validator = null;
let doc = null;
try {
xsd = XmlDocument.fromString(xsdText, { option: SAFE });
validator = XsdValidator.fromDoc(xsd);
doc = XmlDocument.fromString(xmlText, { option: SAFE });
validator.validate(doc);
return { valid: true, errors: [] };
} catch (err) {
if (err instanceof XmlValidateError) {
// Each detail is { line, col, level, message, xpath }.
return { valid: false, errors: err.details };
}
throw err;
} finally {
// dispose() is mandatory: these are WebAssembly heap allocations.
doc?.dispose();
validator?.dispose();
xsd?.dispose();
}
}# pip install lxml
from lxml import etree
parser = etree.XMLParser(
resolve_entities=False, # no entity substitution
no_network=True, # never fetch a schema or DTD
load_dtd=False,
huge_tree=False, # keep the depth and expansion limits
)
schema = etree.XMLSchema(etree.parse('schema.xsd', parser))
doc = etree.parse('document.xml', parser)
if schema.validate(doc):
print('valid')
else:
for e in schema.error_log:
print(f'{e.line}:{e.column} {e.message}')
# lxml wraps libxml2, so this is XSD 1.0. For xs:assert and xs:alternative use
# the pure-Python implementation instead:
# import xmlschema
# xmlschema.XMLSchema11('schema.xsd').validate('document.xml')import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
// With Xerces on the classpath, XSD 1.1 is one string away:
// SchemaFactory.newInstance("http://www.w3.org/XML/XMLSchema/v1.1");
// "file" allows xs:import of a schema next to this one; http is refused.
// Pass "" instead to block every external reference, local ones included.
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "file");
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
Schema schema = factory.newSchema(new StreamSource(new java.io.File("schema.xsd")));
Validator validator = schema.newValidator();
validator.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
// Without an ErrorHandler the first error throws and you see one problem.
// With one, Xerces carries on and reports the lot.
validator.setErrorHandler(new DefaultHandler() {
@Override public void error(SAXParseException e) {
System.out.printf("%d:%d %s%n", e.getLineNumber(), e.getColumnNumber(), e.getMessage());
}
@Override public void fatalError(SAXParseException e) throws SAXParseException {
throw e;
}
});
validator.validate(new StreamSource(new java.io.File("document.xml")));using System;
using System.Xml;
using System.Xml.Schema;
var schemaSettings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null, // no xs:import over the network
};
var schemas = new XmlSchemaSet { XmlResolver = null };
using (var schemaReader = XmlReader.Create("schema.xsd", schemaSettings))
{
schemas.Add(null, schemaReader); // null: take targetNamespace from the file
}
var settings = new XmlReaderSettings
{
ValidationType = ValidationType.Schema,
Schemas = schemas,
DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null,
};
// Never follow xsi:schemaLocation from the instance document.
settings.ValidationFlags &= ~XmlSchemaValidationFlags.ProcessSchemaLocation;
// Without a handler the first error throws; with one, validation continues.
settings.ValidationEventHandler += (_, e) =>
Console.WriteLine($"{e.Exception.LineNumber}:{e.Exception.LinePosition} {e.Message}");
using var reader = XmlReader.Create("document.xml", settings);
while (reader.Read()) { } // validation happens as the document is read
// System.Xml.Schema is XSD 1.0, and Microsoft has said it is not moving past it.<?php
// DOMDocument is libxml2, so this is the same engine and the same XSD 1.0.
libxml_use_internal_errors(true);
$doc = new DOMDocument();
if (!$doc->loadXML($source, LIBXML_NONET)) {
fwrite(STDERR, "The document is not well-formed.\n");
exit(1);
}
// schemaValidateSource() takes the XSD as a string if you already have it.
if (!$doc->schemaValidate('schema.xsd')) {
foreach (libxml_get_errors() as $e) {
fprintf(STDERR, "%d:%d %s\n", $e->line, $e->column, trim($e->message));
}
libxml_clear_errors();
exit(1);
}
echo "valid\n";# xmllint ships with libxml2 and is the same engine as this page.
# --nonet stops it fetching anything the document or the schema references.
xmllint --noout --nonet --schema schema.xsd document.xml
# Exit status is 0 when the document is valid, non-zero otherwise, and the
# diagnostics go to stderr in file:line: form.
# A schema split across xs:import files works here, because xmllint reads the
# sibling files from disk. That is the one thing a browser cannot do.
# No widely installed command-line tool does XSD 1.1; for that you need
# Xerces-J or Saxon-EE, driven from code.Fíjate en el patrón del manejador en los ejemplos de Java y C#: las dos APIs lanzan una excepción en el primer error salvo que instales uno, y por eso tanto código en producción informa de un solo problema cada vez.
Preguntas frecuentes
¿Se suben mi XML y mi XSD a algún sitio?
No. Los dos paneles se quedan en esta pestaña. libxml2 corre como WebAssembly en un Web Worker aquí mismo, no hay endpoint de subida, y la compilación no tiene ninguna vía de red propia.
Aquí eso importa más que en otras páginas: un esquema nombra cada campo, cada lista de códigos y cada frontera interna, y a menudo está bajo acuerdo de confidencialidad cuando el documento son solo datos de prueba. Abre tu panel de red, pulsa Validar y observa cómo el motor se carga una vez y después nada más. Los dos paneles se guardan en localStorage para que un refresco no pierda tu trabajo, y nada por encima de 300 KB se almacena.
¿Admite XSD 1.1, o xs:assert?
No. libxml2 es XSD 1.0, así que xs:assert, xs:alternative, xs:openContent y xs:override no compilarán. En vez de un desconcertante "element assert is not expected here", el panel dice que el esquema es XML bien formado pero no un XSD utilizable, y señala 1.1 como causa probable.
Para 1.1, usa Xerces-J, que es gratuito y te da una SchemaFactory 1.1 cambiando una cadena de espacio de nombres, Saxon-EE, o el paquete xmlschema de Python. Nada basado en navegador lo hace: todos son compilaciones de libxml2.
Mi esquema usa xs:import. ¿Puedo validar aquí igualmente?
Solo si lo aplanas antes. Resolver un xs:import significa leer un archivo o hacer una petición, y esta compilación no registra ningún cargador de recursos, la misma decisión que impide descargar entidades externas, así que el esquema no compilará.
O pegas las definiciones de tipo importadas en el documento contra el que validas, o ejecutas xmllint en local, donde los archivos .xsd hermanos se leen del disco. La mayoría de las grandes familias de esquemas públicos, UBL y FpML entre ellas, necesitan ese tratamiento.
¿Qué significa cvc-complex-type.2.4.a?
Que apareció un elemento donde el esquema no lo permitía. El mensaje termina con los nombres que habrían sido legales ahí, entre llaves, así que "Invalid content was found starting with element 'item'. One of '{qty}' is expected." significa que tocaba qty.
Tres causas cubren casi todo: los hijos están en el orden equivocado para un xs:sequence, con diferencia lo más común; el elemento no está declarado en absoluto, normalmente una errata o un campo que añadió un lado de la integración; o el nombre es correcto y el espacio de nombres es el equivocado, algo que el mensaje no puede mostrarte porque imprime el nombre local.
¿Descargará el esquema indicado en xsi:schemaLocation?
Nunca. La especificación llama a ese atributo una pista y permite que la aplicación invocante indique al procesador que no lo dereferencie, así que negarse es conforme y no un atajo.
Las razones van más allá de la privacidad: seguir una URL controlada por un atacante desde un documento no confiable es una primitiva de falsificación de peticiones, y ata tu resultado a lo que un tercero esté sirviendo hoy.
¿Qué tamaño de documento puede comprobar?
Veinte megabytes. Cargar un archivo mayor se rechaza, porque todo se mantiene en la memoria de esta página y la validación construye un árbol completo más el esquema compilado.
Para documentos realmente grandes, ejecuta xmllint en local: el mismo código de libxml2, sin el techo del navegador.