Quelle est la meilleure façon de valider un fichier XML par rapport à un fichier XSD?

263

Je génère des fichiers xml qui doivent être conformes à un fichier xsd qui m'a été donné. Quelle est la meilleure façon de vérifier leur conformité?

Jeff
la source

Réponses:

336

La bibliothèque d'exécution Java prend en charge la validation. La dernière fois que j'ai vérifié, c'était l'analyseur Apache Xerces sous les couvertures. Vous devriez probablement utiliser un javax.xml.validation.Validator .

import javax.xml.XMLConstants;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.*;
import java.net.URL;
import org.xml.sax.SAXException;
//import java.io.File; // if you use File
import java.io.IOException;
...
URL schemaFile = new URL("http://host:port/filename.xsd");
// webapp example xsd: 
// URL schemaFile = new URL("http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd");
// local file example:
// File schemaFile = new File("/location/to/localfile.xsd"); // etc.
Source xmlFile = new StreamSource(new File("web.xml"));
SchemaFactory schemaFactory = SchemaFactory
    .newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
try {
  Schema schema = schemaFactory.newSchema(schemaFile);
  Validator validator = schema.newValidator();
  validator.validate(xmlFile);
  System.out.println(xmlFile.getSystemId() + " is valid");
} catch (SAXException e) {
  System.out.println(xmlFile.getSystemId() + " is NOT valid reason:" + e);
} catch (IOException e) {}

La constante de fabrique de schéma est la chaîne http://www.w3.org/2001/XMLSchemaqui définit les XSD. Le code ci-dessus valide un descripteur de déploiement WAR par rapport à l'URL, http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsdmais vous pouvez tout aussi facilement valider par rapport à un fichier local.

Vous ne devez pas utiliser DOMParser pour valider un document (à moins que votre objectif ne soit de toute façon de créer un modèle d'objet de document). Cela commencera à créer des objets DOM lors de l'analyse du document, ce qui est inutile si vous ne les utilisez pas.

McDowell
la source
Utilisez-vous un analyseur DOM ou SAX dans cet exemple? Comment puis-je savoir quel analyseur vous utilisez car je ne peux pas voir de référence non plus.
ziggy
1
@ziggy - c'est un détail d'implémentation de l' implémentation JAXP . Le JDK 6 de Sun utilise l'analyseur SAX avec un StreamSource . Une implémentation JAXP pourrait légalement utiliser un analyseur DOM dans ce cas, mais il n'y a aucune raison de le faire. Si vous utilisez explicitement un analyseur DOM pour la validation, vous instancierez certainement une arborescence DOM.
McDowell
Comment utiliser un ErrorHandler avec ce qui précède? Est-ce un cas de simplement créer le ErrorHandler et de l'associer au validateur? c'est-à-dire validator.SetErrorHandler () comme dans l'exemple de cette question SO stackoverflow.com/questions/4864681/… ?
ziggy
Ne devrait pas execptions juste être utilisés pour des situations execptional et non pour le flux de contrôle?
mike
Ce code ne détectera-t-il que les erreurs fatales? Si vous voulez pouvoir attraper des non-fatals (comme des non-fatals), je pense que vous devrez utiliser un ErrorHandler.
mat forsythe
25

Voici comment le faire en utilisant Xerces2 . Un tutoriel pour cela, ici (inscription requise).

Attribution originale: copiée de façon flagrante à partir d' ici :

import org.apache.xerces.parsers.DOMParser;
import java.io.File;
import org.w3c.dom.Document;

public class SchemaTest {
  public static void main (String args[]) {
      File docFile = new File("memory.xml");
      try {
        DOMParser parser = new DOMParser();
        parser.setFeature("http://xml.org/sax/features/validation", true);
        parser.setProperty(
             "http://apache.org/xml/properties/schema/external-noNamespaceSchemaLocation", 
             "memory.xsd");
        ErrorChecker errors = new ErrorChecker();
        parser.setErrorHandler(errors);
        parser.parse("memory.xml");
     } catch (Exception e) {
        System.out.print("Problem parsing the file.");
     }
  }
}
SCdF
la source
9
L'analyseur SAX serait plus efficace - l'analyseur DOM crée des objets DOM; opérations inutiles dans ce cas.
McDowell
La question est de valider un XML par rapport à un XSD. Dans cette réponse, vous allez plus loin et obtenez un objet Parser, ce qui n'est pas nécessaire, non?
Weslor
"ErrorChecker ne peut pas être résolu en un type" .. importation manquante?
Alex
20

Nous construisons notre projet en utilisant ant, afin que nous puissions utiliser la tâche schemavalidate pour vérifier nos fichiers de configuration:

<schemavalidate> 
    <fileset dir="${configdir}" includes="**/*.xml" />
</schemavalidate>

Maintenant, les fichiers de configuration coquins échoueront dans notre build!

http://ant.apache.org/manual/Tasks/schemavalidate.html

pouletinabiscuit
la source
13

Puisque c'est une question populaire, je soulignerai que java peut également valider par rapport aux xsd "référencés", par exemple si le fichier .xml lui-même spécifie les XSD dans l'en-tête, en utilisant xsi:SchemaLocationou xsi:noNamespaceSchemaLocation(ou xsi pour des espaces de noms particuliers) ex :

<document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:noNamespaceSchemaLocation="http://www.example.com/document.xsd">
  ...

ou SchemaLocation (toujours une liste d'espaces de noms aux mappages xsd)

<document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:SchemaLocation="http://www.example.com/my_namespace http://www.example.com/document.xsd">
  ...

Les autres réponses fonctionnent également ici, car les fichiers .xsd sont "mappés" aux espaces de noms déclarés dans le fichier .xml, car ils déclarent un espace de noms, et si cela correspond à l'espace de noms dans le fichier .xml, vous êtes bon. Mais parfois c'est pratique d'avoir un résolveur personnalisé ...

À partir des javadocs: "Si vous créez un schéma sans spécifier d'URL, de fichier ou de source, le langage Java en crée un qui examine le document en cours de validation pour trouver le schéma qu'il doit utiliser. Par exemple:"

SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
Schema schema = factory.newSchema();

et cela fonctionne pour plusieurs espaces de noms, etc. Le problème avec cette approche est que xmlsns:xsic'est probablement un emplacement réseau, donc il va par défaut s'éteindre et frapper le réseau avec chaque validation, pas toujours optimale.

Voici un exemple qui valide un fichier XML par rapport aux références informatiques de n'importe quel XSD (même s'il doit les extraire du réseau):

  public static void verifyValidatesInternalXsd(String filename) throws Exception {
    InputStream xmlStream = new new FileInputStream(filename);
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setValidating(true);
    factory.setNamespaceAware(true);
    factory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage",
                 "http://www.w3.org/2001/XMLSchema");
    DocumentBuilder builder = factory.newDocumentBuilder();
    builder.setErrorHandler(new RaiseOnErrorHandler());
    builder.parse(new InputSource(xmlStream));
    xmlStream.close();
  }

  public static class RaiseOnErrorHandler implements ErrorHandler {
    public void warning(SAXParseException e) throws SAXException {
      throw new RuntimeException(e);
    }
    public void error(SAXParseException e) throws SAXException {
      throw new RuntimeException(e);
    }
    public void fatalError(SAXParseException e) throws SAXException {
      throw new RuntimeException(e);
    }
  }

Vous pouvez éviter de tirer les XSD référencés du réseau, même si les fichiers xml font référence aux URL, en spécifiant le xsd manuellement (voir d'autres réponses ici) ou en utilisant un résolveur de style "catalogue XML" . Spring peut également intercepter les demandes d'URL pour servir des fichiers locaux pour les validations. Ou vous pouvez définir le vôtre via setResourceResolver , par exemple:

Source xmlFile = new StreamSource(xmlFileLocation);
SchemaFactory schemaFactory = SchemaFactory
                                .newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = schemaFactory.newSchema();
Validator validator = schema.newValidator();
validator.setResourceResolver(new LSResourceResolver() {
  @Override
  public LSInput resolveResource(String type, String namespaceURI,
                                 String publicId, String systemId, String baseURI) {
    InputSource is = new InputSource(
                           getClass().getResourceAsStream(
                          "some_local_file_in_the_jar.xsd"));
                          // or lookup by URI, etc...
    return new Input(is); // for class Input see 
                          // https://stackoverflow.com/a/2342859/32453
  }
});
validator.validate(xmlFile);

Voir aussi ici pour un autre tutoriel.

Je crois que la valeur par défaut est d'utiliser l'analyse DOM, vous pouvez faire quelque chose de similaire avec l'analyseur SAX qui valide également saxReader.setEntityResolver(your_resolver_here);

rogerdpack
la source
Ne fonctionne pas pour moi, la méthode resolverResource () n'est appelée que si elle est définie sur schemaFactory, une idée?
tomasb
Ne sais pas, travaille pour moi. Assurez-vous que vous le définissez via setResourceResolvermais au-delà, ouvrez peut-être une nouvelle question ...
rogerdpack
6

En utilisant Java 7, vous pouvez suivre la documentation fournie dans la description du package .

// create a SchemaFactory capable of understanding WXS schemas
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);

// load a WXS schema, represented by a Schema instance
Source schemaFile = new StreamSource(new File("mySchema.xsd"));
Schema schema = factory.newSchema(schemaFile);

// create a Validator instance, which can be used to validate an instance document
Validator validator = schema.newValidator();

// validate the DOM tree
try {
    validator.validate(new StreamSource(new File("instance.xml"));
} catch (SAXException e) {
    // instance document is invalid!
}
Paulo Fidalgo
la source
2
"Utilisation de Java 7 .." Cela était en fait inclus dans Java 5 .
Andrew Thompson
4
C'est fondamentalement la même que la réponse acceptée . Cette solution me semble un peu inefficace si, comme il construit inutilement les DOM pour le XML à analyser: parser.parse(new File("instance.xml")). L' validatoraccepte un Source, vous pouvez: validator.validate(new StreamSource(new File("instance.xml"))).
Alberto
De cette façon, une SAXException serait levée à la première erreur dans le fichier xml et arrête ensuite la validation. Mais je veux connaître toutes les (!) Erreurs. Si j'utilise un ErrorHandler (propre classe qui implémente ErrorHandler) à la place, il reconnaît toutes les erreurs, mais le bloc try-catch de validator.validate ne lève aucune exception. Comment reconnaître une erreur dans la classe qui appelle le validate -méthode de mon validateur? Merci de votre aide!
mrbela
Il y a des "erreurs" (par exemple des erreurs de validation) et des "erreurs fatales" (des erreurs de bonne forme). Une erreur fatale arrête généralement l'analyse. Mais une erreur de validation ne l'arrête pas: vous devez lever explicitement une exception. Ainsi, il est nécessaire de fournir un ErrorHandlersi vous avez besoin de faire une validation.
Ludovic Kuty
1
Je dois admettre que le code semble plus propre et plus facile à lire à ce sujet que la réponse acceptée.
Clockwork
3

Si vous avez une machine Linux, vous pouvez utiliser l'outil de ligne de commande gratuit SAXCount. J'ai trouvé cela très utile.

SAXCount -f -s -n my.xml

Il valide contre dtd et xsd. 5s pour un fichier de 50 Mo.

Dans Debian Squeeze, il se trouve dans le paquet "libxerces-c-samples".

La définition du dtd et du xsd doit être dans le xml! Vous ne pouvez pas les configurer séparément.

juwens
la source
2
Cela permet une validation XML simple à partir de vim (:!! SAXCount -f -n -s%)
Shane
4
ou utilisez le vénérable xmllint xmllint --schema phone.xsd phone.xml(d'après une réponse de 13ren)
rogerdpack
3

Une autre réponse: puisque vous avez dit que vous devez valider les fichiers que vous générez (écriture), vous voudrez peut-être valider le contenu pendant que vous écrivez, au lieu de d'abord écrire, puis relire pour validation. Vous pouvez probablement le faire avec l'API JDK pour la validation Xml, si vous utilisez un écrivain basé sur SAX: si c'est le cas, liez simplement le validateur en appelant 'Validator.validate (source, result)', où la source provient de votre écrivain, et le résultat est où la sortie doit aller.

Alternativement, si vous utilisez Stax pour écrire du contenu (ou une bibliothèque qui utilise ou peut utiliser stax), Woodstox peut également prendre en charge directement la validation lors de l'utilisation de XMLStreamWriter. Voici une entrée de blog montrant comment cela se fait:

StaxMan
la source
Hé StaxMan, y a-t-il des XMLStreamWriters qui font une mise en retrait de jolie impression? J'ai été surpris que ce ne soit pas dans l'implémentation standard. En outre, est-ce que cela devient très utile? Je pense que c'est la bonne voie à suivre, mais cela semble très peu intéressant.
13ren
vient de trouver votre article ici sur StaxMate (mais ce n'est pas un XMLStreamWriter): stackoverflow.com/questions/290326/stax-xml-formatting-in-java/…
13ren
Oui, StaxMate peut le faire. Il utilise XMLStreamWriter en interne pour écrire du contenu, vous pouvez donc également connecter le validateur de cette façon.
StaxMan
2

Si vous générez des fichiers XML par programme, vous souhaiterez peut-être consulter la bibliothèque XMLBeans . À l'aide d'un outil en ligne de commande, XMLBeans générera et conditionnera automatiquement un ensemble d'objets Java basé sur un XSD. Vous pouvez ensuite utiliser ces objets pour créer un document XML basé sur ce schéma.

Il prend en charge la validation de schéma et peut convertir des objets Java en document XML et vice-versa.

Castor et JAXB sont d'autres bibliothèques Java qui ont un objectif similaire à XMLBeans.

Todd
la source
1

Avec JAXB, vous pouvez utiliser le code ci-dessous:

    @Test
public void testCheckXmlIsValidAgainstSchema() {
    logger.info("Validating an XML file against the latest schema...");

    MyValidationEventCollector vec = new MyValidationEventCollector();

    validateXmlAgainstSchema(vec, inputXmlFileName, inputXmlSchemaName, inputXmlRootClass);

    assertThat(vec.getValidationErrors().isEmpty(), is(expectedValidationResult));
}

private void validateXmlAgainstSchema(final MyValidationEventCollector vec, final String xmlFileName, final String xsdSchemaName, final Class<?> rootClass) {
    try (InputStream xmlFileIs = Thread.currentThread().getContextClassLoader().getResourceAsStream(xmlFileName);) {
        final JAXBContext jContext = JAXBContext.newInstance(rootClass);
        // Unmarshal the data from InputStream
        final Unmarshaller unmarshaller = jContext.createUnmarshaller();

        final SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        final InputStream schemaAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(xsdSchemaName);
        unmarshaller.setSchema(sf.newSchema(new StreamSource(schemaAsStream)));

        unmarshaller.setEventHandler(vec);

        unmarshaller.unmarshal(new StreamSource(xmlFileIs), rootClass).getValue(); // The Document class is the root object in the XML file you want to validate

        for (String validationError : vec.getValidationErrors()) {
            logger.trace(validationError);
        }
    } catch (final Exception e) {
        logger.error("The validation of the XML file " + xmlFileName + " failed: ", e);
    }
}

class MyValidationEventCollector implements ValidationEventHandler {
    private final List<String> validationErrors;

    public MyValidationEventCollector() {
        validationErrors = new ArrayList<>();
    }

    public List<String> getValidationErrors() {
        return Collections.unmodifiableList(validationErrors);
    }

    @Override
    public boolean handleEvent(final ValidationEvent event) {
        String pattern = "line {0}, column {1}, error message {2}";
        String errorMessage = MessageFormat.format(pattern, event.getLocator().getLineNumber(), event.getLocator().getColumnNumber(),
                event.getMessage());
        if (event.getSeverity() == ValidationEvent.FATAL_ERROR) {
            validationErrors.add(errorMessage);
        }
        return true; // you collect the validation errors in a List and handle them later
    }
}
razvanone
la source
0

Vous recherchez un outil ou une bibliothèque?

En ce qui concerne les bibliothèques, la norme de facto est à peu près Xerces2, qui a à la fois des versions C ++ et Java .

Soyez averti cependant, c'est une solution lourde. Mais là encore, la validation de XML par rapport aux fichiers XSD est un problème assez lourd.

Quant à un outil pour le faire pour vous, XMLFox semble être une solution freeware décente, mais ne l'ayant pas utilisé personnellement, je ne peux pas le dire avec certitude.

Adam
la source
0

Valider par rapport aux schémas en ligne

Source xmlFile = new StreamSource(Thread.currentThread().getContextClassLoader().getResourceAsStream("your.xml"));
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = factory.newSchema(Thread.currentThread().getContextClassLoader().getResource("your.xsd"));
Validator validator = schema.newValidator();
validator.validate(xmlFile);

Valider par rapport aux schémas locaux

Validation XML hors ligne avec Java

jschnasse
la source
0

À l'aide de Woodstox , configurez l'analyseur StAX pour valider par rapport à votre schéma et analyser le XML.

Si des exceptions sont interceptées, le XML n'est pas valide, sinon il est valide:

// create the XSD schema from your schema file
XMLValidationSchemaFactory schemaFactory = XMLValidationSchemaFactory.newInstance(XMLValidationSchema.SCHEMA_ID_W3C_SCHEMA);
XMLValidationSchema validationSchema = schemaFactory.createSchema(schemaInputStream);

// create the XML reader for your XML file
WstxInputFactory inputFactory = new WstxInputFactory();
XMLStreamReader2 xmlReader = (XMLStreamReader2) inputFactory.createXMLStreamReader(xmlInputStream);

try {
    // configure the reader to validate against the schema
    xmlReader.validateAgainst(validationSchema);

    // parse the XML
    while (xmlReader.hasNext()) {
        xmlReader.next();
    }

    // no exceptions, the XML is valid

} catch (XMLStreamException e) {

    // exceptions, the XML is not valid

} finally {
    xmlReader.close();
}

Remarque : Si vous devez valider plusieurs fichiers, vous devez essayer de réutiliser votre XMLInputFactoryet XMLValidationSchemaafin de maximiser les performances.

Loris Securo
la source
-3

J'ai dû valider un XML contre XSD une seule fois, j'ai donc essayé XMLFox. Je l'ai trouvé très déroutant et bizarre. Les instructions d'aide ne semblaient pas correspondre à l'interface.

J'ai fini par utiliser LiquidXML Studio 2008 (v6) qui était beaucoup plus facile à utiliser et plus immédiatement familier (l'interface utilisateur est très similaire à Visual Basic 2008 Express, que j'utilise fréquemment). L'inconvénient: la capacité de validation n'est pas dans la version gratuite, j'ai donc dû utiliser la version d'essai de 30 jours.

KnomDeGuerre
la source
1
La question est Java, mais cette réponse ne l'est pas. :-(
james.garriss
Pour être juste, le mot "java" n'apparaît jamais dans la question, juste les balises. Je ding la question pour cela, pas la réponse.
Mark Storer
Merci James et Mark, aidez-moi à affûter!
Knom