Validatore XSD

Convalida con un XSD. I due file restano nel tuo browser.

Input
Schema XSD
In attesaIncolla un documento per controllarlo. La convalida gira mentre scrivi.

Tutto viene eseguito in questa scheda. Nulla di ciò che incolli viene caricato, registrato o inviato da qualche parte. Apri il pannello di rete e verifica.

Incolla il documento a sinistra, il suo XSD a destra e premi Convalida con lo schema. libxml2, compilato in WebAssembly, esegue il controllo in un Web Worker di questa scheda, e ogni violazione è elencata con riga, colonna e un segnaposto su cui puoi cliccare per saltarci.

Di solito ci si arriva dopo che qualcos’altro ha rifiutato il documento: un partner restituisce cvc-complex-type.2.4.a, uno step di build fallisce su un file di configurazione, un gateway dice che il payload non corrisponde allo schema e si ferma lì. Hai l’XSD e vuoi l’elemento che non va, non una toolchain Java.

La differenza è dove gira. I browser non hanno un validatore di schemi integrato, quindi gli strumenti gratuiti che offrono la convalida XSD spediscono entrambi i file a un server; il più noto ti fa inviare prima l’XML e poi lo schema in un secondo modulo. Qui nessuno dei due file esce da questa scheda, e il motore, circa 480 KB compressi, viene scaricato quando premi il pulsante e non al caricamento della pagina.

Prima ben formato, poi valido

Sono due controlli, non uno. La buona formazione è sintassi: tag chiusi, annidamento corretto, una radice, e commerciali con escape. Serve solo il documento, quindi gira mentre scrivi. La validità è buona formazione più conformità a uno schema, che richiede un secondo file: da qui un’azione deliberata.

Il motore analizza l’XSD, lo compila, analizza il documento e infine convalida, e ogni passaggio fallisce in modo diverso. Un documento mal formato non viene mai messo a confronto con uno schema, perché non c’è nulla di coerente da controllare. Ogni diagnostica è etichettata come Problema nello schema o Errore di validità, e una posizione interna allo schema non è cliccabile di proposito, dato che quel numero di riga appartiene all’altro riquadro.

Solo XSD 1.0, e che cosa resta fuori

libxml2 implementa XSD 1.0. XSD 1.1 è diventato Raccomandazione W3C il 5 aprile 2012 ed è implementato da Apache Xerces-J, da Saxon-EE e dal pacchetto Python xmlschema, ma non da libxml2, né dall’XmlSchemaSet di .NET, né da lxml, che avvolge libxml2. Uno schema che usa funzioni 1.1 qui non si compila, e il pannello indica 1.1 come causa probabile invece di restituire un oscuro errore di struttura.

  • xs:assert e la faccetta xs:assertion: i vincoli di co-occorrenza, dove la validità di un campo dipende da un altro. XSD 1.0 non sa esprimerli affatto.
  • xs:alternative, l’assegnazione condizionale di tipo, dove il tipo di un elemento è scelto da un test XPath sui suoi stessi attributi.
  • xs:openContent, xs:defaultOpenContent e xs:override, più i tipi 1.1 xs:dateTimeStamp, xs:dayTimeDuration e xs:yearMonthDuration. xs:precisionDecimal, che compare in moltissimi articoli, è stato tolto prima della Raccomandazione finale.
  • In più due limiti di libxml2: xs:redefine finisce da tempo in un percorso non implementato, e xs:import qui non può risolvere uno schemaLocation, perché in questa build nulla può leggere un file. Appiattisci prima un insieme di schemi distribuito su più documenti.

Gli errori che vedrai davvero

La maggior parte nasce dall’ordine. xs:sequence significa che i figli devono comparire nell’ordine dichiarato, e quell’ordine è parte del contratto e non una preferenza di formattazione: spostare l’elemento è quindi di solito la correzione. XSD 1.0 consente xs:all, l’alternativa senza ordine, solo in cima a un modello di contenuto e con ogni elemento al massimo una volta, ed è per questo che quasi tutti gli schemi reali usano una sequenza.

Il resto lo causano i namespace. Se lo schema dichiara targetNamespace="urn:example:orders", ogni elemento che governa deve stare in quel namespace; se non ne dichiara nessuno, devono stare senza namespace, e aggiungere un xmlns predefinito rompe tutto. La trappola vicina è elementFormDefault: se manca vale unqualified, quindi la radice è qualificata e i figli non devono esserlo.

  • cvc-complex-type.2.4.a: è comparso un elemento dove lo schema non lo permetteva. I nomi tra graffe sono quelli che sarebbero stati leciti, quindi "One of {qty} is expected" significa che toccava a qty.
  • cvc-complex-type.2.4.b: il modello di contenuto non è stato soddisfatto, di solito un figlio obbligatorio mancante alla fine.
  • cvc-datatype-valid.1.2.1: il testo non è lessicalmente valido per il suo tipo. Un elemento vuoto dichiarato xs:int, un dateTime senza secondi, un decimal con il separatore delle migliaia.
  • cvc-elt.1: nessuna dichiarazione trovata per l’elemento radice. Quasi sempre un targetNamespace che non combacia, non una dichiarazione mancante.
  • cvc-complex-type.3.2.2: un attributo non è ammesso, spesso xsi:type o xsi:nil senza il namespace XMLSchema-instance dichiarato.

Che cosa non fa mai, e quanto costa

Non scarica mai xsi:schemaLocation. La parte 1 di XSD chiama quell’attributo un suggerimento "sulla posizione fisica dei documenti di schema" e dice che un processore "dovrebbe tentare di dereferenziarlo" "salvo diversa indicazione, per esempio dell’applicazione chiamante". Rifiutare è conforme, ed è comune: SQL Server lo ignora sulle colonne di tipo xml.

Non potrebbe scaricare nulla nemmeno volendo. Ogni analisi usa XML_PARSE_NO_XXE, NONET e NO_SYS_CATALOG, e questa build non registra alcun resource loader: entità esterne, sottoinsieme DTD esterno e URL di schemaLocation si risolvono tutti nel nulla. XML_PARSE_HUGE non viene mai impostato, il che tiene attivi i tetti di libxml2: profondità degli elementi 256, annidamento delle entità 20 e un limite di amplificazione.

Una debolezza da dire onestamente: la documentazione di libxml2 definisce il suo modulo schemi un’implementazione parziale di XML Schema Parte 1, e spesso segnala meno diagnostiche di Xerces sugli stessi input. Leggi l’elenco come "almeno questi" e rilancia dopo ogni correzione.

Convalidare con un XSD da codice

Lo stesso controllo nei linguaggi che consumano XML. Ogni esempio disattiva l’accesso esterno, perché un riferimento a uno schema è un’istruzione di download e la maggior parte di queste librerie la segue per impostazione predefinita.

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

Nota il pattern dell’handler negli esempi Java e C#: entrambe le API sollevano un’eccezione al primo errore finché non ne installi uno, ed è per questo che tanto codice in produzione segnala un problema per volta.

Domande frequenti

Il mio XML e il mio XSD vengono caricati da qualche parte?

No. Entrambi i riquadri restano in questa scheda. libxml2 gira qui come WebAssembly in un Web Worker, non esiste un endpoint di upload, e la build non ha alcun percorso di rete proprio.

Qui conta più che altrove: uno schema nomina ogni campo, ogni lista di codici e ogni confine interno, ed è spesso coperto da un accordo di riservatezza mentre il documento è solo dato di prova. Apri il pannello di rete, premi Convalida e guarda il motore caricarsi una volta e poi più nulla. Entrambi i riquadri sono conservati nel localStorage così un ricaricamento non perde il lavoro, e niente oltre i 300 KB viene salvato.

Supporta XSD 1.1, o xs:assert?

No. libxml2 è XSD 1.0, quindi xs:assert, xs:alternative, xs:openContent e xs:override non si compilano. Invece di un oscuro "element assert is not expected here", il pannello dice che lo schema è XML ben formato ma non un XSD utilizzabile, e indica 1.1 come causa probabile.

Per 1.1 usa Xerces-J, che è gratuito e ti dà una SchemaFactory 1.1 cambiando una stringa di namespace, Saxon-EE, o il pacchetto Python xmlschema. Nulla di browser-based lo fa: sono tutte build di libxml2.

Il mio schema usa xs:import. Posso comunque convalidare qui?

Solo se lo appiattisci prima. Risolvere un xs:import vuol dire leggere un file o fare una richiesta, e questa build non registra alcun resource loader, la stessa decisione che le impedisce di scaricare entità esterne: lo schema quindi non si compilerà.

O incolli le definizioni di tipo importate nel documento con cui convalidi, o lanci xmllint in locale, dove i file .xsd vicini vengono letti dal disco. La maggior parte delle grandi famiglie di schemi pubblici, UBL e FpML comprese, richiede questo trattamento.

Che cosa significa cvc-complex-type.2.4.a?

È comparso un elemento dove lo schema non lo permetteva. Il messaggio finisce con i nomi che sarebbero stati leciti lì, tra graffe: "Invalid content was found starting with element 'item'. One of '{qty}' is expected." vuol dire che toccava a qty.

Tre cause coprono quasi tutto: i figli sono nell’ordine sbagliato per una xs:sequence, di gran lunga il caso più frequente; l’elemento non è dichiarato affatto, di solito un refuso o un campo aggiunto da un lato dell’integrazione; oppure il nome è giusto e il namespace è sbagliato, cosa che il messaggio non può mostrarti perché stampa il nome locale.

Scaricherà lo schema indicato in xsi:schemaLocation?

Mai. La specifica chiama quell’attributo un suggerimento e lascia che l’applicazione chiamante dica al processore di non dereferenziarlo: rifiutare è quindi conforme e non una scorciatoia.

Le ragioni vanno oltre la riservatezza: seguire una URL controllata da un attaccante presa da un documento non fidato è una primitiva di request forgery, e lega il tuo risultato a quello che un terzo sta servendo oggi.

Quanto può essere grande il documento da controllare?

Venti megabyte. Caricare un file più grande viene rifiutato, perché tutto è tenuto nella memoria di questa pagina e la convalida costruisce un albero completo più lo schema compilato.

Per documenti davvero grandi, lancia xmllint in locale: lo stesso codice libxml2, senza il tetto del browser.

Strumenti correlati

Approfondimenti

Errori che risolve