Validar XML con un archivo XSD

En el tutorial pasado aprendimos a generar las clases Java a partir de la definición de un archivo XSD, por lo que ahora veremos como generar el archivo XML usando estas clases y verificar si el contenido de las mismas está conforme a lo definido por el .xsd, para trabajar este tutorial usaremos la tecnología JAXB y el IDE Netbeans.

Lo primero que necesitamos es tener el XSD, usaremos el siguiente:

<?xml version="1.0" ?>

<xs:schema version="1.0"
           xmlns:xs="http://www.w3.org/2001/XMLSchema"
           elementFormDefault="qualified">
    
    <xs:element name="person" >
        <xs:complexType>
            <xs:sequence>
                <xs:element name="id" type="xs:int" />
                <xs:element name="age">
                    <xs:simpleType>
                        <xs:restriction base="xs:integer">
                            <xs:minInclusive value="0"/>
                            <xs:maxInclusive value="120"/>
                        </xs:restriction>
                    </xs:simpleType>
                </xs:element>
                <xs:element name="name">
                    <xs:simpleType>
                        <xs:restriction base="xs:string">
                            <xs:pattern value="([a-z])*"/>
                        </xs:restriction>
                    </xs:simpleType>
                </xs:element>
                <xs:element name="gender" >
                    <xs:simpleType >
                        <xs:restriction base="xs:string">
                            <xs:length value="1" />
                            <xs:enumeration value="F" />
                            <xs:enumeration value="M" />
                        </xs:restriction>
                    </xs:simpleType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

</xs:schema>

En este archivo XSD tenemos las siguientes restricciones:

La edad debe ser un valor entero positivo que este entre 0 y 120.

<xs:element name="age">
    <xs:simpleType>
        <xs:restriction base="xs:integer">
            <xs:minInclusive value="0"/>
            <xs:maxInclusive value="120"/>
        </xs:restriction>
    </xs:simpleType>
</xs:element>

El nombre solo puede contener letras en minúsculas, usando una expresión regular podemos definir cualquier tipo de patrón para la validación de la cadena.

<xs:element name="name">
    <xs:simpleType>
        <xs:restriction base="xs:string">
            <xs:pattern value="([a-z])*"/>
        </xs:restriction>
    </xs:simpleType>
</xs:element>

Para indicar el genero debemos usar la letras F o M, usando una enumeración indicamos los posibles valores a elegir, solo podemos seleccionar uno de ellos, lo hemos definido así.

<xs:element name="gender" >
    <xs:simpleType >
        <xs:restriction base="xs:string">
            <xs:length value="1" />
            <xs:enumeration value="F" />
            <xs:enumeration value="M" />
        </xs:restriction>
    </xs:simpleType>
</xs:element>

Validar nuestra clase usando el XSD

Ahora naturalmente deseamos generar el archivo XML, pero deseamos validarlo y saber si cumple con las especificaciones definidas, lo primero que haremos será crear las clases Java a partir del archivo .xsd como lo hicimos en el tutorial previo, luego creamos una instancia de nuestra clase creada y establecemos los correspondientes campos.

Person person = new Person();
person.setId(111);
person.setAge(100);
person.setName("tutorprogramacion");
person.setGender("F");

Lo siguiente que hacemos es cargar el esquema que usaremos para validar el XML, lo cargaremos desde dentro del paquete de nuestro proyecto, aunque el mismo puede estar el otra ubicación, con el disco C: de la PC o en la red.

// instanciar una fabrica de esquemas
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);

// cargar el esquema XSD
StreamSource validator = new StreamSource(JavaApplicationXsd.class.getResourceAsStream("test.xsd"));
Schema schema = factory.newSchema(validator);

Luego creamos el Marshaller que nos permitirá generar el XML a este debemos indicarle el Schema que usaremos para validar, activaremos la opción JAXB_FORMATTED_OUTPUT para mostrar la salida en forma legible.

// create JAXB context and instantiate marshaller
JAXBContext context = JAXBContext.newInstance(Person.class);

// crear el marshaller y formatear la salida, solo para mejor visualizacion
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
m.setSchema(schema);
m.marshal(person, System.out);

El método marshal(person, System.out) genera el XML, como puedes ver el resultado lo enviamos a la consola de salida, pero puedes indicar un archivo u otro flujo. 

El código completo queda de este modo:

public class JavaApplicationXsd {

    public static void main(String[] args) {

        try {
            
            Person person = new Person();
            person.setId(111);
            person.setAge(100);
            person.setName("tutorprogramacion");
            person.setGender("F");
            
            SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
            
            // cargar el esquema XSD
            StreamSource validator = new StreamSource(JavaApplicationXsd.class.getResourceAsStream("test.xsd"));
            Schema schema = factory.newSchema(validator);
            
            // create JAXB context and instantiate marshaller
            JAXBContext context = JAXBContext.newInstance(Person.class);

            // crear el marshaller y formatear la salida, solo para mejor visualizacion
            Marshaller m = context.createMarshaller();
            m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
            m.setSchema(schema);
            m.marshal(person, System.out);
            
        } catch (JAXBException | SAXException ex) {
            ex.printStackTrace();
        }                    
    }
}

Ahora al ejecutar el programa, se genera el XML y todo bien, pero que pasa si los datos de la clase violan las restricciones, por ejemplo, probemos con esta clase:

Person person = new Person();
person.setId(111);
person.setAge(200);
person.setName("Tutorprogramacion");
person.setGender("X");

Al hacer los cambios y ejecutar podrás ver las excepciones que se producen, pero como hacemos si deseamos capturar cada uno de los errores producidos.

Si lo que deseamos es solamente verificar si la clase es válida, debemos crear una clase que implemente ErrorHandler con ella podremos detectar los errores que se produzcan, ejemplo:

import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

public class CustomValidationErrorHandler implements ErrorHandler {
     
    @Override
    public void warning(SAXParseException ex) throws SAXException {
        System.out.println("WARNING: " + ex.getLocalizedMessage());
    }
  
    @Override
    public void error(SAXParseException ex) throws SAXException {
        System.out.println("ERROR: " + ex.getLocalizedMessage());
    }
  
    @Override
    public void fatalError(SAXParseException ex) throws SAXException {
        System.out.println("FATAL ERROR: " + ex.getLocalizedMessage());
    }
}

El objeto SAXParserException nos proporciona la información referente al error, mensaje, numero de línea y columna en donde se produjo, etc., entre otras cosas.

try {
     Validator xsdValidator = schema.newValidator();
     xsdValidator.setErrorHandler(new CustomValidationErrorHandler());
     xsdValidator.validate(new JAXBSource(context, person));          
} catch (IOException ex) {
     System.out.println("Error en validacion");
}

Esto lo ejecutamos antes de usar el Marshaller aunque el mismo no es necesario.

Otra opción es validar en el momento en que se crea el XML, para ello usaremos el método setEventHandler() para definir un manejador de errores para el Marshaller, si devolvemos true se crea el archivo aunque tenga errores, si devolvemos false se lanza una excepción.

// crear el marshaller y formatear la salida, solo para mejor visualizacion
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
m.setSchema(schema);

m.setEventHandler(handler -> {           
    System.out.println("ERROR: " + handler.getMessage());             
    return true;
});

m.marshal(person, System.out);

Usando handler.getLocator() podemos obtener información más detallada.

Es todo por ahora, puedes descargar el código fuente del proyecto y probar las distintas posibilidades.

Descargar: validar-xml.zip

Comentarios

  1. Hola, me ha servido tu tutorial, pero quisiera saber si hay alguna forma de validar, no el xml que se genera a partir de una clase.. sinó el que me llegaría en los datos de entrada de un webservices.. También sería necesario utilizar la clase CustomValidationErrorHandler?

    ResponderEliminar
    Respuestas
    1. La idea sería similar solo que deberías cargar el XML que envía el webservice y el .xsd que este te proporcione, en cuanto a la clase CustomValidationErrorHandler es para poder manejar los errores detectados.

      Eliminar

Publicar un comentario

Temas relacionados

Entradas populares de este blog

tkinter Grid

Conectar SQL Server con Java

Controles y Contenedores JavaFX 8 - I

tkinter Canvas