Controllo della sintassi XML
Tutti gli errori di sintassi in una volta, con riga e rimedio.
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 un documento qui sopra, oppure trascina un file sull’editor, e viene controllato secondo le regole di buona formazione di XML 1.0 mentre scrivi. Ogni violazione è elencata con riga, colonna e che cosa scrivere al suo posto, e cliccando un risultato il cursore si sposta sul carattere incriminato.
È il controllo che deve passare prima che qualunque altro possa partire. Un validatore di schemi, un processore XSLT, uno stack SOAP e un browser si rifiutano tutti di guardare un documento con la sintassi rotta: un "premature end of data in tag" uscito dalla tua build è quindi un problema di sintassi, e rileggere l’XSD non lo spiegherà mai.
Qui la differenza è che lo scanner non si ferma al primo fallimento. Si riprende e prosegue, così un solo passaggio segnala l’attributo senza virgolette alla riga 4, la e commerciale nuda alla riga 12 e il tag lasciato aperto in fondo. Ognuna delle sue 37 classi di errore ha una formulazione propria e un rimedio proprio, invece di un unico "Syntax Error" per tutto. Nulla viene caricato: apri il pannello di rete e guardalo restare vuoto.
Che cosa significa davvero "ben formato"
XML 1.0 elenca dodici vincoli di buona formazione e lascia il resto alla grammatica. Ridotto a controlli che un documento supera o non supera, viene fuori questo elenco, e qui ogni sua riga è applicata.
- Esattamente un elemento radice, con nulla al di fuori tranne commenti, istruzioni di elaborazione e spazi.
- Ogni tag di apertura chiuso da un tag di chiusura con nome identico. I nomi distinguono maiuscole e minuscole, quindi </Note> non chiude <note>.
- Elementi annidati e non sovrapposti: <b><i>x</b></i> non è XML, per quanto indulgente sia stato il tuo parser HTML.
- Valori degli attributi tra virgolette, e nessun attributo ripetuto sullo stesso elemento.
- Nessun < letterale nel testo o in un valore di attributo, e nessuna & nuda fuori da un riferimento. Un > nel testo è lecito; la sequenza ]]> no.
- Solo le cinque entità predefinite — amp, lt, gt, quot e apos — salvo che una DTD ne dichiari altre. appartiene a HTML e non è fra queste.
- Nessun carattere fuori dall’insieme ammesso da XML, nessun commento che contenga --, e una dichiarazione, se c’è, all’inizio del file e scritta come version, poi encoding, poi standalone.
- Ogni prefisso di namespace associato da una dichiarazione xmlns nello scope. Questa regola viene da Namespaces in XML e non da XML 1.0, ed è quella che più validatori gratuiti saltano: un concorrente di primo piano dichiara valido <ns:root> senza alcuna associazione nel file.
Tutti gli errori in una volta, e perché gli altri strumenti ne danno uno
La specifica dice al processore di non proseguire l’elaborazione normale dopo un errore fatale: per questo DOMParser, Expat e .NET segnalano il primo problema e si fermano. È comportamento corretto e non pigrizia, ed è anche il motivo per cui correggere un documento grande costa un viaggio per ogni errore.
Il recupero qui è voluto. Quando un tag di chiusura non corrisponde, lo scanner cerca quel nome più in alto nello stack degli elementi aperti: trovarlo significa che l’errore vero sono gli elementi rimasti aperti nel mezzo, e ciascuno viene segnalato alla propria riga di apertura invece di dare la colpa al tag di chiusura. Un carattere estraneo dentro un nome assorbe l’intero token, quindi amount$="10" produce un messaggio sul simbolo del dollaro invece di cinque sull’uguale e sulle virgolette. Un avvertimento: un errore strutturale iniziale fa sembrare sbagliato tutto ciò che segue, quindi correggi i primi due o tre e ricontrolla. La segnalazione si ferma a 200 problemi, con una nota che lo dice.
Gli errori che compaiono davvero
I veri errori di sintassi sono raramente esotici. Costano tempo perché il parser nomina un sintomo mentre la causa sta altrove, o è invisibile.
- Un byte order mark UTF-8 prima della dichiarazione. Lecito, invisibile e la causa tipica di "Content is not allowed in prolog". Qui è segnalato per quello che è, non come contenuto misterioso.
- Una dichiarazione scritta <?xml encoding="UTF-8" version="1.0"?>, oppure una che annuncia windows-1252 mentre i byte sono UTF-8. Il secondo caso esce come avviso su ciò che leggerà un parser a livello di byte, perché il file era già decodificato quando è arrivato a questa pagina.
- , — e una cinquantina di altre entità HTML, nominate come HTML invece che come generica entità non definita, con il riferimento numerico da usare al loro posto.
- Un notice di PHP o uno stack trace attaccati dopo il tag radice di chiusura: ecco perché quel messaggio ti rimanda al corpo HTTP grezzo.
- Caratteri di controllo usciti da una colonna di database, che XML 1.0 vieta ovunque, CDATA compreso.
- Un prefisso soap:, xsi: o xlink: non associato, dopo che un frammento è stato copiato da un documento più grande lasciando la dichiarazione xmlns sulla radice originale.
Dove si ferma il controllo di sintassi
Questa pagina risponde a una sola domanda: la sintassi è lecita. Non chiede mai se una <invoice> possa contenere una <line>, né se manchi un elemento obbligatorio. Per quello serve uno schema, quindi vive sui validatori XSD e DTD. Se un sistema ha definito "non valido" il tuo documento, ha eseguito un controllo di schema, e un esito verde qui non lo spiegherà.
Due limiti da dire chiaramente. Il sottoinsieme DTD interno viene letto solo per le dichiarazioni di entità, quindi le dichiarazioni di elemento e di lista di attributi al suo interno vengono attraversate senza essere applicate, e le DTD esterne sono segnalate ma mai scaricate. La dimensione ha un tetto di 20 milioni di caratteri, l’annidamento di 512 livelli e l’espansione delle entità di 10 milioni, perché tutto gira in questa scheda.
Controllare la sintassi XML da codice
Lo stesso controllo nei linguaggi che consumano più XML, ciascuno nella forma sicura rispetto alle entità esterne e all’espansione delle entità. Nota quanti pochi ne segnalano più di uno: è la specifica che parla, non la libreria.
// 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.Ogni esempio disattiva qualcosa, e quella è la sostanza, non il contorno. La risoluzione delle entità esterne è attiva per impostazione predefinita in Java, e la libreria standard di Python non applica alcun limite di espansione: quei flag separano quindi un controllo di sintassi da una primitiva di lettura file puntata sul tuo stesso server.
Domande frequenti
Come controllo la sintassi XML senza installare nulla?
Incolla il documento nell’editor in cima a questa pagina. Il controllo gira mentre scrivi, senza pulsanti da premere e senza account da creare. Puoi anche trascinarci sopra un file .xml: viene letto tramite la File API del browser e non è mai trasmesso.
Da terminale, xmllint fa parte di libxml2 ed è già presente su macOS e sulla maggior parte delle distribuzioni Linux: xmllint --noout --nonet iltuofile.xml non stampa nulla quando la sintassi è lecita. Si ferma al primo errore, ed è questa la differenza pratica.
Il mio XML viene caricato quando lo controllo qui?
No. Lo scanner è JavaScript che gira in questa scheda, in un Web Worker. Non esiste un endpoint a cui mandare un documento, né analytics o script di segnalazione errori con accesso all’editor.
È verificabile, non è una promessa. Apri gli strumenti per sviluppatori, passa alla scheda Rete, incolla un documento e guarda: le risorse di questa pagina si caricano una volta e poi non segue nulla. Conta perché i documenti che si incollano in un controllo di sintassi sono envelope SOAP con token al seguito, asserzioni SAML e file di configurazione con stringhe di connessione.
Qual è la differenza fra un controllo di sintassi e un validatore XML?
I due termini si usano in modo intercambiabile, ed è lì che nasce la confusione. Un controllo di sintassi chiede se il documento rispetta le regole di XML stesso: tag chiusi, annidamento corretto, una radice, attributi tra virgolette, e commerciali con escape. Non gli serve altro che il documento.
La convalida nel senso della specifica chiede se il documento corrisponde a uno schema, un XSD o una DTD: un secondo file che descrive quali elementi possono comparire e dove. Non può partire finché la sintassi non è pulita, ed è per questo che un "non valido" uscito da uno stack di middleware a volte indica un errore di sintassi e a volte una violazione di schema. Questa pagina esegue il primo controllo e lo dichiara; i validatori XSD e DTD di questo sito eseguono il secondo, anch’essi senza caricare nulla.
Perché qui vedo cinque errori mentre il mio editor ne segnala uno?
Perché la specifica XML impone a un processore conforme di fermarsi dopo un errore fatale, e il parser dietro il tuo editor lo fa. La regola è difendibile: una volta che un tag è rimasto aperto, il parser davvero non sa più che cosa volesse dire il resto del documento.
Questo scanner invece si riprende: si risincronizza sui confini dei tag, assorbe per intero un token di nome difettoso e preferisce incolpare un tag di apertura non chiuso piuttosto che il tag di chiusura che lo ha rivelato. Considera affidabili i primi errori e ricontrolla, perché un elenco lungo di solito ha un unico errore strutturale vicino all’inizio che genera quasi tutto il resto.
I nomi dei tag XML distinguono maiuscole e minuscole?
Completamente. <Note> e <note> sono elementi diversi, e </Note> non chiude <note>. Coglie di sorpresa chi arriva da HTML, dove il parser porta i nomi dei tag in minuscolo, ed è la causa più comune di un errore di tag non corrispondenti nell’XML scritto a mano.
Vale anche per i nomi degli attributi e per i prefissi di namespace: xmlns:Soap e xmlns:soap sono prefissi diversi. Quando una discordanza sta solo nelle maiuscole, il messaggio lo dice invece di segnalare una generica non corrispondenza. Diversi riassunti molto copiati delle "regole di sintassi XML" sostengono che i tag XML non distinguano maiuscole e minuscole; sbagliano, e seguirli produce documenti che ogni parser reale rifiuta.
Qual è il file più grande che può controllare?
20 milioni di caratteri, all’incirca 20 MB di ASCII. Un documento da 10 MB viene scansionato in poco più di un secondo e 1 MB in circa 110 millisecondi, in un Web Worker, così l’editor resta reattivo.
Per qualcosa di più grande, usa un parser in streaming in locale: xmllint, iterparse di Python o un qualsiasi parser SAX controllano la buona formazione senza costruire l’intero albero.