Vérificateur de syntaxe XML
Toutes les erreurs de syntaxe d’un coup, avec la correction.
Tout s'exécute dans cet onglet. Rien de ce que vous collez n'est envoyé, journalisé ni transmis où que ce soit. Ouvrez votre panneau réseau et vérifiez.
Collez un document ci-dessus, ou déposez un fichier sur l’éditeur, et il est contrôlé à la frappe au regard des règles de bonne formation de XML 1.0. Chaque violation est listée avec sa ligne, sa colonne et ce qu’il faut écrire à la place, et cliquer sur un résultat place le curseur sur le caractère fautif.
C’est le contrôle qui doit passer avant que tout autre puisse s’exécuter. Un validateur de schéma, un processeur XSLT, une pile SOAP et un navigateur refusent tous de regarder un document dont la syntaxe est cassée : un « premature end of data in tag » sorti de votre build est donc un problème de syntaxe, et relire le XSD ne l’expliquera jamais.
Ce qui change ici, c’est que l’analyseur ne s’arrête pas au premier échec. Il se reprend et continue, si bien qu’une seule passe signale l’attribut sans guillemets ligne 4, l’esperluette brute ligne 12 et la balise laissée ouverte à la fin. Chacune de ses 37 classes d’erreur a sa propre formulation et sa propre correction, plutôt qu’un « Syntax Error » unique pour tout. Rien n’est envoyé : ouvrez votre panneau réseau et regardez-le rester vide.
Ce que « bien formé » veut dire exactement
XML 1.0 énonce douze contraintes de bonne formation et laisse le reste dans la grammaire. Ramené à des contrôles qu’un document passe ou échoue, cela donne la liste suivante, et chacune de ses lignes est appliquée ici.
- Exactement un élément racine, avec rien en dehors sinon des commentaires, des instructions de traitement et des espaces.
- Chaque balise ouvrante fermée par une balise dont le nom correspond exactement. Les noms sont sensibles à la casse : </Note> ne ferme pas <note>.
- Des éléments imbriqués et non chevauchés : <b><i>x</b></i> n’est pas du XML, quelle qu’ait été la tolérance de votre analyseur HTML.
- Des valeurs d’attribut entre guillemets, et aucun attribut donné deux fois sur un même élément.
- Aucun < littéral dans le texte ni dans une valeur d’attribut, et aucun & nu hors d’une référence. Un > est légal dans le texte ; la séquence ]]> ne l’est pas.
- Uniquement les cinq entités prédéfinies — amp, lt, gt, quot et apos — sauf si une DTD en déclare d’autres. appartient à HTML et n’en fait pas partie.
- Aucun caractère hors du jeu autorisé par XML, aucun commentaire contenant --, et une déclaration, si elle existe, en tête de fichier et écrite version, puis encoding, puis standalone.
- Chaque préfixe d’espace de noms lié par une déclaration xmlns dans la portée. Cette règle vient de Namespaces in XML et non de XML 1.0, et c’est celle que le plus de validateurs gratuits sautent : un concurrent de premier plan déclare <ns:root> valide sans aucune liaison dans le fichier.
Toutes les erreurs d’un coup, et pourquoi les autres outils n’en donnent qu’une
La spécification indique au processeur qu’il ne doit pas poursuivre le traitement normal après une erreur fatale : voilà pourquoi DOMParser, Expat et .NET signalent le premier problème et s’arrêtent. C’est un comportement correct et non de la paresse, et c’est aussi pourquoi corriger un gros document coûte un aller-retour par faute.
La reprise est ici délibérée. Quand une balise fermante ne correspond pas, l’analyseur cherche ce nom plus haut dans la pile des éléments ouverts : le trouver signifie que la vraie faute, ce sont les éléments restés ouverts entre-temps, chacun est donc signalé à sa propre ligne d’ouverture plutôt que d’accabler la balise fermante. Un caractère parasite dans un nom absorbe tout le jeton, donc amount$="10" produit un message sur le symbole dollar au lieu de cinq sur le signe égal et le guillemet. Une réserve : une faute structurelle précoce fait paraître fautif tout ce qui suit, corrigez donc les deux ou trois premières et relancez. Le signalement s’arrête à 200 problèmes, avec une note le précisant.
Les échecs qui arrivent vraiment
Les vraies erreurs de syntaxe sont rarement exotiques. Elles coûtent du temps parce que l’analyseur nomme un symptôme alors que la cause est ailleurs, ou invisible.
- Une marque d’ordre d’octets UTF-8 avant la déclaration. Légale, invisible, et la cause habituelle de « Content is not allowed in prolog ». Elle est signalée pour ce qu’elle est plutôt que comme un contenu mystérieux.
- Une déclaration écrite <?xml encoding="UTF-8" version="1.0"?>, ou une qui annonce windows-1252 alors que les octets sont en UTF-8. Le second cas est levé en avertissement sur ce qu’un analyseur travaillant sur les octets lira, car le fichier était déjà décodé quand cette page l’a reçu.
- , — et une cinquantaine d’autres entités HTML, nommées comme telles plutôt que comme une entité indéfinie générique, avec la référence numérique à employer à la place.
- Un avertissement PHP ou une trace de pile ajoutés après la balise racine fermante, ce qui explique que ce message vous renvoie au corps HTTP brut.
- Des caractères de contrôle issus d’une colonne de base de données, que XML 1.0 interdit partout, y compris dans du CDATA.
- Un préfixe soap:, xsi: ou xlink: non lié, après qu’un fragment a été copié d’un document plus grand en laissant sa déclaration xmlns sur la racine d’origine.
Où s’arrête le contrôle de syntaxe
Cette page répond à une seule question : la syntaxe est-elle légale. Elle ne demande jamais si une <invoice> a le droit de contenir une <line>, ni s’il manque un élément obligatoire. Cela demande un schéma, et vit donc sur les validateurs XSD et DTD. Si un système a qualifié votre document d’« invalide », il a lancé un contrôle de schéma, et un résultat vert ici ne l’expliquera pas.
Deux limites méritent d’être dites. Le sous-ensemble DTD interne n’est lu que pour les déclarations d’entités : les déclarations d’éléments et de listes d’attributs qui s’y trouvent sont traversées sans être appliquées, et les DTD externes sont signalées mais jamais récupérées. La taille est plafonnée à 20 millions de caractères, l’imbrication à 512 niveaux et l’expansion d’entités à 10 millions, parce que tout tourne dans cet onglet.
Contrôler la syntaxe XML en code
Le même contrôle dans les langages qui consomment le plus de XML, chacun dans la forme sûre face aux entités externes et à l’expansion d’entités. Notez combien peu en signalent plus d’une : c’est la spécification qui parle, pas la bibliothèque.
// 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.Chaque exemple désactive quelque chose, et c’est la substance plutôt que de la formalité. La résolution des entités externes est active par défaut en Java, et la bibliothèque standard de Python n’applique aucune limite d’expansion : ces drapeaux séparent donc un contrôle de syntaxe d’une primitive de lecture de fichiers dirigée vers votre propre serveur.
Questions fréquentes
Comment vérifier la syntaxe XML sans rien installer ?
Collez le document dans l’éditeur en haut de cette page. Le contrôle tourne à la frappe, sans bouton à presser ni compte à créer. Vous pouvez aussi déposer un fichier .xml dessus : il est lu par l’API File du navigateur et n’est jamais transmis.
Depuis un terminal, xmllint fait partie de libxml2 et est déjà présent sur macOS et sur la plupart des distributions Linux : xmllint --noout --nonet votrefichier.xml n’affiche rien quand la syntaxe est légale. Il s’arrête à la première erreur, c’est la différence pratique.
Mon XML est-il envoyé quand je le vérifie ici ?
Non. L’analyseur est du JavaScript qui tourne dans cet onglet, dans un Web Worker. Il n’y a pas de point d’envoi, ni d’analytics ou de service de remontée d’erreurs ayant accès à l’éditeur.
Cela se vérifie, ce n’est pas une promesse. Ouvrez les outils de développement, passez sur l’onglet Réseau, collez un document et regardez : les ressources de la page se chargent une fois et rien ne suit. C’est important parce que les documents collés dans un vérificateur de syntaxe sont des enveloppes SOAP portant des jetons, des assertions SAML et des fichiers de configuration avec des chaînes de connexion.
Quelle différence entre un vérificateur de syntaxe et un validateur XML ?
Les deux s’emploient l’un pour l’autre, et c’est de là que vient la confusion. Un contrôle de syntaxe demande si le document obéit aux règles de XML lui-même : balises fermées, imbrication correcte, une racine, attributs entre guillemets, esperluettes échappées. Il n’a besoin que du document.
La validation au sens de la spécification demande si le document correspond à un schéma, XSD ou DTD : un second fichier décrivant quels éléments peuvent apparaître et où. Elle ne peut pas s’exécuter tant que la syntaxe n’est pas propre, et c’est pourquoi un « invalide » sorti d’une pile applicative désigne parfois une erreur de syntaxe et parfois une violation de schéma. Cette page effectue le premier contrôle et le dit ; les validateurs XSD et DTD d’ici effectuent le second, sans rien envoyer non plus.
Pourquoi signale-t-il cinq erreurs quand mon éditeur en signale une ?
Parce que la spécification XML demande à un processeur conforme de s’arrêter après une erreur fatale, et l’analyseur derrière votre éditeur le fait. La règle se défend : dès qu’une balise n’est pas fermée, l’analyseur ne sait réellement plus ce que la suite du document voulait dire.
Cet analyseur se reprend au contraire : il se resynchronise sur les frontières de balises, absorbe en entier un jeton de nom défectueux, et préfère accuser une balise ouvrante non fermée plutôt que la balise fermante qui l’a révélée. Considérez les premières erreurs comme les fiables et relancez, car une longue liste vient généralement d’une seule faute structurelle proche du début.
Les noms de balises XML sont-ils sensibles à la casse ?
Entièrement. <Note> et <note> sont des éléments différents, et </Note> ne ferme pas <note>. Cela surprend qui vient de HTML, où l’analyseur ramène les noms de balises en minuscules, et c’est la cause la plus fréquente d’erreur de balises non concordantes en XML écrit à la main.
Cela vaut aussi pour les noms d’attributs et les préfixes d’espace de noms : xmlns:Soap et xmlns:soap sont deux préfixes différents. Quand un écart ne tient qu’à la casse, le message le dit au lieu de signaler une simple non-concordance. Plusieurs résumés très recopiés des « règles de syntaxe XML » affirment que les balises XML ne sont pas sensibles à la casse ; ils ont tort, et les suivre produit des documents que tout analyseur réel rejette.
Quelle est la taille maximale de fichier ?
20 millions de caractères, soit à peu près 20 Mo d’ASCII. Un document de 10 Mo est analysé en un peu plus d’une seconde et 1 Mo en environ 110 millisecondes, dans un Web Worker, l’éditeur reste donc réactif.
Au-delà, utilisez un analyseur en flux en local : xmllint, iterparse en Python ou n’importe quel analyseur SAX vérifie la bonne formation sans construire l’arbre entier.