XSD-Validator
Gegen ein XSD prüfen. Beide Dateien bleiben im Browser.
Alles läuft in diesem Tab. Nichts, was Sie einfügen, wird hochgeladen, protokolliert oder irgendwohin gesendet. Öffnen Sie Ihr Netzwerkpanel und prüfen Sie es.
Fügen Sie das Dokument links ein, sein XSD rechts, und drücken Sie „Gegen Schema prüfen“. libxml2, kompiliert zu WebAssembly, führt die Prüfung in einem Web Worker in diesem Tab aus, und jeder Verstoß erscheint mit Zeile, Spalte und einer Marke, über die Sie hinspringen.
Man landet hier meist, nachdem etwas anderes das Dokument abgelehnt hat: Ein Partner antwortet mit cvc-complex-type.2.4.a, ein Build-Schritt scheitert an einer Konfigurationsdatei, ein Gateway meldet, die Nutzlast passe nicht zum Schema, und hört dort auf. Sie haben das XSD und wollen das fehlerhafte Element, keine Java-Toolchain.
Der Unterschied liegt darin, wo es läuft. Browser haben keinen eingebauten Schemavalidator, also schicken die kostenlosen Werkzeuge mit XSD-Prüfung beide Dateien an einen Server; das bekannteste lässt Sie zuerst das XML absenden und das Schema in einem zweiten Formular. Hier verlässt keine der beiden Dateien diesen Tab, und die Engine, rund 480 KB gzip-komprimiert, wird beim Klick geladen und nicht beim Seitenaufruf.
Erst wohlgeformt, dann gültig
Das sind zwei Prüfungen, nicht eine. Wohlgeformtheit ist Syntax: geschlossene Tags, korrekte Verschachtelung, eine Wurzel, maskierte Und-Zeichen. Sie braucht nur das Dokument und läuft deshalb während der Eingabe. Gültigkeit ist Wohlgeformtheit plus Übereinstimmung mit einem Schema, wofür eine zweite Datei nötig ist – also eine bewusst ausgelöste Aktion.
Die Engine parst das XSD, kompiliert es, parst das Dokument und validiert dann; jeder Schritt scheitert anders. Ein nicht wohlgeformtes Dokument wird nie gegen ein Schema geprüft, weil es nichts Zusammenhängendes zu prüfen gäbe. Jede Diagnose ist als Schema-Problem oder Gültigkeitsfehler ausgewiesen, und eine Position im Schema ist absichtlich nicht anklickbar, denn diese Zeilennummer gehört zum anderen Bereich.
Nur XSD 1.0 – und was damit ausfällt
libxml2 implementiert XSD 1.0. XSD 1.1 wurde am 5. April 2012 W3C-Empfehlung und ist in Apache Xerces-J, in Saxon-EE und im Python-Paket xmlschema implementiert, aber nicht in libxml2, nicht im XmlSchemaSet von .NET und nicht in lxml, das libxml2 umhüllt. Ein Schema mit 1.1-Funktionen lässt sich hier nicht kompilieren, und das Panel nennt 1.1 als wahrscheinliche Ursache, statt einen rätselhaften Strukturfehler zu melden.
- xs:assert und die Facette xs:assertion: Ko-Okkurrenz-Bedingungen, bei denen die Gültigkeit eines Feldes von einem anderen abhängt. XSD 1.0 kann sie überhaupt nicht ausdrücken.
- xs:alternative, die bedingte Typzuweisung, bei der der Typ eines Elements über einen XPath-Test auf seine eigenen Attribute gewählt wird.
- xs:openContent, xs:defaultOpenContent und xs:override sowie die 1.1-Datentypen xs:dateTimeStamp, xs:dayTimeDuration und xs:yearMonthDuration. xs:precisionDecimal, das in vielen Blogbeiträgen auftaucht, wurde vor der finalen Empfehlung gestrichen.
- Dazu zwei libxml2-eigene Grenzen: xs:redefine läuft seit Langem in einen nicht implementierten Pfad, und xs:import kann hier kein schemaLocation auflösen, weil in diesem Build nichts eine Datei lesen darf. Fassen Sie einen über mehrere Dokumente verteilten Schemasatz vorher zusammen.
Die Fehler, die Sie wirklich sehen werden
Die meisten kommen von der Reihenfolge. xs:sequence bedeutet, dass die Kinder in der deklarierten Reihenfolge erscheinen müssen, und diese Reihenfolge ist Teil des Vertrags und keine Formatierungsvorliebe – das Element zu verschieben ist deshalb meist die Lösung. XSD 1.0 erlaubt xs:all, die reihenfolgefreie Alternative, nur an der Spitze eines Inhaltsmodells und mit jedem Element höchstens einmal, weshalb die meisten realen Schemata eine Sequenz verwenden.
Den Rest verursachen Namensräume. Deklariert das Schema targetNamespace="urn:example:orders", muss jedes von ihm regierte Element in diesem Namensraum liegen; deklariert es keinen, müssen sie in keinem liegen, und ein hinzugefügtes Standard-xmlns bricht das. Die verwandte Falle ist elementFormDefault: fehlt es, gilt unqualified, also ist die Wurzel qualifiziert und die Kinder dürfen es nicht sein.
- cvc-complex-type.2.4.a: Ein Element tauchte auf, wo das Schema es nicht erlaubte. Die Namen in geschweiften Klammern wären dort zulässig gewesen, „One of {qty} is expected“ heißt also, dass qty an der Reihe war.
- cvc-complex-type.2.4.b: Das Inhaltsmodell wurde nicht erfüllt, meist ein fehlendes Pflichtkind am Ende.
- cvc-datatype-valid.1.2.1: Der Text ist für seinen Typ lexikalisch ungültig. Ein leeres, als xs:int deklariertes Element, ein dateTime ohne Sekunden, ein decimal mit Tausendertrennzeichen.
- cvc-elt.1: Keine Deklaration für das Wurzelelement gefunden. Fast immer eine targetNamespace-Abweichung und keine fehlende Deklaration.
- cvc-complex-type.3.2.2: Ein Attribut ist nicht erlaubt, oft xsi:type oder xsi:nil ohne deklarierten XMLSchema-instance-Namensraum.
Was es nie tut, und was das kostet
Es lädt niemals xsi:schemaLocation. XSD Teil 1 nennt dieses Attribut einen Hinweis „auf den physischen Ort von Schemadokumenten“ und sagt, ein Prozessor „sollte versuchen, es zu dereferenzieren“, „sofern nicht anders angewiesen, etwa durch die aufrufende Anwendung“. Es zu unterlassen ist konform und verbreitet: SQL Server ignoriert es bei Spalten vom Typ xml.
Es könnte gar nichts laden, selbst wenn es wollte. Jeder Parse-Vorgang nutzt XML_PARSE_NO_XXE, NONET und NO_SYS_CATALOG, und dieser Build registriert keinen Resource Loader, sodass externe Entities, ein externes DTD-Subset und eine schemaLocation-URL allesamt ins Leere laufen. XML_PARSE_HUGE wird nie gesetzt, wodurch die Obergrenzen von libxml2 aktiv bleiben: Elementtiefe 256, Entity-Verschachtelung 20 und eine Amplifikationsgrenze.
Eine ehrliche Schwäche: Die Dokumentation von libxml2 bezeichnet ihr Schemamodul als teilweise Implementierung von XML Schema Teil 1, und es meldet bei gleichen Eingaben oft weniger Diagnosen als Xerces. Lesen Sie die Liste als „mindestens diese“ und prüfen Sie nach jeder Korrektur erneut.
Gegen ein XSD prüfen, in Code
Dieselbe Prüfung in den Sprachen, die XML verarbeiten. Jedes Beispiel schaltet externen Zugriff ab, denn eine Schemareferenz ist eine Ladeanweisung, und die meisten dieser Bibliotheken folgen ihr standardmäßig.
// 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.Achten Sie auf das Handler-Muster in den Java- und C#-Beispielen: Beide APIs werfen beim ersten Fehler eine Ausnahme, solange Sie keinen installieren – deshalb meldet so viel Produktionscode immer nur ein Problem auf einmal.
Häufige Fragen
Werden mein XML und mein XSD irgendwohin hochgeladen?
Nein. Beide Bereiche bleiben in diesem Tab. libxml2 läuft hier als WebAssembly in einem Web Worker, es gibt keinen Upload-Endpunkt, und der Build hat keinen eigenen Netzwerkweg.
Das wiegt hier schwerer als auf den meisten Seiten: Ein Schema benennt jedes Feld, jede Codeliste und jede interne Grenze und fällt oft unter eine Geheimhaltungsvereinbarung, während das Dokument nur Testdaten sind. Öffnen Sie Ihr Netzwerkpanel, drücken Sie Prüfen, und sehen Sie, wie die Engine einmal lädt und danach nichts mehr passiert. Beide Bereiche liegen im localStorage, damit ein Neuladen die Arbeit nicht vernichtet, und alles über 300 KB wird gar nicht gespeichert.
Unterstützt es XSD 1.1 oder xs:assert?
Nein. libxml2 ist XSD 1.0, also lassen sich xs:assert, xs:alternative, xs:openContent und xs:override nicht kompilieren. Statt eines rätselhaften „element assert is not expected here“ sagt das Panel, das Schema sei wohlgeformtes XML, aber kein brauchbares XSD, und nennt 1.1 als wahrscheinliche Ursache.
Für 1.1 nehmen Sie Xerces-J, das kostenlos ist und Ihnen mit dem Wechsel einer Namensraum-Zeichenkette eine 1.1-SchemaFactory gibt, Saxon-EE oder das Python-Paket xmlschema. Nichts Browserbasiertes kann es: Es sind alles libxml2-Builds.
Mein Schema nutzt xs:import. Kann ich hier trotzdem prüfen?
Nur wenn Sie es vorher zusammenführen. Ein xs:import aufzulösen heißt, eine Datei zu lesen oder eine Anfrage zu stellen, und dieser Build registriert keinen Resource Loader – dieselbe Entscheidung, die auch externe Entities blockiert. Das Schema wird sich also nicht kompilieren lassen.
Fügen Sie entweder die importierten Typdefinitionen in das Dokument ein, gegen das Sie prüfen, oder lassen Sie xmllint lokal laufen, wo die benachbarten .xsd-Dateien von der Festplatte gelesen werden. Die meisten großen öffentlichen Schemafamilien, darunter UBL und FpML, brauchen diese Behandlung.
Was bedeutet cvc-complex-type.2.4.a?
Ein Element tauchte auf, wo das Schema es nicht erlaubte. Die Meldung endet mit den Namen, die dort zulässig gewesen wären, in geschweiften Klammern: „Invalid content was found starting with element 'item'. One of '{qty}' is expected.“ heißt, dass qty an der Reihe war.
Drei Ursachen decken fast alles ab: Die Kinder stehen in der falschen Reihenfolge für eine xs:sequence, mit Abstand am häufigsten; das Element ist überhaupt nicht deklariert, meist ein Tippfehler oder ein Feld, das eine Seite der Integration hinzugefügt hat; oder der Name stimmt und der Namensraum nicht, was die Meldung nicht zeigen kann, weil sie den lokalen Namen ausgibt.
Lädt es das in xsi:schemaLocation genannte Schema?
Nie. Die Spezifikation nennt dieses Attribut einen Hinweis und erlaubt der aufrufenden Anwendung, dem Prozessor das Dereferenzieren zu untersagen – es zu unterlassen ist also konform und keine Abkürzung.
Die Gründe reichen über Datenschutz hinaus: Einer vom Angreifer kontrollierten URL aus einem nicht vertrauenswürdigen Dokument zu folgen, ist ein Baustein für Request Forgery, und es bindet Ihr Ergebnis an das, was ein Dritter heute gerade ausliefert.
Wie große Dokumente kann es prüfen?
Zwanzig Megabyte. Eine größere Datei zu laden wird abgelehnt, weil alles im Speicher dieser Seite liegt und die Validierung einen vollständigen Baum plus das kompilierte Schema aufbaut.
Für wirklich große Dokumente lassen Sie xmllint lokal laufen: derselbe libxml2-Code, ohne die Grenze des Browsers.