| Un generatore di Report in ASP |
Home Page | Commento dell'autore | Articoli | Le FAQ | I documenti | La ricerca | Contribuire |
A cura di Davide Bianchi |
Un generatore di report che consente di produrre stampe di tutti (o quasi) i generi
direttamente in ASP, senza la necessità di installare componenti aggiuntivi sul server.
|
La Stampa, che pena... |
Ok, abbiamo fatto il lavoro in ASP, c'è tutto, o quasi. Il database funziona come un orologio svizzero (no,
non fà cucu). I dati possono essere visionati in 42 modi diversi, le tabelle vengono preparate, abbiamo
perfino scovato il maledetto Applet Java per visualizzare i grafici come piace a quel xyz del cliente,
abbiamo tutto.... ma le stampe? Ebbene sì, ammettiamolo: le stampe fatte dal browser fanno veramente schifo! Non c'è modo di controllare se la stampa esce in Orizzontale o in Verticale, và a capo quando vuole lui, spezza le tabelle a metà che fà paura, non si riesce ad impostargli niente! La verità è che il browser non è stato pensato per stampare, e le stampe che si riesce ad ottenre dal browser "nudo e crudo" fanno veramente pietà. Certo, si può intervenire installando dei componenti server-side che consentono di produrre delle bellissime stampe, Crystal Report o Business Object producono dei risultati ottimi in quanto a questo, ma.... costano cari ed inoltre occorre installare le versioni Server sul server e distribuire gli applicativi client. E sappiamo già cosa ci risponderà il gestore del sito alla domanda "possiamo installare stà roba?". Dopo una serie di (dis)avventure con i succitati prodotti, mi sono deciso finalmente a produrre qualche cosa che possa risolvere il problema. Quello che volevo IO era un prodotto/meccanismo che:
Nà roba da ridere insomma (dirà qualcuno).
|
||||||||||||||||||||||||||||||
Produrre un documento Word |
La prima cosa che mi è venuta in mente quanto ho messo le mani sul problema è stata la produzione
di un documento Word sul server da "rigirare" poi sul client. Word è un OLE Server, questo vuole dire che posso "generare" un documento Word esattamente come "attivo" una connessione ad un database, usando CreateObject(), quindi usare i metodi dell'oggetto Word per "pilotare" il documento stesso.
Questa soluzione comporta però alcuni problemi, innanzitutto Word deve essere installato sul server,
cosa che non sempre è possibile, secondariamente i "metodi" di Word non sono la cosa più semplice del
mondo da usare.
|
||||||||||||||||||||||||||||||
Un documento RTF |
L'esperienza Word però mi ha fatto pensare un momento: cosa posso produrre che sia "visto" come un
documento con tali caratteristiche ma senza essere un documento Word? Risposta: un documento
RTF. L'RTF è un formato di documento "portabile" inventato da Microsoft all'inizio degli anni 90 per definire i testi che poi vengono trasformati nel sistema di help di Windows (.hlp). Un file RTF è un file "ascii", cioè scritto in modo da essere (più o meno) leggibile direttamente, ma contiene dei tag, cioè degli indicatori che permottono di specificare come impostare il carattere, il paragrafo, la pagina etc. etc. Diciamo che è un livello più "sofisticato" di HTML, dove oltre a specificare la formattazione del carattere e del paragrafo, possiamo specificare anche quella della pagina, quindi forzare una dimension/orientamento per la stessa, forzare i salti pagina e così via. La cosa che interessa però è che è un testo ASCII, cioè può essere scritto direttamente senza l'ausilio di nessun programma specifico, esattamente come un buon WebMaster può scrivere un documento HTML senza dover usare nessun editor particolare. Nota: per ulteriori informazioni sull'RTF vedere il relativo documento, oppure vedere la documentazione MSDN direttamente sul sito della Microsoft. Per produrre il documento RTF posso utilizzare il FileSystemObject, quindi mi basta redirigere l'utente sul documento RTF prodotto perchè questo venga visualizzato nel browser utilizzando il visualizzatore predefinito, se l'utente ha installato Word verrà aperto Word, altrimenti il browser sceglierà quello che è disponibile al momento (anche il mefitico WordPad è in grado di interpretare l'RTF).
|
||||||||||||||||||||||||||||||
Lo scheletro del report |
Generare un file RTF è un pò come generare un documento HTML, solo che i tag RTF sono molti, ma molti
di più. Ho quindi scartato a priori l'idea di scriverlo direttamente nel codice (usando una sfilza
di Write("....") insomma). Ci vuole qualche cosa di più sofisticato. Il mio "generatore" sarà quindi costituito da una serie di funzioni, il cui scopo è quello di "incapsulare" la generazione del documento finale in una serie di procedure molto semplici e, soprattutto, riutilizzabili. Ho bisogno poi dei Tag RTF che mi servono, siccome sono tanti preferisco averne un elenco e leggerli di volta in volta quando mi servono, in questo modo se voglio aggiungere un tag devo solo aggiungerlo all'elenco, il codice è predisposto per cercarlo nell'elenco e lo troverà. La funzione "principale" è quella che preleva dall'elenco di tag quello richiesto al momento e lo "spara" nel file che stiamo costruendo. Il meccanismo quindi si presenta così:
(1) devo scrivere un tag cerco il tag nell'elenco scrivo il tag ritorno a (1)Per comodità poi ho pensato che sarebbe utile avere due funzioni di "scrittura" sul file: una che scrive senza andare a capo, e l'altra che invece interrompe la linea e porta il testo a capo (equivalente ad inserire un <BR> insomma). Dopo un pò di elucubrazioni sono arrivato al seguente elenco "minimo" di funzioni da implementare:
Dopo molti tentennamenti ho deciso di lasciare perdere (per il momento) la gestione delle vere e proprie tabelle in RTF perchè sono un macello inimmaginabile, inoltre se Word incontra una tabella fatta non proprio come si deve và irrimediabilmente in crash. Ognuna delle funzioni sopra descritte, ha la stessa struttura:
Quindi per prima cosa mi serve la funzione che legge dall'elenco il TAG che voglio. Per evitare di complicare le cose ho deciso di non utilizzare un database per memorizzare i tag, troppo complicato e fondamentalmente inutile, i Tag li memorizzo in un file ASCII, avente tale struttura:
Il "@" separa il codice dal tag vero e proprio, questo significa che nè il tag, nè il codice possono contenere il carattere "@", in quanto a questo siamo a posto perchè nessun tag RTF contiene il "@".
|
||||||||||||||||||||||||||||||
Recuperare un Tag dall'elenco |
La funzione che legge un TAG dall'elenco dei tag è relativamente semplice, utilizziamo il FileSystemObject
per accedere al file in modalità Ascii e leggere una linea alla volta, ogni linea viene poi "spezzata" in
codice e tag, quindi confrontiamo il codice con quello che stiamo cercando, se il codice corrisponde la
funzione ritorna il corrispondente Tag.
/* * Cerca un tag specificato all'interno del file dei TAG * Ritorna il tag cercato o NULL se non trovato. */ function LeggiTag( TagID, fileName ) { var fso; // FileSystemObject var file; // Fiile dei tag var elem; // elementi del tag var tag = null; // tag da ritornare var ro = / /g; // RegularExpression usata per 'magniarè gli spazi // Creo un nuovo oggetto FileSystem fso=new ActiveXObject( "Scripting.FileSystemObject" ); // accedo al file dei tag // N.B. non verifico se il file esiste... mica troppo bello file = fso.OpenTextFile( fileName ) // elimino gli spazi all'interno del codice TagID = TagID.replace( ro, "" ); // ciclo e leggo tutti i Tag while( ! file.AtEndOfStream ) { // leggo il singolo Tag tag = file.ReadLine(); // parserizzo suddividendo in Codice e Testo ed // elimino gli spazi elem = SeparaTag( tag, null ); elem[0] = elem[0].replace( ro, "" ); // verifico di aver trovato il tag che cercavo if( TagID == elem[0] ) { // ritorno il tag tag = elem[1]; break; } } // chiudo il file file.close(); // ritorno il tag trovato o Null return tag; }Questa funzione non è proprio "il massimo", in quanto non verifico se il file esiste o no, mentre sarebbe bene verificarlo sempre. L'unica cosa "strana" è l'utilizzo di una RegularExpression per l'eliminazione degli spazi. La funzione utilizza SeparaTag() per separare il tag letto nei suoi componenti (Codice e Tag), questa funzione è definita come segue: /* SeparaTag Separa un Tag letto dal file nei suoi elementi un codice ed una stringa che vengono ritornati in un array costruito per l'occasione. Parametri: tag è il tag letto dal file sep è il carattere usato per dividere i due pezzi */ function SeparaTag( tag, sep ) { var ro; // se il separatore non è specificato assumo "@" if( sep == null ) { ro = new RegExp( "@", "g" ); } else { ro = new RegExp( sep, "g" ); } return tag.split( ro ); }L'utilizzo delle Regular Expression per effettuare il lavoro semplifica di molto la funzione stessa, in pratica il tutto si riduce ad una linea di codice. L'ultima funzione "generica" che mi serve è la seguente: recupera un Tag dal file usando le due funzioni precedenti e lo scrive dentro nel file del report indicato. /* * Recupera un tag e lo scrive nel report * Funzione che viene usato dappertutto * * tagFile è il nome del file dei tag da usare * reportFile è un File già aperto. * tag è l'ID del tag da inserire * */ function ScriviTagNelReport( tagFile, reportFile, tag ) { var tmpTag; // recupero il tag di inizializzazione e lo scrivo nel file tmpTag = LeggiTag( tag, tagFile ); reportFile.WriteLine( tmpTag ); } |
||||||||||||||||||||||||||||||
Iniziare e finire il Report |
Adesso che abbiamo le due funzioni "basilari" del sistema possiamo cominciare dall'inizio...e dalla
fine: aprire e chiudere il report. Un documento RTF, come un documento HTML deve essere "aperto" e "chiuso" con i tag corretti, questi tag sono scritti nell'elenco dei tag e vengono recuperati dalle funzioni che io ho definito come IniziaReport() e ChiudiReport(), che a loro volta sono definite come segue:
/* * Inizializza il report * Crea il file denominato "reportFile" e scrive * i tag di inizializzazione dell'RTF. I tag sono * letti dal file indicato come "tagFile" * * Ritorna il nuovo oggetto File creato. */ function IniziaReport( reportFile, tagFile ) { var fso; // FileSystemObject var fileReport; // file del Report // creo l'oggetto FSO fso = new ActiveXObject("Scripting.FileSystemObject"); // creo il file del report fileReport = fso.CreateTextFile( reportFile ); // recupero il tag di inizializzazione e lo scrivo nel file ScriviTagNelReport( tagFile, fileReport, "InizializzaReport" ); // ritorno il report appena creato return fileReport; } /* * Termina il report * Scrive nel report indicato i tag di chiusura dell'RTF e * chiude il file. */ function ChiudiReport( reportFile, tagFile ) { // recupero il tag di chiusura e lo scrivo nel file ScriviTagNelReport( tagFile, reportFile, "TerminaReport" ); // chiudo il file reportFile.Close(); } |
||||||||||||||||||||||||||||||
Impostare il formato della pagina |
Il formato della pagina può essere impostato come "Portrait" o "Landscape", si tratta solo
di inserire una sequenza o l'altra nel report.
/* * Imposta il formato pagina a Portrait */ function ImpostaPortrait( reportFile, tagFile ) { ScriviTagNelReport( tagFile, reportFile, "Portrait" ); } /* * Imposta il formato pagina a Landscape */ function ImpostaLandscape( reportFile, tagFile ) { ScriviTagNelReport( tagFile, reportFile, "Landscape" ); }Come si può vedere, una volta definite le funzioni "basilari", le altre funzioni sono di una banalità assurda.
|
||||||||||||||||||||||||||||||
Scrivere linee e paragrafi |
La scrittura di linee e paragrafi richiede il passaggio del testo da scrivere, quindi
la funzione è leggermente diversa dalle precedenti, ma non di molto.
/* * Scrive una linea senza mettere una fine paragrafo * testo è il testo da scrivere * * N.B. 'tagFilè non è usato, ma viene richiesto per * mantenere una certa coerenza con tutte le altre funzioni. */ function ScriviLinea( reportFile, tagFile, testo ) { reportFile.Write( testo + " "); } /* * Scrive una linea aggiungendo la fine del paragrafo */ function ScriviLineaACapo( reportFile, tagFile, testo ) { reportFile.Write( testo ); ChiudiParagrafo( reportFile, tagFile ); } /* * Inserisce la fine del paragrafo */ function ChiudiParagrafo( reportFile, tagFile ) { ScriviTagNelReport( tagFile, reportFile, "FineParagrafo" ); } |
||||||||||||||||||||||||||||||
Definire e scegliere uno stile di testo |
Per definire e poi scegliere uno stile occorre inserire due tag distinti: per prima cosa occorre
definire lo stile creando una apposita "tabella di stili", poi si utilizza il tag di selezione per
scegliere lo stile da usare nel paragrafo. Siccome la tabella degli stili è quasi sempre la stessa, ho deciso di usare una tabella "fissa" definendo in anticipo tutti gli stili che mi servono ed "infilandoli" per default nel mio report, poi decidero quale stile usare di volta in volta. Per selezionare uno stile mi basta riferirmi allo stile della tabella ma specificando la dimensione del font. Per fare questo devo raddoppiare la definizione degli stili, inserendo la prima volta lo stile nella tabella e la seconda volta lo stile con la dimensione del carattere. Il codice che esegue la gestione degli stili è molto semplice: /* * Inserisce l'inizio della Tabella degli Stili */ function IniziaTabellaStili( reportFile, tagFile ) { ScriviTagNelReport( tagFile, reportFile, "InizioTabellaStili" ); } /* * Inserisce lo stile nella tabella */ function InserisciStile( reportFile, tagFile, stile ) { ScriviTagNelReport( tagFile, reportFile, stile ) } /* * Chiude la tabella stili */ function ChiudiTabellaStili( reportFile, tagFile ) { ScriviTagNelReport( tagFile, reportFile, "FineTabellaStili" ); } /* * Inserisce la tabella degli stili 'default' */ function InserisciTabellaStili( reportFile, tagFile ) { // inizializzo la tabella IniziaTabellaStili( reportFile, tagFile ); // inserisco gli stili InserisciStile( reportFile, tagFile, "Titolo1" ); InserisciStile( reportFile, tagFile, "Titolo2" ); InserisciStile( reportFile, tagFile, "Titolo3" ); InserisciStile( reportFile, tagFile, "Titolo4" ); InserisciStile( reportFile, tagFile, "Normale" ); InserisciStile( reportFile, tagFile, "Testo" ); InserisciStile( reportFile, tagFile, "PieDiPagina" ); InserisciStile( reportFile, tagFile, "Intestazione" ); // chiudo la tabella degli stili ChiudiTabellaStili( reportFile, tagFile ); }Per "attivare" uno stile occorre pescare dall'elenco il tag "corretto": /* * Inizia a scrivere con uno stile */ function IniziaStile( reportFile, tagFile, stile ) { ScriviTagNelReport( tagFile, reportFile, "Stile_" + stile ); } /* * Termina lo stile corrente */ function ChiudiStile( reportFile, tagFile ) { ScriviTagNelReport( tagFile, reportFile, "FineStile" ); }Il trucco consiste nel fatto che per ogni stile ci sono due "tag" nel nostro file dei tag, per lo stile "Normale" abbiamo il tag definito come "Normale" e quello "Stile_Normale", in cui viene richiamato il primo con l'aggiunta della dimensione del carattere: Normale@{\f5\froman\fcharset0\fprq2 Times New Roman;} Stile_Normale@{\f5\fs20 |
||||||||||||||||||||||||||||||
Grassetto e corsivo |
La gestione del Grassetto e del Corsivo (Italico), è paurosamente semplice a questo punto: basta
inserire i due tag necessari:
/* * Mette il testo in grassetto */ function AttivaGrassetto( reportFile, tagFile ) { ScriviTagNelReport( tagFile, reportFile, "InizioGrassetto" ); } /* * Mette il testo in corsivo */ function AttivaCorsivo( reportFile, tagFile ) { ScriviTagNelReport( tagFile, reportFile, "InizioCorsivo" ); } /* * Disattiva il grassetto */ function FineGrassetto( reportFile, tagFile ) { ScriviTagNelReport( tagFile, reportFile, "FineGrassetto" ); } /* * Disattiva il corsivo */ function FineCorsivo( reportFile, tagFile ) { ScriviTagNelReport( tagFile, reportFile, "FineCorsivo" ); } |
||||||||||||||||||||||||||||||
Le Tabulazioni (tabelle) |
Ho deciso di lasciare perdere le Tabelle vere e proprie RTF in quanto non tutti i Viewer le supportano
e non tutti nello stesso modo, quindi utilizzerò solamente dei tabulatori per costruire delle
visualizzazioni tabellari. La definizione di una tabulazione in RTF consiste fondamentalmente nell'inserire un tag che specifica dove deve trovarsi la tabulazione, ed un tag per usare la tabulazione stessa. In RTF le tabulazione devono essere specificate in Twips (ventesimi di punto tipografico), per evitare problemi però preferisco convertira i dati internamente, permettendo così di specificare il tutto in millimetri.
/* * Imposta una tabulazione */ function ImpostaTabulazione( reportFile, tagFile, tabulazione ) { var tabTwips; var tag; tabTwips = tabulazione * 56.7 tag = LeggiTag( "Tabulazione", tagFile ) + tabTwips + " "; reportFile.WriteLine( tag ); } /* * Inizia una linea tabulata */ function IniziaLinea( reportFile, tagFile ) { ScriviTagNelReport( tagFile, reportFile, "InitTab" ); } /* * Chiude una linea tabulata */ function FinisciLinea( reportFile, tagFile ) { ScriviTagNelReport( tagFile, reportFile, "EndTab" ); } /* * Inserisce un carattere di tabulazione */ function InserisiciTab( reportFile, tagFile ) { ScriviTagNelReport( reportFile, tagFile, "Tab" ); }La ImpostaTabulazione è una delle poche funzioni in cui non viene usata la ScriviTagNelReport ma si usa la WriteLine direttamente. Le IniziaLinea e FinisciLinea sono necessarie in quanto ogni linea deve essere raggruppata all'interno di un blocco definito da parentesi.
|
||||||||||||||||||||||||||||||
L'elenco dei Tag |
Fino ad ora abbiamo definito le funzioni che leggono i vari Tag e li inseriscono nel report,
ora dobbiamo definire i singoli Tag. L'elenco dei Tag è il seguente: InizializzaReport@{\rtf1\ansi MarginiPagina@\margl1134\margr1134\margt1418\margb1134 TerminaReport@} Portrait@\paperw11907\paperh16840 Landscape@\paperw16840\paperh11907 FineParagrafo@\par InizioTabellaStili@{\fonttbl Titolo1@{\f1\fswiss\fcharset0\fprq2 Arial;} Titolo2@{\f2\fswiss\fcharset0\fprq2 Arial;} Titolo3@{\f3\fswiss\fcharset0\fprq2 Arial;} Titolo4@{\f4\froman\fcharset0\fprq2 Times New Roman;} Normale@{\f5\froman\fcharset0\fprq2 Times New Roman;} Testo@{\f6\froman\fcharset0\fprq2 Courier;} PieDiPagina@{\f6\froman\fcharset0\fprq2 Courier;} Intestazione@{\f6\froman\fcharset0\fprq2 Courier;} FineTabellaStili@} Stile_Titolo1@{\f1\fs48 Stile_Titolo2@{\f2\fs40 Stile_Titolo3@{\f3\fs36 Stile_Titolo4@{\f4\fs24\b Stile_Normale@{\f5\fs20 Stile_Testo@{\f6\fs20 Stile_PieDiPagina@{\f7\fs16 Stile_Intestazione@{\f8\fs16 FineStile@} InizioGrassetto@{\b FineGrassetto@} InizioCorsivo@{\i FineCorsivo@} Tabulazione@\tx Tab@\tab InitTab@{ EndTab@}\parIl Tag MarginiPagina è predisposto per dimensionare la pagina come un A4 (21x29.7 cm), se si volesse usare una dimensione di pagina diversa basterà modificare la dimensione o aggiungere altri Tag "specializzati" per le varie dimensioni. I margini sono specificati in Twips.
|
||||||||||||||||||||||||||||||
Usare il generatore |
Per utilizzare il generatore è sufficiente includere il codice ASP dello stesso, quindi
richiamare le singole funzioni. Un esempio di generazione di un report molto semplice è il seguente:
Dim fileTag Dim fileReport ' apro una connessione ad un database e creo un recordset ' ---- OMISSIS ' inizializzo il report fileTag = Server.MapPath("Tag.Lst") Set fileReport = IniziaReport( Server.MapPath("report.rtf"), fileTag ) ImpostaLandscape fileReport, fileTag InserisciTabellaStili fileReport, fileTag IniziaStile fileReport, fileTag, "Titolo1" ScriviLineaACapo fileReport, fileTag, "Report di verifica" ChiudiStile fileReport, fileTag IniziaStile fileReport, fileTag, "Normale" ' imposto le tabulazioni ImpostaTabulazione fileReport, fileTag, 20 ImpostaTabulazione fileReport, fileTag, 40 ImpostaTabulazione fileReport, fileTag, 60 ImpostaTabulazione fileReport, fileTag, 80 IniziaLinea fileReport, fileTag ' visualizzo i titoli InserisiciTab fileReport, fileTag ScriviLinea fileReport, fileTag, "colonna 1" InserisiciTab fileReport, fileTag ScriviLinea fileReport, fileTag, "colonna 2" InserisiciTab fileReport, fileTag ScriviLinea fileReport, fileTag, "colonna 3" InserisiciTab fileReport, fileTag ScriviLinea fileReport, fileTag, "colonna 4" FinisciLinea fileReport, fileTag ScriviLineaACapo fileReport, fileTag, "" ' ciclo e stampo tutti i dati Do While Not recordset.Eof IniziaLinea fileReport, fileTag InserisiciTab fileReport, fileTag ScriviLinea fileReport, fileTag, recordset.Fields("primo") InserisiciTab fileReport, fileTag ScriviLinea fileReport, fileTag, recordset.Fields("secondo") InserisiciTab fileReport, fileTag ScriviLinea fileReport, fileTag, recordset.Fields("terzo") InserisiciTab fileReport, fileTag ScriviLinea fileReport, fileTag, recordset.Fields("quarto") FinisciLinea fileReport, fileTag recordset.MoveNext Loop ' eventuali totali etc. |
||||||||||||||||||||||||||||||
Il codice completo |
Quì è possibile vedere/copiare l'intero codice del generatore di Report, il corrispondente file
dei Tag RTF (necessario), ed un piccolo Esempio di funzionamento dello stesso.
Il codice del generatore
|
L'autore | Davide Bianchi, pomposamente definito Software Engineer dal biglietto da visita, lavora per la Square bv, società olandese con sede a Roermond, che si occupa di sviluppo di software con orientamento grafico. Attualmente stà lavorando in Java (e si diverte un sacco). |
Commenti |
Vuoi mandare un commento su questo articolo ? Scrivi all'autore. |
Contribuire |
Ti senti in grado di scrivere un articolo e vorresti vederlo pubblicato? Leggi come fare |
Copyright |
Il presente sito è frutto del sudore della mia fronte (e delle mie dita), se siete interessati
a ripubblicare uno degli articoli, documenti o qualunque altra cosa presente in questo sito
per cortesia datemene comunicazione, così il giorno che faccio delle aggiunte potrò
avvisarvi e magari mandarvi il testo aggiornato.
|