/* ExcelXMLcpp.cpp ------------------------------------------------------------------------------

  Modulo per creare file Excel formato Foglio XML (Excel 2003)
  Può essere usato per creare un file completamente nuovo o per aggiungere WorkSheet
  o Stili ad un file Excel precedentemente salvato nello stesso formato (ottenendo un nuovo file)

  La logica d'uso è la seguente:

  Definire se necessario una o più strutture di tipo
    xlsSheetStruct            per definire uno o più fogli con caratteristiche diverse dal default
                              (qui vi sono i margini, l'intestazione, il piè di pagina, ecc.)
    xlsStyleStruct            per definire uno o più stili di cella diversi dal default
                              (qui vi sono colori, bordatura, font, ecc. della cella con questo stile)
  e variarne le propietà, da usare successivamente

  Istanziare
  - xlsExcel(NomeFile, Autore)
    per inizializzare il file e preparare i tre stili di default (txt, num e date)

  Richiamare le funzioni (in quest'ordine):
  - xlsAddStyle(StyleStruct, IDstile)
    lanciata opzionalmente (anche più volte) per aggiungere gli stili aggiuntivi voluti delle celle
    StyleStruct è una variabile struttura precedentemente istanziata di tipo xlsStyleStruct,
    con tutti i valori necessari definiti all'interno; si può usare 'defaultStyle' che è predefinito
    IDstile è l'ID della struttura, che viene richiamato alla scrittura dei dati; se non è presente
    deve essere assegnato all'interno della struttura stessa

  - xlsAddSheet(nomeFoglio, SheetStruct)
    inizializza la scrittura di un nuovo foglio
    SheetStruct è una variabile struttura precedentemente istanziata di tipo xlsSheetStruct, 
    con tutti i valori necessari definiti all'interno; si può usare 'defaultSheet' che è predefinito
    (Più chiamate con nomeFoglio diversi permetteranno di scrivere più fogli Excel nello stesso file)

    A questa funzione devono seguire una o più chiamate alla funzione xlsColumn() (se si vogliono
    differenziare le colonne tra loro) e poi
       xlsWriteCell() che scrive materialmente i dati del foglio in esame, in sequenza da sinistra
    a destra e dall'alto al basso, finchè
     - o si richiama nuovamente xlsAddSheet per chiudere il precedente e aprire un nuovo foglio
     - o si chiude tutto con xlsClose()

  - xlsColumn(numColonna, Larghezza, IDstile)
    imposta la Larghezza in visualizzazione di ciascuna colonna ed eventualmente lo stile;
    deve seguire immediatamente la funzione xlsAddSheet()
    le colonne specificate devono essere in ordine crescente a partire da 1, eventualmente
    saltando quelle in cui si vuole lasciare i default
    La Larghezza da impostare è circa uguale al numero di caratteri * 4 (con i caratteri di default)
    IDstile = "" per nessuno stile

  - xlsWriteCell(Valore, [Tipo, IDstile, formula, skipCol, MergeAcross, MergeDown])
    scrittura del dato nella cella; le celle devono essere in ordine crescente dentro la stessa riga
    Tipo può essere "S" (Stringa, di default), "N" (Numero) e "D" (Data, non ancora gestito);
    Valore in input può essere un unico valore o una lista di valori, nel qual caso ciascuno
    andrà in una cella distinta della stessa riga
    IDstile deve essere tra quelli definiti, altrimenti avrà uno stile di default; se è = "" non gli
    verrà assegnato alcun stile e quindi prenderà lo stile della riga, della colonna o del tipo di dato.
    Se MergeAcross > 0, indica che la cella si estende anche ad una o più celle successive a destra
    Se MergeDown > 0, indica che la cella si estende anche ad una o più celle successive in basso

  - xlsNewLine([skip, RowWidth, IDStile])
    passa alla riga successiva (o alla successiva + skip); la nuova riga può avere la
    larghezza impostata a RowWidth e lo stile = IDStile ("" per nessuno stile)

  - PosizioneCella = xlsGetPosition()
    restituisce le coordinate assolute della cella relativa attuale come stringa (per l'uso nelle formule):
    PosizioneCella è la posizione nella forma Excel "Lettera" + "numRiga" (ossia colonna + riga)
    Riga, Colonna attuali (in formato numerico) si possono ottenere dalle variabili di istanza:
       ActualRow, ActualCol

  - stringa = xlsGetRectangle(R1, C1, R2, C2)
    date in input le posizioni assolute Riga1+Colonna1, Riga2+Colonna2, che definiscono un rettangolo di celle,
    viene restituita la stringa che definisce la posizione relativa del rettangolo rispetto la cella attuale
    Serve come aiuto per definire le formule

  - xlsClose()
    deve essere l'ultima istruzione che chiude il file


  Se si vuole fare il Merge con un altro file Excel esistente in formato Foglio XML:

  Richiamare
  xlsInitMerge(NomeFileOld, NomeFileOutput)
    Viene generato un file che contiene i fogli vecchi presi dal file "NomeFileOld" più quelli nuovi generati.
    A questa funzione seguono le altre nel solito modo, in cui gli stili che sono già presenti con lo stesso ID
    non vengono aggiunti se scritti da programma e gli sheet generati vengono aggiunti in fondo a quelli già presenti

  Richiamare xlsSheetDelete(nomeFoglio)
    nel caso di Merge: nel file generato cancella il foglio indicato; deve essere usata 
    (anche più volte) prima di generare il primo sheet
    Deve essere richiamata subito dopo xlsInitMerge e prima di xlsAddSheet

*/

#include <cstdlib>
#include <string>
#include <iostream>
#include <sstream>
#include <fstream>
#include <time.h>

using namespace std;

#include "ExcelXMLcpp.h"
#define MAXDATALEN 4000


/* --- Metodi della classe xlsExcel --- */
 
/* Costruttore: Crea intestazione XML e prepara stili di default */
xlsExcel::xlsExcel(string NomeFile, string Author, string OldExcelFile) {  
   string sAuthor, sdt;
   char dt[30];
   time_t rawtime;
   struct tm *timeinfo;
   
   /* inizializzazione variabili globali */
   listaSheet = "";
   listaStili = "Default,num,txt,date";
   SheetsToDel= "";
   nSheets = 0;
   ActualRow = 1;
   ActualCol = 1;
   __Sequenza = 0; 
   __flagOpenRow = 0;
   flagMerge = 0;
   if (Author == "") sAuthor = "ExcelExport";
   else sAuthor = Author;   
   if (NomeFile == "") FileName = "ExcelExport";
   else FileName = NomeFile;
     
   /* ---- inizializzazione defaultSheet: */
   strcpy(defaultSheet.SheetName, "NoName");
   strcpy(defaultSheet.Description, "");
   strcpy(defaultSheet.FootPage, "");
   strcpy(defaultSheet.HeaderMargin, "0.5");
   strcpy(defaultSheet.FooterMargin, "0.5");
   strcpy(defaultSheet.MarginTop, "1");
   strcpy(defaultSheet.MarginBottom, "1");
   strcpy(defaultSheet.MarginLeft, "1");
   strcpy(defaultSheet.MarginRight, "1");
   defaultSheet.PrintGrid    = '1';
   defaultSheet.Protected    = '0';
   defaultSheet.RowHeight    = 0;
   defaultSheet.Layout       = 'L';    
    
   /* ----- inizializzazione defaultStyle  */
   strcpy(defaultStyle.ID, "");
   strcpy(defaultStyle.Align, "Left");
   strcpy(defaultStyle.VAlign, "Bottom");
   defaultStyle.Indent       = 0;
   strcpy(defaultStyle.BorderType, "Continuous");
   strcpy(defaultStyle.BorderColor, "");
   defaultStyle.BorderTop    = 0;
   defaultStyle.BorderBottom = 0;
   defaultStyle.BorderLeft   = 0;
   defaultStyle.BorderRight  = 0;
   strcpy(defaultStyle.FontName, "");
   defaultStyle.FontBold     = '0';
   defaultStyle.FontItalic   = '0';
   defaultStyle.FontSize     = 0;
   strcpy(defaultStyle.FontColor, "");
   strcpy(defaultStyle.BGcolor, "");
   strcpy(defaultStyle.BGpattern, "Solid");
   strcpy(defaultStyle.BGpatternColor, "");
   strcpy(defaultStyle.NumberFormat, "");
   defaultStyle.WrapText     = '0';
   defaultStyle.VerticalText = '0';
   defaultStyle.Rotate       = 0;
   defaultStyle.Protected    = '0';   

   //defaultSheet = {"NoName", "", "", "0.5", "0.5", "1", "1", "1", "1", '1', '0', 0, 'L' };
   //defaultStyle = {"", "Left", "Bottom", 0, "Continuous", "", 0, 0, 0, 0, "", '0', '0', 0, "", "", "Solid", "", "", '0', '0', 0, '0' } ;

   /* compone il timestamp  */
   time ( &rawtime );
   timeinfo = localtime ( &rawtime );
   strftime(dt, 22, "%Y-%m-%dT%H:%M:%SZ", timeinfo);
   sdt = dt;
         
   /* Elementi fissi, contenenti la testata e gli stili di base */
   _xmlBuffer = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\" ?>\n";
   _xmlBuffer += "<?mso-application progid=\"Excel.Sheet\"?>\n";
   _xmlBuffer += "<Workbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\" xmlns:o=\"urn:schemas-microsoft-com:office:office\" ";
   _xmlBuffer += "xmlns:x=\"urn:schemas-microsoft-com:office:excel\" xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\" xmlns:html=\"http://www.w3.org/TR/REC-html40\">\n";
   _xmlBuffer += " <DocumentProperties xmlns=\"urn:schemas-microsoft-com:office:office\">\n";
   _xmlBuffer += "  <LastAuthor>" + sAuthor + "</LastAuthor>\n";
   _xmlBuffer += "  <Created>" + sdt + "</Created>\n";
   _xmlBuffer += "  <Version>11.0000</Version>\n";
   _xmlBuffer += " </DocumentProperties>\n";
   _xmlBuffer += " <ExcelWorkbook xmlns=\"urn:schemas-microsoft-com:office:excel\">\n";
   _xmlBuffer += "  <ProtectStructure>False</ProtectStructure>\n";
   _xmlBuffer += "  <ProtectWindows>False</ProtectWindows>\n";
   _xmlBuffer += " </ExcelWorkbook>\n";
   _xmlBuffer += " <Styles>\n";
   _xmlBuffer += "  <Style ss:ID=\"Default\" ss:Name=\"Normal\">\n";
   _xmlBuffer += "   <Alignment ss:Vertical=\"Bottom\"/>\n";
   _xmlBuffer += "   <Borders/>\n";
   _xmlBuffer += "   <Font/>\n";
   _xmlBuffer += "   <Interior/>\n";
   _xmlBuffer += "   <NumberFormat/>\n";
   _xmlBuffer += "   <Protection/>\n";
   _xmlBuffer += "  </Style>\n";
   _xmlBuffer += "  <Style ss:ID=\"txt\">\n";
   _xmlBuffer += "   <Alignment ss:Horizontal=\"Left\" ss:Vertical=\"Bottom\"/>\n";
   _xmlBuffer += "   <NumberFormat/>\n";
   _xmlBuffer += "   <Protection ss:Protected=\"0\"/>\n";
   _xmlBuffer += "  </Style>\n";
   _xmlBuffer += "  <Style ss:ID=\"num\">\n";
   _xmlBuffer += "   <Alignment ss:Horizontal=\"Right\" ss:Vertical=\"Bottom\"/>\n";
   _xmlBuffer += "   <NumberFormat/>\n";
   _xmlBuffer += "   <Protection ss:Protected=\"0\"/>\n";
   _xmlBuffer += "  </Style>\n";
   _xmlBuffer += "  <Style ss:ID=\"date\">\n";
   _xmlBuffer += "   <Alignment ss:Horizontal=\"Center\" ss:Vertical=\"Bottom\"/>\n";
   _xmlBuffer += "   <NumberFormat ss:Format=\"Short Date\"/>\n";
   _xmlBuffer += "   <Protection ss:Protected=\"0\"/>\n";
   _xmlBuffer += "  </Style>\n";

   /* Se c'è "OldExcelFile", bisogna fare il merge con un file Excel esistente  */
   error_msg = "";
   if (OldExcelFile != "") {
      flagMerge = -1;      
      xlsInitMerge(OldExcelFile, NomeFile);
   }
   else { 
      /* Apre il file di output e scrive la testata */ 
      fOut.open( FileName.data());
      if(!fOut.fail()) {
         fOut << _xmlBuffer; 
      }
      else {
         error_msg = "Errore in apertura del file '" + FileName + "'";
         cerr << error_msg << endl;       
      } 
   }   
}

/* Aggiunge uno Style nella sezione <Styles> */
EXPORT int xlsExcel::xlsAddStyle(xlsStyleStruct Stile, string IDstile){
   string s="", s1, s2;
   
   /* Controlli */
   if (IDstile == "" && strlen(Stile.ID) == 0) {
      error_msg = "Manca l'ID dello stile in xlsAddStyle()\n";
      cerr << error_msg << endl;
      return 0;
   } 
   if (IDstile != "") strcpy(Stile.ID, IDstile.data());
   if (__Sequenza != 0) {
      error_msg = "Non si puo' aggiungere uno stile con 'xlsAddStyle()' a questo punto.\n";
      cerr << error_msg << endl;
      return 0;
   }   
   if ((int)listaStili.find(Stile.ID) >= 0) {
      error_msg = "ID stile '" + (string)Stile.ID + "' duplicato\n"; 
      cerr << error_msg << endl;
      return 0;
   }   
   else { 
      listaStili += "," + (string)Stile.ID;
   }   
   
   /* Inizia ad aggiungere lo stile  */
   _xmlBuffer = "";
   
   /* Allineamento  */
   if (strlen(Stile.Align) == 0) strcpy(Stile.Align, "Left");            /* "Left" (default), "Right", "Center" */
   if (strlen(Stile.VAlign) == 0) strcpy(Stile.VAlign, "Bottom");        /* "Top", "Bottom" (default), "Center" */
   if (Stile.Indent != 0) {
      s = " ss:Indent=\"" + itos(Stile.Indent) + "\"";
   }
   if (Stile.WrapText == '1') s += " ss:WrapText=\"1\"";
   if (Stile.VerticalText == '1') s += " ss:VerticalText=\"1\"";
   if (Stile.Rotate != 0) {
      s += " ss:Rotate=\"" + itos(Stile.Rotate) + "\"";
   }   
   _xmlBuffer = "  <Style ss:ID=\"" + (string)Stile.ID + "\">\n   <Alignment ss:Horizontal=\"" + 
                (string)Stile.Align + "\" ss:Vertical=\"" + (string)Stile.VAlign + "\"" + s + "/>\n"; 

   /* bordo della cella  */
   if (Stile.BorderTop != 0 || Stile.BorderBottom != 0 || Stile.BorderLeft != 0 || Stile.BorderRight != 0) {
      s = "   <Borders>\n";
      if (strlen(Stile.BorderType) == 0) strcpy(Stile.BorderType, "Continuous");
      if (strlen(Stile.BorderColor) != 0) s2 = " ss:Color=\"" + (string)Stile.BorderColor + "\"";
      if (Stile.BorderTop != 0) {
         s += "    <Border ss:Position=\"Top\" ss:LineStyle=\"" + (string)Stile.BorderType + 
              "\" ss:Weight=\"" + itos(Stile.BorderTop) + "\"" + s2 + "/>\n";
      }   
      if (Stile.BorderBottom != 0) {
         s += "    <Border ss:Position=\"Bottom\" ss:LineStyle=\"" + (string)Stile.BorderType + 
              "\" ss:Weight=\"" + itos(Stile.BorderBottom) + "\"" + s2 + "/>\n";
      }   
      if (Stile.BorderLeft != 0) {
         s += "    <Border ss:Position=\"Left\" ss:LineStyle=\"" + (string)Stile.BorderType + 
              "\" ss:Weight=\"" + itos(Stile.BorderLeft) + "\"" + s2 + "/>\n";
      }   
      if (Stile.BorderRight != 0) {
         s += "    <Border ss:Position=\"Right\" ss:LineStyle=\"" + (string)Stile.BorderType + 
              "\" ss:Weight=\"" + itos(Stile.BorderRight) + "\"" + s2 + "/>\n";
      }   
      /* Stile.BorderColor        # di default è "#000000" = nero  */
      _xmlBuffer += s + "   </Borders>\n";
   }
   
   /* Caratteristiche del Font usato nella cella */
   if (Stile.FontName != "" || Stile.FontBold != '0' || Stile.FontItalic != '0' || Stile.FontSize != 0 || strlen(Stile.FontColor) != 0) {
      s = "   <Font ";
      if (strlen(Stile.FontName) != 0) {
         s += "ss:FontName=\"" + (string)Stile.FontName + "\" "; 
      }   
      if (Stile.FontBold == '1') {
         s += "ss:Bold=\"1\" ";
      }
      if (Stile.FontItalic == '1') {
         s += "ss:Italic=\"1\" ";
      }
      if (Stile.FontSize != 0) {
         s += "ss:Size=\"" + itos(Stile.FontSize) + "\" ";
      }   
      if (strlen(Stile.FontColor) != 0) {
         s += "ss:Color=\"" + (string)Stile.FontColor + "\" ";
      }
      _xmlBuffer += s + "/>\n";
   }
   /* Caratteristiche dello sfondo della cella (Interior) */
   if (strlen(Stile.BGcolor) != 0 || strlen(Stile.BGpattern) != 0) {
      if (strlen(Stile.BGpattern) == 0) strcpy(Stile.BGpattern, "Solid");
      s = "   <Interior";
      if (strlen(Stile.BGcolor) != 0){ 
         s += " ss:Color=\"" + (string)Stile.BGcolor + "\" ";
      }   
      if (strlen(Stile.BGpattern) != 0) { 
         s += " ss:Pattern=\"" + (string)Stile.BGpattern + "\" ";
      }   
      if (strlen(Stile.BGpatternColor) != 0) { 
         s += " ss:PatternColor=\"" + (string)Stile.BGpatternColor + "\" ";
      }   
      _xmlBuffer += s + "/>\n";
   }     
    
   if (strlen(Stile.NumberFormat) != 0) {
      _xmlBuffer += "   <NumberFormat ss:Format=\"" + (string)Stile.NumberFormat + "\" />\n";
   }      
   else _xmlBuffer += "   <NumberFormat/>\n";
   if (Stile.Protected == '1') _xmlBuffer += "   <Protection ss:Protected=\"1\"/>\n";
   else _xmlBuffer += "   <Protection ss:Protected=\"0\"/>\n";

   _xmlBuffer += "  </Style>\n";

   /* scrive lo stile in output */
   if(error_msg == "") {
       fOut << _xmlBuffer;
   }    
}

EXPORT int xlsExcel::xlsAddSheet(string nomeSheet) {
    return xlsAddSheet(nomeSheet, defaultSheet);
}

/* Aggiunge un foglio Excel, chiudendo eventualmente il precedente e aprendo il nuovo */
EXPORT int xlsExcel::xlsAddSheet(string nomeSheet, xlsSheetStruct Sheet) {
      
   /* Controlli */
   if (nomeSheet != "") strncpy(Sheet.SheetName, nomeSheet.data(), sizeof Sheet.SheetName);
   if (strlen(Sheet.SheetName) == 0) sprintf(Sheet.SheetName, "Sheet%d", nSheets);
   if (strlen(Sheet.SheetName) == 0) {
      error_msg = "Manca il nome dello Sheet in xlsAddSheet().\n";
      cerr << error_msg << endl;
      return 0;
   }  
   if ((int)listaSheet.find(Sheet.SheetName) >= 0) {
      error_msg = "Nome Sheet '" + (string)Sheet.SheetName + "' duplicato in xlsAddSheet().\n";
      cerr << error_msg << endl;
      return 0;
   }
   else { 
      listaSheet += "," + (string)Sheet.SheetName;
   }   

   if (__Sequenza > 0)          /* chiude lo sheet precedente */
      __ChiudiSheet();
   else {                       /* chiude gli Styles, perchè questo è il primo sheet */
      if (error_msg == "") {
          fOut << " </Styles>\n";
      }    
      /* Se c'è "OldExcelFile", bisogna ricopiare i fogli esistenti nel vecchio file  */
      if (flagMerge) __CopiaSheet();
   }
   _xmlBuffer = "";
   
   if (strlen(Sheet.Description) == 0)
      sprintf(Sheet.Description, "Foglio Excel '%s' generato automaticamente", Sheet.SheetName);
   else 
      strncpy(Sheet.Description, AsciiToUtf8(Sheet.Description).data(), sizeof Sheet.Description);

   if (strlen(Sheet.FootPage) == 0)
      strcpy(Sheet.FootPage, "Pagina &amp;P");
   else 
      strncpy(Sheet.FootPage, AsciiToUtf8(Sheet.FootPage).data(), sizeof Sheet.FootPage);

   /* Imposta i default  */
   /* stringa numerica in Inch, default = "0.1" */
   if (strlen(Sheet.MarginTop) == 0 || strcmp(Sheet.MarginTop, "0") == 0) strcpy(Sheet.MarginTop, "0.1");    
   
   /* Salva lo sheet per la chiusura */
   __Sheet = Sheet;        
   nSheets++;

   /* Apre il nuovo Sheet  */
   _xmlBuffer = " <Worksheet ss:Name=\"" + (string)Sheet.SheetName + 
           "\" >\n  <Table ss:ExpandedColumnCount=\"256\" x:FullColumns=\"1\" x:FullRows=\"1\">\n";

   /* scrive lo stile in output */
   if(error_msg == "") {
       fOut << _xmlBuffer;
   }    
   /* Inizialzza variabili di appoggio  */
   ActualRow = 1;
   ActualCol = 1;
   __flagOpenRow = 0;
   __RowAttribute = "";
   __Sequenza = 1;
}

/* Genera l'XML di chiusura Sheet */
void xlsExcel::__ChiudiSheet(void) {
   string s, s1, s2, s3, s4;
   
   /* se c'è una riga aperta, la chiude */    
   if(__flagOpenRow && (error_msg == "")) {
       fOut << "   </Row>\n";
   }    
   __flagOpenRow = 0;

   if (strlen(__Sheet.HeaderMargin) != 0) {
      s1 = "x:Margin=\"" + (string)__Sheet.HeaderMargin + "\" ";
      if (strlen(__Sheet.FooterMargin) != 0)
          s2 = "    <Footer x:Margin=\"" + (string)__Sheet.FooterMargin + "\" x:Data=\"" + (string)__Sheet.FootPage + "\"/>\n";
      if (__Sheet.Layout == 'L')
          s3 = "    <Layout x:Orientation=\"Landscape\"/>\n";
      if (__Sheet.Layout == 'P')
          s3 = "    <Layout x:Orientation=\"Portrait\"/>\n";
      if (__Sheet.PrintGrid == '1')
          s4 = "    <Gridlines/>\n";
   }

   /* Genera l'XML di chiusura Sheet */
   _xmlBuffer = "  </Table>\n";
   _xmlBuffer += "  <WorksheetOptions xmlns=\"urn:schemas-microsoft-com:office:excel\">\n";
   _xmlBuffer += "   <PageSetup>\n" + s3 + "    <Header " + s1 + "x:Data=\"" + (string)__Sheet.Description + "\"/>\n" + s2;
   _xmlBuffer += "    <PageMargins x:Bottom=\"" + (string)__Sheet.MarginBottom + "\" x:Left=\"" + (string)__Sheet.MarginLeft + "\"";
   _xmlBuffer += " x:Right=\"" + (string)__Sheet.MarginRight + "\" x:Top=\"" + (string)__Sheet.MarginTop + "\"/>\n   </PageSetup>\n";   
   _xmlBuffer += "   <Print>\n" + s4 + "   </Print>\n";
   _xmlBuffer += "   <Selected/>\n";
   _xmlBuffer += "   <ProtectObjects>False</ProtectObjects>\n";
   _xmlBuffer += "   <ProtectScenarios>False</ProtectScenarios>\n";
   _xmlBuffer += "  </WorksheetOptions>\n";
   _xmlBuffer += " </Worksheet>\n";
   /* scrive lo sheet in output */
   if(error_msg == "") {
       fOut << _xmlBuffer;
   }    
}

/* Cambia le caratteristiche di default di una colonna, come larghezza e stile
      La Larghezza da impostare è circa uguale al numero di caratteri * 4 (con i caratteri di default) */
EXPORT int xlsExcel::xlsColumn(int numColonna, int Larghezza, string IDstile) {
   string s;
   
   /* Controlli  */
   if (numColonna < 1 || numColonna > 256) {
      error_msg = "Numero di colonna errato in xlsColumn()\n";
      cerr << error_msg << endl;
      return 0;
   }   
   if (__Sequenza != 1) {
      error_msg = "Non si puo' aggiungere una definizione Colonna a questo punto\n";
      cerr << error_msg << endl;
      return 0;
   }   
   /* controlla che lo stile sia definito */
   if (IDstile != "") {   
      if ((int)listaStili.find(IDstile) < 0) {
         error_msg = "ID stile '" + (string)IDstile + "' errato o mancante.\n";
         cerr << error_msg << endl;
         return 0;
      }   
   } 
   _xmlBuffer = "";  

   if (IDstile != "") s = "ss:StyleID=\"" + (string)IDstile + "\" ";

   _xmlBuffer = "   <Column ss:Index=\"" + itos(numColonna) + "\" " + s + 
         " ss:AutoFitWidth=\"0\" ss:Width=\"" + itos(Larghezza) + "\"/>\n";
   /* scrive la colonna in output */
   if(error_msg == "") {
       fOut << _xmlBuffer;
   }    
   return -1;   
}

/* Scrive nella cella attuale un dato di tipo Stringa, Numero o Data */
EXPORT int xlsExcel::xlsWriteCell(string Valore, char Tipo, string IDstile, string Formula, 
       int nColSkip, int MergeAcross, int MergeDown) {
   string v, s1, s2, s3, s4, sm;

   /* Controlli: Tipo può essere: "N", "D" o "S"  */
   if (__Sequenza < 1) {
      error_msg = "Non si puo' scrivere una cella a questo punto\n";
      cerr << error_msg << endl;
      return 0;
   }
   if (IDstile != "") {   /* controlla che lo stile sia definito */
      if ((int)listaStili.find(IDstile) < 0) {
         error_msg = "ID stile '" + (string)IDstile + "' errato o mancante in xlsWriteCell().\n";
         cerr << error_msg << endl;
         return 0;
      }
   }
   if (nColSkip < 0 || nColSkip > 200) nColSkip = 0;
   _xmlBuffer = "";  

   Tipo = toupper(Tipo);
   if (Tipo == 'N') {
      s1 = "Number";
      s4 = "ss:StyleID=\"num\"";
   }
   else if (Tipo == 'D') {
      s1 = "DateTime";
      s4 = "ss:StyleID=\"date\"";
   }
   else {
      Tipo = 'S';
      s1 = "String";
      s4 = "ss:StyleID=\"txt\"";
   }

   if (MergeAcross > 0) sm = "ss:MergeAcross=\"" + itos(MergeAcross) + "\" ";
   if (MergeDown > 0) sm = "ss:MergeDown=\"" + itos(MergeDown) + "\" ";

   /* Apre la eventuale nuova riga  */
   if(!__flagOpenRow && (error_msg == "")) {
      fOut << "   <Row" + (string)__RowAttribute + ">\n";
      __flagOpenRow = -1;
   }    

   /* si posiziona nella colonna giusta */
   ActualCol += nColSkip;
   if (nColSkip > 0)
      s2 = "ss:Index=\"" + itos(ActualCol) + "\" ";

   if (Formula != "") s3 = "ss:Formula=\"" + (string)Formula + "\" ";
   if (IDstile == "") s4 = "";    /* toglie lo stile */      
   else  s4 = "ss:StyleID=\"" + (string)IDstile + "\" ";

   /* Valore è sempre una stringa, ne controlla la lunghezza, rimpiazza '&<>' e converte in UTF8  */
   //v = Valore;
   v = Replace(Valore, "&", "&amp;");
   v = Replace(v, "<", "&lt;");
   v = Replace(v, ">", "&gt;");
   v = AsciiToUtf8(v);
        
   _xmlBuffer = "    <Cell " + sm + s2 + s3 + s4 + "><Data ss:Type=\"" + s1 + "\">" + v + "</Data></Cell>\n";

   /* scrive la colonna in output */
   if(error_msg == "") {
      fOut << _xmlBuffer;
   }    

   ActualCol += 1;
   __RowAttribute = "";
   __Sequenza = 2;
   return -1;
}

/* Passa alla riga successiva del foglio Excel */
EXPORT int xlsExcel::xlsNewLine(int skip, int RowWidth, string IDstile) {
   string s, s1, s2, s3;

   /* Controlli */
   if (__Sequenza < 1) {
      error_msg = "Non si puo' saltare una linea a questo punto.\n";
      cerr << error_msg << endl;
      return 0;
   }
   /* aggiorna i contatori riga-colonna */
   ActualRow += skip + 1;
   ActualCol = 1;

   if (skip > 0 && skip <= 200) s1 = " ss:Index=\"" + itos(ActualRow) + "\" ";
   if (RowWidth > 0 && RowWidth < 200) s2 = " ss:AutoFitHeight=\"0\" ss:Height=\"" + itos(RowWidth) + "\" ";
   if (IDstile != "") {   /* controlla che lo stile sia definito */
      if ((int)listaStili.find(IDstile) < 0) {
         error_msg = "ID stile '" + (string)IDstile + "' errato o mancante in xlsNewLine().\n";
         cerr << error_msg;
         return 0;
      }   
      s3 = " ss:StyleID=\"" + (string)IDstile + "\" ";
   }   

   /* se la riga precedente è ancora aperta, la chiude */
   if(__flagOpenRow && (error_msg == "")) {
       fOut << "   </Row>\n";
   }    
   __flagOpenRow = 0;
   __RowAttribute = s1 + s2 + s3;
   return -1;
}

/* Chiusura XML ed eventuale output del file */
EXPORT int xlsExcel::xlsClose(void) {
   if (__Sequenza < 2) {
      error_msg = "Non si puo' chiudere a questo punto.\n";
      cerr << error_msg;
      return 0;
   }   

   __ChiudiSheet();
   
   if(error_msg == "") {
      fOut << "</Workbook>\n"; 
      fOut.close();
   }
   /* Azzera le variabili globali  */      
   listaSheet = "";
   listaStili = "";
   SheetsToDel = "";
   error_msg = "";
   nSheets = 0;
   __Sequenza = 0; 
   __flagOpenRow = 0;
   return -1;
}

/* Ritorna la posizione attuale in forma di sigla Lettera+Numero */
EXPORT string xlsExcel::xlsGetPosition(void) {
   char s[20];
   int r, n;
   
   if (ActualCol > 26) {          /* 26 è il numero di lettere dell'alfabeto = Len(__Alfabeto) */
      n = ActualCol / 26;    /* divisione intera */
      r = ActualCol % 26;    /* resto */
      if (n > 26) return 0;
      sprintf(s, "%c%c%d", Alfabeto[n], Alfabeto[r], ActualRow);
   }   
   else 
      sprintf(s, "%c%d", Alfabeto[ActualCol-1], ActualRow);
   return (string)s;
}

/* Ritorna la stringa che rappresenta il rettangolo di celle voluto, per usarlo nelle formule */
EXPORT string xlsExcel::xlsGetRectangle(int Riga1, int Col1, int Riga2, int Col2) {
   string s;
   char s1[20];
   
   s = "R";
   if (ActualRow != Riga1) {
      sprintf(s1, "R[%d]", (Riga1-ActualRow));
      s = s1;
   }    
   s += "C";
   if (ActualCol != Col1) {
      sprintf(s1, "[%d]", (Col1-ActualCol));
      s += s1;
   }    
   s += ":R";
   if (ActualRow != Riga2) {
      sprintf(s1, "[%d]", (Riga2-ActualRow));
      s += s1;
   }    
   s += "C";
   if (ActualCol != Col2) {
      sprintf(s1, "[%d]", (Col2-ActualCol));
      s += s1;
   }    
   return s;
}

/* Esclude uno Sheet tra quelli da copiare facendo il Merge con un Excel vecchio */
EXPORT int xlsExcel::xlsSheetDelete(string NomeSheet) {
   if (NomeSheet != "") {
      SheetsToDel += "," + (string)NomeSheet;      
   }
}

/* Inizia la copia da un file Excel all'altro controllando quali stili sono presenti */
EXPORT int xlsExcel::xlsInitMerge(string OldFile, string NewFile) {
   string linea, s;
   char rec[MAXDATALEN];
   int n, nStyles, flagStyle;
   int Posiz1, Posiz2; 
   
   /* Chiude eventuale file aperto in sospeso */
   /*if (!fOut.fail() ) {    
      fOut.close();
   }*/
   
   listaStili = "";
   listaSheet = "";
   SheetsToDel = "";
   error_msg = "";
   nSheets = 0;
   __Sequenza = 0; 
   __flagOpenRow = 0;
   flagStyle = 0;
   nStyles = 0;
   flagMerge = -1;
   
   /* Apre il file di output  */  
   if (NewFile == "") FileName = "ExcelExport";
   else FileName = NewFile;  
   fOut.open( FileName.data(), ios::out);  
   if( fOut.fail() ) { 
      error_msg = "Errore in apertura del file in output '" + FileName + "'";
      cerr << error_msg << endl;
      return 0; 
   }

   /* Apre file in input da mergiare  */
   fIn.open( OldFile.data());
   if( fIn.fail() ) {
      error_msg = "Errore in apertura del file in input '" + OldFile + "'";
      cerr << error_msg << endl;
      return 0;
   }
   
   /* legge il file in input una linea alla volta e la copia contando gli stili */
   /* si ferma alla fine degli style: </Styles>    */
   n = 0;
   while (! fIn.eof()) {
      fIn.getline(rec, MAXDATALEN);
      linea = (string)rec;
      n++;      
      /* Controlla se il file è XML nel primo record */
      if (n == 1) {
         s = linea.substr(0, 6);     
         if (s != "<?xml ") {
            error_msg = "File in input non XML Excel.";
            cerr << error_msg << endl;
            break;
         }
      } 
      /* Controlla se il file è Excel nel secondo record */
      if (n == 2) {
         s = linea.substr(0, 18);
         if (s != "<?mso-application ") {
            error_msg = "File in input non sembra un file Excel valido.";
            cerr << error_msg << endl;
            break;
         }
      } 

      /*  Test di inizio Style */
      if ((int)linea.find("<Style ") >= 0) {
         flagStyle = -1;
         Posiz1 = (int)linea.find(":ID=\"");
         if (Posiz1 < 0) {
            error_msg = "File in input con tag <Style> con ID mancante.";
            cerr << error_msg << endl;
            break;
         }
         Posiz1 += 5;
         Posiz2 = (int)linea.find("\"", Posiz1);
         if (Posiz2 < 0 || Posiz2 <= Posiz1) {
            error_msg = "File in input con tag <Style> con ID malformato.";
            cerr << error_msg << endl;
            break;
         }
         
         /* Estrae lo ID dello Style e lo aggiunge ai precedenti */
         s = linea.substr(Posiz1, Posiz2-Posiz1);
         listaStili +=  "," + s;
         nStyles++;
      }
      /* evita di scrivere in output la fine degli stili "</Styles>" */
      if ((int)linea.find("</Styles>") >= 0) break;
      fOut << linea; 
      if ((int)linea.length() < MAXDATALEN) fOut << endl;
   }
   
   if (flagStyle == 0) {
      error_msg = "File in input con tag <Style> mancante.";
      cerr << error_msg << endl;
      return 0;
   }
   /* Scrive gli Style di default se non sono già stati scritti */
   if ((int)listaStili.find("txt") < 0) {
      s = "  <Style ss:ID=\"txt\">\n   <Alignment ss:Horizontal=\"Left\" ss:Vertical=\"Bottom\"/>\n";
      s += "   <NumberFormat/>\n   <Protection ss:Protected=\"0\"/>\n  </Style>\n";
      fOut << s; 
      listaStili += ",txt";
      nStyles += 1;
   }   
   if ((int)listaStili.find(",num") < 0) {
      s = "  <Style ss:ID=\"num\">\n   <Alignment ss:Horizontal=\"Right\" ss:Vertical=\"Bottom\"/>\n";
      s += "   <NumberFormat/>\n   <Protection ss:Protected=\"0\"/>\n  </Style>\n";
      fOut << s; 
      listaStili += ",num";
      nStyles += 1;
   }   
   if ((int)listaStili.find(",date") < 0) {
      s = "  <Style ss:ID=\"date\">\n   <Alignment ss:Horizontal=\"Center\" ss:Vertical=\"Bottom\"/>\n";
      s += "   <NumberFormat ss:Format=\"Short Date\"/>\n   <Protection ss:Protected=\"0\"/>\n  </Style>\n";
      fOut << s; 
      listaStili += ",date";
      nStyles += 1;
   }  

   /* Entrambi i files rimangono aperti  */  
}

/* Copia da un file Excel all'altro eliminando eventualmente degli sheet, nel Merge */
int xlsExcel::__CopiaSheet(void) {
   string linea, s;
   char rec[MAXDATALEN];
   int  n, flagSkip;
   int Posiz1, Posiz2;
   
   if( fOut == NULL || fIn == NULL ) {
      error_msg = "Errore in apertura dei file in __CopiaSheet().";
      cerr << error_msg << endl;
      return 0; 
   }
   flagSkip = 0;   

   n = 0;
   while (! fIn.eof()) {
      fIn.getline(rec, MAXDATALEN);
      linea = (string)rec;
      n++;

      /* Esce a fine </Workbook> senza scrivere questa linea di chiusura */
      if ((int)linea.find("</Workbook>") >= 0) break;
      /* raccoglie il nome degli sheet presenti, se è un inizio Sheet */
      if ((int)linea.find("<Worksheet ") >= 0) {
         flagSkip = 0;
         Posiz1 = (int)linea.find(":Name=\"");
         if (Posiz1 < 0) {
            error_msg = "File in input con tag <Worksheet> con Name mancante.";
            cerr << error_msg << endl;
            break;
         }
         Posiz1 += 7;
         Posiz2 = (int)linea.find("\"", Posiz1 += 7);
         if (Posiz2 < 0) {
            error_msg = "File in input con tag <Worksheet> con Name malformato.";
            cerr << error_msg << endl;
            break;
         }
         /* Estrae il nome dello Sheet e lo aggiunge ai precedenti, se non è da saltare */
         s = linea.substr(Posiz1, Posiz2-Posiz1);

         if ((int)SheetsToDel.find(s) >= 0) {
            flagSkip = -1;   /* lo Sheet è da saltare */
         }
      }
      /* memorizza la linea appena letta */
      if (! flagSkip) {
         fOut << linea;
         if ((int)linea.length() < MAXDATALEN) fOut << endl;
      }   
   }
   fIn.close();

   /* rimane aperto solo fOut  */
}

/* Funzione per convertire da ANSI a UTF-8 (by Jose Roca)  */
EXPORT string AsciiToUtf8 (string strAscii) { 
    int i, limite;       /* Loop counter    */
    char strUtf8[MAXDATALEN];      /* UTF-8 encoded string  */
    int idx;             /* Position in the output string  */
    unsigned char c;     /* ASCII code  */
    unsigned char b2;    /* Second byte */

    limite = (int)strAscii.length();   /* lunghezza stringa ascii in input  */ 
    if (limite == 0) return ""; 

    /* The maximum length of the translated string will be     */
    /* twice the length of the original string.                */
    /* We are pre-allocating the buffer for faster operation   */
    /* than concatenating each character one by one.           */
    //strUtf8.append(limite * 2, ' ');

    /* Initialize index position in the string buffer           */
    /* used to store the UTF-8 encoded string                  */
    idx = 0;

    /* Examine the contents of each character in the Ascii string  */
    for (i = 0; i < limite; i++) {
        if (idx >= (MAXDATALEN-4)) break;   
        /* Get the Ascii code of the character     */
        c = strAscii[i];
        /* se è un carattere speciale lo trasforma in Spazio  */
        if (c < 32) {  
           strUtf8[idx] = ' ' ;
           idx++;    
        }   
        else {
            /* If it is between 0 and 127...   */
            if (c < 128) {
                /* ...we simply copy it to the string buffer...      */
                strUtf8[idx] = strAscii[i];
                /* ...and increase the position by 1.    */
                idx++;
            }    
            else {
                /* We need to split the character into two characters.
                // For the second byte, we only need the lower six bits of the character,
                // and to ensure that the two upper bits will be 10 (in binary),
                // i.e. (00111111 AND xxxxxxxx) OR 10000000    */
                b2 = (c & 0x3F) | 0x80;
                /* For the first byte, we need only the upper two bits from the character,   */
                /* and to ensure that the three upper bits will be 110 (in binary).          */
                //Shift Right c, 6
                c = c >> 6;
                c = c | 0xC0;
                /* Copy the bytes to the buffer string and increase the index position by 2. */
                strUtf8[idx] = c;  idx++;
                strUtf8[idx] = b2; idx++;
            }
        }
    }
    strUtf8[idx] = '\0' ;

    /* Return the encoded string    */
    return (string)strUtf8;
}

/* Funzione per rimpiazzare delle sottostringhe  */
EXPORT string Replace (string strIn, string strMatch, string strNew) { 
    string strOut="";                  /* stringa trasformata  */
    int PosizIn, PosizOut, Ptr;        /* puntatori all'interno delle due stringhe */
    int limite;               /* Lunghezza stringa in input  */
    int lMatch, lNew;         /* Lunghezze stringhe  */
    int nChar;                /* contatore dei caratteri copiati */

    limite = (int)strIn.length();           /* lunghezza stringa ascii in input  */
    if (limite == 0) return ""; 
    lMatch = (int)strMatch.length(); 
    if (lMatch == 0) return strIn; 
    lNew = (int)strNew.length();
    if (lMatch >= 100 || lNew >= 100) return strIn;
       
    /* Inizializza i puntatori all'interno delle stringhe in input e output */
    PosizIn  = 0;
    PosizOut = 0;
    /* nChar conta i caratteri copiati in output  */
    nChar = 0;

    Ptr = (int)strIn.find(strMatch);  /* cerca la sottostringa  */
    while (Ptr >= 0) {  
       if ((Ptr-PosizIn) > 0 ) {  /* se il match NON è all'inizio ....  */
          if ((nChar + Ptr-PosizIn) >= MAXDATALEN) break;  
          /* ... copia il pezzo di stringa che precede il match */
          strOut += strIn.substr(PosizIn, Ptr-PosizIn);
          nChar += Ptr-PosizIn;
       }   
       if (lNew > 0) {  /* se la stringa di sostituzione c'è ...  */
          if ((nChar + lNew) >= MAXDATALEN) break;
          /* ... copia la stringa nuova che sostituisce la vecchia */
          strOut += strNew;
          nChar += lNew;
       }
       PosizIn = Ptr + lMatch;
       Ptr = (int)strIn.find(strMatch, PosizIn);  /* cerca di nuovo la sottostringa  */
    }
    /* Copia il rimanente della stringa  */
    strOut += strIn.substr(PosizIn, limite-PosizIn);

    /* Ritorna la stringa trasformata    */
    return strOut;
}

/* Conversione da int a string   */
string itos(int i) {	// convert int to string
	stringstream s;
	s << i;
	return s.str();
}

