Validateur XSD

Validez avec un XSD. Les deux fichiers restent dans le navigateur.

Entrée
Schéma XSD
En attenteCollez un document pour le vérifier. La validation se fait à la frappe.

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 le document à gauche, son XSD à droite, puis appuyez sur Valider avec le schéma. libxml2, compilé en WebAssembly, exécute le contrôle dans un Web Worker de cet onglet, et chaque violation est listée avec une ligne, une colonne et un repère cliquable pour y sauter.

On arrive ici en général après qu’autre chose a rejeté le document : un partenaire renvoie cvc-complex-type.2.4.a, une étape de build échoue sur un fichier de configuration, une passerelle annonce que la charge utile ne correspond pas au schéma et s’arrête là. Vous avez le XSD et vous voulez l’élément fautif, pas une chaîne d’outils Java.

La différence, c’est l’endroit où cela tourne. Les navigateurs n’ont pas de validateur de schéma natif, donc les outils gratuits qui proposent la validation XSD envoient les deux fichiers à un serveur ; le plus connu vous fait soumettre d’abord le XML, puis le schéma dans un second formulaire. Ici aucun des deux fichiers ne quitte cet onglet, et le moteur, environ 480 Ko compressés, est récupéré au moment où vous cliquez, pas au chargement de la page.

Bien formé d’abord, valide ensuite

Ce sont deux contrôles, pas un. La bonne formation relève de la syntaxe : balises fermées, imbrication correcte, une racine, esperluettes échappées. Elle n’a besoin que du document, elle tourne donc à la frappe. La validité, c’est la bonne formation plus la conformité à un schéma, ce qui exige un second fichier : d’où une action délibérée.

Le moteur analyse le XSD, le compile, analyse le document, puis valide, et chaque étape échoue différemment. Un document mal formé n’est jamais confronté à un schéma, faute de quelque chose de cohérent à vérifier. Chaque diagnostic est étiqueté Problème de schéma ou Erreur de validité, et une position située dans le schéma n’est délibérément pas cliquable, puisque ce numéro de ligne appartient à l’autre volet.

XSD 1.0 seulement, et ce que cela exclut

libxml2 implémente XSD 1.0. XSD 1.1 est devenu Recommandation du W3C le 5 avril 2012 et est implémenté par Apache Xerces-J, par Saxon-EE et par le paquet Python xmlschema, mais pas par libxml2, ni par le XmlSchemaSet de .NET, ni par lxml, qui enveloppe libxml2. Un schéma utilisant des fonctions 1.1 ne compilera pas ici, et le panneau désigne 1.1 comme cause probable au lieu de rendre une erreur de structure déroutante.

  • xs:assert et la facette xs:assertion : les contraintes de cooccurrence, où la validité d’un champ dépend d’un autre. XSD 1.0 ne sait pas les exprimer du tout.
  • xs:alternative, l’affectation conditionnelle de type, où le type d’un élément est choisi par un test XPath sur ses propres attributs.
  • xs:openContent, xs:defaultOpenContent et xs:override, plus les types 1.1 xs:dateTimeStamp, xs:dayTimeDuration et xs:yearMonthDuration. xs:precisionDecimal, qui traîne dans quantité d’articles, a été retiré avant la Recommandation finale.
  • Deux limites propres à libxml2 en prime : xs:redefine tombe depuis longtemps sur un chemin non implémenté, et xs:import ne peut pas résoudre un schemaLocation ici, puisque rien dans cette build n’a le droit de lire un fichier. Aplatissez d’abord un ensemble de schémas réparti en plusieurs documents.

Les échecs que vous verrez vraiment

L’ordre en cause la plupart. xs:sequence signifie que les enfants doivent apparaître dans l’ordre déclaré, et cet ordre fait partie du contrat plutôt que d’être une préférence de mise en forme : déplacer l’élément est donc généralement la correction. XSD 1.0 n’autorise xs:all, l’alternative sans ordre, qu’au sommet d’un modèle de contenu et avec chaque élément au plus une fois, ce qui explique que la plupart des schémas réels utilisent une séquence.

Les espaces de noms causent le reste. Si le schéma déclare targetNamespace="urn:example:orders", chaque élément qu’il régit doit appartenir à cet espace de noms ; s’il n’en déclare aucun, ils ne doivent appartenir à aucun, et ajouter un xmlns par défaut casse tout. Le piège voisin est elementFormDefault : absent, il vaut unqualified, donc la racine est qualifiée et les enfants ne doivent pas l’être.

  • cvc-complex-type.2.4.a : un élément est apparu là où le schéma ne l’autorisait pas. Les noms entre accolades sont ceux qui auraient été légaux, donc « One of {qty} is expected » veut dire que qty était attendu.
  • cvc-complex-type.2.4.b : le modèle de contenu n’a pas été satisfait, en général un enfant obligatoire manquant à la fin.
  • cvc-datatype-valid.1.2.1 : le texte n’est pas lexicalement valide pour son type. Un élément vide déclaré xs:int, un dateTime sans secondes, un decimal avec séparateur de milliers.
  • cvc-elt.1 : aucune déclaration trouvée pour l’élément racine. Presque toujours un targetNamespace qui ne correspond pas, plutôt qu’une déclaration absente.
  • cvc-complex-type.3.2.2 : un attribut n’est pas autorisé, souvent xsi:type ou xsi:nil sans l’espace de noms XMLSchema-instance déclaré.

Ce qu’il ne fait jamais, et ce que cela coûte

Il ne va jamais chercher xsi:schemaLocation. La partie 1 de XSD qualifie cet attribut d’indication « quant à l’emplacement physique des documents de schéma » et dit qu’un processeur « devrait tenter de le déréférencer » « sauf indication contraire, par exemple de l’application appelante ». Refuser est conforme, et courant : SQL Server l’ignore sur les colonnes de type xml.

Il ne pourrait rien récupérer même s’il le voulait. Chaque analyse utilise XML_PARSE_NO_XXE, NONET et NO_SYS_CATALOG, et cette build n’enregistre aucun chargeur de ressources : entités externes, sous-ensemble DTD externe et URL de schemaLocation ne résolvent donc vers rien. XML_PARSE_HUGE n’est jamais activé, ce qui maintient les plafonds de libxml2 : profondeur d’éléments 256, imbrication d’entités 20 et une limite d’amplification.

Une faiblesse à reconnaître : la documentation de libxml2 qualifie son module de schémas d’implémentation partielle de XML Schema Partie 1, et il signale souvent moins de diagnostics que Xerces sur les mêmes entrées. Lisez la liste comme « au moins ceux-là » et relancez après chaque correction.

Valider avec un XSD en code

Le même contrôle dans les langages qui consomment du XML. Chaque exemple désactive l’accès externe, parce qu’une référence de schéma est une instruction de récupération et que la plupart de ces bibliothèques la suivent par défaut.

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

Notez le motif du gestionnaire dans les exemples Java et C# : les deux API lèvent une exception à la première erreur tant que vous n’en installez pas un, ce qui explique que tant de code de production ne signale qu’un problème à la fois.

Questions fréquentes

Mon XML et mon XSD sont-ils envoyés quelque part ?

Non. Les deux volets restent dans cet onglet. libxml2 tourne ici en WebAssembly dans un Web Worker, il n’existe pas de point d’envoi, et la build n’a aucun chemin réseau propre.

Cela compte davantage ici qu’ailleurs : un schéma nomme chaque champ, chaque liste de codes et chaque frontière interne, et il est souvent couvert par un accord de confidentialité alors que le document n’est que du jeu d’essai. Ouvrez votre panneau réseau, appuyez sur Valider, et regardez le moteur se charger une fois puis plus rien. Les deux volets sont conservés dans localStorage pour qu’un rafraîchissement ne perde pas votre travail, et rien au-delà de 300 Ko n’est stocké.

Prend-il en charge XSD 1.1, ou xs:assert ?

Non. libxml2, c’est XSD 1.0 : xs:assert, xs:alternative, xs:openContent et xs:override ne compileront pas. Au lieu d’un obscur « element assert is not expected here », le panneau indique que le schéma est du XML bien formé mais pas un XSD utilisable, et désigne 1.1 comme cause probable.

Pour 1.1, utilisez Xerces-J, gratuit, qui donne une SchemaFactory 1.1 en changeant une chaîne d’espace de noms, Saxon-EE, ou le paquet Python xmlschema. Rien de basé sur le navigateur ne le fait : ce sont toutes des builds de libxml2.

Mon schéma utilise xs:import. Puis-je quand même valider ici ?

Seulement en l’aplatissant d’abord. Résoudre un xs:import suppose de lire un fichier ou d’émettre une requête, et cette build n’enregistre aucun chargeur de ressources, la même décision qui l’empêche de récupérer des entités externes : le schéma ne compilera donc pas.

Soit vous collez les définitions de types importées dans le document contre lequel vous validez, soit vous lancez xmllint en local, où les fichiers .xsd voisins sont lus sur le disque. La plupart des grandes familles de schémas publics, UBL et FpML en tête, réclament ce traitement.

Que signifie cvc-complex-type.2.4.a ?

Un élément est apparu là où le schéma ne l’autorisait pas. Le message se termine par les noms qui auraient été légaux à cet endroit, entre accolades : « Invalid content was found starting with element 'item'. One of '{qty}' is expected. » veut dire que qty était attendu.

Trois causes couvrent presque tout : les enfants sont dans le mauvais ordre pour un xs:sequence, de loin le cas le plus fréquent ; l’élément n’est pas déclaré du tout, en général une faute de frappe ou un champ ajouté d’un côté de l’intégration ; ou le nom est bon et l’espace de noms est mauvais, ce que le message ne peut pas vous montrer puisqu’il n’imprime que le nom local.

Va-t-il chercher le schéma nommé dans xsi:schemaLocation ?

Jamais. La spécification qualifie cet attribut d’indication et permet à l’application appelante de demander au processeur de ne pas le déréférencer : refuser est donc conforme et non un raccourci.

Les raisons dépassent la confidentialité : suivre une URL contrôlée par un attaquant depuis un document non fiable est une primitive de falsification de requêtes, et cela lie votre résultat à ce qu’un tiers sert aujourd’hui.

Quelle taille de document peut-il contrôler ?

Vingt mégaoctets. Charger un fichier plus gros est refusé, parce que tout est gardé dans la mémoire de cette page et que la validation construit un arbre complet en plus du schéma compilé.

Pour des documents réellement volumineux, lancez xmllint en local : le même code libxml2, sans le plafond du navigateur.

Outils associés

Pour aller plus loin

Erreurs que cet outil résout