MokaByte 84 - Aprile 2004 
Jakarta Commons
VI parte: JXPath
di
Alessandro "Kazuma"
Garbagnati
Nei mesi precedenti abbiamo visto come fosse possibile utilizzare e sfruttare la potenza dello standard XML all'interno delle nostre applicazioni Java. Tramite l'ausilio di Commons Digester abbiamo letto un file realizzato con questo standard trasformandolo in un JavaBean o semplicemente utilizzandolo per configurare la nostra applicazione. Aggiungendo poi Commons Betwixt abbiamo anche visto come fosse possibile utilizzare XML per gestire la persistenza dei JavaBeans, senza dover ricorrere all'uso del database per piccole applicazioni.
Vi sono situazioni, però, per cui è necessario accedere ai dati contenuti all'interno di un documento XML in maniera meno convenzionale, per estrarne informazioni semplici o agglomerate in base ad alcuni criteri, come e vere proprie "query". Per questo tipo di operazioni esiste uno standard del W3C, ossia XPath.

Cos'è XPath
XPath definisce la sintassi e le regole tramite le quali è possibile accedere alle informazioni contenute all'interno di un documento XML, attraverso una rappresentazione logica ad albero del documento stesso. Questo semplifica maggiormente l'accesso ai nodi grazie a delle espressioni che sono quasi identiche a quelle che siamo abituati ad utilizzare quando stiamo lavorando su un file system di un computer. Oltre a questo, XPath fornisce anche una serie di funzioni che permettono di gestire e manipolare i dati come numeri, stringhe e valori booleani.

Prendiamo, ad esempio, un documento di test che rappresenta una semplice raccolta di video, in differenti formati:

<?xml version="1.0" encoding="ISO-8859-1"?>
<videoteca>
<video tipo="DVD">
<titolo>Titolo 1</titolo>
<anno>2000</anno>
<registi>
<nome>Regista 1</nome>
</registi>
<attori>
<nome>Attore 1</nome>
<nome>Attore 2</nome>
</attori>
</video>
<video tipo="VHS">
<titolo>Titolo 2</titolo>
<anno>2000</anno>
<registi>
<nome>Regista 2</nome>
</registi>
<attori>
<nome>Attore 3</nome>
<nome>Attore 4</nome>
</attori>
</video>
<video tipo="DVD">
<titolo>Titolo 3</titolo>
<anno>2001</anno>
<registi>
<nome>Regista 2</nome>
</registi>
<attori>
<nome>Attore 5</nome>
<nome>Attore 1</nome>
<nome>Attore 2</nome>
</attori>
</video>
<video tipo="VHS">
<titolo>Titolo 4</titolo>
<anno>2002</anno>
<registi>
<nome>Regista 3</nome>
</registi>
<attori>
<nome>Attore 3</nome>
<nome>Attore 5</nome>
</attori>
</video>
<video tipo="DVD">
<titolo>Titolo 5</titolo>
<anno>2003</anno>
<registi>
<nome>Regista 1</nome>
</registi>
<attori>
<nome>Attore 4</nome>
<nome>Attore 5</nome>
</attori>
</video>
<video tipo="VHS">
<titolo>Titolo 6</titolo>
<anno>2004</anno>
<registi>
<nome>Regista 1</nome>
<nome>Regista 3</nome>
</registi>
<attori>
<nome>Attore 1</nome>
<nome>Attore 2</nome>
</attori>
</video>
<video tipo="DVD">
<titolo>Titolo 7</titolo>
<anno>2004</anno>
<registi>
<nome>Regista 2</nome>
</registi>
<attori>
<nome>Attore 1</nome>
<nome>Attore 3</nome>
</attori>
</video>
</videoteca>

Vediamo, in breve, le caratteristiche principali di questo linguaggio, partendo proprio da modo utilizzato per localizzare un nodo all'interno del documento.
XPath sfrutta delle espressioni che seguono lo stesso pattern utilizzato per i percorsi all'interno del file system dei computer, ossia una lista di elementi, separati dalla barra "/". Il pattern seleziona l'elemento che corrisponde a quel percorso. E così, applicato al file di esempio, l'espressione "/videoteca/video/attori/attore" indica tutti gli attori all'interno della videoteca.
Così come per i file system, se il percorso inizia con la singola barra ("/"), rappresenta una posizione assoluta, altrimenti ci si riferisce ad una posizione relativa.
Diversamente dal file system, però, esiste anche la possibilità di indicare un nodo, precedendolo dalla doppia barra ("//"). In questo caso verrebbero selezionati tutti gli elementi, indipendentemente dalla loro posizione gerarchica. Se l'espressione "/videoteca/video/attori/nome" serve per indicare tutti gli attori, l'espressione "//nome" indicherebbe non solo gli attori, ma anche i registi.
Con XPath è possibile utilizzare la "wildcard", ossia un carattere jolly che serve per indicare gli elementi sconosciuti. Il pattern "/videoteca/video/*/nome" indica tutti i registi e tutti gli attori, mentre l'espressione composta "//*" seleziona tutti gli elementi del documento.
La sintassi del lingaggio prevede anche la possibilità di creare espressioni che sono in realtà composte da più espressioni, separate fra loro dal carettere "|" che significa, come spesso nel gergo informatico, "or". Se volessimo selezionare, ad esempio, tutti i registi e gli attori, si potrebbe ricorrere all'espressione "/videoteca/video/registi/nome | /videoteca/video/attori/nome" oppure "//registi/nome | //attori/nome" o anche "/*/*/registi/nome | /*/*/attori/nome".
Tutti queste espressioni servono per selezionare tutti i nodi che corrispondono al pattern, ma è anche possibile utilizzare le parentesi quadre ("[]") per identificarne uno con precisione. Ad esempio, l'espressione "/videoteca/video[2]/titolo" corrisponderebbe al titolo del secondo video e, quindi, il risultato sarebbe "Titolo 2".
L'ultimo carattere speciale all'interno delle espressioni è il simbolo "@", che serve per identificare un attributo all'interno di un elemento. Per selezionare tutti i tipi di video presenti nel documento, si può utilizzare un pattern come "/videoteca/video/@tipo", oppure selezionare tutti i titoli dei dvd, attraverso l'espressione "/videoteca/video[@tipo='DVD']/nome". Se si volesse poi ottenere la lista di tutti gli attori presenti nei vari video in formato VHS si potrebbe utilizzare questo pattern "/videoteca/video[@tipo='VHS']//attori/nome". A dire il vero, il "//" potrebbe anche essere sostituito con la barra singola, ma è stato utilizzato per mostrare che non deve necessariamente essere all'inizio dell'espressione.

Come accennato in precedenza, XPath fornisce anche una serie di funzioni che permettono di ottenere ulteriori informazioni contenute nel documento. Ad esempio è possibile sapere il numero di elementi che corrispondono ad una espressione attraverso l'uso della funzione "count()": "count(/videoteca/video[@tipo='VHS'])" ritornerebbe "3". mentre con "count(/videoteca/video[@tipo='DVD'])" si otterrebbe "4".


Figura 1
- L'elenco delle funzioni di XPath è accessibile all'interno delle specifiche, http://www.w3.org/TR/xpath#corelib

Così come "count()", esistono altre funzioni che operano direttamente sugli elementi del documento, come "position()", che ritorna la posizione dell'elemento all'interno della gerarchia, oppure come "last()", che fornisce la posizione dell'ultimo elemento del documento corrispondente al pattern indicato.
Ma in aggiunta a questa tipologia di funzioni, ne esistono altre che consentono di manipolare numeri, stringhe e valori booleani. Funzioni come "concat()" per concatenare tra loro delle stringhe, come "round()" che arrotonda un valore decimale o semplicemente di conversione tra i vari tipi attraverso "string()", "number()" o "boolean()".

L'elenco delle funzioni, divise per tipologia, è disponibile all'interno della pagina del W3Consortium che contiene le specifiche del linguaggio, all'indirizzo http://www.w3.org/TR/xpath.

 

Commons JXPath
Dopo aver introdotto le caratteristiche principali XPath, si può cercare di capire come sia possibile mettere in pratica la potenza di questo linguaggio attraverso il componente Jakarta Commons JXPath (http://jakarta.apache.org/commons/jxpath/), attualmente giunto alla versione 1.1.


Figura 2
- La homepage di Commons JXPath, all'indirizzo http://jakarta.apache.org/commons/jxpath/

Come sempre, prima di tutto, è necessario acquisire il pacchetto, come sempre disponibile sia in versione sorgente (all'indirizzo http://jakarta.apache.org/site/sourceindex.cgi) che in versione binaria (http://jakarta.apache.org/site/binindex.cgi).
Se si conosce la sintassi di XPath, la fase di acquisizione è senza dubbio la parte più complessa, perché utilizzare il pacchetto è tanto facile quanto potente.
L'esempio più semplice consiste nell'ottenere alcune informazioni dal documento XML visto in precedenza:

import java.net.*;
import org.apache.commons.jxpath.*;
import org.apache.commons.jxpath.xml.*;

public class Esempio1 {

  public static final void main(String[] args) {
    // lettura documento XML all'interno di un container
    URL docUrl = Esempio1.class.getResource("test.xml");
    Container videoteca = new DocumentContainer(docUrl);

    // creazione del contesto JXPath
    JXPathContext context = JXPathContext.newContext(videoteca);

    // Quanti sono in totale i video?
    Double video = (Double)context.getValue("count(/videoteca/video)");

    // e quanti VHS e DVD?
    String quantiVhs = "count(/videoteca/video[@tipo = 'VHS'])";
    String quantiDvd = "count(/videoteca/video[@tipo = 'DVD'])";
    Integer dvd = (Integer)context.getValue(quantiVhs, Integer.class);
    Integer dvd = (Integer)context.getValue(quantiDvd, Integer.class);

    // risultato
    System.out.println("Totale video = " + video.toString());
    System.out.println(" VHS: " + vhs.toString());
    System.out.println(" DVD: " + dvd.toString());
  }
}

Le prime tre righe di codice sono il cuore di questo piccolo esempio.
Prima di tutto viene definito il "Container", un oggetto che fornisce un meccanismo per accedere al contenuto del documento XML in modo completamente trasparente.

URL docUrl = Esempio1.class.getResource("test.xml");
Container videoteca = new DocumentContainer(docUrl);

Il documento, in questo caso passato sottoforma di URL, viene passato come argomento al costruttore del contenitore e non verrà letto ed interpretato sino a quando non sarà ritenuto necessario a seguito di una esplicita richiesta.
A questo punto, per poter accedere alle informazioni contenute nel documento attraverso l'uso del linguaggio XPath, è necessario creare il JXPathContext, ossia il contesto che si occupa di interpretare le nostre richieste e fornire le risposte.

JXPathContext context = JXPathContext.newContext(videoteca);

A questo punto abbiamo lo strumento per poter utilizzare il linguaggio XPath all'interno di una applicazione Java, ad esempio utilizzando il metodo "getValue()". Per vedere quanti siano in totale i video della videoteca basta utilizzare l'espressione "count(/videoteca/video)":

Double video = (Double)context.getValue("count(/videoteca/video)");

Il metodo ritorna un oggetto il cui tipo dipende dall'espressione utilizzata. Un numero come in questo caso viene restituito, di default, sottoforma di Double. Esiste però la possibilità di chiedere a JXPath di ritornare il valore in un tipo differente, semplicemente fornendo la classe che si intende ottenere, come nell'esempio, dove il numero di video in formato vhs o dvd sarà ritornato come Integer:

String quantiVhs = "count(/videoteca/video[@tipo = 'VHS'])";
String quantiDvd = "count(/videoteca/video[@tipo = 'DVD'])";
Integer dvd = (Integer)context.getValue(quantiVhs, Integer.class);
Integer dvd = (Integer)context.getValue(quantiDvd, Integer.class);

Il programma termina poi stampando a video i risultati ottenuti.
Le espressioni, però, non sempre ritornano un valore singolo come si è visto in questo esempio e vi sono infatti situazioni in cui al pattern XPath corrisponde più di un nodo o di un elemento. In questi casi, "getValue()" ritornerebbe un solo valore, ossia il "primo" trovato, ignorando gli altri. Se provassimo questa linea di codice:

String val = (String)context.getValue("//nome");

otterremmo solo il primo valore, ossia "Regista 1". Per poter accedere a tutti i valori che corrispondono a quel pattern è necessario utilizzare il metodo "iterate()", come in questo secondo esempio:

import java.net.*;
import java.util.*;
import org.apache.commons.jxpath.*;
import org.apache.commons.jxpath.xml.*;

public class Esempio2 {

  public static final void main(String[] args) {

    // lettura documento XML all'interno di un container
    URL docUrl = Esempio1.class.getResource("test.xml");
    Container videoteca = new DocumentContainer(docUrl);

    // creazione del contesto JXPath
    JXPathContext context = JXPathContext.newContext(videoteca);

    // Lista video VHS
    System.out.println("VHS:");
    Iterator iter;
    iter = context.iterate("/videoteca/video[@tipo = 'VHS']/titolo");
    while (iter.hasNext()) {
      String titolo = (String)iter.next();
      System.out.println("\t" + titolo);
    }

    // Lista video DVD
    System.out.println("DVD:");
    iter = context.iterate("/videoteca/video[@tipo = 'DVD']/titolo");
    while (iter.hasNext()) {
      String titolo = (String)iter.next();
      System.out.println("\t" + titolo);
    }
  }
}

Il metodo "iterate()" ritorna sempre un java.util.Iterator contenente tutti i valori che corrispondono all'espressione indicata. In questo codice viene utilizzato prima per raccogliere tutti i titoli dei video in formato vhs e, quindi, dvd.
Teoricamente l'articolo potrebbe chiudersi qui dopo questa veloce, ma semplicissima, analisi di come JXPath sia l'interfaccia Java allo standard XPath. Invece...

 

Non solo XML
Il gruppo che si occupa del progetto e dello sviluppo di questo componente, ha voluto andare oltre, pensando che la stessa identica logica di XPath poteva anche essere applicata agli oggetti Java, in particolare ai JavaBeans. Prendiamone uno:

public class Video {


  // attributi
  private String tipo;
  private String titolo;
  private int anno;
  private String[] attori;


  // getters and setters

  // tipo
  public void setTipo(String arg) {
    tipo = arg;
  }

  public String getTipo() {
    return tipo;
  }

  // titolo
  public void setTitolo(String arg) {
    titolo = arg;
  }

  public String getTitolo() {
    return titolo;
  }

  // anno
  public void setAnno(int arg) {
    anno = arg;
  }

  public int getAnno() {
    return anno;
  }

}

Questo Javabean rappresenta un video, per ora caratterizzato solo da tipo, titolo e anno di produzione. Ora è tempo di scrivere una piccolissima applicazione di prova:

import org.apache.commons.jxpath.*;

public class Esempio3 {

  public static final void main(String[] args) {

    // crea e popola un video
    Video video = new Video();
    video.setTipo("DVD");
    video.setTitolo("Titolo 1");
    video.setAnno(2004);

    // creazione del contesto JXPath
    JXPathContext context = JXPathContext.newContext(video);

    // estrai il titolo e mostralo a video
    String val = (String)context.getValue("/titolo");
    System.out.println(val);

  }
}

Le prime righe servono per costruire l'oggetto, popolandolo con dei dati semplici. A questo punto, l'oggetto viene utilizzato per creare un nuovo contesto JXPath. Come si può intuire, il JavaBean può tranquillamente essere considerato un contenitore a tutti gli effetti, esattamente come il file XML e, come tale, è possibile accedervi secondo la sintassi XPath.
Nell'esempio viene estratto e mostrato a video l'attributo "titolo".


Figura 3
- Il Javadoc della classe JXPathContext, che contiene alcune informazioni ed esempi relativi all'uso del componente. Disponibile all'indirizzo http://jakarta.apache.org/commons/jxpath/apidocs/

Ma XPath non si ferma solo all'interrogazione, visto che attraverso il metodo "setValue()" si puo definire un valore all'interno del container:

import org.apache.commons.jxpath.*;

public class Esempio4 {

  public static final void main(String[] args) {

    // creazione del contesto JXPath
    JXPathContext context = JXPathContext.newContext(new Video());

    // popola il bean usando JXPath
    context.setValue("/tipo", "DVD");
    context.setValue("/titolo", "Titolo 1");
    context.setValue("/anno", new Integer(2004));

    String val = (String)context.getValue("/titolo");
    System.out.println(val);

  }
}

In questo caso il contesto viene creato subito e come argomento viene fornito un nuovo oggetto di tipo video. Quindi, attraverso "setValue()" si popola il container con i dati per poi sceglierne uno per verificare che tutto sia andato come previsto.

 

Oggetti più complessi
Il JavaBean utilizzato per l'esempio è molto semplice visto che è composto solo da String e int ma, come è possibile intuire, gli oggetti di questo genere non sono frequenti. Per complicare lievemente la situazione si può iniziare a creare un oggetto di tipo Persona, che abbia il solo attributo "nome", che potremo utilizzare sia per i registi che per gli attori:

public class Persona {

String nome;

  public Persona() {
    // costruttore nullo
  }

  public Persona(String nome) {
    setNome(nome);
  }

  public void setNome(String arg) {
    nome = arg;
  }

  public String getNome() {
    return nome;
  }

}

Questo oggetto verrà utilizzato all'interno del bean Video, per definire gli attori ed i registi, semplicemente aggiungendo gli attributi:

private List registi = new ArrayList();
private List attori = new ArrayList();

ed il codice per accedervi (getters and setters):

// attori
public void setAttori(Persona arg) {
  attori.add(arg);
}

public List getAttori() {
  return attori;
}

public Persona getAttori(int idx) {
  return (Persona)attori.get(idx);
}

// registi
public void setRegisti(Persona arg) {
  registi.add(arg);
}


public List getRegisti() {
  return registi;
}

public Persona getRegisti(int idx) {
  return (Persona)registi.get(idx);
}

Il nuovo esempio definirà anche due registi e due attori, quindi stamperà il titolo, il secondo regista e l'elenco degli attori:

import org.apache.commons.jxpath.*;

public class Esempio5 {

  public static final void main(String[] args) {

    // crea e popola un video
    Video video = new Video();
    video.setTipo("DVD");
    video.setTitolo("Titolo 1");
    video.setAnno(2004);
    video.setRegisti(new Persona("Regista 1"));
    video.setRegisti(new Persona("Regista 2"));
    video.setAttori(new Persona("Attore 1"));
    video.setAttori(new Persona("Attore 2"));


    // creazione del contesto JXPath
    JXPathContext context = JXPathContext.newContext(video);

    // prendi e stampa il titolo
    String val = (String)context.getValue("/titolo");
    System.out.println(val);

    // prendi e stampa il secondo regista
    val = (String)context.getValue("/registi[2]/nome");
    System.out.println(val);

    // prendi e stampa gli attori
    Iterator iter = context.iterate("//attori/nome");
    while (iter.hasNext()) {
      System.out.println(iter.next());
    }
  }
}

In una situazione più realistica, voi potreste avere tutte le vostre informazioni riguardo ai video all'interno di un database e, magari, sareste interessati ad avere una pagina di informazioni sulla vostra videoteca contenuti all'interno di una java.util.List o di un semplice array di oggetti.
In questo caso è necessario creare il JXPathContext basandosi sull'oggetto che contiene la lista (il "container", appunto) che viene considerata poi la "root" (ossia la radice principale) per XPath.

 

Conclusioni
A mio parere, il punto di forza di questo componente consiste nella possibilità offerta a tutti gli sviluppatori di sfruttare il linguaggio XPath per accedere a oggetti Java, oltre che ai documenti XML, per il quali è stato sviluppato. Proprio questa sua versatilità pone Jakarta Commons JXPath ad un livello differente rispetto agli altri componenti Jakarta, visti nei mesi scorsi, senza per questo esserne in competizione diretta. Sia Digester che Betwixt sono stati progettati e sviluppati solo ed esclusivamente per gestire i documenti in formato XML secondo una logica lievemente differente da quella offerta da JXPath che, oltretutto, ha un campo di azione molto più ampio.
A chi già conosce XPath consiglio vivamente di investire una po' del proprio tempo per analizzare e valutare le potenzialità offerte da JXPath, in particolare alla possibilità di utilizzarlo al di fuori del mondo strettamente XML. Ma questo consiglio è diretto anche a tutti coloro che non conoscono ancora questo linguaggio, indipendentemente dalla necessità di lavorare con documenti costruiti seguendo lo standard XML.


Figura 4
- Frammento della pagina principale del tutorial per XPath offerto da W3Schools, all'indirizzo http://www.w3schools.com/xpath/default.asp

In questo articolo, purtroppo, non abbiamo avuto la possibilità di analizzare con maggior attenzione il discorso XPath che è molto più ampio, considerato anche che è nato per essere utilizzato all'interno di altre tecnologie, come ad esempio l'eXtensible Stylesheet Language(XSL).
Se voleste approfondire la vostra conoscenza del linguaggio potete sempre utilizzare le specifiche pubbliche, oppure seguire il veloce e semplice corso online di "W3Schools", presente, insieme ad altri, all'indirizzo http://www.w3schools.com/.

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it