Esportare il contenuto di una Flex DataGrid in Excel Spreadsheet: tre soluzioni tipiche
di Nicola Laino - info@laino.it
L’export di dati su file .XLS è una delle funzionalità più richieste nei progetti di web application. Nel corso degli anni sono state realizzate diverse librerie per facilitarne la creazione e noi ne impiegheremo due nel secondo e nel terzo esempio applicativo. Alcune di esse richiedono la presenza del programma Excel installato sul server ed altre DLL che consentono l’interazione tra la libreria e l’object model di Excel. Il vantaggio di queste soluzioni consiste nel formato del file prodotto che è quello nativo dell’applicazione.
Excel, tuttavia, può aprire e gestire anche altri formati ed è proprio questa capacità ad essere frequentemente sfruttata nella maggior parte dei casi. In questo articolo ci occuperemo proprio delle soluzioni che non necessitano della presenza del programma. Tra i formati più usati uno è HTML ed è abbastanza facile – come è noto – mascherarlo salvando il file con l’estensione “xls”: con un doppio click su di esso Excel ne mostrerà prontamente il contenuto senza evidenziare particolari differenze rispetto al formato nativo.
Cominciamo realizzando una semplice applicazione MXML contenente la DataGrid i cui dati vogliamo esportare
<?xml version=”1.0” encoding=”iso-8859-1”?> <mx:Application xmlns:mx=”http://www.adobe.com/2006/mxml”> <mx:Script> <![CDATA[ // Button click event handler private function bceh():void { var s:String = “”; for each (var o:Object in dg.dataProvider) { s += “<tr><td>” + o.A + “</td><td>” + o.B + “</td><td>” + o.C + “</td></tr>”; } var ur:URLRequest = new URLRequest(“myApp.aspx”); var uv:URLVariables = new URLVariables(); uv.grid = s; ur.data = uv; ur.method = URLRequestMethod.POST; navigateToURL(ur, “_self”); } ]]> </mx:Script> <mx:Panel width=”250” height=”166”> <mx:DataGrid id=”dg” width=”100%” rowCount=”4”> <mx:ArrayCollection> <mx:Object A=”1” B=”2” C=”3” /> <mx:Object A=”4” B=”5” C=”6” /> <mx:Object A=”7” B=”8” C=”9” /> </mx:ArrayCollection> </mx:DataGrid> <mx:ControlBar> <mx:Spacer width=”100%” /> <mx:Button label=”Esporta” click=”bceh()” /> </mx:ControlBar> </mx:Panel> </mx:Application>
Al click sul pulsante “Esporta” un ciclo for raccoglie il contenuto di ogni cella della DataGrid e lo concatena in una stringa – tra comuni tag delle tabelle HTML, in modo da formare un blocco TBODY – che subito dopo verrà inviata ad una pagina .ASPX
<%@ Page validateRequest=”False” %> <%@ Import Namespace=”System.Web” %> <script language=”C#” runat=”server”> private void Page_Load(object o, System.EventArgs ea) { StringBuilder sb = new StringBuilder(); sb.Append(“<html xmlns:x=\”urn:schemas-microsoft-com:office:excel\”><head>”); sb.Append(“<xml><x:ExcelWorksheet><x:WorksheetOptions><x:ProtectContents>False”); sb.Append(“</x:ProtectContents></x:WorksheetOptions></x:ExcelWorksheet></xml>”); sb.Append(“</head><body><table><tr><th>A</th><th>B</th><th>C</th></tr>”); sb.Append(Request.Form[“grid”]); sb.Append(“</table></body></html>”); Response.ContentType = “application/vnd.ms-excel”; Response.AddHeader(“content-disposition”, “attachment;filename=myGrid.xls”); Response.Write(sb.ToString()); } </script>
In questa pagina viene semplicemente completato il codice dello “spreadsheet” formato HTML. Prima di mandare in output il contenuto dello StringBuilder, però, abbiamo inserito due istruzioni chiave: il ContentType che presenta il MIME di un file Excel e l’header “content-disposition” che definisce come “attachment” ciò che viene inviato al client e ne specifica nome ed estensione.
Un ultimo rilievo sul frammento XML nell’HEAD e il relativo namespace: servono, in generale, per definire delle opzioni per il worksheet, qui per ripristinare la visualizzazione della griglia.
Compiliamo, carichiamo il file .SWF in un browser qualsiasi, facciamo click sul tasto “Esporta” e, dopo aver scelto “Salva su disco” nella finestra successiva, ancora click su tasto “OK”: dopo pochi istanti avremo il nostro “pseudo” .XLS (Immagine 1)
Passiamo alla seconda soluzione. Questa volta l’applicazione Flex contiene due DataGrid
<?xml version=”1.0” encoding=”iso-8859-1”?> <mx:Application xmlns:mx=”http://www.adobe.com/2006/mxml”> <mx:Script> <![CDATA[ // Button click event handler private function bceh():void { var o:Object; var s:String = “<grid name=\”First\”>”; for each (o in dg1.dataProvider) { s += “<row><A>” + o.A + “</A><B>” + o.B + “</B><C>” + o.C + “</C></row>”; } s += “</grid><grid name=\”Second\”>”; for each (o in dg2.dataProvider) { s += “<row><D>” + o.D + “</D><E>” + o.E + “</E><F>” + o.F + “</F></row>”; } var ur:URLRequest = new URLRequest(“myApp.aspx”); var uv:URLVariables = new URLVariables(); uv.grids = s + “</grid>”; ur.data = uv; ur.method = URLRequestMethod.POST; navigateToURL(ur, “_self”); } ]]> </mx:Script> <mx:Panel width=”250” height=”262”> <mx:DataGrid id=”dg1” width=”100%” rowCount=”4”> <mx:ArrayCollection> <mx:Object A=”1” B=”2” C=”3” /> <mx:Object A=”4” B=”5” C=”6” /> <mx:Object A=”7” B=”8” C=”9” /> </mx:ArrayCollection> </mx:DataGrid> <mx:DataGrid id=”dg2” width=”100%” rowCount=”4”> <mx:ArrayCollection> <mx:Object D=”1” E=”2” F=”3” /> <mx:Object D=”4” E=”5” F=”6” /> <mx:Object D=”7” E=”8” F=”9” /> </mx:ArrayCollection> </mx:DataGrid> <mx:ControlBar> <mx:Spacer width=”100%” /> <mx:Button label=”Esporta” click=”bceh()” /> </mx:ControlBar> </mx:Panel> </mx:Application>
La stringa con i dati delle DataGrid inviata al file .ASPX in questa occasione contiene un markup differente: i tag HTML non servono più quindi adoperiamo una struttura utile alla logica dell’elaborazione successiva
<%@ Page validateRequest=”False” %> <%@ Import Namespace=”System.Xml” %> <%@ Import Namespace=”CarlosAg.ExcelXmlWriter” %> <script language=”C#” runat=”server”> private void Page_Load(object o, System.EventArgs ea) { Workbook wb = new Workbook(); WorksheetStyle wst = wb.Styles.Add(“Default”); wst.Font.FontName = “Tahoma”; wst.Font.Size = 10; wst.Alignment.Horizontal = StyleHorizontalAlignment.Center; wst = wb.Styles.Add(“Head”); wst.Font.FontName = “Tahoma”; wst.Font.Bold = true; wst.Font.Color = “#FFFFFF”; wst.Interior.Color = “#808080”; wst.Interior.Pattern = StyleInteriorPattern.Solid; Worksheet wsh; WorksheetRow wr; XmlDocument xd = new XmlDocument(); xd.LoadXml(String.Concat(“<grids>”, Request.Form[“grids”], “</grids>”)); foreach (XmlNode grid in xd.DocumentElement.ChildNodes) { wsh = wb.Worksheets.Add(grid.Attributes.Item(0).Value); foreach (XmlNode cell in grid.FirstChild.ChildNodes) { wsh.Table.Columns.Add(new WorksheetColumn(60)); } wr = wsh.Table.Rows.Add(); foreach (XmlNode cell in grid.FirstChild.ChildNodes) { wr.Cells.Add(new WorksheetCell(cell.Name, “Head”)); } foreach (XmlNode row in grid.ChildNodes) { wr = wsh.Table.Rows.Add(); foreach (XmlNode cell in row.ChildNodes) { wr.Cells.Add(cell.FirstChild.Value); } } } Response.ContentType = “application/vnd.ms-excel”; Response.AddHeader(“content-disposition”, “attachment;filename=myGrids.xls”); wb.Save(Response.OutputStream); } </script>
Il formato prodotto in questo secondo esempio è “Foglio di calcolo XML”: molto più versatile del precedente (si veda la XML Spreadsheet Reference: http://msdn.microsoft.com/en-us/library/aa140066(office.10).aspx). La creazione del file è affidata alla libreria free ExcelXmlWriter sviluppata da Carlos Aguilar Mares e scaricabile da http://www.carlosag.net/Tools/ExcelXmlWriter/ con la relativa documentazione (ricordo che la DLL va posta in una cartella “bin” nella root del sito).
Per sommi capi: dopo aver istanziato l’oggetto principale Workbook definiamo prima lo stile predefinito per tutte le celle e poi quello che impiegheremo nelle intestazioni, quindi carichiamo il frammento XML proveniente dall’applicazione Flex in un parser (XmlDocument) e con dei cicli foreach ne attraversiamo la struttura per raccogliere i dati che adopereremo immediatamente dopo nei metodi degli oggetti contenuti nella library.
I nomi di questi oggetti sono abbastanza esplicativi ed è facile dedurre qual è la loro funzione (in ogni caso il file .CHM della guida ne documenta ogni aspetto). L’ultima istruzione salva l’esito dell’elaborazione nell’OutputStream. Compiliamo e verifichiamo l’esito dell’esportazione. (Immagine 2)
Nel terzo ed ultimo caso prendiamo in esame le funzionalità di una libreria realizzata in AS3 che consente di esportare un file .XLS binario: AS3XLS (http://code.google.com/p/as3xls/). L’autore della library, Manuel Wudka-Robles, ci mette sull’avviso circa i limiti della sua realizzazione: Currently as3xls saves spreadsheets in the format used by Excel 2 which supports only one sheet per document. Ciò nonostante il lavoro di Manuel può rivelarsi una risorsa preziosa quando abbiamo la necessità di produrre un file binario e non possiamo contare sulla presenza del programma Excel sul server (o non vogliamo acquistare una costosa libreria ad hoc per un semplice progetto che mai ci ripagherà della spesa: AS3XLS è free…). Inoltre la soluzione del giovane developer di Claremont è ancora in divenire (perlomeno questo lascia pensare il TODO): magari a breve avremo una library con nuove ed interessanti features… Nel frattempo sfruttiamo quello che c’è
<?xml version=”1.0” encoding=”iso-8859-1”?> <mx:Application xmlns:mx=”http://www.adobe.com/2006/mxml”> <mx:Script> <![CDATA[ import com.as3xls.xls.Sheet; import com.as3xls.xls.ExcelFile; // Button click event handler private function bceh():void { var ef:ExcelFile = new ExcelFile(); var s:Sheet = new Sheet(); s.resize(4, 3); s.setCell(0, 0, “A”); s.setCell(0, 1, “B”); s.setCell(0, 2, “C”); var r:int = 1; var o:Object; for each (o in dg.dataProvider) { s.setCell(r, 0, o.A); s.setCell(r, 1, o.B); s.setCell(r, 2, o.C); r++; } ef.sheets.addItem(s); var ba:ByteArray = ef.saveToByteArray(); var ur:URLRequest = new URLRequest(“myApp.aspx”); ur.data = ba; ur.method = URLRequestMethod.POST; navigateToURL(ur, “_self”); } ]]> </mx:Script> <mx:Panel width=”250” height=”166”> <mx:DataGrid id=”dg” width=”100%” rowCount=”3”> <mx:ArrayCollection> <mx:Object A=”1” B=”2” C=”3” /> <mx:Object A=”4” B=”5” C=”6” /> <mx:Object A=”7” B=”8” C=”9” /> </mx:ArrayCollection> </mx:DataGrid> <mx:ControlBar> <mx:Spacer width=”100%” /> <mx:Button label=”Esporta” click=”bceh()” /> </mx:ControlBar> </mx:Panel> </mx:Application>
La libreria viene distribuita come archivio .SWC e, naturalmente, dovremo indicarne il percorso al momento della compilazione
mxmlc.exe -library-path+=as3xls-1.0.swc myApp.mxml
L’uso di AS3XLS è molto semplice: creiamo l’istanza dell’oggetto ExcelFile; definiamo un nuovo Sheet; ne impostiamo le dimensioni con il metodo resize (4 X 3 celle); con setCell inseriamo i nomi delle colonne nell’head e, con lo stesso metodo, i valori nel body scorrendo con il solito ciclo for il contenuto del dataProvider della DataGrid; aggiungiamo lo Sheet alla collection sheets; infine salviamo come ByteArray. Stavolta alla proprietà data di URLRequest assegniamo direttamente il ByteArray da inviare alla pagina .ASPX
<%@ Import Namespace=”System.IO” %> <%@ Import Namespace=”System.Web” %> <script language=”C#” runat=”server”> private void Page_Load(object o, System.EventArgs ea) { byte[] b = Request.BinaryRead(Request.ContentLength); Response.ContentType = “application/vnd.ms-excel”; Response.AddHeader(“content-disposition”, “attachment;filename=myGrids.xls”); using (BinaryWriter bw = new BinaryWriter(Response.OutputStream)) { for (int i = 0; i < b.Length; i++) { bw.Write(b[i]); } } } </script>
Il codice è banale e non necessita di commento. Compiliamo e testiamo l’applicazione. (Immagine 3)
Come di consueto potrete trovare il codice completo dei progetti sul mio sito, all’indirizzo http://www.laino.it/usermatter/xls_export.zip
______________________________________________________________
Nicola Laino
info@laino.it
Si occupa prevalentemente dello sviluppo di soluzioni web based per una nota società di telecomunicazioni. Da qualche anno si dedica alle RIA e, in particolare, al framework Flex, che sta adoperando con un certo profitto in diversi casi.



