Recherche sur le blog

jeudi 17 octobre 2013

[Java] Exemple d’utilisation de l’API StAX 2

StAX 2 est une API expérimentale, de type “pull”, basée sur l’API StAX. La seule implémentation connue jusqu’à présent est Woodstox. Elle vise à étendre les possibilités de StAX en ajoutant des fonctionnalités supplémentaires : l’accès à des informations qui étaient inaccessibles auparavant (DOCTYPE, index de l’attribut, vérifier si l’élément est vide, obtenir le niveau actuel de l’élément), la conversion du contenu textuel en contenu typé (v3.0 – Woodstock v4.0),etc. Le téléchargement des packages se fait sur la page officielle.

L’utilisation de StAX est intéressante, surtout pour effectuer un traitement sur des gros fichiers, car sa consommation mémoire est très basse. De plus, l’accès est séquentiel : les retours arrière ne sont pas possibles.

Dans l’exemple, nous utilisons l’API de type “curseur”. Celui-ci nous renvoie la position dans le document, ensuite il suffit d’utiliser une série de fonctions pour obtenir telle ou telle information.

Initialisation et configuration

Il faut tout d’abord initialiser et configurer notre parseur. Nous utilisons ici l’API expérimentale, donc certaines classes se voient suffixées d’un “2”. On utilise la fonction “newInstance()” de la factory (“fabrique”).
// Créer une instance de la factory XMLInputFactory
XMLInputFactory2 xmlf = (XMLInputFactory2) XMLInputFactory2.newInstance();
xmlif.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, Boolean.FALSE);
xmlif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
xmlif.setProperty(XMLInputFactory.IS_COALESCING, Boolean.FALSE);
Il faut ensuite configurer les propriétés. Certaines sont booléennes, d’autres sont des propriétés objet. Par exemple, la propriété “IS_COALESCING” à “False” permet de renvoyer de multiples évènements de types “caractère”, plutôt que de stocker tout le texte en mémoire. Petite note : lors du parcours, le buffer est limité à 8000 caractères, ensuite il se vide pour se remplir à nouveau.

Des fonctions supplémentaires, comme configureForSpeed(), sont utiles dans le cas où on ne souhaite pas définir chaque paramètre un à un. En effet, les valeurs de certains paramètres seront choisies en fonction de la méthode utilisée. La documentation officielle indique quels sont les paramètres impactés et les valeurs effectives.

Récupérer un parser

La seconde étape consiste à récupérer un parser grâce à la méthode createXMLStreamReader(). Celle-ci est surchargée, on peut donc lui passer un objet de type “File”.
// Créer le reader pour lire le fichier XML
// Passage du chemin en paramètre
xmler = xmlif.createXMLStreamReader(new File("CHEMIN"));
Cela crée un parser de type “curseur”. Pour savoir s’il reste des éléments à lire, on doit utiliser la méthode “hasNext()”. Pour passer à l’élément suivant, on utilise la fonction next(). Cela renvoie un entier qui correspond à une constante (exemples : CHARACTERS, START_ELEMENT, END_ELEMENT, ATTRIBUTE, etc). Dans une boucle, on pourra tester cet entier.

Effectuer un traitement différent selon l’évènement

Les constantes pour les types d’évènements sont définies dans la classe “XMLEvent2”. Il en existe une bonne dizaine mais en général, elles ne sont pas toutes utilisées. On peut par exemple effectuer un "switch…case…" pour effectuer un traitement différent, comme dans l’exemple ci-dessous.
// Déclarer le type d'événement
int eventType;
while (xmler.hasNext()) 
{
 eventType = xmler.next();
 switch(eventType)
 {
  // Balise ouvrante
  case XMLEvent2.START_ELEMENT:
   stElt = xmler.getName().toString();
   switch(stElt)
   {
    // Traitement de l'élément
   }
   
   break;
   
  // Elément texte (contenu élément)
  case XMLEvent2.CHARACTERS:
   break;
 }
}
Lorsqu’on se trouve face à un élément de type CHARACTERS, on peut commencer à traiter le contenu et stocker le tout dans une ou plusieurs chaines différentes. Par exemple, on souhaite récupérer le contenu de l’élément “<UPC>”. Comme nous l’avons mentionné ci-dessus, nous avons désactivé la propriété “Is Coalescing”. Nous travaillons avec un buffer pré-défini de 8000 caractères. Il faut donc s’assurer de bien compléter chaque chaine pour retrouver l’ensemble du contenu.
// On est dans le case CHARACTERS
// Si traitement de l'article (UPC correct + présent en DB)
if (bTrtArt == true)
{
 String txt = xmler.getText().trim();
 if (!xmler.isWhiteSpace()) 
 {
  // Vérifier l'élément dans lequel on était
  switch(stElt) 
  {
   case "UPC": 
    sUPC += txt;
    if (sUPC.length==13)
    {
     if (treeMap.containsKey(sUPC))
      sCart = treeMap.get(sUpc);
     else
      bTrtArt = false;
    }
    
    break;
  }
 }
}
La méthode getText() permet de récupérer le contenu et de le stocker dans une chaine de caractères. Une fois le contenu récupéré, on regarde à quel élément il appartient (on connait la position actuelle car on a stocké le nom de l’élément parcouru lors du traitement de l’évènement START_ELEMENT). Ensuite, comme on voit qu’il s’agit de l’élément “<UPC>”, on ajoute le contenu à la chaine “sUPC” puis on vérifie si elle a atteint 13 caractères (souvenez-vous : le buffer…)

Traitement de l'élément de fin (END_ELEMENT)

En général, c’est là qu’on va appliquer une opération d’insertion dans la base de données par exemple. Effectivement, supposons la structure suivante, qui décrit un article (un livre), que l’on veut ajouter à la base de données.
<article>
 <upc>9781234567890</upc>
 <title>Ceci est un livre</title> 
</article>
C’est au moment où l’on rencontre la balise fermante qu’il faut idéalement enregistrer les données dans votre table. En résumé, dans votre boucle, vous devez tester si votre évènement est de type END_ELEMENT, ensuite indiquer les différents paramètres dans un “PreparedStatement”, éventuellement initialisé à l’avance.

Libérer les ressources

En Java, il est très important de faire attention aux ressources utilisées, et il faut bien entendu les libérer à un moment donné, afin de vider la mémoire. Toujours prendre l’habitude d’appeler la méthode close() quand elle est disponible. C’est le cas ici pour notre parser.
if (xmler != null) try {
 xmler.close();
} catch (XMLStreamException ex) {
 Logger.setLogger(XMLParser.class.getName())
    .log(Level.SEVERE, null, ex);
}
Une bonne pratique consiste à placer ce code dans le bloc d’instruction “finally” de votre traitement d’exceptions. Ainsi, même si une erreur se produit, tout le code placé dans ce bloc sera exécuté quoi qu’il arrive.

Sources
   
Introduction à StAX – Développez

Aucun commentaire: