EPITA
XML et les services Web
Jean-François Perrot
XPath en Java
- Principe
- Exemple
- Contexte de
l'évaluation
- Compilation
de l'expression
- Pour en
savoir plus
Principe
Le package javax.xml.xpath
est disponible
depuis Java 1.5.0
http://java.sun.com/j2se/1.5.0/docs/api/javax/xml/xpath/package-summary.html
Comme l'explique Elliotte Rusty Harold, sur cette
API deux langages se rencontrent, avec chacun leur système conceptuel :
XPath qui parle de NodeSets, de Nodes,
de Strings, de Booleans et de Numbers,
et Java-DOM qui connaît des NodeList
s, et
aussi des Node
s, des String
s,
des Boolean
s et des Number
s,
mais sans aucun doute en des sens différents.
On va donc y trouver une traduction.
Les objets XPath
sont dotés d'une méthode evaluate
- qui prend comme arguments
- une chaîne de caractères représentant une expression
en XPath (cf. XSLT) ;
- une
InputSource
(org.xml.sax.InputSource
),
ou un Document
, ou un Node
,
ou une NodeList
;
- une constante, attribut statique de la classe
javax.xml.xpath.XPathConstants
qui détermine le type du résultat.
- et qui renvoie comme résultat un objet qui pourra être
converti en le type indiqué par le 3ème argument,
à savoir NodeSet
(synonyme de NodeList
),
Node
, Boolean
,
String
ou Number
(en fait, Double
= réels en double précision).
Exemple
- On obtient on objet
XPath
par
XPath xp = XPathFactory.newInstance().newXPath();
- Une
InputSource
s'obtient
directement à partir du nom du fichier :
InputSource ip = new InputSource("Nom_note1.xml");
- Une expression XPath bien adaptée à ce fichier :
String
exp = "/liste/eleve[@note=20]"
- la constante est l'un des 5 attributs
static
de la classe XPathConstants
XPathConstants.NODESET
,
synonyme de org.w3c.dom.NodeList
XPathConstants.NODE
,
synonyme
de org.w3c.dom.Node
XPathConstants.STRING
,
synonyme de java.lang.String
XPathConstants.BOOLEAN
,
synonyme de la classe java.lang.Boolean
et non du type primitif boolean
,
puisqu'il s'agit de convertir un objet
XPathConstants.NUMBER
,
synonyme de la classe java.lang.Double
et non du type primitif double
pour la même raison.
Avec ce matériel nous pouvons demander [ tous ces essais sont rassemblés dans le fichier EssaiXPath.java
]
- avec une signification évidente
NodeList les_meilleurs =
(NodeList)
xp.evaluate(expr, ip, XPathConstants.NODESET);
qui nous renvoie la liste des élèves dont la note est 20.
Nous pourrons exploiter cette liste suivant le procédé ordinaire :
for( int i = 0; i <
les_meilleurs.getLength();
i++ ){
Sytem.out.println(les_meilleurs.item(i).getAttribute("nom"));
}
- avec une signification moins évidente
Node le_meilleur = (Node)
xp.evaluate(expr, ip, XPathConstants.NODE);
qui nous renvoie le premier de la liste des élèves dont la note est 20
(ou null
si cette liste est vide).
Nous pouvons alors demander :
System.out.println(((Element)le_meilleur).getAttribute("nom"));
- le type de retour
String
s'applique
là où une chaîne apparaît : typiquement une valeur d'attribut,
ou un contenu textuel - autrement on obtient une chaîne vide.
String le_nom_du_meilleur =
(String)
xp.evaluate( expr+"/@nom", ip, XPathConstants.STRING);
System.out.println(le_nom_du_meilleur);
- comme une expression XPath est normalement destinée à
choisir un ensemble de sommets de l'arbre XML,
l'interpétation du type de retour Boolean
est claire : ce sera Boolean.FALSE
si l'ensemble est vide,
et Boolean.TRUE
si on a trouvé au
moins un sommet.
Boolean quid
= (Boolean)
xp.evaluate( expr, ip, XPathConstants.BOOLEAN);
if( quid ){ // autoboxing : plus besoin de
booleanValue()
System.out.println("Ya d'l'espoir...");
}else{
System.out.println("Le niveau baisse !");
}
- enfin, l'interprétation numérique s'applique à une
chaîne
dont la forme autorise cette interpétation,
sans quoi on obtient comme réponse NaN
(Not
a Number),
et ce nombre est un réel en double précision, non un entier.
Son emploi n'a de sens que si l'intégrité du nombre ne joue aucun rôle,
par exemple dans le calcul d'une moyenne :
String[] noms = {"Pierre", "Paul", "Jacques"};
double total = 0.0;
for( int i = 0; i < noms.length; i++ ){
String expr =
"/liste/eleve[@nom='"+noms[i]+"']/@note"
;
// ne pas oublier les quotes autour du nom !
Double
note =(Double) xp.evaluate(
expr
,
ip, XPathConstants.NUMBER);
total += note; // avec autoboxing
}
double moyenne = total/noms.length;
System.out.println(moyenne);
Contexte
de l'évaluation
Il s'agit du deuxième argument de xp.evaluate
.
Il désigne le document
XML dans lequel la recherche va s'effectuer.
Comme nous venons de le voir, ce document peut être donné par une InputSource
,
mais il peut aussi bien se réaliser
- comme un
org.w3c.dom.Document
,
la recherche a lieu dans tout le Document
en
question
- comme un sommet de l'arbre XML de ce document, donné
par un objet
org.w3c.dom.Node
,
la recherche a lieu en principe dans le sous-arbre issu de ce sommet,
mais tout dépend de l'expression !
p. ex. un chemin "absolu" partant de la racine, gardera la même
interprétation quel que soit le sommet de départ
- (d'après la littérature) comme une liste de sommets,
donnée par un objet
org.w3c.dom.NodeList
.
mais je ne suis pas arrivé à faire fonctionner cette configuration !
On peut donc remplacer dans le code expérimental ci-dessus
InputSource ip = new InputSource("Nom_note1.xml");
par
Document ip =
NNTxt2Xml.lireText("Nom_note1.xml",
"UTF-8");
Document doc =
NNTxt2Xml.lireText("Nom_note1.xml", "UTF-8");
Element ip = doc.getDocumentElement();
Exemple : fichier HistoXPath.java
,
qui donne une autre réalisation de la transformation tableau
de notes --> histogramme
où la partie centrale de la construction est effectuée en une seule
boucle par :
BufferedReader entree = new BufferedReader(new FileReader(nomFich));
Element liste = NNTxt2Xml.lireText(entree).getDocumentElement();
XPath xp = XPathFactory.newInstance().newXPath();
for( int i = 0; i < nmax ; i++ ){
Element nbr = histo.createElement("number");
val.appendChild(nbr);
String expr = "count(eleve[@note="+i+"])";
String combien = (String) xp.evaluate(expr, liste, XPathConstants.STRING);
nbr.appendChild(histo.createTextNode(combien));
}
Compilation
de l'expression
Si une même requête doit être souvent répétée, on gagnera en efficacité
par
XPathExpression cexpr = xp.compile(expr);
Object res = cexpr.evaluate(ip, typeDeRetour);
sans rien changer à la suite.
Pour
en savoir plus
notamment sur la conduite à tenir en présence de namespaces,
voir http://www.ibm.com/developerworks/library/x-javaxpathapi.html
.