Selenium: testing PDF with Calibre

17 07 2011

During my test activities I have arrived at the point that I need to test generated PDFs containing the same text that I have in a html page.

So I searched different PDF utilities that can be used through a command line to extract text out of a PDF, among these I found: pdftotext (a command line tool coming with xpdf), Calibre (an ebook manager/converter), Tika (used by Solr to index pdf content), pdfbox (a java application library that extract pdf).

Giving a better look Calibre is using poppler as library and pdftotext is using poppler as well while Tika is based on pdfbox library to extract text.

At the end I chose Calibre for different reasons:

  1. Calibre allows to extract hyperlinks from the pdf (main reason) when using a wiki syntax like textile (I didn’t find this possibility in pdftotext, tika or pdfbox) on the other side the generated text is with wiki syntax
  2. The development life cycle of Calibre is quite fast (compared with xpdf)
  3. Calibre points to release the application for Windows, Mac and Linux (with xpdf you can have different versions but the one for Mac is released on 2007)

Note that I don’t care the possibility to extract text from images, I know that with pdfimages command it is possible to extract them or other formats that tika can support.

I have to say that in terms of performance on the pdf I tested pdftotext is the fastest (0.01 sec), followed by pdfbox (1.7 sec), Calibre (2.3 sec) and then Tika (2.8 sec) on page , so if you don’t care about hyperlinks but just PDF text I would suggest to go to pdftotext.

After choosing Calibre I created a simple php application that:

  1. receives as parameter the URL of a PDF
  2. calls Calibre with different options on the PDF downloaded
  3. removes  some text (like empty lines, etc.)
  4. gives back an xml file with all the text generated.

I created then a selenium command that calls the php file (through an xmlhttp request) and search for a particular text (specified as input together with the url of the pdf file).

Php application (convert.php):

<?php
$content = file_get_contents($_GET['url']);
$filefrom = 'extract.pdf';
$fileto = preg_replace("/\.pdf$/","",$filefrom).".txt";
file_put_contents($filefrom,$content);
//$t_start = microtime(true);
//system("java -jar tika.jar -t ".escapeshellcmd($filefrom)." > ".escapeshellcmd($fileto),$ret);
system("ebook-convert ".escapeshellcmd($filefrom)." ".escapeshellcmd($fileto)."  >nul 2>&1",$ret);
//system("pdftotext -nopgbrk -raw ".escapeshellcmd($filefrom)." ".escapeshellcmd($fileto),$ret);
//system("java -jar pdfbox.jar ExtractText  ".escapeshellcmd($filefrom)." ".escapeshellcmd($fileto),$ret);
//$t_end = microtime(true);
if($ret==0){
  $value=file_get_contents($fileto);
  $value_empty_line=preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "\n", $value);
  $text=preg_replace("/(\[.*\]\n)/","",$value_empty_line);
  unlink($fileto);
  header('Content-type: text/xml');
  echo "<result>".htmlspecialchars($text)."</result>";
}
else{
  header('Content-type: text/xml');
  echo "<result>Convertion failed</result>";
}
?>

Selenium command (assertSearhInPDF):

Selenium.prototype.assertSearchInPDF = function(uri,names){
  var baseurl = globalStoredVars['host'];
  var params = [{"param" : "url","value" : storedVars[uri],}];
  var lista = "";
  for(var i=0; i<params.length; i++){
    lista +="&" + params[i].param + "=" + encodeURIComponent(params[i].value);
  }
  var indirizzo = baseurl+"/test/convert.php?"+lista;
  LOG.info( 'indirizzo = ' + indirizzo);
  var responseXML = chiamaWebService(indirizzo);
  LOG.info( 'response = ' + responseXML);
  var text = responseXML.getElementsByTagName('result')[0].firstChild.nodeValue;
  text = text.replace(/(\n)/g, " ");
  var array = names.split('|');
  var result=0;
  var length=array.length;
  LOG.info( 'text = ' + text);
  for (var i = 0; i < length; i++){
    if(text.indexOf(array[i]) !==-1){
      LOG.info( 'Found = ' + array[i]);
      result=result+1;
      text=text.substring(text.indexOf(array[i]));
    }
    else{
      LOG.info( 'Element ' + array[i]+' not found');
      break;
    }
  }
  if(result!=length)
    Assert.fail("Not all the elements have been found");
};

In particular the selenium command can accept a list of elements (separated by “|”) to searched in the respective order, if they are not found the command fails.





Selenium e il controllo sul tipo di file

4 09 2010

Come caso d’uso mi era stato chiesto di verificare che il tipo di file  generato da una pagina web fosse PDF (qualora si cliccasse sull’icona in alto a destra) , vedi: http://ec.europa.eu/ewsi/en/info_sheet.cfm?ID_CSHEET=53

Come forse già sapete non potete sapere via javascript il tipo di file (potete controllare l’estensione ma non è certamente la stessa cosa) però è possibile in PHP tramite le funzioni FileInfo (vedi: http://www.php.net/manual/en/ref.fileinfo.php).

Volevo quindi creare un comando di Selenium che dato l’url di un file mi verifica se questo è del tipo che mi interessa (nel mio caso PDF, ma l’ho generalizzato).

Guardiamo prima il test selenium:

test_pdf
open /ewsi/en/index.cfm
clickAndWait link=Country info >
clickAndWait link=Italy
storeAttribute //div[@id='fontpriv']/a[1]@href linkfile
checkFileType linkfile PDF

Dunque nella variabile linkfile è contenuto il link (relativo alla base url nel mio caso http://ec.europa.eu) al file generato, chiamo poi il mio comando checkFileType che controlla che a quel link (trasformandolo in indirizzo assoluto) ci sia un file di tipo PDF.

Andiamo a vedere il file javascript da usare come user extension:

Selenium.prototype.doCheckFileType = function(uri,filetype){

    var baseurl = "http://ec.europa.eu";

    var params = [
	 {
	 "param" : "url",
	 "value" : baseurl+storedVars[uri],
	 }
	 ];

     var lista = "";
	 for(var i=0; i<params.length; i++){
	 	lista +="&" + params[i].param + "=" + encodeURIComponent(params[i].value);
	 }

	proxytype = "http://localhost/tipo.php?";
	var indirizzo = proxytype+lista;
	LOG.info( 'indirizzo = ' + indirizzo);

	var responseXML = chiamaWebService(indirizzo);
	LOG.info( 'response = ' + responseXML);
	var valore = responseXML.getElementsByTagName('filetype')[0].firstChild.nodeValue;

    valore = valore.substring(0,3);

    Assert.matches(filetype, valore);
};

Vi manca la funzione chiamaWebService che potete trovare nell’articolo precedente.

Potete vedere che ho lasciato il tutto molto generico (ciclo for che non serve a molto):

  1. ho semplicemente aggiunto la base url ed estratto l’url dalla variabile uri memorizzata nell’array storedVars
  2. chiamo il web service all’indirizzo http://localhost/tipo.php (che andiamo a vedere dopo)
  3. salvo nella variabile valore la risposta XML (ed estraggo i primi 3 caratteri che contengono il tipo del file). Il contenuto della risposta si trova dentro il tag <filetype>.
  4. verifico che il contenuto della variabile valore corrisponda al tipo che ho richiesto con la Assert.matches (se corrisponde il comando passa altrimenti genera un errore del tipo: [error] Actual value ‘GIF’ did not match ‘PDF’).

Ovviamente se la variabile linkfile contiene un indirizzo assoluto potete lasciare vuota la variabile baseurl o rimuoverla.

Andiamo a vedere il codice del file tipo.php:

<?php
    $content = file_get_contents($_GET['url']);
    $finfo = new finfo;
    $fileinfo = $finfo->buffer($content);
    header('Content-type: text/xml');
    echo "<filetype>".$fileinfo."</filetype>";
?>

Come potete vedere il file php è molto semplice, prima otteniamo il contenuto del file con file_get_content dalla url passata per parametro (nota che la url passata da javascript è codificata, in questa maniera potete passare url con parametri) e poi con finfo->buffer otteniamo il tipo di file che scriviamo dentro il file xml che restituiamo.

Buon weekend !





Estendere Selenium per validare pagine web, parte 2

31 07 2010

Nel precedente articolo abbiamo visto come sia semplice fare delle chiamate XmlhttpRequest da Selenium testando il tutto sul css validator.

In un’ottica di automatizzare i test di selenium con Hudson (vedi plugin), potremmo pensare anche di salvare o di inviare per email il report generato da guardare in un secondo momento.

Per fare ciò invece di chiamare direttamente il validator potremmo chiamare un proxy scritto in PHP (o in un altro linguaggio) che:

  1. prende in input l’indirizzo della pagina da validare, l’indirizzo email e il tipo di validazione (CSS21, CSS3 ecc)
  2. chiama il validator e ottiene il report in xml
  3. salva il report in html e invia l’html per email (usando un file xslt per fare la trasformazione da xml a html)
  4. restituisca il report xml.

Andiamo a vedere dunque il comando da selenium:

storeLocation entry
checkCss ${entry} validity, email=emidiostani@gmail.com, profile=CSS21
verifyExpression ${validity} true

Come potete vedere una volta memorizzato l’indirizzo della pagina corrente nella variable entry, passiamo tale indirizzo al comando checkCss che restituisce il valore della variabile validity e prende come opzioni l’email e il profile (magari è meglio spostarli nella colonna di centro in futuro). Verifichiamo alla fine che il valore della variabile validity sia true.

Andiamo a vedere il codice (vi risparmio la funzione chiama WebService che abbiamo già visto nel precedente articolo):

Selenium.prototype.doCheckCss = function( uri, names ){

 var email = "";
 var profile = "";
 var array = names.split(',');
 for (var i = 0; i < array.length; i++){
 var name = array[i].trim();
 if(name.substr(0,5)=="email"){
 LOG.info( 'email = ' + name);
 var email_array = name.split('=');
 email = email_array[1];
 }
 else if(name.substr(0,7)=="profile"){
 LOG.info( 'profile = ' + name);
 var profile_array = name.split('=');
 profile = profile_array[1];
 }
 }

 var params = [
 {
 "param" : "uri",
 "value" : uri,
 },
 {
 "param" : "email",
 "value" : email,
 },
 {
 "param" : "profile",
 "value" : profile,
 },
 ];

 var lista = "";
 for(var i=0; i<params.length; i++){
 lista +="&" + params[i].param + "=" + encodeURIComponent(params[i].value);
 }

 var proxycss ="http://192.39.226.51/proxycss.php?";
 var indirizzo = proxycss+lista;
 LOG.info( 'indirizzo = ' + indirizzo);

 var responseXML = chiamaWebService(indirizzo);
 LOG.info( 'response = ' + responseXML);

 for (var i = 0; i < array.length; i++){
 var name = array[i].trim();
 if(name.substr(0,5)!="email" && name.substr(0,7)!="profile"){
 var name2 = "m:"+name;
 LOG.info( 'name2 = ' + name2);
 storedVars[name] = responseXML.getElementsByTagName(name2)[0].firstChild.nodeValue;
 LOG.info( 'callWebService: returned [' + storedVars[name] + ']' );
 }
 }
};

Nella prima parte estraiamo le opzioni email e profile da inserire insieme all’indirizzo (uri) nell’array params.

Concateniamo poi i parametri all’inidirizzo del proxy che contattiamo con la chiamaWebService (notate l’indirizzo ip che ovviamente dovete cambiare).

Estraiamo infine dalla risposta xml i parametri che abbiamo passato (ad eccezione di email e profile) da memorizzare nell’array associativo storedVars (predefinito per Selenium).

Ci resta da vedere il proxy php che dunque deve ricevere uri, email e profile e intrinsecamente:

  1. l’indirizzo della css validator (più altri parametri impliciti)
  2. il path del file xslt da applicare
  3. il path del report da salvare

Ed ecco il codice:

<?php

$uri = trim($_REQUEST['uri']);
$profile = trim(strtolower($_REQUEST['profile']));
$email = trim(strtolower($_REQUEST['email']));
$css = "http://192.39.226.51:8081/css-validator/validator?";
$usermedium = "all";
$warning = "1";
$lang = "en";
$output = "soap12";

if($profile =='')
 $profile="css21";
$address = $css."uri=".$uri."&profile=".$profile."&usermedium=".$usermedium."&warning=".$warning."&lang=".$lang."&output=".$output;
$result = file_get_contents($address);

$XSL = new DOMDocument();
$XSL->load( '/var/www/rest_style_css.xslt', LIBXML_NOCDATA);
$xslt = new XSLTProcessor();
$xslt->importStylesheet( $XSL );
$XML = new DOMDocument();
$XML->loadXML( $result );
$html = $xslt->transformToXML( $XML );

$dir = "reports/css/";
$report = "css-report-". date("H:i:s").".html";
$pathmyFile = "/var/www/".$dir.$report;
$fh = fopen($pathmyFile, 'w') or die("can't open file");
fwrite($fh, $html);
fclose($fh);

if($email !=''){
 $to = $email;
 $subject = 'CSS validation';
 $addresstomyfile = "<a href=http://".$_SERVER['SERVER_ADDR']."/".$dir.$report.">".$report."</a>";
 $message = "Report generated: ".$addresstomyfile." on ".date("j F, Y, g:i a")."\n".$html;
 $headers  = "From: Selenium Server<selenium.server@gmail.com>\r\n";
 $headers .= 'MIME-Version: 1.0' . "\r\n";
 $headers .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n";
 mail($to, $subject, $message, $headers);
}

header('Content-type: text/xml');
echo $result;
?>

Si suppone dunque che:

  1. avete  la libreria xml e xsl per php (riavviate apache dopo averla installata)
  2. avete creato la cartella reports/css sotto la var/www
  3. avete creato un file xslt (che trovate alla fine dell’articolo) nella var/www
  4. abbiate una applicazione come sendmail che è in ascolto per mandare email

Il codice è facile da leggere:

  • una volta estratti i parametri li concateniamo per chiamare il validator con il metodo file_get_content(),
  • convertiamo il file xml ottenuto in html tramite il file xslt
  • salviamo tale file nella cartella reports/css
  • se l’email era stata passata come opzione allora mandiamo email l’html ottenuto
  • restituiamo (possiamo vedere anche tramite browser) il risultato xml

Potremmo in futuro passare solo una parte del risultato xml per risparmiare banda usata.

Ora potete fare lo stesso con w3c validator (su Ubuntu installate w3c-markup-validator), rss validator (ho testato in remoto ma non installato), l’achecker per l’accessibilità (che potete traquillamente installare (vi consiglio da svn).

File xslt da usare:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:m="http://www.w3.org/2005/07/css-validator" version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="html" encoding="UTF-8"/>
 <xsl:template match="m:cssvalidationresponse">
 <html>
 <head>
 <title>CSS report</title>
 </head>
 <body>
 <table width="100%">
 <TR bgcolor="#f7f3fd">
 <TD colspan="6">Analyzed web page address:
 <a href="{m:uri}" onclick="popup('{m:uri}; return false;')" title="{m:uri}" target="_new">
 <xsl:value-of select="m:uri" />
 </a>
 </TD>
 </TR>
 <TR bgcolor="#f7f3fd">
 <TD colspan="3"><B>Errors</B></TD>
 </TR>
 <TR bgcolor="#f7f3fd">
 <TD><B>Line</B></TD>
 <TD><B>Error Type</B></TD>
 <TD><B>Context</B></TD>
 <TD><B>Error Subtype</B></TD>
 <TD><B>Skipped String</B></TD>
 <TD><B>Message</B></TD>
 </TR>
 <xsl:for-each select="m:result/m:errors/m:errorlist/m:error">
 <tr>
 <xsl:if test="(position() mod 2 = 1)">
 <xsl:attribute name="bgcolor">#EEEEFF</xsl:attribute>
 </xsl:if>
 <TD><xsl:value-of select="m:line" /></TD>
 <TD><xsl:value-of select="m:errortype" /></TD>
 <TD><xsl:value-of select="m:context" /></TD>
 <TD><xsl:value-of select="m:errorsubtype" /></TD>
 <TD><xsl:value-of select="m:skippedstring" /></TD>
 <TD><xsl:value-of select="m:message" /></TD>
 </tr>
 </xsl:for-each>
 </table>
 <br/>
 <table width="100%">
 <TR rowspan="4" bgcolor="#f7f3fd">
 <TD width="10%"><B>Warnings</B></TD>
 </TR>
 <TR bgcolor="#f7f3fd">
 <TD width="10%"><B>Line</B></TD>
 <TD width="10%"><B>Level</B></TD>
 <TD width="70%"><B>Message</B></TD>
 <TD width="10%"><B>Context</B></TD>
 </TR>
 <xsl:for-each select="m:result/m:warnings/m:warninglist/m:warning">
 <tr>
 <xsl:if test="(position() mod 2 = 1)">
 <xsl:attribute name="bgcolor">#EEEEFF</xsl:attribute>
 </xsl:if>
 <TD width="10%"><xsl:value-of select="m:line" /></TD>
 <TD width="10%"><xsl:value-of select="m:level" /></TD>
 <TD width="70%"><xsl:value-of select="m:message" /></TD>
 <TD width="10%"><xsl:value-of select="m:context" /></TD>
 </tr>
 </xsl:for-each>
 </table>
 </body>
 </html>
 </xsl:template>
</xsl:stylesheet>




Estendere Selenium per validare pagine web

25 07 2010

Come webmaster potreste trovarvi a coordinare un team composto da webdesigner, sviluppatore ed editore. Dovreste quindi verificare per esempio:

  • che il webdesigner abbia aggiustato il css del sito web,
  • che lo sviluppatore abbia realizzato la funzionalità di cercare nel sito o semplicemente quella di ingrandire il testo non appena si prema su un pulsante
  • che l’editore abbia effettivamente inserito un determinato testo nella pagina web.

Tutto ciò è già possibile testarlo con Selenium.

Sempre come webmaster potreste trovarvi nella situazione di validare le vostre pagine web verificando che esse rispettano standard w3c, css o ancora standard di accessibilità. Per fare tutto ciò esistono validatori online per cui indicando l’indirizzo della pagina web vi restituiscono un report di validità (in html o in xml).

Perchè dunque non aggiungere ai vari test fatti da Selenium la possibilità di chiamare i validatori sulla pagina che si sta analizzando ?

Dovete sapere che, poichè i comandi di Selenium sono scritti in javascript, è possibile dunque fare delle chiamate XmlHttpRequest dove indicate l’indirizzo del vostro validatore e farvi restituire il risultato XML da cui per esempio estraete il numero di errori trovato.

Per poter estendere Selenium dovete crearvi un file chiamato user-extensions.js da far caricare al Selenium IDE o da passare come parametro al selenium server. Ricordatevi che dovete caricare tale file nel Selenium IDE ogni qual volta lo modificate (almeno così noto che funziona).

Andiamo ora a vedere un file che definisce il comando ControllaCss che riceve come input l’indirizzo della pagina web da verificare e da come output  uno dei valori presenti nella risposta XML dal css validator:

function chiamaWebService(indirizzo){

 var req  = null;
 if (window.XMLHttpRequest){
 req = new XMLHttpRequest();
 }
 else if (window.ActiveXObject){// per Internet Explorer
 req = new ActiveXObject("Microsoft.XMLHTTP");
 }  

 if (req){
 req.open( "GET", indirizzo, false );
 req.send(null);
 try{
 if ( req.status != 200 ){
 throw { status_code: req.status, status_text: req.statusText, response_text: req.responseText };
 }
 return req.responseXML;
 }
 catch  (e){
 return "errore:"+e.description;
 }
 }
 return "";
};

Selenium.prototype.doControllaCss = function( indirizzo_pagina, risultati ){

 var lista = "uri=" + encodeURIComponent(indirizzo_pagina);    

 var params = [
 {
 "param" : "profile",
 "value" : "css21",
 },
 {
 "param" : "usermedium",
 "value" : "all",
 },
 {
 "param" : "warning",
 "value" : "1",
 },
 {
 "param" : "lang",
 "value" : "en",
 },
 {
 "param" : "output",
 "value" : "soap12",
 },
 ];

 for(var i=0; i<params.length; i++){
 lista +="&" + params[i].param + "=" + encodeURIComponent(params[i].value);
 }

 var url = "http://jigsaw.w3.org/css-validator/validator?";
 var indirizzo = url+lista;

 var rispostaXML = chiamaWebService(indirizzo);

 var array = risultati.split(',');
 for (var i = 0; i < array.length; i++){
 var variabile = array[i].trim();
 var variabile2 = "m:"+variabile;
 storedVars[variabile] = rispostaXML.getElementsByTagName(variabile2)[0].firstChild.nodeValue;
 LOG.info( 'valore ottenuto: ' + storedVars[variabile] );
 }
};

In breve abbiamo una funzione ChiamaWebService che riceve l’indirizzo del web service da chiamare (completo di parametri), effettua l’operazione di GET e restituisce la risposta XML.

La seconda parte definisce il comando ControllaCss (notare il prefisso “do”) e riceve l’indirizzo della pagina da validare e un array di variabili separate da virgola dove memorizzare valori estrapolati dalla rispota XML.

Notate che per i vari parametri chiamo la funzione encodeURIComponent per codificare particolari caratteri.

La parte più importante è l’ultima:

 var array = risultati.split(',');
 for (var i = 0; i < array.length; i++){
 var variabile = array[i].trim();
 var variabile2 = "m:"+variabile;
 storedVars[variabile] = rispostaXML.getElementsByTagName(variabile2)[0].firstChild.nodeValue;
 LOG.info( 'valore ottenuto: ' + storedVars[variabile] );
 }

In pratica dividiamo i vari valori che sono separati dalla virgola, eliminiamo spazi bianchi, aggiungiamo il prefisso “m:” (questo perchè nella risposta xml del validatore css i tag hanno il prefisso “m:”), memorizziamo nell’array storedVars (array usato da Selenium) il valore della variabile estratta con quel particolare tag e poi stampiamo il valore della variabile con il comando di log.

Ora con Selenium IDE carichiamo il file user-extensions.js (vedere tra le opzioni) e creiamo un test case del tipo:

testcss
open /
storeLocation page
controllaCss ${page} validity
verifyExpression ${validity} true

In pratica memorizziamo nella variabile “validity” il valore estratto dalla risposta XML e verifichiamo che tale valore sia “true”. Potremmo anche verificare il valore di “errorcount” per vedere il numero degli errori sia 0.

Come errore dovreste avere qualcosa come:

[error] Actual value 'false' did not match 'true.'

Ora siete pronti a chiamare altri servizi come il w3c validator e Achecker (di cui vi avevo già parlato).

Nota: magari volete installarvi il css validator in locale, potete scaricare il war e farne il deploy su Tomcat o Glassfish (io l’ho fatto su Glassfish 3.0.1, attenzione che dentro il file web-inf.xml dovete invertire le righe mime types con welcome file list).





Gestire mirror con Metalink e Mirrorbrain

15 03 2010

Se avete un sito web che permette di scaricare grandi file e disponete di qualche mirror allora dovete conoscere il concetto di Metalink.

In pratica un Metalink non è altro che un file xml che contiene la lista dei file da scaricare associato al suo MD5/SHA1/firma PGP e la lista dei repository dove questo file si trova, un esempio puo’ essere:

<metalink version="3.0" xmlns="http://www.metalinker.org/">
  <files>
    <file name="example.zip">
    <verification>
      <hash type="md5">example-md5-hash</hash>
      <hash type="sha1">example-sha1-hash</hash>
    </verification>
    <resources>
      <url type="ftp" location="us" preference="90">ftp://ftp.example1.com/example.zip</url>
      <url type="http" location="uk" preference="90">ftp://ftp.example2.com/example.zip</url>
      <url type="bittorrent" preference="100">http://www.ex.com/example.zip.torrent</url>
    </resources>
    </file>
  </files>
</metalink>

Come potete vedere non solo è possibile indicare il tipo di protocollo ma anche un valore di preferenza.

Questi file metalink sono riconosciuti da download managers come DownthemAll! (una estensione di Firefox), il quale recupera l’MD5, per esempio, e scarica il file dagli url in ordine di preferenza. In questo modo il download manager puo’ anche gestire il caso in cui un link non funzioni e passare a quello successivo.

Ora, supponiamo che dal punto di vista del server conosciamo la banda che ogni mirror dispone e magari anche recuperiamo l’indicazione geografica tramite IP del client, potremmo generare un metalink dinamico che si adatta alle esigenze del client, ecco quello che fa MirrorBrain.

MirrorBrain è difatti un content delivery network basato su mirror, diciamo quindi una specie di loadbalancing su diversi fattori.

MirrorBrain è rilasciato con licenza GPL ed è stato da poco (11 Febbraio), totalmente adottato da OpenOffice 3.2, ne vedete un’esempio qui:

http://openoffice.mirrorbrain.org/stable/3.2.0/

MirrorBrain è anche usato da Free Software Foundation e OpenSuse, meglio l’autore Peter Poeml ha potuto creare il progetto grazie a Novell per cui lavora dal 2000.

Buon mirroring !





Esempio con XML e XSLT

15 01 2010

Oggi in azienda mi è stato chiesto di fare una breve lista di software libero che puo’ essere interessante da usare per testing, bug tracking; cosi ho preso a scrivere l’elenco in xml per transformalo in una tabella HTML:

<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="open_source_tools.xslt"?>
<tools>
 <tool>
 <name>Hudson</name>
 <url>http://hudson-ci.org/</url>
 <description>Hudson is a scalable continuous integration server, it allows to do automatic building, document generation, perform unit tests and support for several language, Java in particular</description>
 <license>MIT license</license>
 <requirements>JDK, an Application Server is optional but recommended</requirements>
 <documentation>http://wiki.hudson-ci.org/display/HUDSON/Use+Hudson</documentation>
 <latestversion>1.340, 11 January 2010</latestversion>
 </tool>
 <tool>
 <name>Junit</name>
 <url>http://www.junit.org/</url>
 <description>Junit allows to perform unit test on the java source code</description>
 <license>Common Public License 1.0</license>
 <requirements>JDK</requirements>
 <documentation>http://junit.sourceforge.net/</documentation>
 <latestversion>4.8.1, 8 December 2009</latestversion>
 </tool>
 <tool>
 <name>Jmeter</name>
 <url>http://jakarta.apache.org/jmeter/</url>
 <description>Java desktop application designed to load test functional behavior and measure performance</description>
 <license>Apache License 2.0</license>
 <requirements>JVM 1.4+</requirements>
 <documentation>http://jakarta.apache.org/jmeter/usermanual/index.html</documentation>
 <latestversion>2.3.4, 17 June 2009</latestversion>
 </tool>
 <tool>
 <name>soapUI</name>
 <url>http://www.soapui.org/</url>
 <description>Tool for Web Service Testing</description>
 <license>GNU Library or Lesser General Public License (LGPL)</license>
 <requirements>JVM</requirements>
 <documentation>http://www.soapui.org/userguide/index.html</documentation>
 <latestversion>3.5, 15 January 2010</latestversion>
 </tool>
 <tool>
 <name>Selenium</name>
 <url>http://seleniumhq.org/</url>
 <description>Selenium allows to do automatic User Interface test</description>
 <license>Apache License 2.0</license>
 <requirements>IDE Based on Firefox, RC based on Java</requirements>
 <documentation>http://seleniumhq.org/docs/</documentation>
 <latestversion>1.0.2, 30 June 30 2009 </latestversion>
 </tool>
 <tool>
 <name>Sonar</name>
 <url>http://sonar.codehaus.org/</url>
 <description>Sonar allows to evaluate the quality of the source code using a combination of tools like PMD, Checkstile and Findbugs</description>
 <license>GNU General Public License</license>
 <requirements>JDK, Maven, Database (Derby/MySQL/Oracle/PostgreSQL)</requirements>
 <documentation>http://sonar.codehaus.org/documentation/</documentation>
 <latestversion>1.12, 7 December 2009</latestversion>
 </tool>
 <tool>
 <name>Bugzilla</name>
 <url>http://www.bugzilla.org/</url>
 <description>Bug tracking tool</description>
 <license>Mozilla Public License</license>
 <requirements>Web server (Apache), Perl, Database (MySQL/PostgreSQL)</requirements>
 <documentation>http://www.bugzilla.org/docs/</documentation>
 <latestversion>3.4.4, 18 November 2009</latestversion>
 </tool>
 <tool>
 <name>Redmine</name>
 <url>http://www.redmine.org/</url>
 <description>Bug tracking tool</description>
 <license>GNU General Public License</license>
 <requirements>Ruby, Database (MySQL/PostgresSQL, SQLite)</requirements>
 <documentation>http://www.redmine.org/wiki/redmine/Guide</documentation>
 <latestversion>0.8.7 15 November 2009</latestversion>
 </tool>
</tools>

Come potete vedere il file xml contiene alla radice il nodo tools con dentro i singoli nodi tool ognuno con suoi sotto nodi. E’ un semplice file XML con incluso il file xslt nella prima riga.

Vediamo ora il file XSLT:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="UTF-8"/>
<xsl:template match="/">
<html>
<head><title>Open Source Tools</title>
</head>
<body>
<table width="100%" border="1">
 <thead>
 <tr>
 <td width="5%"><b>Name</b></td>
 <td width="15%"><b>URL</b></td>
 <td width="35%"><b>Description</b></td>
 <td width="15%"><b>License</b></td>
 <td width="10%"><b>Requirements</b></td>
 <td width="10%"><b>Documentation</b></td>
 <td width="10%"><b>Latest Version</b></td>
 </tr>
 </thead>
 <tbody>
 <xsl:for-each select="tools/tool">
 <xsl:sort select="name"/>
 <tr>
 <xsl:if test="(position() mod 2 = 1)">
 <xsl:attribute name="bgcolor">#EEEEFF</xsl:attribute>
 </xsl:if>
 <td width="5%">
 <xsl:value-of select="name" />
 </td>
 <td width="15%">
 <a href="{url}">
 <xsl:value-of select="url"/>
 </a>
 </td>
 <td width="35%">
 <xsl:value-of select="description" />
 </td>
 <td width="15%">
 <xsl:value-of select="license" />
 </td>
 <td width="10%">
 <xsl:value-of select="requirements" />
 </td>
 <td width="10%">
 <a href="{documentation}">
 <xsl:value-of select="documentation" />
 </a>
 </td>
 <td width="10%">
 <xsl:value-of select="latestversion" />
 </td>
 </tr>
 </xsl:for-each>
 </tbody>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

Come notiamo che:

  • alla riga 02 diciamo che vogliamo generare un output html con codifica UTF-8
  • alla riga 03 usiamo il tag template che punta alla radice del file XML di origine (carattere /)
  • dalla riga 04 alla 20 inseriamo il codice per una tabella HTML dove indichiamo da mettere tutti sottocampi dei nodi tool uno per ogni colonna
  • alla riga 21 usiamo il tag for-each dicendo di selezionare tutti nodi tool sotto il nodo originale tools.
  • alla riga 22 usiamo il tag sort diciamo di ordinare i nodi tool per i loro sottonodi name
  • alla riga 23 iniziamo a scrivere la riga che dovrà comparire e dovremo estrarre i vari campi uno per ogni cella
  • alla riga 24-25 inserisco una condizione che mi permette di aggiungere il colore alle righe dispari (position() mod 2 = 1), il metodo position() parte da 1; dunque se le condizione if è soddisfatta aggiungiamo, con la riga 25, l’attributo bgcolor alla riga tramite il tag attribute
  • ora per ogni cella td dalla riga 27 alla riga 51 estraiamo il ogni singolo sottonodo attraverso il tag value-of
  • Siccome vorrei creare un link al sito ufficiale per ogni software trasformo il testo nel sottonodo url in un hyperlink (html tag a) usando le parentesi graffe per indicare tramite XPATH il sottonodo (url per esempio) ed inserisco il valore dello stesso sottonodo usando il tag value-of .

Aprite il file xml con il vostro browser. A voi un po di divertimento nel provare.





C14n: cosa è la canonicalizzazione

14 09 2009

Normalmente possiamo comparare facilmente file di testo per vedere se ci sono differenze (vedi file comparison tools) ma cosa accade quando abbiamo 2 file xml e vogliamo vedere se sono uguali in termini di contenuti ?

Se considerariamo un file xml come una lista di elementi e abbiamo 2 file con un ordine diverso degli elementi, non sono pure logicamente uguali ? non lo sono anche quando l’ordine degli attributi cambia ?

In questo contesto il W3c ha presentato 2 standards (algoritmi per trasformare XML) : XML-C14N del 2001 e XML-Exc-C14n del 2002, si parla infatti di Canonicalizzazione inclusiva ed esclusiva, la differenza sostanziale è che la canonicalizzazione esclusiva può prendere in considerazione anche solo parti di un documento xml in modo che siano indipendenti dall’elemento padre (e quindi entrano in gioco namespace e attributi ereditati).

Esempi di regole da quella inclusiva (prese dal primo documento):

  • The document is encoded in UTF-8
  • Line breaks normalized to #xA on input, before parsing
  • Attribute values are normalized, as if by a validating processor
  • Character and parsed entity references are replaced
  • CDATA sections are replaced with their character content
  • The XML declaration and document type declaration (DTD) are removed
  • Empty elements are converted to start-end tag pairs
  • Whitespace outside of the document element and within start and end tags is normalized
  • All whitespace in character content is retained (excluding characters removed during line feed normalization)
  • Attribute value delimiters are set to quotation marks (double quotes)
  • Special characters in attribute values and character content are replaced by character references
  • Superfluous namespace declarations are removed from each element
  • Default attributes are added to each element
  • Lexicographic order is imposed on the namespace declarations and attributes of each element

Potete vedere qualche esempio nel documento stesso.

Vi chiederete dove la canonicalizzazione viene usata in pratica. Nei web services sapete che i dati vengono trasmessi in formato XML, ebbene con le specifiche WS-Security del consorzio OASIS, con cui si cripta e firma un messaggio SOAP, la canonicalizzazione permette di garantire l’unicità della rappresentazione del file XML.

Una implementazione delle specifiche viene dalla Apache Foundation con WSS4j che prevede i 2 tipi di canonicalizzazione con la possibilità di includere o escludere i commenti nel file xml.

Principalmente gli autori della canonicalizzazione sono 3:

Non vi resta che augurarvi una buona lettura :-)





XML, Java e dintorni

22 02 2009

Nel mio ultimo progetto ho lavorato molto con l’XML per via dei web services e dei relativi data-binding. Più che altro sono stato incuriosito da questo libro e cosi’ ho cominciato a spulciare in rete se ne vale l’acquisto, partendo dalle origini delle librerie JAVA per XML perchè dovete sapere che al di là di uno standard ci sono le sue implementazioni….

Per chi non lo sapesse in pratica esistono 2 famosi parser XML chiamati DOM (giunto alla versione 3 nell’Aprile 2004) e SAX (giunto alla versione 2.0.2 nello stesso mese), a cui si è aggiunto nel tempo STAX cercando di prendere i vantaggi da entrambi.

Per cercare di omogeneizzare l’XML estrapolato da DOM, SAX e STAX, quelli di SUN (principalmente Jeff Suttor, Norman Walsh) realizzano una serie di interfacce e classi  sotto il nome di JAXP 1.3, sorgente che viene donato nell’aprile 2005 (Neeraj Bajaj compare tra i nomi della documentazione java) a quelli di Apache.

Vediamo poi che JAXP 1.3 finisce il suo corso nel Febbraio 2008 a cui segue JAXP 1.4  per essere incluso in JAVA SE 6. Tuttavia JAXP 1.3 è ancora un punto di riferimento.

Affianco ai parser troviamo l’XSL che include:

  • XSLT per trasformare un documento XML in un altro che sia anche HTML o WML, ha bisogno di un processore XSLT che sappia interpretare il foglio di stile XSLT.
  • XPath che è un linguaggio usato da XSLT per accedere a parti di un documento XML
  • XSL-FO usato per formattare documenti XML la cui implementazione è realizzata dalle librerie Apache FOP ( il cui sito è davvero interessante) principalmente pensato per convertire il documento in PDF ma non solo.

Tornando alle librerie Java abbiamo:

  • Xalan 2.7.1 che è un XSLT processor il quale si basa su JAXP 1.3, XSLT 1.0 e XPATH 1.0
  • Xerces 1.4.4 che è un parser java basato su XML 1.0, DOM 1/2 e SAx 1/2

Tornando indietro nel tempo, il 23 gennaio 2007 al W3C i vari working group rilasciano diverse specifiche che coinvolgono XQuery 1.0, XPath 2.0 e XSLT 2.0. Nei working group vediamo principalmente IBM, SUN (ritroviamo Norman Walsh coinvolto) e Oracle.

XQuery nasce come linguaggio per fare query su database in XML, il linguaggio è, diciamo, più naturale del XSLT.

L’unico editor dell’ XSLT 2.0 è Michael Kay, il quale prima lavorava per Software AG e poi si mette in proprio per fondare Saxonica da cui Saxon l’XSLT processor.

Saxon è quindi l’unico ad implementare XSLT 2.0 (ci dovrebbe essere uno di Oracle) che rispetta le specifiche …del suo stesso autore :-) . Saxon, viene rilasciato nella versione Saxon-B come open source mentre la versione Saxon-SA rilasciato con licenza commerciale; solo che la versione open souce è veramente limitata.

Di conseguenza non credo per ora di prendere il libro anche se puo’ essere uno stimolo per studiare XSLT 1.0 almeno.

Note:








Follow

Get every new post delivered to your Inbox.