Share |

domenica 27 giugno 2010

Javascript: Stampare documenti HTML

In questo articolo Javascript mostrerò l'uso della funzione print() per la stampa di documenti e pagine HTML.

Scenario

Capita a volte di dover dotare della funzione di stampa alcune parti di applicazioni WEB, per esempio nel caso che solo alcune zone della pagina sono ritenute interessanti o dopo la registrazione ad un servizio.

In questi casi, la funzione di stampa dei browsers non è ottimale in quanto permette solo di stampare l'intero documento, senza mettere in risalto le parti d'interesse.

Spesso, quindi, si ha la necessità di cambiare on the fly il layout del documento per migliorarne l'aspetto, rimuovendo parti non necessarie o cambiando alcuni oggetti contenuti nella pagina (select, campi di testo ecc..) sostituendoli con puro testo, al fine di ottenere una pagina di stampa il più possibile ottimizzata e comprensibile.


Javascript = Dinamicità

Grazie a JavaScript, ed in particolare alla funzione di stampa print() che l'oggetto window offre, si è in grado di inviare il contenuto della pagina visualizzata alle API del sistema operativo, che si farà carico della gestione di tutta la fase di stampa.

Lo script, che in questo articolo propongo, funziona con tutte le recenti versioni di IE ed Mozilla FireFox e Chrome, forse anche per i browsers Safari e Camino.

Di seguito è riportato il codice HTML, di esempio, di una pagina molto semplice composta di alcune parti utili alla stampa ed altre completamente inutili.

All'interno della pagina sono presenti 2 link:

  • il primo stampa tutto il contenuto del layout;
  • il secondo stampa solo l'esito di una fantomatica query di ricerca;

Il secondo link richiama la funzione Javascript Stampa() che prima di attivare il processo di stampa cambia on the fly il layout della pagina, lo stampa ed infine lo riporta come era in precedenza.

Di seguito la pagina HTML di esempio con il codice Javascript, come al solito ben commentato e testato:

<html>
<head>
<title> Pagina di Stampa</title>

<script type="text/JavaScript">
 <!--
  function Stampa() {
    var intro1Div = document.getElementById("intro1");
    var ricercaDiv = document.getElementById("ricerca");
    var footerDiv = document.getElementById("ricerca");
    var stampaTB = document.getElementById("stampa");

    //Nascondo le parti non necessarie alla stampa
    intro1Div.style.display = "None";
    ricercaDiv.style.display = "None";
    footerDiv.style.display = "None";

    //Imposto la tabella con l'esito della ricerca
    //in modo che occupi tutta la pagina
    stampaTB.style.width = "100%";

    //Lancia la funzione di stampa
    window.print();

    //Riporto tutto come era visualizzato precedentemente
    stampaTB.style.width = "750px";
    intro1Div.style.display = "";
    ricercaDiv.style.display = "";
    footerDiv.style.display = "";
  }
 //-->
</script>
</head>
<body>
<div id="intro1">
    <h1>TITOLO DELLA PAGINA + LOGO + BANNER</h1>
    <a href="javascript:window.print()">Stampa tutta la pagina</a>
</div>
<div id="ricerca">
    <input type="text" value="Esegui una ricerca"> 
    <input type="button" value="Cerca">
</div>
<div id="contenuto">
    <table align="center" width="750px" border="1" id="stampa">
        <tr>
            <td colspan="3">Risultato della Ricerca</td>
        </tr>
        <tr>
            <th valign="top" width="150">ID</th>
            <th valign="top" width="450">Titolo</th>
            <th valign="top" width="150">Autore</th>
        </tr>
        <tr>
            <td valign="top" width="150">rttrr55</td>
            <td valign="top" width="450">Paperino</td>
            <td valign="top" width="150">Walt Disney</td>
        </tr>
        <tr>
            <td valign="top" width="150">knknhty7</td>
            <td valign="top" width="450">Ciao Ciao</td>
            <td valign="top" width="150">AA.VV.</td>
        </tr>
    </table>
</div>
<a href="javascript:Stampa()">Stampa solo l'esito della ricerca</a>
<br><br>
<div id="footer" style="text-align: center;">
    <a href="http://mirkoagrati.110mb.com">Home</a>
</div>
</body>
</html>

Come si può notare eseguendo l'esempio, il tutto é completamente gestibile da parte dello sviluppatore e l'avvio del processo di stampa avviene in maniera trasparente, cosa molto gradita agli internauti.

Alla prossima, MA.

mercoledì 23 giugno 2010

Java: Codifica UTF-8 di uno StringBuffer

In questo articolo Java esporrò una soluzione custom per risolvere il problema del trasporto e salvataggio di dati con codifica UTF-8, provenienti dal WEB, attraverso diversi strati software e differenti codifiche.


Contesto

Internazionalizzazione di un applicativo web based di supporto ad alcune banche estere per l'inserimento di prospects della clientela. Gli istituti appartengono tutti all'area dell'Est Europeo: Česká Republika, Hungary e Republika Hrvatska.

L'applicazione rientra nella categoria delle JEE Applications e si compone di diversi strati software, i quali utilizzano ognuno una codifica particolare e ben definita.
Lo strato web è costituito da HTML e JavaScript per la parte client e dall'accoppiata JSP / Struts lato server.

L'application server fornisce connettività a diverse risorse tra le quali il CICS aziendale e Datasources a MS SQL Server, ORACLE 11g ed IBM DB2.

Purtroppo ogni suddetto strato software è stato configurato con un charset differente e, ad esclusione di SQL Server ed ORACLE che sono di recente installazione, manco per farlo apposta tra i più restrittivi:
CICS è configurato per ricevere ed inviare i dati in formato EBCDIC mentre il database DB2 utilizza la codifica Latin1.

In questa situazione non basta, ovviamente, settare la HTTP Request per l'utilizzo della codifica UTF-8: questa garantisce solo il corretto riconoscimento dei caratteri che non fanno parte del set installato sul server (es: Č), ma basta per dire che lo strato WEB garantisce il trasporto di dati codificati con il charset UTF-8.

Per quanto riguarda invece il corretto trasporto e salvataggio dei dati attraverso il CICS aziendale ho provato ad utilizzare delle funzionalità messe a disposizione dalle API del SDK1.3, piattaforma su cui si fonda l'applicativo.
Tra le informazioni trovate a riguardo vi erano un paio di metodi molto simili che pensavo facessero al caso mio.

Di questi ne mostro uno solo perchè logicamente sono molto simili, ed uguali nel risultato, ovvero non funzionavano come mi sarei aspettato:

/* convert from internal Java String Format -> UTF-8 
 * encoded HTML/JSP-Pages  
 */
public static String convertToUTF8(String s) {
  String out = null;
  try {
    out = new String(s.getBytes("UTF-8"));
  } catch (java.io.UnsupportedEncodingException e) {
    return null;
  }
  return out;
}

Il secondo metodo trasforma il charset encoding di un carattere alla volta e poi li concatena ottenendo la nuova stringa codificata in UTF-8, la quale risulta essere differente dal quella restituita dal metodo convertToUTF8(String s) sopra mostrato. Ma entrambe non restituiscono ciò che mi aspettavo.


Perchè i metodi standard di Java sbagliano?

La cosa strana è che alcuni caratteri venivano codificati correttamente ed altri no.
Dopo 1 giorno di analisi veramente a basso livello, ho capito che l'errore si verificava al momento di trasformare il carattere che in decimale (base 10) aveva un valore superiore a 256 e questo errore influenzava poi tutte le trasformazioni dei caratteri successivi. Si provi a codificare con il suddetto metodo la stringa "¢¡¶©þýåÜýáýáííéíá¢" e successivamente rileggerla in una pagina HTML.


La mia soluzione

Dunque, scoperto il problema ci si attrezza per superare l'ostacolo!

La soluzione sta nel parsare i dati per trasformare in entità HTML ogni carattere la cui rappresentazione numerica non rientra in un range predeterminato.

Una entità è la rappresentazione di un carattere o un simbolo che viene trattato in maniera particolare dal browser e dai parser XML ; ogni entità inizia con il carattere "&" e termina con il ";".

Sono famose le entità per esprimere quei caratteri vietati in XML come ">" e "<" che devono essere scritti come "& gt;" e "& lt;".

Ma per poter esprimere caratteri e simboli avendone la rappresentazione decimale le entità prendono la seguente forma "&#nnn;".

La soluzione è rappresentata da un metodo, scritto da me, che ricavato il valore numerico di ogni carattere lo concatena in una stringa per creare la corrispondente entità.

Questo metodo, che ora esporrò, funziona correttamente e mi permette di non dover elaborare le stringhe salvate in DB2 prima di mostrarle perchè sono già XML well-formed e correttamente interpretate dai browsers.

/**
 * Trasforma tutti i caratteri UTF-8 
 * che non esistono nel set di caratteri ISO-8859-1
 * nella relativa entità (&#nnn;)
 *
 * @param utf8 stringa UTF-8 da codificare in ISO-8859
 * @return la stringa codificata.
 */
public static String encodeUTF8ToASCII(String utf8){
    if(utf8.equals(null) || utf8.length == 0)
     return "";
     
    StringBuffer sb = new StringBuffer();
    char[] c = utf8.toCharArray();
        
    for(int i=0; i<c.length; i++){
        if(32 <= (int)c[i] && (int)c[i]<= 122)
            sb.append(c[i]);
        else   
            sb.append("&#" + (int)c[i] + ";");
    }
   
    return sb.toString();
}

Grazie a questo semplice metodo statico è possibile salvare e rileggere correttamente dati attraverso differenti strati software senza alcuna perdita di dati.

Alla prossima,
MA

sabato 12 giugno 2010

Java: Codifica e Decodifica Esadecimale

In questo articolo Java verrà mostrato un componente, da me creato ad hoc, per compiere le operazioni di codifica e decodifica esadecimale di stringhe di testo.

L'utilizzo della base esadecimale per la lavorazione ed il controllo di dati e buffer di dati è abbastanza normale e diffusa, soprattutto quando si adoperano connettori a risorse esterne ed in casi di attraversamento di diversi strati software.

Si utilizza questa trasformazione, per esempio, al fine di verificare che i caratteri che si stanno per scrivere in un campo del DB Server IBM DB2 non contengano i valori LOW-VALUE(in esadecimale X'00') e HIGH-VALUE(in esadecimale X'FF').

Questi caratteri possono provocare la troncatura dei dati al momento della loro estrazione, sebbene sia i LOW-VALUES che i HIGH-VALUES, siano dei caratteri che possono essere, al pari di tutti gli altri caratteri, salvati ed utilizzati in campi del DB.


La Classe HexEncoder

Il componente che sto per presentare è tanto semplice quanto comodo e funzionale.

Si tratta di una semplice classe base che brucia uno StringBuffer codificandolo e decodificandolo in un altro buffer esadecimale privo dei caratteri LOW e HIGH VALUES, eventualmente presenti nel buffer in ingresso.

Per convenzione, si decide che ogni carattere trasformato in esadecimale ha una lunghezza fissa e predefinita, a piacimento.

package org.ma.decode;

/**
 * Brucia uno StringBuffer codificandolo o de-codificandolo 
 * in sequenze esadecimali prive dei caratteri LOW e HIGH VALUES, 
 * eventualmente presenti nel buffer originale.
 * 
 * Per convenzione, è deciso che ogni carattere trasformato 
 * in esadecimale ha una lunghezza fissa e predefinita. 
 * 
 * @author mirko Agrati
 */
public final class HexEncoder {
  /**
   * Lunghezza che ogni sequenza esadecimale deve avere 
   * per rappresentare un carattere.
   */
  private static final int HEX_SEQUENCE_LENGHT = 34; 
 
  /** Rappresentazione Java del carattere LOW_VALUE. */ 
  private static final char HEX_LOW_VALUE = '\u0000';

  /** Rappresentazione Java del carattere HIGH_VALUE. */
  private static char HEX_HIGH_VALUE = '\uFFFF';
 
  /**
   * Codifica un buffer restituendolo in formato esadecimale 
   * e senza caratteri LOW e HIGH VALUE.
   * 
   * @param in  Il buffer da codificare
   * @return    Un buffer con codifica esadecimale
   */
  public final static StringBuffer encode(StringBuffer in){
    StringBuffer out = new StringBuffer();
  
    for(int i=0; i<in.length(); i++){
      char c = in.charAt(i);
      if( c != HEX_LOW_VALUE && c != HEX_HIGH_VALUE )
        out.append(charToHex(c));
    }
  
    return out;
  }
 
  /**
   * Decodifica un buffer esadecimale restituendolo in formato leggibile.
   * Ogni sequenza di {@link #HEX_SEQUENCE_LENGHT} caratteri esadecimali 
   * rappresenta un carattere leggibile. 
   * 
   * @param inHex  Il buffer esadecimale da de-codificare;
   * @return       Uno StringBuffer con contenuto leggibile.
   */
  public final static StringBuffer decode(StringBuffer inHex){
    StringBuffer out = new StringBuffer();
  
    for(int i=0; i<inHex.length(); i++){
      char c = ' ';
      String t = "";
   
      for(int j=0;j<HEX_SEQUENCE_LENGHT;j++){
        if(i+1 <= inHex.length()){
          c = inHex.charAt(i);  
          t += ( c != HEX_LOW_VALUE && c != HEX_HIGH_VALUE && c != '0')
                    ? ""+ c : "";
          i++;
        }
      }
      out.append((char)Integer.parseInt(t,16));
    }
   
    return out;
  }
 
 /**
  * Trasforma un carattere in una sequenza esadecimale 
  * di lunghezza predeterminata pari al valore della
  * costante {@link #HEX_SEQUENCE_LENGHT}.
  * 
  * @param c Il carattere che deve essere codificato.
  * @return La sequenza esadecimale generata 
  *          codificando il parametro c.
  */
 private static String charToHex(char c){
  String t = Integer.toHexString(c);
  
  while(t.length() < HEX_SEQUENCE_LENGHT)
   t = "0" + t;
  
  return t;
 }
 
 public static void main(String[] args){
  StringBuffer test = new StringBuffer("Ciao, sono la Stringa di test! :)");
  StringBuffer buffer = HexEncoder.encode(test);

  System.out.println("ORIGINALE :\n" + test + "\n\n\n");
  System.out.println("ENCODING :\n" + buffer + "\n\n\n");
  System.out.println("DECODING :\n" + HexEncoder.decode(buffer));
  
 }
}

Conclusioni

Come premesso, la classe è semplice ed il suo funzionamento trasparente all'utilizzatore. Come è possibile testare, ed immaginare, agendo sul numero di byte che ogni carattere deve contenere per la sua rappresentazione esadecimale, in questa maniera i buffer esadecimali mutano la loro lunghezza rispetto alla stringa in ingresso. Solitamente utilizzo la classe per ritornare ad avere lo StringBuffer iniziale ripulito però dai suddetti caratteri in eccesso. Tra l'altro la classe si presta a facili implementazioni e modifiche per parsare altri tipi di dati aumentandone notevolmente le funzionalità. Alla prossima, MA

martedì 1 giugno 2010

Java: Creare la mappa XPath di files XML

In questo articolo Java verrà mostrato come scorrere un intero documento XML, in modo ricorsivo, senza che lo si conosca a priori, e salvando l'intera mappatura interna dei sui path in modo tale da poterne conoscere la struttura ed avere già a disposizione le chiavi di accesso ai dati, utilizzabili per esempio come argomento di predicati_XPath.

Caso d'uso

Tempo fa, mi fu chiesto di creare una classe Java da utilizzare all'interno del DB Oracle 10g per ovviare alle scarse performances del DB nel trattare dati in formato XML. Questa classe avrebbe dovuto scorrere un documento XML contenuto in ogni record di una tabella e restituire un array di stringhe rappresentanti le chiavi di accesso ai dati pronte per essere usate da interpreti xPath per estrarre ogni foglia del documento XML.

Per foglie sono da intendere tutti quei nodi che sono terminatori di un ramo dell'albero gerarchico XML e che quindi non hanno alcun nodo figlio.

Quindi da un documento XML così fatto:

<pippo>
   <ciao>
      <tag/>
   </ciao>
   <dona>56</dona>
   <beppe>
      <mirko/>
   </beppe>
   <beppe>ii</beppe>
   <nano>1</nano>
</pippo>

La classe avrebbe dovuto restituire questi path XPath:

  • /pippo/ciao/tag
  • /pippo/dona
  • /pippo/beppe/mirko
  • /pippo/beppe
  • /pippo/nano

Come si può notare i predicati XPath restituiti estrarrebbero solo i nodi foglia.

Osservazioni

Analizzando il problema, si può affermare quindi che la classe Java, per ogni nodo che incontrerà scorrendo ricorsivamente il documento XML, dovrà:

  • verificare che il nodo in esame non abbia nodi figlio;
  • ricreare tutti i gradi di parentela dei nodi esaminati;
  • salvare i path creati in formato utile a xPath;

Poiché Oracle 10g utilizza le librerie del SDK1.4 di Java, per la rappresentazione del documento XML sono stati utilizzati packages standard forniti nel suddetto jdk.

E' ora di vedere il codice sorgente della classe e di analizzarlo.

La classe XmlReader

Come mio solito preferisco presentare il codice sorgente della classe commentanto abbondantemente nei punti più importanti:

import java.io.IOException;
import java.io.InputStream;
import java.io.StringBufferInputStream;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * Scorre ricorsivamente un documento XML e 
 * salva le chiavi di accesso ad ogni nodo di testo
 * in formato utile ad interpreti XPath.
 *
 * @author  Agrati Mirko
 * @version 1.0.0
 */
public class XmlReader {
  /** Lista contenente gli xPath generati */
  public static List xPathArray ;

  /** 
   * Restituisce un Array di stringhe XPath 
   * 
   * @param  xml  XML in formato String da parsare;
   * @return      Un array di oggetti;
   */
  public static Object[] getXPath(String xml){
    InputStream is =  new StringBufferInputStream(xml);
    xPathArray = new ArrayList();

    try {
      DocumentBuilder db = 
DocumentBuilderFactory.newInstance().newDocumentBuilder();

      //Parse della stringa XML e creazione di un DOM Document
      Document document = db.parse(new InputSource(is));

      //Mi posiziono sulla Root del documento 
      Element root = document.getDocumentElement();

      //Inizio a scorrere il documento
      XmlReader.walkTree(root);
    } 
    catch (SAXException e) { e.printStackTrace(); }
    catch (ParserConfigurationException e){ 
      e.printStackTrace(); 
    }
    catch (IOException ee) { ee.printStackTrace(); }

    return xPathArray.toArray();
  }

  /**
   * Ricorsivamente ricostruisce i gradi di parentela 
   * del nodo ricevuto in input aggiornando la stringa 
   * ricevuta come parametro, per poi stoccarla nella 
   * lista contenente i path.
   *
   * @param nd  Nodo da esaminare 
   * @param str Path dei gradi di parentela
   */
  private static void findParents(Element nd,String str){
    String t = new String(str);
    Element e = nd;
    t = e.getParentNode().getNodeName() + "/" + t;

    if(e.getParentNode() != null 
        && e.getParentNode().getNodeType() == 1){
      findParents((Element)e.getParentNode(),t);
    }
    else{
      if(!xPathArray.contains(t.replaceFirst("#document","")))
        xPathArray.add(t.replaceFirst("#document",""));
    } 
  }


  /**
   * Scorre tutto l'albero XML.
   * Quando trova le foglie le passa al metodo 
   * {@link #findParents} per ricostruire l'albero parentale.
   */
  private static void walkTree(Element node){
    String t = new String(node.getNodeName());

    //Se il nodo ha nodi figliio di tipo TEXT
    if(node.getParentNode() != null && 
        (!node.hasChildNodes() || 
          node.getFirstChild().getNodeType() == 3) ){
      findParents((Element)node,t);
    }

    if(node.hasChildNodes()){
      for(int i=0; i<node.getChildNodes().getLength();i++){
        if(node.getChildNodes().item(i).getNodeType() == 1)
          walkTree((Element)node.getChildNodes().item(i));
      }
    }
  }

  public static void main(String[] args){
    String xmlFile = 
      "<pippo>"
      + "<ciao><tag/></ciao>"
      + "<dona>56</dona>"
      + "<beppe><mirko/></beppe>"
      + "<beppe>ii</beppe>"
      + "<nano>1</nano>"
      + "</pippo>";
      
    Object[] arr = XmlReader.getXPath(xmlFile);

    for(int i=0; i<arr.length;i++){
      System.out.println(arr[i]);
    }
  }
}

Il funzionamento della classe è abbastanza chiaro e trasparente, e tutto sommato anche semplice, ma quel che conta di più è che compie il suo sporco lavoro.

Alla prossima,
MA