XML-Syntaxprüfung

Alle Syntaxfehler auf einmal, mit Zeile, Spalte und Lösung.

Eingabe
WartetFügen Sie ein Dokument ein. Die Prüfung läuft während der Eingabe.

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 oben ein Dokument ein oder ziehen Sie eine Datei auf den Editor, und es wird während der Eingabe gegen die Wohlgeformtheitsregeln von XML 1.0 geprüft. Jeder Verstoß erscheint mit Zeile, Spalte und dem, was stattdessen dort stehen sollte, und ein Klick auf ein Ergebnis setzt den Cursor auf das betreffende Zeichen.

Das ist die Prüfung, die bestehen muss, bevor irgendeine andere laufen kann. Ein Schemavalidator, ein XSLT-Prozessor, ein SOAP-Stack und ein Browser weigern sich alle, ein Dokument mit kaputter Syntax überhaupt anzusehen. Ein „premature end of data in tag“ aus Ihrem Build ist also ein Syntaxproblem, und noch so gründliches Lesen des XSD wird es nicht erklären.

Anders ist hier, dass der Scanner beim ersten Fehler nicht aufhört. Er setzt wieder auf und macht weiter, sodass ein Durchlauf das Attribut ohne Anführungszeichen in Zeile 4, das rohe Und-Zeichen in Zeile 12 und das am Ende offen gebliebene Tag meldet. Jede seiner 37 Fehlerklassen hat eine eigene Formulierung und eine eigene Lösung, statt eines einzigen „Syntax Error“ für alles. Nichts wird hochgeladen: Öffnen Sie Ihr Netzwerkpanel und sehen Sie zu, wie es leer bleibt.

Was „wohlgeformt“ tatsächlich bedeutet

XML 1.0 benennt zwölf Wohlgeformtheitsbedingungen und überlässt den Rest der Grammatik. Auf Prüfungen heruntergebrochen, die ein Dokument besteht oder nicht besteht, ergibt sich diese Liste, und jede ihrer Zeilen wird hier durchgesetzt.

  • Genau ein Wurzelelement, außerhalb davon nichts außer Kommentaren, Verarbeitungsanweisungen und Leerraum.
  • Jedes Start-Tag geschlossen von einem End-Tag, dessen Name exakt übereinstimmt. Namen unterscheiden Groß- und Kleinschreibung, </Note> schließt also kein <note>.
  • Elemente verschachtelt, nicht überlappend: <b><i>x</b></i> ist kein XML, so nachsichtig Ihr HTML-Parser auch war.
  • Attributwerte in Anführungszeichen, und kein Attribut zweimal an einem Element.
  • Kein wörtliches < im Text oder in einem Attributwert, und kein nacktes & außerhalb einer Referenz. Ein > ist im Text erlaubt; die Folge ]]> nicht.
  • Nur die fünf vordefinierten Entities amp, lt, gt, quot und apos, sofern nicht eine DTD weitere deklariert. &nbsp; gehört zu HTML und ist keine davon.
  • Kein Zeichen außerhalb der von XML erlaubten Menge, kein Kommentar, der -- enthält, und eine Deklaration – falls vorhanden – zuerst in der Datei und geschrieben als version, dann encoding, dann standalone.
  • Jedes Namensraum-Präfix durch eine im Gültigkeitsbereich sichtbare xmlns-Deklaration gebunden. Diese Regel stammt aus Namespaces in XML und nicht aus XML 1.0, und sie ist die, die die meisten kostenlosen Validatoren überspringen: Ein führender Wettbewerber erklärt <ns:root> für gültig, obwohl nirgends in der Datei eine Bindung steht.

Alle Fehler auf einmal, und warum andere Werkzeuge einen liefern

Die Spezifikation weist einen Prozessor an, nach einem fatalen Fehler die normale Verarbeitung nicht fortzusetzen – deshalb melden DOMParser, Expat und .NET das erste Problem und halten an. Das ist korrektes Verhalten und keine Bequemlichkeit, und zugleich der Grund, warum das Korrigieren eines großen Dokuments einen Durchlauf pro Fehler kostet.

Die Wiederaufnahme hier ist Absicht. Passt ein End-Tag nicht, sucht der Scanner diesen Namen weiter oben im Stapel der offenen Elemente: Findet er ihn, liegt der eigentliche Fehler bei den dazwischen offen gebliebenen Elementen, und jedes wird an seiner eigenen Startzeile gemeldet, statt dem End-Tag die Schuld zu geben. Ein Fremdzeichen mitten in einem Namen verschluckt das ganze Token, also erzeugt amount$="10" eine Meldung über das Dollarzeichen statt fünf über das Gleichheitszeichen und das Anführungszeichen. Ein Vorbehalt: Ein früher struktureller Fehler lässt alles danach falsch aussehen – beheben Sie die ersten zwei, drei und prüfen Sie neu. Die Meldung endet bei 200 Problemen, mit einem entsprechenden Hinweis.

Die Fehler, die tatsächlich auftauchen

Echte Syntaxfehler sind selten exotisch. Sie kosten Zeit, weil der Parser ein Symptom benennt, während die Ursache woanders sitzt oder unsichtbar ist.

  • Eine UTF-8-Byte-Order-Mark vor der Deklaration. Erlaubt, unsichtbar und die übliche Ursache von „Content is not allowed in prolog“. Sie wird als sie selbst gemeldet und nicht als rätselhafter Inhalt.
  • Eine Deklaration als <?xml encoding="UTF-8" version="1.0"?>, oder eine, die windows-1252 behauptet, während die Bytes UTF-8 sind. Letzteres kommt als Warnung darüber, was ein Parser auf Byte-Ebene lesen wird, denn die Datei war bereits dekodiert, als diese Seite sie erhielt.
  • &nbsp;, &mdash; und rund fünfzig weitere HTML-Entities, benannt als HTML statt als generische undefinierte Entity, samt der numerischen Referenz, die stattdessen gehört.
  • Ein PHP-Hinweis oder ein Stacktrace hinter dem schließenden Wurzel-Tag – deshalb verweist diese Meldung auf den rohen HTTP-Body.
  • Steuerzeichen aus einer Datenbankspalte, die XML 1.0 überall verbietet, auch in CDATA.
  • Ein ungebundenes soap:-, xsi:- oder xlink:-Präfix, nachdem ein Fragment aus einem größeren Dokument kopiert wurde und seine xmlns-Deklaration auf der ursprünglichen Wurzel zurückblieb.

Wo die Syntaxprüfung aufhört

Diese Seite beantwortet eine Frage: Ist die Syntax zulässig. Sie fragt nie, ob eine <invoice> eine <line> enthalten darf oder ob ein Pflichtelement fehlt. Dafür braucht es ein Schema, das gehört auf die XSD- und DTD-Validatoren. Hat ein System Ihr Dokument „ungültig“ genannt, hat es eine Schemaprüfung ausgeführt, und ein grünes Ergebnis hier erklärt das nicht.

Zwei Grenzen gehören gesagt. Das interne DTD-Subset wird nur wegen der Entity-Deklarationen gelesen; Element- und Attributlisten-Deklarationen darin werden überlesen statt durchgesetzt, und externe DTDs werden gemeldet, aber nie geladen. Die Größe ist auf 20 Millionen Zeichen gedeckelt, die Verschachtelung auf 512 Ebenen und die Entity-Expansion auf 10 Millionen, weil alles in diesem Tab läuft.

XML-Syntax in Code prüfen

Dieselbe Prüfung in den Sprachen, die am meisten XML verarbeiten, jeweils in der Form, die gegen externe Entities und Entity-Expansion sicher ist. Achten Sie darauf, wie wenige mehr als einen Fehler melden: Das ist die Spezifikation, nicht die Bibliothek.

// DOMParser never throws. Given broken input it returns a document whose
// root is <parsererror>, which is why so much code silently accepts XML
// that no other parser would take.
function checkXml(source) {
  const doc = new DOMParser().parseFromString(source, 'application/xml');
  const error = doc.querySelector('parsererror');
  if (!error) return { wellFormed: true, message: null };

  // The text is browser-specific. Firefox includes a line and column,
  // Chrome's wording differs, and neither exposes them as fields.
  return { wellFormed: false, message: error.textContent.trim() };
}

// Browsers do not resolve external entities, so XXE is not reachable here.
// Internal entity expansion is, so cap the input length before parsing:
//   if (source.length > 20_000_000) throw new Error('too large');
# lxml is the only common Python parser that keeps going after a syntax
# error and hands back the whole list rather than the first entry.
from lxml import etree

parser = etree.XMLParser(
    recover=True,            # keep parsing so error_log fills up
    resolve_entities=False,  # do not expand entities
    no_network=True,         # never fetch an external DTD
    load_dtd=False,
    huge_tree=False,         # keep libxml2's depth and expansion limits
)
etree.fromstring(source.encode('utf-8'), parser)

for entry in parser.error_log:
    print(f"line {entry.line}, column {entry.column}: {entry.message}")
if not parser.error_log:
    print('well-formed')

# Without recover=True, fromstring raises etree.XMLSyntaxError and
# e.position gives a (line, column) tuple for the first failure only.
# For untrusted input with the standard library, use defusedxml.
import javax.xml.XMLConstants;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
// Drop the next line only if your documents legitimately carry a DOCTYPE.
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);

var reader = factory.newSAXParser().getXMLReader();
reader.setErrorHandler(new DefaultHandler() {
    @Override public void warning(SAXParseException e) { print("warning", e); }
    @Override public void error(SAXParseException e) { print("error", e); }
    @Override public void fatalError(SAXParseException e) throws SAXParseException {
        print("fatal", e);
        throw e;  // the specification requires processing to stop here
    }
    private void print(String kind, SAXParseException e) {
        System.err.printf("%s at line %d, column %d: %s%n",
            kind, e.getLineNumber(), e.getColumnNumber(), e.getMessage());
    }
});
reader.parse(new InputSource(new java.io.StringReader(source)));
using System.Xml;

var settings = new XmlReaderSettings
{
    DtdProcessing = DtdProcessing.Prohibit,  // no DTD, so no entity expansion
    XmlResolver = null,                      // never fetch anything
    MaxCharactersFromEntities = 1024 * 1024,
    MaxCharactersInDocument = 20L * 1024 * 1024,
    ConformanceLevel = ConformanceLevel.Document,
};

try
{
    using var reader = XmlReader.Create(new StringReader(source), settings);
    while (reader.Read()) { }   // pull every node; syntax errors surface here
    Console.WriteLine("well-formed");
}
catch (XmlException e)
{
    // LinePosition is the column, 1-based. Reading stops at this point.
    Console.Error.WriteLine(
        $"line {e.LineNumber}, column {e.LinePosition}: {e.Message}");
}
<?php
// libxml recovers internally, so libxml_get_errors() is the closest a PHP
// script gets to a full list rather than a first failure.
libxml_use_internal_errors(true);

$doc = new DOMDocument();
$doc->loadXML($source, LIBXML_NONET);   // NONET blocks external fetches

$errors = libxml_get_errors();
libxml_clear_errors();

foreach ($errors as $e) {
    $kind = $e->level === LIBXML_ERR_FATAL ? 'fatal' : 'error';
    fprintf(
        STDERR,
        "%s at line %d, column %d: %s\n",
        $kind,
        $e->line,
        $e->column,
        trim($e->message)
    );
}
exit($errors ? 1 : 0);
# xmllint ships with libxml2 and is already installed on most machines.
# --nonet stops it fetching a DTD the document points at.
xmllint --noout --nonet document.xml

# --recover keeps parsing after a failure, so you see more than the first.
xmllint --noout --nonet --recover document.xml

# Check a whole tree, one file at a time:
find . -name '*.xml' -print0 | xargs -0 -n1 xmllint --noout --nonet

# Exit status: 0 well-formed, 1 unclassified, 3 well-formedness error,
# 4 validation error against a DTD or schema.

Jedes Beispiel schaltet etwas ab, und das ist der Kern und keine Formalität. Die Auflösung externer Entities ist in Java standardmäßig an, und Pythons Standardbibliothek setzt überhaupt keine Expansionsgrenze – diese Flags trennen also eine Syntaxprüfung von einem Dateilese-Primitiv, das auf Ihren eigenen Server zielt.

Häufige Fragen

Wie prüfe ich XML-Syntax, ohne etwas zu installieren?

Fügen Sie das Dokument in den Editor oben auf dieser Seite ein. Die Prüfung läuft während der Eingabe, ohne Schaltfläche und ohne Konto. Sie können auch eine .xml-Datei darauf ziehen: Sie wird über die File-API des Browsers gelesen und nie übertragen.

Im Terminal ist xmllint Teil von libxml2 und auf macOS und den meisten Linux-Distributionen bereits vorhanden: xmllint --noout --nonet ihredatei.xml gibt nichts aus, wenn die Syntax stimmt. Es hält beim ersten Fehler an – das ist der praktische Unterschied.

Wird mein XML hochgeladen, wenn ich es hier prüfe?

Nein. Der Scanner ist JavaScript, das in diesem Tab in einem Web Worker läuft. Es gibt keinen Endpunkt, an den sich ein Dokument senden ließe, und kein Analytics- oder Fehlerberichts-Skript mit Zugriff auf den Editor.

Das ist überprüfbar und kein Versprechen. Öffnen Sie die Entwicklerwerkzeuge, wechseln Sie auf den Netzwerk-Tab, fügen Sie ein Dokument ein und schauen Sie zu: Die Assets dieser Seite laden einmal, danach folgt nichts. Es zählt, weil die Dokumente, die Leute in Syntaxprüfer einfügen, SOAP-Envelopes mit Bearer-Token, SAML-Assertions und Konfigurationsdateien mit Connection Strings sind.

Was ist der Unterschied zwischen Syntaxprüfer und XML-Validator?

Beide Begriffe werden austauschbar benutzt, und dort beginnt die Verwirrung. Eine Syntaxprüfung fragt, ob das Dokument die Regeln von XML selbst befolgt: geschlossene Tags, korrekte Verschachtelung, eine Wurzel, Attributwerte in Anführungszeichen, maskierte Und-Zeichen. Sie braucht nichts außer dem Dokument.

Validierung im Sinne der Spezifikation fragt, ob das Dokument zu einem Schema passt, einem XSD oder einer DTD: einer zweiten Datei, die beschreibt, welche Elemente wo erscheinen dürfen. Sie kann erst laufen, wenn die Syntax sauber ist – deshalb meint ein „ungültig“ aus einem Middleware-Stack mal einen Syntaxfehler und mal eine Schemaverletzung. Diese Seite führt die erste Prüfung aus und sagt das; die XSD- und DTD-Validatoren hier führen die zweite aus, ebenfalls ohne etwas hochzuladen.

Warum meldet das hier fünf Fehler, wo mein Editor einen meldet?

Weil die XML-Spezifikation einem konformen Prozessor vorschreibt, nach einem fatalen Fehler anzuhalten, und der Parser hinter Ihrem Editor tut genau das. Die Regel ist vertretbar: Sobald ein Tag nicht geschlossen ist, weiß der Parser tatsächlich nicht, was der Rest des Dokuments sagen sollte.

Dieser Scanner setzt stattdessen wieder auf: Er synchronisiert sich an Tag-Grenzen, verschluckt ein fehlerhaftes Namens-Token als Ganzes und beschuldigt lieber ein nicht geschlossenes Start-Tag als das End-Tag, das es aufgedeckt hat. Behandeln Sie die frühesten Fehler als die verlässlichen und prüfen Sie erneut – eine lange Liste hat meist einen strukturellen Fehler nahe dem Anfang, der den Rest erzeugt.

Wird bei XML-Tag-Namen Groß- und Kleinschreibung unterschieden?

Vollständig. <Note> und <note> sind verschiedene Elemente, und </Note> schließt kein <note>. Das erwischt alle, die von HTML kommen, wo der Parser Tag-Namen in Kleinbuchstaben überführt, und es ist die häufigste Ursache für einen Mismatch-Fehler in handgeschriebenem XML.

Es gilt auch für Attributnamen und Namensraum-Präfixe: xmlns:Soap und xmlns:soap sind verschiedene Präfixe. Unterscheidet sich ein Mismatch nur in der Schreibweise, sagt die Meldung das, statt einen allgemeinen Mismatch zu melden. Mehrere viel kopierte Zusammenfassungen der „XML-Syntaxregeln“ behaupten, XML-Tags seien nicht case-sensitiv; sie irren, und wer ihnen folgt, erzeugt Dokumente, die jeder echte Parser ablehnt.

Wie groß darf die Datei höchstens sein?

20 Millionen Zeichen, grob 20 MB ASCII. Ein 10-MB-Dokument wird in etwas über einer Sekunde gescannt, 1 MB in etwa 110 Millisekunden, und zwar in einem Web Worker, sodass der Editor bedienbar bleibt.

Für alles Größere nehmen Sie lokal einen Streaming-Parser: xmllint, Pythons iterparse oder irgendein SAX-Parser prüfen die Wohlgeformtheit, ohne den ganzen Baum aufzubauen.

Verwandte Werkzeuge

Zum Weiterlesen

Fehler, die das behebt