Valider des fichiers XML en Java

La vérification de fichiers XML en fonction de schémas XSD peut vite devenir plus complexe qu'elle n'en a l'air...

Imaginons une application Java qui utilise des schémas imbriqués situés dans un certain répertoire pour valider des fichiers situer dans un autre répertoire :

Dans C:\schemas\ :

schema-parent.xsd


<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" version="3.0">
 <xs:include schemaLocation="schema-fils.xsd"/>
 <xs:element name="OPS">
  <xs:annotation>
   <xs:documentation>bla bla.</xs:documentation>
  </xs:annotation>
  <xs:complexType>
   <xs:choice>
    ...
</xs:schema>

schema-fils.xsd


<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" version="3.0">
 <xs:include schemaLocation="schema-petit-fils.xsd"/>
 <xs:element name="TOT">
  <xs:annotation>
   <xs:documentation>bla bla.</xs:documentation>
  </xs:annotation>
  <xs:complexType>
   <xs:choice>
    ...
</xs:schema>

schema-petit-fils.xsd


<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" version="3.0">
 <xs:element name="XDF">
  <xs:annotation>
   <xs:documentation>bla bla.</xs:documentation>
  </xs:annotation>
  <xs:complexType>
   <xs:choice>
    ...
</xs:schema>

Dans C:\xml\ :

avalider.xml


<?xml version="1.0" encoding="UTF-8"?>
<OPS xmlns="..." xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="schema-parent.xsd" AD="FRA" FR="AA"
 ON="540" OD="2012-11-12" OT="15:08">
 <DAT TM="CU">
  ...
 </DAT>
</OPS>

Et ci-dessous le code permettant d'exécuter la validation :


...
import org.xml.sax.helpers.DefaultHandler;
...

XSDHandler handler = new XSDHandler();

SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setNamespaceAware(true);
spf.setValidating(true);
SAXParser sp = spf.newSAXParser();

sp.getParser().setLocale(Locale.ENGLISH);

sp.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema");

InputStream schemaIs = getClass().getResourceAsStream("schema-parent.xsd");
sp.setProperty("http://java.sun.com/xml/jaxp/properties/schemaSource", schemaIs);
sp.parse(fichierAvalider, handler);

...

class XSDHandler extends DefaultHandler {

 private final static Logger logger = Logger.getLogger(XSDHandler.class);

 @Override
 public void fatalError(SAXParseException e) {
  logger.error("Erreur fatale :" + message);
 }

 @Override
 public void error(SAXParseException e) {
  logger.error("Erreur :" + message);
 }

 @Override
 public void warning(SAXParseException e) {
  logger.warn("Warning :" + message);
 }
}

Lors de l'exécution, l'erreur suivante va être levée :

Echec de la lecture du document de schéma 'schema-fils.xsd' pour les raisons suivantes : 1) Le document est introuvable ; 2) Le document n'a pas pu être lu ; 3) L'élément racine du document n'est pas .

Ceci est du à la présence de l'attribut schemaLocation au sein même du fichier XML, qui prévaut sur toute autre déclaration schemaLocation dans les XSD. Le SAXParser va donc tenter de trouver le schéma relativement au fichier en cours de vérification (dans C:\xml\)... Il est donc préférable de supprimer cet attribut.

Cependant, même après cette correction, l'erreur persiste... En effet, dans ce cas de schémas imbriqués, le SAXParser utilise l'attribut schemaLocation de schema-parent.xsd contenant normalement le chemin relatif vers le schéma fils, mais recherche toujours de manière relative au fichier XML!

Pour régler ce problème, sans avoir à indiquer de chemin absolu dans les schémas, il est possible d'indiquer au SAXParser comment retrouver ses petits. Pour ce faire, il faut modifier le XSDHandler qui lui est passé pour surcharger la méthode resolveEntity :


class XSDHandler extends DefaultHandler {

 private final static Logger logger = Logger.getLogger(XSDHandler.class);

 @Override
 public void fatalError(SAXParseException e) {
  logger.error("Erreur fatale :" + message);
 }

 @Override
 public void error(SAXParseException e) {
  logger.error("Erreur :" + message);
 }

 @Override
 public void warning(SAXParseException e) {
  logger.warn("Warning :" + message);
 }

 @Override
 public InputSource resolveEntity(String arg0, String arg1)
   throws IOException, SAXException {
  File tmp = new File(arg1);
  return new InputSource(new FileInputStream(new File(getClass().getResource("/v3/"+tmp.getName()).toURI())));
 }
}

Le deuxième argument arg1 contient le chemin complet où le parser cherche le schéma imbriqué. Il suffit donc d'un petit tour de passe-passe pour renvoyer le bon fichier!

Hope this helps!


Fichier(s) joint(s) :

0 commentaires: