Qual è il modo migliore per convalidare un file XML su un file XSD?


263

Sto generando alcuni file xml che devono essere conformi a un file xsd che mi è stato dato. Qual è il modo migliore per verificarne la conformità?

Risposte:


336

La libreria di runtime Java supporta la convalida. L'ultima volta che ho controllato questo era il parser Apache Xerces sotto le coperte. Probabilmente dovresti usare 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 costante di fabbrica dello schema è la stringa http://www.w3.org/2001/XMLSchemache definisce gli XSD. Il codice sopra convalida un descrittore di distribuzione WAR rispetto all'URL http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsdma è possibile convalidare altrettanto facilmente su un file locale.

Non è necessario utilizzare DOMParser per convalidare un documento (a meno che l'obiettivo non sia comunque quello di creare un modello a oggetti documento). Questo inizierà a creare oggetti DOM mentre analizza il documento, inutile se non li utilizzerai.


In questo esempio stai usando un parser DOM o SAX? Come faccio a sapere quale parser stai usando come non riesco a vedere un riferimento a nessuno dei due.
ziggy,

1
@ziggy: questo è un dettaglio dell'implementazione dell'implementazione JAXP . JDK 6 di Sun utilizza un parser SAX con StreamSource . In questo caso, un'implementazione JAXP potrebbe utilizzare legalmente un parser DOM, ma non c'è motivo di farlo. Se si utilizza esplicitamente un parser DOM per la convalida, verrà creata un'istanza di un albero DOM.
McDowell,

Come uso un ErrorHandler con quanto sopra? È solo il caso di creare ErrorHandler e associarlo al validatore? vale a dire validator.SetErrorHandler () come nell'esempio in questa domanda SO stackoverflow.com/questions/4864681/… ?
ziggy,

Non dovrebbero execptions solo essere utilizzati per le situazioni è eccezionale e non per il flusso di controllo?
mike,

Questo codice non rileverà solo errori fatali? Se vuoi essere in grado di catturare non fatali (come quelli non strutturali) penso che dovrai usare un ErrorHandler.
matt forsythe,

25

Ecco come farlo usando Xerces2 . Un tutorial per questo, qui (richiesta di iscrizione).

Attribuzione originale: palesemente copiata da qui :

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.");
     }
  }
}

9
Il parser SAX sarebbe più efficiente: il parser DOM crea oggetti DOM; operazioni dispendiose in questo caso.
McDowell,

La domanda è convalidare un XML contro un XSD. In questa risposta andrai oltre e otterrai un oggetto Parser, che non è necessario, giusto?
Weslor,

"Cannor ErrorChecker essere risolto in un tipo" .. importazione mancante?
Alex


13

Poiché questa è una domanda popolare, sottolineo che java può anche validare contro "riferito a" xsd, ad esempio se il file .xml stesso specifica XSD nell'intestazione, usando xsi:SchemaLocationo xsi:noNamespaceSchemaLocation(o xsi per particolari spazi dei nomi) ex :

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

o SchemaLocation (sempre un elenco di spazi dei nomi per i mapping 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">
  ...

Le altre risposte funzionano anche qui, perché i file .xsd "mappano" gli spazi dei nomi dichiarati nel file .xml, perché dichiarano uno spazio dei nomi e se corrispondono allo spazio dei nomi nel file .xml, sei a posto. Ma a volte è conveniente poter avere un resolver personalizzato ...

Da javadocs: "Se si crea uno schema senza specificare un URL, un file o un'origine, il linguaggio Java ne crea uno che appare nel documento in fase di convalida per trovare lo schema da utilizzare. Ad esempio:"

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

e questo funziona per più spazi dei nomi, ecc. Il problema con questo approccio è che xmlsns:xsiprobabilmente è un percorso di rete, quindi per impostazione predefinita uscirà e colpirà la rete con ogni singola convalida, non sempre ottimale.

Ecco un esempio che convalida un file XML rispetto a qualsiasi XSD a cui fa riferimento (anche se deve estrarlo dalla rete):

  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);
    }
  }

Puoi evitare di estrarre gli XSD di riferimento dalla rete, anche se i file xml fanno riferimento agli url, specificando l'xsd manualmente (vedi alcune altre risposte qui) o usando un risolutore di stile "Catalogo XML" . Apparentemente Spring può anche intercettare le richieste URL per servire file locali per le convalide. Oppure puoi impostare il tuo tramite setResourceResolver , ad esempio:

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);

Vedi anche qui per un altro tutorial.

Credo che il valore predefinito è di usare DOM analisi, si può fare qualcosa di simile con SAX parser che sta convalidando così saxReader.setEntityResolver(your_resolver_here);


Non funziona per me, il metodo resolResource () non viene chiamato a meno che non sia impostato su schemaFactory, qualche idea?
tomasb,

Non so, funziona per me. Assicurati di impostarlo tramite, setResourceResolverma oltre a ciò, forse apri una nuova domanda ...
rogerdpack,

6

Utilizzando Java 7 è possibile seguire la documentazione fornita nella descrizione del pacchetto .

// 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!
}

2
"Utilizzo di Java 7 .." In realtà era incluso in Java 5 .
Andrew Thompson,

4
Questo è fondamentalmente lo stesso della risposta accettata . Questa soluzione mi sembra un po 'però inefficiente, come si costruisce inutilmente il DOM per l'XML per analizzare: parser.parse(new File("instance.xml")). Il validatoraccetta una Source, in modo da poter: validator.validate(new StreamSource(new File("instance.xml"))).
Alberto,

Funzionando in questo modo, una SAXException verrebbe lanciata al primo errore nel file xml e interrompe quindi la convalida. Ma voglio sapere tutti gli errori (!). Se uso invece ErrorHandler (la propria classe che implementa ErrorHandler), riconosce tutti gli errori, ma il blocco try-catch di validator.validate non genera alcuna eccezione. Come posso riconoscere un errore nella classe che richiama il validate -metodo del mio validatore? Grazie per l'aiuto!
mrbela,

Vi sono "errori" (ad es. Errori di validazione) e "errori fatali" (errori di buona formazione). Un errore fatale in genere interrompe l'analisi. Ma un errore di validazione non lo ferma: devi esplicitamente lanciare un'eccezione. Pertanto, è necessario fornire un ErrorHandlerse è necessario eseguire la convalida.
Ludovic Kuty,

1
Devo ammettere, il codice sembra più pulito e più facile da leggere su questo rispetto alla risposta accettata.
Orologio

3

Se disponi di una macchina Linux, puoi utilizzare lo strumento da riga di comando gratuito SAXCount. L'ho trovato molto utile.

SAXCount -f -s -n my.xml

Si convalida contro dtd e xsd. 5s per un file da 50 MB.

In debian squeeze si trova nel pacchetto "libxerces-c-samples".

La definizione di dtd e xsd deve essere nel file XML! Non puoi configurarli separatamente.


2
Ciò consente una semplice convalida XML da vim (:! SAXCount -f -n -s%)
Shane

4
o usa il venerabile xmllint xmllint --schema phone.xsd phone.xml(da una risposta di 13ren)
rogerdpack,

3

Un'altra risposta: dal momento che hai detto che devi convalidare i file che stai generando (scrivendo), potresti voler convalidare il contenuto mentre stai scrivendo, invece di scrivere prima, quindi rileggere per la convalida. Probabilmente puoi farlo con l'API JDK per la convalida Xml, se usi un writer basato su SAX: in tal caso, collega il validatore chiamando 'Validator.validate (source, risultato)', dove source proviene dal tuo writer e il risultato è dove l'output deve andare.

In alternativa, se si utilizza Stax per scrivere contenuti (o una libreria che utilizza o può utilizzare stax), Woodstox può anche supportare direttamente la convalida quando si utilizza XMLStreamWriter. Ecco un post sul blog che mostra come viene fatto:


Ehi StaxMan, ci sono XMLStreamWriters che eseguono il rientro di stampa carina? Sono rimasto sorpreso dal fatto che non sia nell'implementazione standard. Inoltre, sta diventando molto utile? Penso che sia la strada giusta da percorrere, ma sembra esserci ben poco interesse.
13ren,

appena trovato il tuo post qui su StaxMate (ma non è un XMLStreamWriter): stackoverflow.com/questions/290326/stax-xml-formatting-in-java/...
13ren

Sì, StaxMate può farlo. Utilizza XMLStreamWriter internamente per la scrittura di contenuti, quindi puoi collegare anche il validatore in quel modo.
StaxMan

2

Se stai generando file XML a livello di codice , potresti voler consultare la libreria XMLBeans . Utilizzando uno strumento da riga di comando, XMLBeans genererà e impacchetterà automaticamente un insieme di oggetti Java basati su un XSD. È quindi possibile utilizzare questi oggetti per creare un documento XML basato su questo schema.

Ha un supporto integrato per la convalida dello schema e può convertire oggetti Java in un documento XML e viceversa.

Castor e JAXB sono altre librerie Java che hanno uno scopo simile a XMLBeans.


1

Con JAXB, è possibile utilizzare il codice seguente:

    @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
    }
}

0

Stai cercando uno strumento o una libreria?

Per quanto riguarda le librerie, praticamente lo standard di fatto è Xerces2 che ha entrambe le versioni C ++ e Java .

Attenzione però, è una soluzione pesante. Ma ancora una volta, la convalida di XML su file XSD è un problema piuttosto pesante.

Per quanto riguarda uno strumento per fare questo per te, XMLFox sembra essere una soluzione freeware decente, ma non averlo usato personalmente non posso dirlo con certezza.


0

Convalida contro schemi online

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);

Convalida rispetto agli schemi locali

Convalida XML offline con Java


0

Usando Woodstox , configura il parser StAX per convalidare lo schema e analizzare l'XML.

Se vengono rilevate eccezioni, l'XML non è valido, altrimenti è valido:

// 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();
}

Nota : se devi convalidare più file, dovresti provare a riutilizzare il tuo XMLInputFactorye XMLValidationSchemaal fine di massimizzare le prestazioni.


-3

Ho dovuto convalidare un XML contro XSD solo una volta, quindi ho provato XMLFox. L'ho trovato molto confuso e strano. Le istruzioni di aiuto non sembrano corrispondere all'interfaccia.

Ho finito per usare LiquidXML Studio 2008 (v6) che era molto più facile da usare e immediatamente più familiare (l'interfaccia utente è molto simile a Visual Basic 2008 Express, che uso frequentemente). Lo svantaggio: la funzionalità di convalida non è nella versione gratuita, quindi ho dovuto utilizzare la versione di prova di 30 giorni.


1
La domanda è Java, ma questa risposta non lo è. :-(
james.garriss,

Per essere onesti, la parola "java" non appare mai nella domanda, solo i tag. Farei la domanda per quello, non la risposta.
Mark Storer,

Grazie James e Mark, aiutami ad affinare!
Knom,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.