¿Cuál es la mejor manera de validar un archivo XML contra un archivo XSD?

263

Estoy generando algunos archivos xml que deben ajustarse a un archivo xsd que me dieron. ¿Cuál es la mejor manera de verificar que cumplen?

Jeff
fuente

Respuestas:

336

La biblioteca de tiempo de ejecución Java admite validación. La última vez que revisé esto fue el analizador Apache Xerces debajo de las cubiertas. Probablemente deberías usar 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 fábrica del esquema es la cadena http://www.w3.org/2001/XMLSchemaque define los XSD. El código anterior valida un descriptor de despliegue WAR contra la URLhttp://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd pero podría validarlo fácilmente con un archivo local.

No debe usar DOMParser para validar un documento (a menos que su objetivo sea crear un modelo de objeto de documento de todos modos). Esto comenzará a crear objetos DOM a medida que analiza el documento, un desperdicio si no los va a usar.

McDowell
fuente
¿Está utilizando un analizador DOM o SAX en este ejemplo? ¿Cómo puedo saber qué analizador está utilizando ya que no puedo ver una referencia a ninguno de ellos?
ziggy
1
@ziggy: este es un detalle de implementación de la implementación de JAXP . Sun JDK 6 usa el analizador SAX con StreamSource . Una implementación JAXP podría usar legalmente un analizador DOM en este caso, pero no hay razón para hacerlo. Si utiliza un analizador DOM explícitamente para la validación, definitivamente creará una instancia de un árbol DOM.
McDowell
¿Cómo uso un ErrorHandler con lo anterior? ¿Se trata simplemente de crear ErrorHandler y asociarlo con el validador? es decir, validator.SetErrorHandler () como en el ejemplo de esta pregunta SO stackoverflow.com/questions/4864681/… ?
ziggy
¿No deberían usarse las ejecuciones solo para situaciones ejecutivas y no para el flujo de control?
mike
¿Este código solo detectará errores fatales? Si desea poder atrapar no fatales (como los no estructurales), creo que necesitará usar un ErrorHandler.
Matt forsythe
25

Aquí se explica cómo hacerlo con Xerces2 . Un tutorial para esto, aquí (solicitud de registro).

Atribución original: copiada descaradamente de aquí :

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
fuente
9
El analizador SAX sería más eficiente: el analizador DOM crea objetos DOM; operaciones derrochadoras en este caso.
McDowell
La pregunta es validar un XML contra un XSD. En esta respuesta, vas más allá y obtienes un objeto Parser, que no es necesario, ¿verdad?
Weslor
"ErrorChecker no se puede resolver a un tipo" ... ¿falta importación?
Alex
20

Construimos nuestro proyecto usando ant, por lo que podemos usar la tarea de validación de esquema para verificar nuestros archivos de configuración:

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

¡Ahora los archivos de configuración traviesos fallarán en nuestra compilación!

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

galletas de pollo
fuente
13

Dado que esta es una pregunta popular, señalaré que Java también puede validar contra xsd "referidos", por ejemplo, si el archivo .xml en sí mismo especifica XSD en el encabezado, usando xsi:SchemaLocationo xsi:noNamespaceSchemaLocation(o xsi para espacios de nombres particulares) ej :

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

o SchemaLocation (siempre una lista de asignaciones de espacio de nombres a 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">
  ...

Las otras respuestas también funcionan aquí, porque los archivos .xsd "se asignan" a los espacios de nombres declarados en el archivo .xml, porque declaran un espacio de nombres, y si coincide con el espacio de nombres en el archivo .xml, está bien. Pero a veces es conveniente poder tener un resolutor personalizado ...

De los javadocs: "Si crea un esquema sin especificar una URL, un archivo o una fuente, el lenguaje Java crea uno que busca en el documento que se está validando para encontrar el esquema que debe usar. Por ejemplo:"

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

y esto funciona para múltiples espacios de nombres, etc. El problema con este enfoque es que xmlsns:xsiprobablemente sea una ubicación de red, por lo que saldrá por defecto y llegará a la red con cada validación, no siempre óptima.

Aquí hay un ejemplo que valida un archivo XML contra cualquier XSD al que hace referencia (incluso si tiene que extraerlo de la red):

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

Puede evitar extraer los XSD referenciados de la red, aunque los archivos xml hagan referencia a las URL, especificando el xsd manualmente (vea algunas otras respuestas aquí) o utilizando un resolutor de estilo "catálogo XML" . Spring aparentemente también puede interceptar las solicitudes de URL para servir archivos locales para validaciones. O puede configurar el suyo a través de setResourceResolver , por ejemplo:

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

Vea también aquí para otro tutorial.

Creo que el defecto es usar DOM análisis, se puede hacer algo similar con analizador SAX que está validando así saxReader.setEntityResolver(your_resolver_here);

rogerdpack
fuente
No funciona para mí, el método resolveResource () no se llama a menos que esté establecido en schemaFactory, ¿alguna idea?
tomasb
No sé, funciona para mí. Asegúrese de configurarlo, setResourceResolverpero más allá de eso, tal vez abra una nueva pregunta ...
rogerdpack
6

Usando Java 7 puede seguir la documentación provista en la descripción del paquete .

// 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
fuente
2
"Usando Java 7 .." Eso fue realmente incluido en Java 5 .
Andrew Thompson
44
Esto es básicamente lo mismo que la respuesta aceptada . Esta solución me parece un poco aunque ineficiente, ya que se basa innecesariamente el DOM para el XML para analizar: parser.parse(new File("instance.xml")). El validatoracepta una Source, para que pueda: validator.validate(new StreamSource(new File("instance.xml"))).
Alberto
Al trabajar de esta manera, se generaría una SAXException al primer error en el archivo xml y luego se detiene la validación. Pero quiero saber todos (!) Errores. Si uso un ErrorHandler (clase propia que implementa ErrorHandler), reconoce todos los errores, pero el bloque try-catch-de validator.validate no arroja ninguna excepción. ¿Cómo reconozco un error en la clase que invoca la validación? -método de mi validador? ¡Gracias por tu ayuda!
mrbela
Hay "errores" (p. Ej., Errores de validación) y "errores fatales" (errores de buena formación). Un error fatal normalmente detiene el análisis. Pero un error de validación no lo detiene: debe lanzar explícitamente una excepción. Por lo tanto, es necesario proporcionar un ErrorHandlersi necesita hacer la validación.
Ludovic Kuty
1
Debo admitir que el código parece más limpio y fácil de leer que la respuesta aceptada.
Mecanismo el
3

Si tiene una máquina Linux, puede usar la herramienta gratuita de línea de comandos SAXCount. Esto me pareció muy útil.

SAXCount -f -s -n my.xml

Valida contra dtd y xsd. 5s para un archivo de 50MB.

En debian squeeze se encuentra en el paquete "libxerces-c-samples".

¡La definición de dtd y xsd debe estar en el xml! No puedes configurarlos por separado.

juwens
fuente
2
Esto permite una validación XML simple desde vim (:! SAXCount -f -n -s%)
Shane
44
o use el venerable xmllint xmllint --schema phone.xsd phone.xml(de una respuesta de 13ren)
rogerdpack
3

Una respuesta más: ya que dijiste que debes validar los archivos que estás generando (escribiendo), es posible que desee validar el contenido mientras escribe, en lugar de escribir primero y luego volver a leer para validar. Probablemente pueda hacer eso con la API JDK para la validación Xml, si usa un escritor basado en SAX: si es así, solo conecte el validador llamando a 'Validator.validate (fuente, resultado)', donde la fuente proviene de su escritor, y el resultado es donde la salida necesita ir.

Alternativamente, si usa Stax para escribir contenido (o una biblioteca que usa o puede usar stax), Woodstox también puede soportar directamente la validación cuando usa XMLStreamWriter. Aquí hay una entrada de blog que muestra cómo se hace:

StaxMan
fuente
Hola StaxMan, ¿hay algún XMLStreamWriters que haga sangrías de impresión bonita? Me sorprendió que no esté en la implementación estándar. Además, ¿se está usando mucho? Creo que es el camino correcto, pero parece haber muy poco interés en ello.
13ren
acabo de encontrar su publicación aquí sobre StaxMate (pero no es un XMLStreamWriter): stackoverflow.com/questions/290326/stax-xml-formatting-in-java/…
13ren
Sí, StaxMate puede hacer eso. Utiliza XMLStreamWriter internamente para escribir contenido, por lo que también puede conectar el validador de esa manera.
StaxMan 01 de
2

Si está generando archivos XML mediante programación, puede consultar la biblioteca XMLBeans . Usando una herramienta de línea de comandos, XMLBeans generará y empaquetará automáticamente un conjunto de objetos Java basados ​​en un XSD. Luego puede usar estos objetos para crear un documento XML basado en este esquema.

Tiene soporte incorporado para validación de esquemas y puede convertir objetos Java en un documento XML y viceversa.

Castor y JAXB son otras bibliotecas de Java que tienen un propósito similar a XMLBeans.

Todd
fuente
1

Con JAXB, puede usar el siguiente código:

    @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
fuente
0

¿Estás buscando una herramienta o una biblioteca?

En cuanto a las bibliotecas, el estándar de facto es Xerces2, que tiene versiones C ++ y Java .

Sin embargo, tenga en cuenta que es una solución de gran peso. Pero, de nuevo, validar XML contra archivos XSD es un problema bastante pesado.

En cuanto a una herramienta para hacer esto por usted, XMLFox parece ser una solución gratuita decente, pero no lo he usado personalmente, no puedo decir con certeza.

Adán
fuente
0

Validar contra esquemas en línea

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

Validar contra esquemas locales

Validación XML sin conexión con Java

jschnasse
fuente
0

Usando Woodstox , configure el analizador StAX para validar contra su esquema y analizar el XML.

Si se detectan excepciones, el XML no es válido; de lo contrario, es válido:

// 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 : Si necesita validar varios archivos, debe intentar reutilizar su XMLInputFactoryy XMLValidationSchemapara maximizar el rendimiento.

Loris Securo
fuente
-3

Tuve que validar un XML contra XSD solo una vez, así que probé XMLFox. Lo encontré muy confuso y extraño. Las instrucciones de ayuda no parecían coincidir con la interfaz.

Terminé usando LiquidXML Studio 2008 (v6), que era mucho más fácil de usar y más familiarizado de inmediato (la interfaz de usuario es muy similar a Visual Basic 2008 Express, que uso con frecuencia). El inconveniente: la capacidad de validación no está en la versión gratuita, por lo que tuve que usar la prueba de 30 días.

KnomDeGuerre
fuente
1
La pregunta es Java, pero esta respuesta no lo es. :-(
james.garriss
Para ser justos, la palabra "java" nunca aparece en la pregunta, solo las etiquetas. Me gustaría hacer la pregunta por eso, no la respuesta.
Mark Storer
Gracias James y Mark, ¡ayúdame a agudizar!
Knom