Share |

domenica 28 febbraio 2010

Java: Tradurre Applicazioni in tutte le Lingue

Mostrerò in questo articolo come creare un componente software da utilizzare nelle applicazioni Java (server o client, web o Swing) per tradurre tutte le vostre etichette, messaggi e segnalazioni in qualsiasi lingua vogliate.

Questo post, con l'esposizione del codice di implementazione della classe Translator, conclude l'analisi affrontata precedentemente nell'articolo UML: Progettare un Traduttore Singleton.

Nel suddetto articolo sono esposte, anche grazie a diagrammi UML, tutte le specifiche richieste per il componente e la struttura delle classi coinvolte.
Sono presenti tutte le informazioni necessarie per comprendere l'attività ed il ruolo che ogni membro interno alle classi dovrà svolgere.

Ne consiglio la lettura prima di affrontare l'implementazione java del componente; aiuterà sicuramente a mettere a fuoco il contesto applicativo, struttura della tabella SQL compresa.

Vale la pena ricordare che tra i requisiti che il componente deve soddisfare vi sono:

  • la necessità che esso sia mono istanza, ovvero aderisca al design pattern Singleton;
  • la necessità che la base dati dell'intero dizionario multi-lingua sia collocata sul DB Server MS SQL Server;

Passo subito ad esporre il codice sorgente delle classi che costituiscono il mio sistema di traduzione, iniziando dalla classe di Model Label.

Il codice java esposto è molto ben commentato e segue le regole di formattazione javadocs.

package common;

import java.io.Serializable;

/**
 * Classe di Model per il wrapping dei record 
 * della tabella dictionary.
 * La classe dopo essere stata instanziata non 
 * permette di modficare lo stato delle proprietà 
 * key e lang.
 * 
 * @author magrati
 */
public class Label implements Serializable {
  private static final long serialVersionUID = 1L;
  private String key;
  private String lang;
  private String desc;
 
  /**
   * Costruttore privato.
   * 
   * @param k la chiave univoca che identifica 
              la nuova istanza.
   * @param l la lingua in cui è tradotto il termine.
   * @param d il termine tradotto,
   */
  private Label(String k, String l, String d){
    this.key = k;
    this.lang = l;
    this.desc = d;
  }
 
  /**
   * Overload del metodo toString(): 
   * è utile per tracciare lo stato dell'oggetto.
   * 
   * @return una descrizione dello stato dell'oggetto. 
   */
  public String toString(){  
    return "Label : key=" + this.key + ", lang=" 
        + this.lang + ", desc=" + this.desc;
  }
 
  /* Getters e Setters */
  public String getDesc() {return desc;}
  public String getKey() {return key;}
  public String getLang() {return lang;}
  public void setDesc(String d){desc = d;}
 
  /**
   * Accesso pubblico al costruttore privato di classe.
   * 
   * @param k la chiave univoca che identifica 
              la nuova istanza.
   * @param l la lingua in cui è tradotto il termine
   * @param d il termine tradotto
   * 
   * @return una nuova istanza di classe. 
   */
  public static Label getInstance(String k, String l, String d){
    return new Label(k, l, d);
  }
}

Niente di particolare, la classe Label è fondamentalmente un semplice javabean tranne per la particolarità che non permette di modificare alcune sue proprietà a runtime.

Queta scelta è stata fatta per salvaguardare l'integrità relazionale del dato conservato all'interno della HashMap, che rappresenta il dizionario, e della chiave univoca con cui lo stesso dato può essere recuperato.

Passo ora a mostrare il codice della classe Translator, il componente che deve essere invocato per eseguire le traduzioni.

package traduzioni;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Hashtable;

import common.Label;

/**
 * La classe incapsula un'istanza singleton
 * del dizionario da utilizzare per la 
 * traduzione di termini specifici.
 * 
 * Il componente software in caso non conoscesse 
 * la traduzione richiesta provvede
 * ad inserire nella fonte dati il 
 * nuovo termine richiesto.
 * 
 * @author magrati
 */
public class Translator {
 
  /**
   * Istruzione SQL per caricare tutto 
   * il database dalla base dati.
   */
  private static final String SQL_LOADER = 
      "select language_id,description_code"
    + ",description_des from dictionary";

  /**
   * Istruzione SQL per inseire nella 
   * base dati i nuovi termini 
   * di cui non si conosce la traduzione.
   */
  private static final String SQL_SETTER = 
      "insert into dictionary"
    + " (language_id, description_code,"
    + " description_des, context_des, uts_code)"
    + " values(?,?,null,?,current_timestamp)";
 
  /**
   * Istanza singleton del dizionario 
   * dei termini tradotti.
   * Garantisco una gestione corretta 
   * degli accessi concorrenziali,
   * quindi utilizzo una Map di tipo Hashtable.
   */
  private static Hashtable globalDictionary = 
      new Hashtable();
 
  /**
   * Blocco static che viene chiamato 
   * all'avvio dell'applicazione.
   */
  static{ 
    Translator.init(); 
  }
 
  /**
   * Inizializzatore delle proprietà della classe.
   * Accede alla base dati; 
   * carica il dizionario; 
   * crea le chiavi di accesso alla 
   * hashmap globalDictionary;
   * riempie la hashmap globalDictionary 
   * con tutte le traduzioni;
   */
  private static void init(){
    Connection cn = null;
  
    try {
      cn = DriverManager.getConnection(
          "URL_DI_CONNESSIONE","loginName","Password");

      PreparedStatement stm = 
          cn.prepareStatement(Translator.SQL_LOADER);
      ResultSet rs = stm.executeQuery();
   
      while ( rs.next() ){
        /**
         * la chiave di accesso alla HashMap 
         * è composta unendo 
         * "language_id" + "description_code"
         */
        String desc = (rs.getString(3) != null) 
            ? rs.getString(3).trim() : null;
 Label l = Label.getInstance(
            rs.getString(1).trim() + rs.getString(2).trim(),
            rs.getString(1).trim().trim(), desc);
        Translator.globalDictionary.put(l.getKey(),l);
      }

      rs.close();
      stm.close();    
    }
    catch(SQLException e){
      System.err.println(e.getMessage());
    }
    finally {
      if ( cn != null ) {
        try{
          cn.close();
        }
        catch(SQLException e){
          System.err.println(e.getMessage());
        }
      }
    }     
  }
 
  /**
   * Inserisce in tabella i nuovi termini di cui è stata 
   * richiesta una traduzione che invece non è stata trovata.
   * 
   * @param lang lingua in cui deve essere tradotto il termine
   * @param key  chiave univoca di accesso al dizionario
   * @param pathContext stringa per tenere traccia di 
   *                    informazioni aggiuntive
   * @return true se l'inserimento è avvenuto con successo, 
   *         altrimenti false
   */
  public static boolean addNonExistingLabel(String lang, 
      String key, String pathContext){
    boolean insertIsDone = false;
    Connection cn = null;
  
  
    //Aggiorno il dizionario HashMap   
    Label l = Label.getInstance(key.toUpperCase()
        ,lang.trim().toUpperCase(),null);
    Translator.globalDictionary.put(
        lang.trim().toUpperCase() + key, l);
  
    //Aggiorno la tabella SQL 'dictionary'
    try {
      cn = DriverManager.getConnection(
          "URL_DI_CONNESSIONE","loginName","Password");
      PreparedStatement stm = cn.prepareStatement(SQL_SETTER);
      stm.setString(1,lang.trim().toUpperCase());
      stm.setString(2,key.toUpperCase());
      stm.setString(3, pathContext);
      insertIsDone = (stm.executeUpdate() == 1);
    }
    catch(SQLException e){
      System.err.println("Key: '" + key + "';\n" +  e.getMessage());
    }
    finally {
      if ( cn != null ) {
        try{
          cn.close();
        }
        catch(SQLException e){
          System.err.println(e.getMessage());
        }
      }
    }    
    return insertIsDone;
  }  
 
  /**
   * Cerca la traduzione del termine richiesto e la restituisce.
   * 
   * Se non fosse presente, 
   * effettua una chiamata al membro addNonExistingLabel
   * e restituisce il termine originale da tradurre.
   * 
   * @param lang lingua della traduzione
   * @param name termine da tradurre
   * @param pathContext note aggiuntive in caso non esistesse
   *    la traduzione.
   * @return La traduzione richiesta o il termine originale.
   */
   public static String translate(String lang
      ,String name, String pathContext){  
    String key = lang.trim().toUpperCase() + name.toUpperCase();
    Label temp = (Label)Translator.globalDictionary.get(key);
    if( temp != null && temp.getDesc() != null){
      name = temp.getDesc();
    }
    else{
      Translator.addNonExistingLabel(lang
          ,name.toUpperCase(),pathContext);
    }
    return name;
  }
 
  /**
   * Svuota e ricarica il dizionario.
   */
  public static void refreshDictionary(){
    Translator.globalDictionary.clear();
    Translator.init();
  }
}

Il codice sorgente della classe Translator è molto efficiente senza essere troppo complesso.

Ottenere la traduzione di un termine è veramente cosa banale, si tratta solo di una riga di codice:

System.out.print(
  Translator.translate("EN","Ciao Mondo","Test Hello World"));

Io utilizzo questa classe, incapsulandone il funzionamento in un custom tag,
nelle pagine JSP delle applicazioni che gestisco e progetto presso un cliente che offre servizi web alle proprie filiali estere. In questa maniera è stato possibile supportare il lavoro svolto all'estero fornendo lo stesso applicativo ma in lingua diversa.

Alla prossima,
MA.

sabato 27 febbraio 2010

UML: Progettare un Traduttore Singleton

In questo articolo esporrò, anche grazie a diagrammi UML, come poter progettare un traduttore software, che chiamerò Translator, utilizzabile in qualsiasi applicazione, server o client non importa.

Il componente software in questione è stato da me realizzato su richiesta di un cliente che di recente ha aperto servizi web a filiali estere che, non conoscendo nulla della lingua italiana, altrimenti non avrebbero potuto utilizzare con profitto i servizi forniti.

Chiaramente, il componente non è istruito, nel senso di essere in grado di eseguire delle traduzioni di testi, e quindi bisognerà prepararlo in questo senso.

Il set di dizionari in cui cercare ed attingere i termini tradotti saranno rappresentati da una tabella del database relazionale MS SQL Server e possono essere infiniti linguaggi diversi.

Grazie al supporto fornito dalla suddetta tabella, il componente è in grado di estrarre la traduzione corretta conoscendo il relativo termine da tradurre e la lingua, o idioma, dell'utente.

Le specifiche che la classe Translator deve soddisfare (cioè le richieste avanzate dal cliente), riassunte comodamente nel caso d'uso UML "Translator Features" dicui sopra, sono le seguenti:

  • Il componente deve tradurre tutte le etichette, avvisi e testi dell'applicativo nella lingua dell'utente utilizzatore;
  • Il dizionario deve risiedere su un Server DB, in particolare è stato scelto MS SQL Server;
  • Il componente deve soddisfare tutte le richieste di traduzione con un unica istanza di classe per tutto il ciclo di vita dell'applicativo e per ogni sessione utente (pattern Singleton);
  • Deve segnalare tutti i termini di cui è stata richiesta la traduzione ma della quale non c'é traccia nel dizionario;
  • Deve poter essere utilizzabile sia dalla parte di View che di Business Logic dell'applicativo (pattern MVC);

Direi che le specifiche sono tutte chiare e perseguibili.
Il quarto punto, però, ha bisogno di essere chiarito in miglior modo: come è possibile che Translator possa segnalare la mancanza di traduzione per determinati termini? Ed in che modo?

Dunque, la classe è intelligente e conoscendo lo stato dei dati di proprio dominio ogni volta che non riesce a fornire una traduzione, per mancanza del termine tradotto in tabella, eseguirà una istruzione SQL di INSERT in tabella, aggiungendo il termine richiesto; sarà poi compito di qualcun altro (una persona incaricata) aggiungere le traduzioni per i termini segnalati.

Il vantaggio di questa soluzione è che il dizionario crescerà pari passo con l'espandersi dell'applicazione, quindi il dizionario sarà sempre ottimizzato, nel senso che non conterrà mai termini e traduzioni non utilizzati.

Di contro, invece, vi è la necessità dell'intervento di una persona esterna che dovrà colmare la mancanza delle traduzioni, ma solo su termini essenziali.


Base Dati: La Tabella Dictionary

Come anticipato sopra, la base dati scelta per lo storing dei dizionari di termini necessari per rendere l'applicazione multi-lingua è MS SQL Server.

Dopo aver creato il database, al suo interno deve essere creata la tabella dictionary: è richiesta dal componente Translator per il caricamento dei dati e per l'aggiunta e la segnalazione di nuovi termini da tradurre.

Il comando SQL per generare la tabella dictionary è il seguente:

CREATE TABLE [dbo].[dictionary] (
  [language_id] [char] (2) 
      COLLATE Latin1_General_CI_AS NOT NULL ,
  [description_code] [varchar] (200) 
      COLLATE Latin1_General_CI_AS NOT NULL ,
  [description_des] [nvarchar] (50) 
      COLLATE Latin1_General_CI_AS NULL ,
  [context_des] [varchar] (100) 
      COLLATE Latin1_General_CI_AS NULL ,
  [uts_code] [varchar] (50) 
      COLLATE Latin1_General_CI_AS NULL 
) ON [PRIMARY]

I campi della sono utilizzati nella maniera seguente:

  • language_id: la sigla dell'idioma con cui è stato tradotto il termine richiesto;
  • description_code: il termine del quale è stata richiesta la traduzione;
  • description_des: la traduzione del termine nell'idioma richiesto;
  • context_des: questo è un campo accessorio di controllo che viene utilizzato per salvare l'origine di eventuali segnalazioni di traduzioni mancanti;
  • uts_code: anche questo è un campo accessorio per il salvataggio del momento in cui è scattata una eventuale segnalazione, in formato UTS;

La classe Translator: design e struttura

La classe Translator incapsula un'istanza singleton di una HashTable (un componente che conserva i dati abbinando un oggetto ad una chiave univoca ed atomica) che viene caricata, con tutto il contenuto della tabella SQL dictionary, al suo primo utilizzo e che viene utilizzata e gestita attraverso una serie di metodi pubblici e privati a livello di classe (o static).

I dati estratti dalla tabella SQL vengono conservati attraverso molteplici istanze della classe Label che è fondamentalmente un javabean comune tranne per il fatto che non permette a runtime di cambiare il valore della proprietà key e che possiede un solo costruttore privato accessibile tramite il metodo pubblico di classe getInstance(String, String, String);

La struttura delle classi coinvolte nonché il loro design e le relazioni che intercorrono tra esse sono ottimamente descritte nel seguente diagramma UML di Classe:

Sebbene il diagramma UML sia chiaro ed il suo contenuto facilmente intuibile, espongo il fine di ogni proprietà della classe Translator:

  • globalDictionary: è la hashmap nel quale viene organizzato il dizionario per le traduzioni. Ha l'attributo static per garantire che sia l'unica istanza alla quale accederanno i client per richiedere la traduzione dei termini richiesti. Soddisfa le condizioni imposte dal design pattern Singleton.
  • SQL_LOADER: è l'istruzione SQL di select per caricare l'intero dizionario all'avvio dell'applicazione.
  • SQL_SETTER: è l'istruzione SQL di insert per aggiungere nuovi termini da tradurre per i quali bisognerà intervenire a mano per abbinare la traduzione.

Passo ad illustrare la logica dei metodi di classe, che sono:

  • addNonExistingLabel(String, String, String): membro pubblico e statico che la classe utilizza per segnalare un nuovo termine richiesto di cui però manca la traduzione. Accede al DB ed esegue l'istruzione SQL_SETTER per inserire in tabella il nuovo termine da tradurre. Potrebbe benissimo essere un metodo private ma in questa maniera può tornare utile se si decidesse di caricare per la prima volta il database SQL Server attraverso la classe Translator.
  • init(): membro privato e statico che la classe richiama all'avvio per inizializzare e caricare il dizionario. Si preoccupa di accedere alla tabella dictionary del DB per leggere le traduzioni, di wrappare i dati in istanze della classe Label e di caricare con esse l'istanza singleton del dizionario, ovvero la proprietà globalDictionary.
  • refreshDictionary(): questo metodo, anch'esso statico e pubblico, è utile per ricaricare lato server il dizionario senza stoppare l'applicazione, nel caso per esempio si trattasse di una web application.
  • translate(String, String, String): è il metodo da richiamare per eseguire la traduzione di un termine. Cerca la traduzione all'interno della hashmap: l'accesso avviene tramite la chiave creata componendo la lingua, passata come primo parametro, ed il termine da tradurre, cioè il secondo parametro. Nel caso non fosse trovata la traduzione viene invocato il membro addNonExistingLabel(String, String, String) e restituito il termine richiesto originale. Il terzo parametro lo utilizzo per passare il nome della classe in cui è stata richiesta la traduzione, per tenerne traccia.

La classe Label è un semplice javabean per wrappare i record della tabella dictionary.

Alla prossima,
MA.

lunedì 22 febbraio 2010

BlackBerry: Navigatore GPS UbiNav

In questo articolo descriveró le funzionalità disponibili nella versione di prova del software di navigazione GPS UbiNav.

In questi giorni, dovendo muovermi parecchio con la macchina in zone a me poco conosciute del hinterland milanese, ho scelto di scaricare un navigatore GPS gratuito dal BlackBerry App World per il mio Bold 9700.

Come anticipato, la mia attenzione si è soffermata sull'applicazione UbiNav, che ho installato ed utilizzato per gli spostamenti.

Dunque, il software è ben fatto, funziona molto bene e ed è dotato di un'interfaccia grafica molto moderna ed accattivante.

Al primo avvio il software richiede quali mappe caricare e che lingua utilizzare per assistere l'utente durante la guida.


Funzionalità presenti

Le funzionalità presenti nel menù principale sono:

  • Naviga Verso: avvia la navigazione dal punto in cui ci si trova verso una qualunque destinazione, nuova o salvata precedentemente;
  • Mappa: mostra la mappa panoramica del luogo in cui ci si trova permettendo di selezionare un punto sulla mappa con il cursore e di attivare la navigazione GPS verso di esso;
  • Ricerche: É possibile ricercare luoghi da raggiungere conoscendone l'indirizzo, scegliendolo da un elenco di luoghi d'interesse (monumenti, farmacie ecc..) oppure direttamente dalle informazioni salvate in rubrica abbinate ad un contatto;
  • Ultime Ricerche: ogni ricerca effettuata viene salvata, quindi se dovessimo decidere di farci guidare verso un luogo giá conosciuto basta selezionarlo dall'elenco per avviare la navigazione GPS;
  • Bussola:Il software ha anche la funzionalità di bussola con i 4 quadranti NSEO ed un indicatore, o ago, per puntare il Nord.

Durante il viaggio si è assistiti da una voce femminile piacevole, discreta e precisa mentre ci si può orientare anche visivamente grazie ai disegni 3D e anche panoramici.

Ricalcolo del percorso

Volutamente a volte sceglievo altre strade per obbligare il software a ricalcolare il percorso suggerito, cosa che è sempre avvenuta trovando la strada corretta.


Impostazioni Avanzate

Come ogni navigatore GPS può essere configurato per suggerire il percorso più breve in termini di tempo o di distanza da percorrere e può essere configurato per considerare fattori come autostrade, tangenziali e traghetti.

Quando il periodo di test sarà esaurito provvederò certamente ad acquistare la licenza definitiva.

Giudizio personale: molto buono.

Alla prossima,
MA.


Inviato dal dispositivo wireless BlackBerry®

venerdì 19 febbraio 2010

XPath: La Ricerca per Assi

XPath ci fornisce una tecnica molto utile e potente per estrapolare informazioni da un documento XML qualsiasi: la ricerca per Assi.


Ma che cos'è un Asse?

Con il termine Asse si indica un NodeSet (in italiano: gruppo di nodi) relativamente al nodo corrente.

La sintassi XPath per eseguire una ricerca per Assi è:

asse::nomenodo[ predicatoXPath ]

Gli Assi sono:

Lista degli Assi
Nome Asse
Descrizione
ancestorSeleziona tutti i nodi ascendenti del nodo corrente
ancestor-or-selfSeleziona tutti i nodi ascendenti del nodo corrente ed il nodo stesso
attributeSeleziona gli attributi del nodo corrente
childSeleziona tutti i nodi Figlio del nodo corrente
descendantSeleziona tutti i nodi Discendenti del nodo corrente
descendant-or-selfSeleziona tutti i nodi discendenti del nodo corrente ed il nodo stesso
followingSeleziona tutti i nodi discendenti di tutti i nodi di pari livello successivi al nodo corrente
following-siblingSeleziona tutti i nodi di pari livello successivi al nodo corrente
namespaceSeleziona tutti i namespace del nodo corrente
parentSeleziona il nodo Padre del nodo corrente
precedingSeleziona tutti i nodi discendenti di tutti i nodi di pari livello precedenti al nodo corrente
preceding-siblingSeleziona tutti i nodi di pari livello precedenti al nodo corrente
selfSeleziona il nodo corrente

Acuni esempi per semplificare l'argomento. Per gli esempi faccio riferimento al file serra.xml.


Esempi di utilizzo degli Assi
EsempioDescrizione
child::piantaSeleziona tutti i nodi <pianta> figli del nodo corrente
attribute::id
Seleziona l'attributo 'id' del nodo corrente
child::*Seleziona tutti i nodi di tipo ELEMENT figli del nodo corrente
attribute::*Seleziona tutti gli attributi del nodo corrente
child::node()Seleziona tutti i nodi figli del nodo corrente
descendant::piantaSeleziona tutti i nodi <pianta> discendenti del nodo corrente
ancestor::piantaSeleziona tutti i nodi <pianta> ascendenti del nodo corrente
ancestor-or-self::piantaSeleziona tutti i nodi <pianta> ascendenti del nodo corrente compreso se stesso

Sicuramente prima o poi vi capiterà di dover attraversare un documento XML utilizzando queste soluzioni, vale la pena quindi provare ad utilizzare gli Assi fin da subito magari sforzandosi di rivoluzionare il modo di approcciare il solito problema.

Alla prossima,
MA.

domenica 7 febbraio 2010

Dichiarare uno Schema XSD

In questo articolo spiegherò le regole per dichiarare correttamente uno Schema XSD per eseguire successivamente la validazione_XSD di un documento XML.

Lo Schema XSD permette di definire la struttura e i tipi di dati contenuti in un documento XML.

Ovviamente,
anche lo Schema XSD è un documento XML, con un aspetto simile a questo:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <xs:complexType name="piantaItem">
    <xs:attribute name="id" type="xs:integer" />
    <xs:sequence>
      <xs:element name="nome" maxOccurs="1" type="xs:string" />
      <xs:element name="esterno" maxOccurs="1" type="xs:boolean" />
      <xs:element name="colore" maxOccurs="1" type="xs:string" />
    </xs:sequence>
  </xs:complexType>

  <xs:element name="serra">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="pianta" type="piantaItem" />
      </xs:sequence>
    </xs:complexType> 
  </xs:element>

</xs:schema> 

Caratteristiche di uno Schema XSD

Il documento dello Schema XSD deve innanzitutto dichiarare l'elemento di primo livello di nome schema ed in esso il riferimento al namespace (in italiano, spazio dei nomi) http://www.w3.org/2001/XMLSchema.

Comunemente si assegna il prefisso xs al namespace, sebbene sia possibile dichiararlo come default.

Oltre al namespace obbligatorio di cui sopra, l'elemento Schema può dichiarare altri spazi di nomi:

<xs:schema 
   xmlns="http://serra.org/schema"
   targetNamespace="http://serra.org/schema"
   xmlns:xs="http://www.w3.org/2001/XMLSchema">
   
   ........ 
</xs:schema>

In questo esempio, l'elemento <xs:schema> dichiara molteplici namespaces:

  • xmlns="http://serra.org/schema": il namespace di default è http://serra.org/schema;
  • targetNamespace="http://serra.org/schema": gli elementi definiti dallo schema appartengono al namespace http://serra.org/schema;
  • xmlns:xs="http://www.w3.org/2001/XMLSchema": gli elementi e i tipi di dati usati nello schema appartengono al namespace http://www.w3.org/2001/XMLSchema e devono comparire con il prefisso xs;

Referenziare lo Schema XSD nel documento XML

All'interno di un documento XML che aderisce ad uno Schema XSD, per garantirne la validazione, è necessario referenziare lo Schema stesso:

<serra xmlns="http://serra.org/schema"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://serra.org/schema serra.xsd">
 <pianta id="112">
   <nome>Geranio</nome>
   <esterno>true</esterno>
   <colore>rosso</colore>
 </pianta>
 <attrezzo id="212">
   <nome>Rastrello</nome>
   <dimensioni>1,8 m.</dimensioni>
   <materiale>acciaio</materiale>
   <util>Utilizzato per prati di piccole dimensioni</util>
 </pianta>
</serra> 

L'elemento radice <xs:serra> possiede alcuni riferimenti:

  • xmlns="http://serra.org/schema: rappresenta il namespace che è dichiarato nel targetNamespace dello Schema;
  • xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance": è la dichiarazione del namespace di istanza di XML Schema;
  • xsi:schemaLocation="http://serra.org/schema serra.xsd": ottenuto un riferimento ad xsi si deve specificare la collocazione dello
    Schema con il namespace di riferimento seguito dal percorso del file dello schema preceduto da uno spazio;

Alla prossima,
MA.

XSD: Validare un documento XML

In questo articolo descriverò che cosa è la validazione XSD ed in quali scenari è consigliabile utilizzare un Schema_XSD per validare documento XML.

La validazione XSD di un documento XML ha niente a che fare con verificarne la correttezza formale, bensì ciò che viene valutato e verificato è l'aderenza del documento a determinate specifiche obbligatorie che ne determinano la struttura e tipi di dati contenuti.

Un documento XML potrebbe essere infatti perfettamente corretto dal punto di vista delle regole sintattiche definite da XML, detto in questo caso well-formed, ma non per questo essere valido rispetto alle regole dello schema dichiarato.

Per capire meglio ciò di cui sopra, pensa ad un documento HTML 4.01 nel quale è presente una tabella disegnata in un paragrafo:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
    "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>Documento HTML Well-formed non valido</title>
  </head>
  <body>
    <p>
      <h2>Tabella di esempio</h2>
      <table>
        <tr><th>Nome</th><th>Cognome</th></tr>
        <tr><td>Mirko</td><td>Agrati</td></tr>
      </table>
    </p>
  </body>
</html>

Come puoi notare il documento è formalmente corretto: vi è la dichiarazione DTD, gli elementi HTML, HEAD e BODY ed ogni loro child ( o elemento figlio) è correttamente aperto e successivamente chiuso. Ma ...

Ma se voi provate a validarlo riceverete un errore!

Questo perchè le regole scritte nella DTD non prevedono che i paragrafi possano contenere le tabelle. L'unica maniera per ottenere la validazione, rispetto alla DTD dichiarata, è ri-posizionare la tabella HTML al di fuori del paragrafo.

Gli XSD_Schema contengono ulteriori regole definite dall'utente per descrivere le strutture dati che i documenti XML devono contenere per poter essere considerati validi, quindi quali elementi ed attributi poter utilizzare.

In XML la creazione di elementi e di attributi è libera tuttavia, grazie a XSD, questa libertà può essere volutamente limitata a:

  • regole per il posizionamento degli elementi(A non può essere contenuto da B) o degli attributi;
  • regole per la presenza di elementi e attributi(A non e' previsto, XX e' permesso);
  • regole per la valorizzazione degli elementi.

Come si definisce uno schema per i documenti XML?

Gli schemi per la validazione possono essere definiti attraverso tre tecniche differenti:

  • DTD o Document Type Definition: uno degli elementi che possono comparire nel prologo di un documento XML;
  • XDR o XML-Data Reduced: una forma semplificata di schema che utilizza XML;
  • XSD o XML Schema Definition Language: è il linguaggio standard attualmente utilizzato per la definizione di schemi di validazione ed è basato su XML;

Quando serve uno Schema XSD?

Ritengo che se si sviluppa un'applicazione che utilizza XML come repository di dati, cioè in uno scenario dove gli elementi ce li siamo inventati per noi stessi, non è necessario creare uno schema XSD ne ricorrere alla validazione, in quanto siamo noi stessi creatori ed utilizzatori del documento allo stesso tempo.

Diventa invece necessario, a mio avviso, validare un documento XML nella situazione in cui dovremo scambiare documenti con altri programmi o persone.

In Conclusione:
il linguaggio XSD è uno strumento potente e versatile per definire lo schema che un documento XML deve avere perchè sia conforme ad un determinato standard.

Consideriamo poi che XSD è diventato uno strumento fondamentale per la modellazione delle classi e della serializzazione degli oggetti in molti ambienti di programmazione quali JEE ed il framework .NET.

Alla prossima,
MA.

mercoledì 3 febbraio 2010

XSLT: Creare immagini SVG trasformando XML

In questo articolo mostrerò come poter creare immagini SVG trasformando dati XML. Sarà spiegato come poter eseguire questa trasformazione, da XML a SVG, creando l'immagine in una pagina XHTML grazie alla tecnologia XSLT.

La sigla SVG sta per Scalable-Vector-Graphics, permette cioè di creare immagini vettoriali.

Poter scegliere di creare e mostrare grafici e altre immagini senza avvalersi di plug-in come Flash, applet Java o evitando di creare immagini sfruttando la potenza della programmazione server side ma utilizzando semplicemente un browser fornito dell'interprete XSLT è un rivoluzione!

Per poter eseguire l'esempio che riporto nell'articolo è necessario avvalersi di un browser fornito del plug-in Adobe-SVG-Viewer: FireFox lo ha di default, mentre per IE no.

Passiamo all'esempio: questo è composto da una base dati in formato XML e da un foglio di stile XSLT che crea una pagina XHTML nella quale è dichiarato il namespace SVG.

Tramite il prefisso svg creerò una serie di immagini all'interno di una pagina XHTML interpretando all'istante i dati contenuti nel documento XML.

L'esempio è molto semplice, quasi si commenta da solo.

Cliccando il seguente link puoi vedere l'anteprima del risultato finale di questo articolo.

Per quanto riguarda la base dati XML, siccome ho un'inclinazione al giardinaggio, mi sono ispirato ad una ipotetica serra da principiante, che contiene alcune specie di vegetali ed alcuni attrezzi necessari ad un giardiniere: il file XML serra.xml.

Il foglio di stile XSLT da abbinare al documento XML è stato pensato per fare risaltare i tag SVG, quindi si troverà poco codice HTML e poco codice CSS;

Come al solito tutti i passaggi chiave sono commentati all'interno del codice sorgente.

<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="serra">
  <!-- dichiarazione del contenuto della trasformazione xslt
          e del namespace SVG -->
  <html xmlns:svg="http://www.w3.org/2000/svg">

  <!-- dichiarazione ed importazione del plugin di Adobe -->
  <object id="AdobeSVG"
      CLASSID="clsid:78156a80-c6a1-4bbf-8e6a-3cd390eeb4e2">
  </object>
  <xsl:processing-instruction name = "import" >
      namespace="svg" implementation="#AdobeSVG"
  </xsl:processing-instruction>

  <head>
    <title>Analisi Serra SVG</title>
    <style>
      th{
        text-align: center;
        font-size: 18pt;
        color:red;}
      td{ text-align:center; width:100%;height:100%;}
      h2{ color: blue;}

      <!-- testo del namespace SVG -->
      text{ 
        font-family:arial;
        text-anchor:middle;
        baseline-shift:-15;
        fill: orange;
        font-size: 9pt;}
    </style>
  </head>

  <body>
    <center>
      <table>
        <tr> 
          <th>La mia Serra con SVG e XHTML.</th> 
        </tr>
       <tr> 
          <td> <h2>Vegetali</h2> </td> 
        </tr>
        <tr>
          <td>

            <!-- creo lo spazio per contenere un
                gruppo di oggetti SVG -->
            <svg:svg width="70%" height="200px">
              <!-- creo il gruppo di oggetti SVG :
                  transform="translate(0,200)" 
                  permette di traslare la posizione 
                  dell'elemento grafico di tx unita' 
                  lungo l'asse X e di ty unita' lungo l'asse Y-->
              <svg:g id="piante" transform="translate(0,200)">
                
                <xsl:for-each select="pianta">
                  <xsl:variable name="valore" select="qta"/>
                      
                    <!-- creo un'immagine di solo Testo -->
                    <svg:text 
                        x="{position()*55 + 7.5}" y="0">
                      <xsl:value-of select="nome"/>
                    </svg:text>
                      
                    <!-- 
                        Creo un'immagine rettangolare posizionata
                        ad x e y unita' dall'angolo in alto 
                        a destra dello spazio dedicato, 
                        di altezza = height e larghezza = 15. 

                        Lo coloro con il valore XML {color}
                        e valorizzo l'attributo title in modo 
                        che appaia un tip quando il mouse si 
                        ferma sull'oggetto. -->
                    <svg:rect x="{position()*55}"
                        y="-{$valore*5 + 20}"
                        height="{$valore*5}"
                        width="15"
                        style="fill:{color}"
                        title="{colore}: {qta} pz."/>
                </xsl:for-each>
              </svg:g>
            </svg:svg>
          </td>
        </tr>
        <tr> 
          <td> <h2>Attrezzi</h2> </td>
        </tr>
        <tr>
          <td>

            <svg:svg width="70%" height="300px">

              <svg:g id="attrezzi" 
                  transform="translate(0,200)">

                <xsl:for-each select="accessorio">
                  <xsl:variable name="valore" 
                      select="qta"/>

                  <svg:text 
                      x="{position()*90 + 7.5}" y="0">

                    <xsl:value-of select="nome"/>
                  </svg:text>

                  <svg:rect x="{position()*90}"
                      y="-{$valore*5 + 20}"
                      height="{$valore*5}"
                      width="15"
                      style="fill:{color}"
                      title="{colore}: {qta} pz."/>

                </xsl:for-each>
              </svg:g>
            </svg:svg>
          </td>
        </tr>
      </table>
    </center>
  </body>
</html>
</xsl:template>

</xsl:stylesheet>

Come anticipato, il codice non è per nulla complesso e la trasformazione ha in se qualcosa di magico ed eccezionale.

Per chi volesse approfondire il tema SVG può visitare i siti del Consorzio W3C W3Consortium_SVG e W3School_SVG_Tutorial.

Alla prossima,
MA.