Étiquette : XML

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

[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 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

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

[C#] Webservice : XML vers Dataset

[C#] Webservice : XML vers Dataset

Ce que nous venons de voir avec WinDev est également réalisable en C# ou même en Visual Basic. En effet, à partir d’un document XML possédant une structure particulière, il est possible de générer et d’exploiter une variable de type “Dataset”.

Un exemple est donné sur le site de Microsoft MSDN, le centre de ressources pour les développeurs. Initialement, les services du site WebserviceX sont destinés à être exploités en .NET, et donc, les réponses renvoyées ont parfois été adaptées en fonction.

[WD15] Utiliser un Webservice qui renvoie du XML

[WD15] Utiliser un Webservice qui renvoie du XML

Aujourd’hui, présentation d’un cas plutôt intéressant, qui va nous faire découvrir des fonctions XML. Nous allons voir ici comment traiter la réponse d’un Webservice qui renvoie une chaîne au format XML et comment parcourir les différents éléments.

Une deuxième solution sera également envisagée: l’utilisation de la fonction HImportXML, qui permet d’intégrer à une table (d’une base de données) le contenu d’un document XML, pour autant qu’il respecte une certaine structure. Pour vous aider, nous avons, en tant qu’exemple, importé et utilisé le Webservice “Global Weather”, que l’on retrouve sur le site WebserviceX.NET.

Que va-t-on réaliser?

Le Webservice utilisé comme exemple ne sera pas détaillé et exploité dans son entièreté. Seul l’affichage d’une liste de couples “Pays/Ville” sera affichée, lorsqu’on saisira un nom partiel ou complet de pays dans un champ texte classique. Nous aurons donc une fenêtre, un champ de type “saisie”, ainsi qu’un champ de type “combo” (liste déroulante). Si vous êtes un habitué de WinDev, vous pouvez déjà créer ces éléments et les nommer comme ceci :

  • Champ de saisie : SAI_Pays.
  • Liste déroulante : COMBO_Cities.

Importer le Webservice

Pour importer le web service, n’hésitez pas à suivre la procédure détaillée dans l’article suivant : importer un web service. Pour rappel, WinDev générera les types et les opérations adéquates, qui permettront d’affecter les paramètres d’appel, mais aussi de récupérer chacune des réponses.

Récupérer la liste des pays/villes

Comme vous êtes maintenant à l’aise avec les services web, vous devriez rapidement comprendre le code suivant, dans lequel on déclare trois variables : la première servira à contenir les paramètres, la seconde contiendra la réponse, et la troisième est une variable dans laquelle on stockera le nom du pays suivi du nom de la ville.

On affecte alors le paramètre, “CountryName” dans ce cas, avec la valeur du champ “Nom du pays”. Ensuite, on appelle l’opération “GetCitiesByCountry” avec la liste de paramètres, puis on stocke le résultat dans la variable “réponse”. On peut ensuite analyser la réponse en affichant ou bien en se plaçant en mode “debug”. Dans notre cas, il s’agit d’une chaîne : on peut donc l’afficher (par exemple, avec Info).

Pour cette opération, nous avons obtenu un XML qui possède la structure suivante :

Nous allons voir les différentes méthodes employées pour gérer cette réponse.

Solution 1: parcourir le document XML

La première solution consiste à parcourir le document XML. Comment faire ? Depuis que la version 15 de WinDev est parue, “parser” un document XML a été simplifié. On dispose d’un bon nombre de méthodes, telles que XMLSuivant, XMLTermine, XMLPremier, XMLDonnée, etc.

Nous allons donc expliquer, avec un bouton de code, comment descendre et analyser chacune des données pour les ajouter à la liste. On ne passe donc pas par une quelconque table ! Notez bien qu’il s’agit d’une première ébauche, et que celle-ci pourrait sans doute être améliorée…

Ce qui a été fait :

  • On initialise le document XML avec la réponse donnée par le service.
  • On lit le premier élément grâce à XMLPremier.
  • On effectue une boucle avec comme condition “tant que l’on n’est pas en dehors du document…”. Pour cela on utilise une fonction appelée XMLEnDehors(document).
  • On vérifie que l’élément est “Table”. Si ce n’est pas le cas, on va descendre d’un nœud grâce à la fonction XMLFils, puis lire l’élément qui suit. Si par contre, l’élément valait bien “Table”, alors on descend encore d’un nœud.
  • Si l’élément précédent valait table, on descend d’un nœud, et on vérifie que l’élément suivant vaut “Country”. Si c’est le cas, on ajoute la donnée à la variable “sLigne” grâce à XMLDonnée(document). On continue à descendre jusqu’à l’élément “City”. Ensuite, on ajoutera à la liste et on remontera au parent (XMLParent), tout en effectuant une lecture supplémentaire.
  • Lorsqu’on sort de la boucle, on ferme le document (XMLTermine).

Notez que les fonctions comme XMLFils et XMLParent effectuent une lecture, donc ils se positionnent sur l’enregistrement suivant !

Testez votre fenêtre et saisissez un nom de pays (par exemple, “Belgium”). Ensuite, déroulez la liste pour voir le résultat. Si cela fonctionne et que vous obtenez quelque chose de similaire à la fenêtre ci-dessous, vous pouvez passer à la seconde solution.

Solution 2 : utiliser un fichier HFSQL

Comme le document XML obtenu possède une structure bien spécifique, on va pouvoir importer celui-ci directement dans une table de base de données. Rappelons-nous la structure du fichier obtenu grâce à l’image suivante :

D’après la documentation officielle de la fonction HImportXML, notre fichier, que nous allons soit décrire via l’analyse, soit par programmation, aurait dû s’appeler “NewDataSet” et contenir une rubrique nommée “Table” avec deux sous-rubriques. Cependant, les deux sous-rubriques de “Table” auraient dû être liées à deux sous-éléments “Table_1” et “Table_2“. Ici, les sous-éléments sont “Country” et “City“. Nous allons donc faire abstraction de l’élément “Table“.

Au préalable, il est nécessaire de créer la table correspondante (NewDataSet – rubriques : Country [texte] – City [texte]) et réutiliser le code d’appel au service web avant d’insérer la partie qui suit.

Ce qui a été fait :

  • On déclare une chaine dans laquelle on va stocker le résultat, et rajouter une balise XML conforme en début de document.
  • On sauvegarde le tout dans un fichier texte avec l’extension XML.
  • On appelle la fonction HImportXML avec en paramètres, le nom de la table, et le chemin vers le fichier XML.
  • On lit le premier élément dans la table.
  • On ajoute un élément texte à la liste, qui est composé du pays, et de la ville.
  • On lit l’élément suivant jusqu’à arriver à la fin du fichier.

Si cela fonctionne, vous ne devriez pas obtenir d’erreur ou d’exception à l’exécution.

Bon développement !