Serve per l'invio dei solleciti via mail. git-svn-id: svn://10.65.10.50/branches/R_10_00@22869 c028cbd2-c16b-5b4b-a496-9718f37d4682
		
			
				
	
	
		
			1664 lines
		
	
	
		
			36 KiB
		
	
	
	
		
			C++
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			1664 lines
		
	
	
		
			36 KiB
		
	
	
	
		
			C++
		
	
	
		
			Executable File
		
	
	
	
	
| #include "../xvaga/incstr.h"
 | |
| 
 | |
| #include <applicat.h>
 | |
| #include <codeb.h>
 | |
| #include <colors.h>
 | |
| #include <dongle.h>
 | |
| #include <modaut.h>
 | |
| #include <progind.h>
 | |
| #include <recset.h>
 | |
| #include <relation.h>
 | |
| #include <utility.h>
 | |
| #include <xml.h>
 | |
| 
 | |
| #include <statbar.h>
 | |
| 
 | |
| ///////////////////////////////////////////////////////////
 | |
| // Utility
 | |
| ///////////////////////////////////////////////////////////
 | |
| 
 | |
| static bool is_numeric(const char* str)
 | |
| {
 | |
|   if (str == NULL || *str == '\0' || (*str == '0' && isdigit(str[1])))
 | |
|     return false;    // Se comincia per zero va preservato!
 | |
|   if (*str == '-')
 | |
|   {
 | |
|     str++;
 | |
|     if (*str <= ' ')
 | |
|       return false;
 | |
|   }
 | |
|   while (*str)
 | |
|   {
 | |
|     if (strchr("0123456789.,", *str) == NULL)
 | |
|       return false;
 | |
|     str++;
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| static void num_reformat(TString& val)
 | |
| {
 | |
| 	xvt_str_number_format(val.get_buffer(), val.size());
 | |
|   const int comma = val.find(',');
 | |
|   const int point = val.find('.');
 | |
|   if (comma >= 0 && comma < point) 
 | |
|     val.strip(","); else
 | |
|   if (point >= 0 && point < comma) 
 | |
|     val.strip(".");
 | |
| }
 | |
| 
 | |
| ///////////////////////////////////////////////////////////
 | |
| // TRecordset
 | |
| ///////////////////////////////////////////////////////////
 | |
| 
 | |
| const TString& TRecordset::query_text() const
 | |
| {
 | |
|   return EMPTY_STRING;
 | |
| }
 | |
| 
 | |
| const TToken_string& TRecordset::sheet_head() const
 | |
| {
 | |
|   TToken_string head;
 | |
|   TToken_string tablefield(32, '.');
 | |
| 	const unsigned int cols = columns();
 | |
| 
 | |
|   for (unsigned int c = 0; c < cols; c++)
 | |
|   {
 | |
|     const TRecordset_column_info& ci = column_info(c);
 | |
|     tablefield = ci._name;
 | |
|     int maxlen = 0;
 | |
|     FOR_EACH_TOKEN(tablefield, tok)
 | |
|     {
 | |
|       if (maxlen == 0)
 | |
|         head.add(tok);
 | |
|       else
 | |
|         head << '\n' << tok;
 | |
|       const int len = strlen(tok);
 | |
|       if (len > maxlen)
 | |
|         maxlen = len;
 | |
|     }
 | |
|     head << '@' << max(ci._width, maxlen);
 | |
|     switch (ci._type)
 | |
|     {
 | |
|     case _boolfld: head << 'C'; break;
 | |
|     case _wordfld:
 | |
|     case _intfld:
 | |
|     case _longfld: head << 'R'; break;
 | |
|     case _datefld: head << 'D'; break;
 | |
|     case _realfld: head << (ci._width > 7 ? 'V' : 'R'); break;
 | |
|     default: break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Creo la stringa temporanea solo ora, altrimenti puo' essere sovrascritta!
 | |
|   TToken_string& h = get_tmp_string();
 | |
|   h = head;
 | |
|   return h;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool TRecordset::save_as_html(const char* path)
 | |
| {
 | |
|   ofstream out(path);
 | |
|   if (out.fail())
 | |
|     return false;
 | |
| 
 | |
|   TProgind pi(items(), TR("Esportazione in corso..."), true, true);
 | |
|   
 | |
|   out << "<html>" << endl;
 | |
|   save_html_head(out, main_app().title());
 | |
|   out << "<body>" << endl;
 | |
| 
 | |
|   /* More annoyng than useful
 | |
|   TString qry; parsed_text(qry);
 | |
|   if (qry.full())
 | |
|   {
 | |
|     for (int i = qry.find('\n'); i > 0; i = qry.find('\n', i+1))
 | |
|       qry.insert("<br/>", i+1);
 | |
|     out << "<p><b>" << qry << "</b></p>" << endl;
 | |
|   }*/
 | |
| 
 | |
|   out << "<table border=\"1\">";
 | |
|   out << " <caption>" << main_app().title() << "</caption>" << endl;
 | |
| 
 | |
|   const unsigned int cols = columns();
 | |
| 	TAttributes attr;
 | |
| 
 | |
| 	if (cols > 0)
 | |
|   {
 | |
|     out << " <thead>" << endl;
 | |
|     for (unsigned int c = 0; c < cols; c++)
 | |
|     {
 | |
|       const TRecordset_column_info& ci = column_info(c);
 | |
|       out << "  <col ";
 | |
|       switch (ci._type)
 | |
|       {
 | |
|       case _intfld :
 | |
|       case _longfld:
 | |
|       case _realfld: out << "align=\"right\""; break;
 | |
|       case _boolfld: out << "align=\"center\""; break;
 | |
|       case _datefld: out << "style=\"mso-number-format:\\Short Date\""; break;
 | |
|       default      : out << "style=\"mso-number-format:\\@\""; break; // Stringa!
 | |
|       }
 | |
|       out << " />" << endl;
 | |
|     }
 | |
| 
 | |
|     TXmlItem tr; tr.SetTag("tr"); 
 | |
|     tr.SetColorAttr("bgcolor", BTN_BACK_COLOR);
 | |
|     tr.Write(out, 2);
 | |
|     out << endl;
 | |
| 
 | |
|     for (unsigned int c = 0; c < cols; c++)
 | |
|     {
 | |
|       const TRecordset_column_info& ci = column_info(c);
 | |
|       TToken_string header(ci._name, '\n');
 | |
|       TString str;
 | |
|       FOR_EACH_TOKEN(header, tok)
 | |
|       {
 | |
|         if (str.not_empty())
 | |
|           str << "<br/>";
 | |
|         str << tok;
 | |
|       }
 | |
| 			if (get_attr(c, attr, true))
 | |
| 				out << "   <th style=\"color : #" << format("%06x", attr.get_foreground() & 0xFFFFFF) 
 | |
|             << "; background-color : #" << format("%06x", attr.get_background() & 0xFFFFFF) << "\">" ;
 | |
| 			else
 | |
| 				out << "   <th>";
 | |
| 
 | |
| 			out << str << "</th>" << endl;
 | |
|     }
 | |
|     out << "  </tr>" << endl;
 | |
|     out << " </thead>" << endl;
 | |
|   }
 | |
| 
 | |
|   out << " <tbody>" << endl;
 | |
|   TString val;
 | |
|   for (bool ok = move_first(); ok; ok = move_next())
 | |
|   {
 | |
|     if (!pi.addstatus(1))
 | |
|       break;
 | |
| 
 | |
|     out << "  <tr>" << endl;
 | |
|     for (unsigned int c = 0; c < cols; c++)
 | |
|     {
 | |
|       const TRecordset_column_info& ci = column_info(c);
 | |
| 
 | |
| 			if (get_attr(c, attr))
 | |
| 				out << "   <td style=\"color : #" << format("%06x", attr.get_foreground() & 0xFFFFFF) 
 | |
|             << "; background-color : #" << format("%06x", attr.get_background() & 0xFFFFFF) << "\">" ;
 | |
| 			else
 | |
|       out << "   <td>";
 | |
|       switch (ci._type)
 | |
|       {
 | |
|       case _intfld:
 | |
|       case _longfld:
 | |
|         {
 | |
|           const TVariant& var = get(c);
 | |
|           const long r = var.as_int();
 | |
|           val.cut(0);
 | |
|           if (r != 0)
 | |
|             val << r;
 | |
| 					else
 | |
| 		        var.as_string(val); 
 | |
|         }
 | |
|         break;
 | |
|       case _realfld:
 | |
|         {
 | |
| 					get(c).as_string(val);
 | |
| 					if (val.full() && real::is_real(val))
 | |
| 					{
 | |
| 						const real r(val);
 | |
|             if (r.is_zero())  //elimina gli '0' dalle celle vuote in excel, rendendo leggibile il file excel
 | |
|               val.cut(0);
 | |
|             else
 | |
|               val = r.stringe();
 | |
| 					}
 | |
|         }
 | |
|         break;
 | |
|       default: 
 | |
|         get(c).as_string(val); 
 | |
|         break;
 | |
|       }
 | |
|       if (val.full())
 | |
|       {
 | |
|         val.rtrim();
 | |
|         out << val;
 | |
|       }
 | |
|       out << "</td>" << endl;
 | |
|     }
 | |
|     out << "  </tr>" << endl;
 | |
|   }
 | |
|   out << " </tbody>" << endl;
 | |
|   out << "</table>" << endl;
 | |
|   out << "</body>" << endl;
 | |
|   out << "</html>" << endl;
 | |
| 
 | |
|   return !pi.iscancelled();
 | |
| }
 | |
| 
 | |
| bool TRecordset::save_as_text(const char* path)
 | |
| {
 | |
|   ofstream out(path);
 | |
|   if (out.fail())
 | |
|     return false;
 | |
| 
 | |
|   TProgind pi(items(), TR("Esportazione in corso..."), true, true);
 | |
| 
 | |
|   TString val;
 | |
| 	const char sep = text_separator();
 | |
| 	const bool as400 = (sep == ' ');
 | |
| 
 | |
| 	const unsigned int cols = columns();
 | |
|   for (bool ok = move_first(); ok; ok = move_next())
 | |
|   {
 | |
|     for (unsigned int c = 0; ; c++)
 | |
|     {
 | |
|       const TVariant& var = get(c);
 | |
|       if (var.is_null() && c >= cols)
 | |
| 				break;
 | |
| 
 | |
|       if (c > 0 && !as400)
 | |
|         out << sep;
 | |
| 			if (as400)
 | |
| 			{
 | |
|         var.as_string(val);
 | |
| 				if (var.type() == _realfld || (cols == 0 && is_numeric(val)))
 | |
| 				{
 | |
| 					num_reformat(val);
 | |
| 					val.lpad(column_info(c)._width);
 | |
| 				}
 | |
| 				else
 | |
| 					val.rpad(column_info(c)._width);
 | |
| 				out << val;
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				if (!var.is_empty())
 | |
| 				{
 | |
| 					var.as_string(val);
 | |
| 					val.rtrim();
 | |
| 					if (val.find('\n') >= 0 || val.find('\t') >= 0)
 | |
| 						out << unesc(val); // Evitiamo doppi separatori di campo e record
 | |
| 					else
 | |
| 					{
 | |
| 						if (var.type() == _realfld || (cols == 0 && is_numeric(val)))
 | |
| 							num_reformat(val);
 | |
| 						out << val;
 | |
| 					}
 | |
| 				}
 | |
|       }
 | |
|     }
 | |
|     out << endl;
 | |
|     if (!pi.addstatus(1))
 | |
|       break;
 | |
|   }
 | |
|  
 | |
|   return !pi.iscancelled();
 | |
| }
 | |
| 
 | |
| bool TRecordset::save_as_campo(const char* path)
 | |
| {
 | |
|   ofstream out(path);
 | |
|   if (out.fail())
 | |
|     return false;
 | |
| 
 | |
|   TProgind pi(items(), TR("Esportazione in corso..."), true, true);
 | |
|   
 | |
|   out << "[Head]" << endl;
 | |
|   out << "Version=0";
 | |
| 
 | |
| 	const unsigned int cols = columns();
 | |
|   for (unsigned int c = 0; c < cols; c++)
 | |
|   {
 | |
|     const TRecordset_column_info& ci = column_info(c);
 | |
|     if ((c % 8) == 0)
 | |
|       out << endl << "Fields=";
 | |
|     else
 | |
|       out << '|';
 | |
|     out << ci._name;
 | |
|   }
 | |
|   out << endl << endl << "[Data]" << endl;
 | |
| 
 | |
|   TString val;
 | |
|   for (bool ok = move_first(); ok; ok = move_next())
 | |
|   {
 | |
|     for (unsigned int c = 0; ; c++)
 | |
|     {
 | |
|       const TVariant& var = get(c);
 | |
|       if (var.is_null() && c >= cols)
 | |
| 				break;
 | |
| 
 | |
|       if (c > 0)
 | |
|         out << '|';
 | |
|       if (!var.is_empty())
 | |
|       {
 | |
|         var.as_string(val);
 | |
|         if (var.type() == _alfafld)
 | |
|         {
 | |
|           val.rtrim();
 | |
|           val.replace('|', SAFE_PIPE_CHR);  // Evitiamo doppi separatori di campo
 | |
|           if (val.find('\n') >= 0)          // Evitiamo doppi separatori di record  
 | |
|             out << unesc(val);
 | |
|           else
 | |
|             out << val;
 | |
|         }
 | |
|         else
 | |
|           out << val;
 | |
|       }
 | |
|     }
 | |
|     out << endl;
 | |
|     if (!pi.addstatus(1))
 | |
|       break;
 | |
|   }
 | |
|   return !pi.iscancelled();
 | |
| }
 | |
| 
 | |
| bool TRecordset::save_as_dbf(const char* table, int mode)
 | |
| {
 | |
|   char volume[_MAX_DRIVE];
 | |
|   char dirname[_MAX_PATH];
 | |
|   char name[_MAX_FNAME];
 | |
|   char ext[_MAX_EXT];
 | |
|   xvt_fsys_parse_pathname (table, volume, dirname, name, ext, NULL);
 | |
|   
 | |
|   const int logicnum = table2logic(name);
 | |
|  
 | |
|   if (mode == 0x0)
 | |
|     mode = 0x1;
 | |
|   if (mode & 0x4)
 | |
|   {
 | |
|     if (logicnum >= LF_USER && *dirname == '\0')
 | |
|     {
 | |
|       TSystemisamfile isam(logicnum);
 | |
|       if (isam.zap() != NOERR)
 | |
|         return error_box(TR("Impossibile cancellare il file '%s'"), table);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       TFilename n;
 | |
|       n = volume; n.add(dirname); n.add(name); n.ext("*");
 | |
|       TString_array files; list_files(n, files);
 | |
|       FOR_EACH_ARRAY_ROW(files, f, row)
 | |
|         xvt_fsys_remove_file(*row);
 | |
|     }
 | |
|     mode = 0x1;
 | |
|   }
 | |
| 
 | |
|   TLocalisamfile* pisam = NULL;
 | |
|   if (logicnum >= LF_USER)
 | |
|   {
 | |
|     if (*dirname)
 | |
|       pisam = new TIsamtempfile(logicnum, table);
 | |
|     else
 | |
|       pisam = new TLocalisamfile(logicnum);
 | |
|   }
 | |
|   if (pisam == NULL)
 | |
|     return error_box("Impossibile creare il file %s", table);
 | |
| 
 | |
|   TLocalisamfile& isam = *pisam;
 | |
| 
 | |
|   TProgind pi(items(), TR("Esportazione in corso..."));
 | |
|   TRectype& rec = isam.curr();
 | |
| 
 | |
|   TString_array names;
 | |
|   int valid = 0;
 | |
|   for (unsigned int j = 0; j < columns(); j++)
 | |
|   {
 | |
|     const TVariant& var = get(j);
 | |
|     const TString& name = column_info(j)._name;
 | |
|     if (rec.exist(name))
 | |
|     {
 | |
|       names.add(name);
 | |
|       valid++;
 | |
|     }
 | |
|     else
 | |
|       names.add(EMPTY_STRING);
 | |
|   }
 | |
| 
 | |
|   bool ok = true;
 | |
|   for (bool go = move_first(); go; go = move_next())
 | |
|   {
 | |
|     pi.addstatus(1);
 | |
|     rec.zero();
 | |
|     FOR_EACH_ARRAY_ROW(names, j, name) if (name->not_empty())
 | |
|       rec.put(*name, get(j).as_string());
 | |
| 
 | |
|     int err = NOERR;
 | |
|     bool to_be_written = true;
 | |
| 
 | |
|     // Devo solo aggiornare parte dei campi?
 | |
|     if ((mode & 0x2) && valid < rec.items())
 | |
|     {
 | |
|       err = isam.read(_isequal, _lock);
 | |
|       if (err != NOERR)
 | |
|         rec.zero();
 | |
|       FOR_EACH_ARRAY_ROW(names, j, name) if (name->not_empty())
 | |
|         rec.put(*name, get(j).as_string());
 | |
|       if (err == NOERR)
 | |
|         to_be_written = isam.rewrite() != NOERR;
 | |
|     }
 | |
|     if (to_be_written)
 | |
|     {
 | |
|       err = (mode & 0x1) ? isam.write() : isam.rewrite();
 | |
|       if (err != NOERR && (mode & 0x3) != 0)
 | |
|         err = (mode & 0x1) ? isam.rewrite() : isam.write();
 | |
|     }
 | |
|     if (err != NOERR)
 | |
|     {
 | |
|       ok = error_box(FR("Errore %d durante la scrittura del record %s"), err, rec.build_key());
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return ok;
 | |
| }
 | |
| 
 | |
| bool TRecordset::save_as(const char* path, TRecordsetExportFormat fmt, int mode)
 | |
| {
 | |
|   if (fmt == fmt_unknown)
 | |
|   {
 | |
|     TString ext;
 | |
|     xvt_fsys_parse_pathname(path, NULL, NULL, NULL, ext.get_buffer(), NULL);
 | |
|     ext.lower();
 | |
|     if (ext == "htm" || ext == "html" || ext == "xml" || ext == "xls")
 | |
|       fmt = fmt_html;
 | |
| 		else
 | |
| 			if (ext == "dbf")
 | |
| 				fmt = fmt_dbf;
 | |
| 			else
 | |
| 				if (ext == "csv")
 | |
| 					fmt = fmt_csv;
 | |
|   }
 | |
|   bool ok = false;
 | |
|   switch (fmt)
 | |
|   {
 | |
|   case fmt_html  : ok = save_as_html(path);  break;
 | |
|   case fmt_campo : ok = save_as_campo(path); break;
 | |
|   case fmt_dbf   : ok = save_as_dbf(path, mode);   break;
 | |
| 	case fmt_as400 : ok = save_as_as400(path);	break;
 | |
| 	case fmt_csv   : ok = save_as_csv(path); break;
 | |
|   default        : ok = save_as_text(path);  break;
 | |
|   }
 | |
| 
 | |
|   return ok;
 | |
| }
 | |
| 
 | |
| int TRecordset::find_column(const char* column_name) const
 | |
| {
 | |
| 	int i;
 | |
|   for (i = columns()-1; i >= 0; i--)
 | |
|   {
 | |
|     const TRecordset_column_info& info = column_info(i);
 | |
|     if (info._name == column_name)
 | |
|       break;
 | |
|   }
 | |
|   return i;
 | |
| }
 | |
| 
 | |
| TVariant& TRecordset::get_tmp_var() const
 | |
| {
 | |
|   static TArray _page;      // Variants to be returned by get
 | |
|   static int _next_var = 0; // Index of next variant to be returned
 | |
| 
 | |
|   if (_next_var >= 32)
 | |
|     _next_var = 0;
 | |
|   TVariant* var = (TVariant*)_page.objptr(_next_var);
 | |
|   if (var == NULL)
 | |
|   {
 | |
|     var = new TVariant;
 | |
|     _page.add(var, _next_var);
 | |
|   }
 | |
|   _next_var++;
 | |
|   return *var;
 | |
| }
 | |
| 
 | |
| const TVariant& TRecordset::get(const char* column_name) const
 | |
| {
 | |
|   if (*column_name == '#')
 | |
|     return get_var(column_name);
 | |
| 
 | |
|   char* colon = (char*)strchr(column_name, ':');  //antica porcata
 | |
|   if (colon != NULL)
 | |
|   {
 | |
|     *colon = '\0';
 | |
|     const int i = find_column(column_name);
 | |
|     *colon = ':';
 | |
|     if (i >= 0)
 | |
|     {
 | |
|       const TString& str = get(i).as_string();
 | |
|       TString subfield; subfield << (colon+1) << '=';
 | |
|       int s = str.find(subfield);
 | |
|       if (s == 0 || (s > 0 && str[s-1] < ' '))
 | |
|       {
 | |
|         s += subfield.len();
 | |
|         const int e = str.find('\n', s);
 | |
|         TVariant& var = get_tmp_var();
 | |
|         var.set(str.sub(s, e));
 | |
|         return var;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     const int i = find_column(column_name);
 | |
|     if (i >= 0)
 | |
|       return get(i);
 | |
|   }
 | |
| 
 | |
|   return NULL_VARIANT;
 | |
| }
 | |
| 
 | |
| const TVariant& TRecordset::get_var(const char* name) const
 | |
| {
 | |
| 	const TVariant* var = (const TVariant*)_var.objptr(name);
 | |
| 
 | |
|   // Non so che variabile sia: provo con quelle di sistema
 | |
|   if (var == NULL && *name == '#')
 | |
|   {
 | |
|     const TFixed_string n(name);
 | |
|   // Se mi accorgo che posso e voglio accedere ad un campo del recordset padre
 | |
|     if (_parentset != NULL && n.starts_with("#PARENT.")) 
 | |
|       var = &_parentset->get(name+8); // Attenzione! E' giusto usare get() e non get_var()
 | |
|     else
 | |
|   {
 | |
|       if (n.starts_with("#RECORD."))
 | |
|       {
 | |
|         const TFixed_string fn(name + 8);
 | |
| 
 | |
|         TVariant& v = get_tmp_var();
 | |
|         if (fn == "NUMBER")
 | |
|           v.set(current_row()+1); else
 | |
|         if (fn == "LAST")
 | |
|           v.set(items());
 | |
|         var = &v;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return var != NULL ? *var : NULL_VARIANT;
 | |
| }
 | |
| 
 | |
| bool TRecordset::set_var(const char* name, const TVariant& var, bool create)
 | |
| {
 | |
|   bool ok = false;
 | |
|   TVariant* old = (TVariant*)_var.objptr(name);
 | |
|   if (old != NULL)
 | |
|   {
 | |
|     *old = var;
 | |
|     ok = true;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     if (create)
 | |
|     {
 | |
|       const TFixed_string n(name);
 | |
|       if (n.starts_with("#PARENT.") || n.starts_with("#RECORD."))
 | |
|       {
 | |
|         // Aggiungo solo il nome alla lista: niente valore!
 | |
|         _varnames.add(name);
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|       _var.add(name, var);
 | |
|       _varnames.add(name);
 | |
|       }
 | |
|       ok = true;
 | |
|     }
 | |
|   }
 | |
|   if (ok)
 | |
|     requery();
 | |
|   return ok;
 | |
| }
 | |
| 
 | |
| bool is_var_separator(char c)
 | |
| {
 | |
|   if (isspace(c))
 | |
|     return true;
 | |
|   return strchr("<=>+-*/(,.", c) != NULL;
 | |
| }
 | |
| 
 | |
| // Cerca le variabili nel testo SQL:
 | |
| // Una variabile comincia per # ed e' composta da soli caratteri alfanumerici.
 | |
| // Prima del simbolo # e dopo il nome della variabile deve esserci un separatore o blank
 | |
| void TRecordset::find_and_reset_vars()
 | |
| {
 | |
|   _var.destroy();
 | |
|   _varnames.destroy();
 | |
|   
 | |
|   const TString& sql = query_text();
 | |
|   int diesis = sql.find('#'); // cerco il primo #
 | |
|   for ( ; diesis > 0; diesis = sql.find('#', diesis+1)) // Cerco tutti i #
 | |
|   {
 | |
|     if (is_var_separator(sql[diesis-1])) // Controllo che ci sia un separatore prima del #
 | |
|     {
 | |
|       int i = diesis+1;
 | |
|       for ( ; sql[i] && (isalnum(sql[i]) || strchr("@_.#", sql[i]) != NULL); i++);
 | |
|       if (i > diesis+1)
 | |
|       {
 | |
|         const TString& name = sql.sub(diesis, i);
 | |
|         set_var(name, NULL_VARIANT, true);
 | |
| 				diesis = i;		//ricomincia a cercare variabili dopo la fine della variabile corrente;senza questa istruzione non possono funzionare cose tip #PARENT.#PARENT.33.CAMPO
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void TRecordset::parsed_text(TString& sql) const
 | |
| {
 | |
|   sql = query_text();
 | |
|   const bool is_isam = sql.starts_with("US");
 | |
|   const bool is_sql = !is_isam;
 | |
|   const char* apici = is_isam ? "\"" : "'";
 | |
| 
 | |
|   const bool vars = ((TRecordset*)this)->ask_variables(false);
 | |
|   if (vars) // Se ci sono variabili faccio le sostituzioni
 | |
|   {
 | |
|     const TString_array& names = variables();
 | |
|     TString s;
 | |
|     FOR_EACH_ARRAY_ROW(names, i, name) // Scandisco tutte le variabili
 | |
|     {
 | |
|       TVariant var = get_var(*name);
 | |
|       int pos = sql.find(*name);
 | |
|       for ( ; pos > 0; pos = sql.find(*name, pos+1))
 | |
|       {
 | |
|         const TString& after = sql.mid(pos+name->len());
 | |
|         sql.cut(pos);
 | |
|         
 | |
|         if (var.type() == _datefld)
 | |
|         {
 | |
|           if (var.is_empty())
 | |
|             s.cut(0) << *apici << *apici;
 | |
|           else
 | |
|             s.format("%ld", var.as_date().date2ansi());
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|           s = var.as_string();
 | |
|           if (is_sql) // Raddoppia gli apici in SQL
 | |
|           {
 | |
|             for (int i = 0; s[i]; i++)
 | |
|             {
 | |
|               if (s[i] == '\'')
 | |
|                 s.insert("'", i++);
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|         if ((var.is_string() && s[0] != *apici && sql.right(1) != apici) || var.is_null())
 | |
|         {
 | |
|           s.insert(apici);
 | |
|           s << apici;
 | |
|         }
 | |
|         sql << s << after;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool ask_variable(const char* name, TVariant& var)
 | |
| {
 | |
|   TMask m(TR("Richiesta variabile"), 1, 52, 5);
 | |
|   m.add_static(-1, 0, name, 1, 0);
 | |
|   m.add_string(101, 0, "", 1, 1, 80, "", 50);
 | |
|   m.add_button(DLG_OK, 0, "", -12, -1, 10, 2);
 | |
|   m.add_button(DLG_CANCEL, 0, "", -22, -1, 10, 2);
 | |
|   m.set(101, var.as_string());
 | |
|   const bool ok = m.run() == K_ENTER;
 | |
|   if (ok)
 | |
|   {
 | |
|     const TString& str = m.get(101);
 | |
|     if (str.match("??-??-2???"))
 | |
|       var = TDate(str); else
 | |
|     if (is_numeric(str))
 | |
|       var = real(str);
 | |
|     else
 | |
|       var = str;
 | |
|   }
 | |
|   return ok;
 | |
| }
 | |
| 
 | |
| bool TRecordset::ask_variables(bool all)
 | |
| {
 | |
|   const bool ok = variables().items() > 0;
 | |
|   if (ok) // Se ci sono variabili faccio le sostituzioni
 | |
|   {
 | |
|     FOR_EACH_ARRAY_ROW(_varnames, i, name)
 | |
|     {
 | |
|       TVariant var = get_var(*name);
 | |
|       if (var.is_null() || all) 
 | |
|       {
 | |
|         ask_variable(*name, var);
 | |
|         if (var.is_null())
 | |
|           var.set(""); // Mi serve assolutamente un valore!
 | |
|         set_var(*name, var);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return ok;
 | |
| }
 | |
| 
 | |
| const TString& TRecordset::driver_version() const
 | |
| { return EMPTY_STRING; }
 | |
| 
 | |
| TRecordset::TRecordset() : _parentset(NULL), _text_separator('\t')
 | |
| { }
 | |
| 
 | |
| ///////////////////////////////////////////////////////////
 | |
| // Utility
 | |
| ///////////////////////////////////////////////////////////
 | |
| 
 | |
| static void sort_files(TString_array& files)
 | |
| {
 | |
|   TFilename path;
 | |
| 
 | |
|   // Trasforma i path completi in nomi senza estensione
 | |
|   FOR_EACH_ARRAY_ROW(files, i, row)
 | |
|   {
 | |
|     path = *row; 
 | |
|     path = path.name(); 
 | |
|     path.ext("");
 | |
|     path.lower();
 | |
|     *row = path;
 | |
|   }
 | |
|   files.sort();  // Ordina alfabeticamente
 | |
| 
 | |
|   // Rimuove i files doppi proveninenti da Campo e Custom
 | |
|   for (int j = files.last(); j > 0; j--)
 | |
|   {
 | |
|     if (files.row(j) == files.row(j-1))
 | |
|       files.destroy(j);
 | |
|   }
 | |
|   files.pack();
 | |
| }
 | |
| 
 | |
| static bool get_xml_attr(const TString& line, const char* attr, TString& value)
 | |
| {
 | |
|   TString80 str; str << ' ' << attr << "=\"";
 | |
|   const int pos = line.find(str);
 | |
|   if (pos >= 0)
 | |
|   {
 | |
|     const int apicia = line.find('"', pos)+1;
 | |
|     const int apicic = line.find('"', apicia);
 | |
|     if (apicic > apicia)
 | |
|     {
 | |
|       value = line.sub(apicia, apicic);
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
| 	value.cut(0);
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| static bool get_xml_child(const TString& line, const char* tag, TString& value)
 | |
| {
 | |
|   TString80 str; str << '<' << tag << '>';
 | |
|   const int pos = line.find(str);
 | |
|   if (pos >= 0)
 | |
|   {
 | |
|     const int apicia = line.find('>', pos)+1;
 | |
|     const int apicic = line.find('<', apicia);
 | |
|     if (apicic > apicia)
 | |
|     {
 | |
|       value = line.sub(apicia, apicic);
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
| 	value.cut(0);
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool list_custom_files(const char* ext, const char* classe, TString_array& files)
 | |
| {
 | |
|   TString_array lista;
 | |
|   TFilename path;
 | |
|   TWait_cursor hourglass;
 | |
| 	TFilename name = path.name();
 | |
| 	const bool wild = (name.find('*') >= 0) || (name.find('?') >= 0);
 | |
| 
 | |
| 	if (!wild)
 | |
| 		name = "*";
 | |
| 
 | |
|   // Leggo i files in custom
 | |
|   if (main_app().has_module(RSAUT))
 | |
|   {
 | |
|     TFilename custom = firm2dir(-1);
 | |
|     custom.add("custom");
 | |
|     if (!custom.exist())
 | |
|       xvt_fsys_mkdir(custom);
 | |
| 
 | |
|     path = custom;
 | |
|     path.add(name);
 | |
|     path.ext(ext);
 | |
| 	}
 | |
|   list_files(path, lista);
 | |
|   path = name; path.ext(ext);  // Leggo i files in campo
 | |
| 	list_files(path, lista);
 | |
| 	sort_files(lista);           // Ordino i files e rimuovo i doppioni
 | |
| 
 | |
|   TString8 acqua;
 | |
|   TString stringona, desc;
 | |
| 
 | |
|   FOR_EACH_ARRAY_ROW(lista, i, row)
 | |
|   {
 | |
|     path = *row; path.ext(ext);
 | |
|     bool ok = path.custom_path();
 | |
|     if (ok)
 | |
|     { 
 | |
|       TScanner scan(path);
 | |
|       stringona.cut(0);
 | |
|       for (int i = 0; i < 4 && scan.good(); i++) // Leggo solo le prime righe
 | |
|         stringona << scan.line();
 | |
|   
 | |
|       get_xml_attr(stringona, "class", acqua);
 | |
|       if (classe && *classe)
 | |
|         ok = acqua == classe;
 | |
|    
 | |
|       if (ok)
 | |
|       {
 | |
|         get_xml_child(stringona, "description", desc);
 | |
|         TToken_string* riga = new TToken_string;
 | |
|         riga->add(*row);
 | |
|         riga->add(acqua);
 | |
|         riga->add(desc); 
 | |
|         riga->add(path.find("custom") > 0 ? "X" : "");
 | |
|         files.add(riga);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return !files.empty();
 | |
| }
 | |
| 
 | |
| bool select_custom_file(TFilename& path, const char* ext, const char* classe)
 | |
| {
 | |
|   TArray_sheet sheet(-1, -1, 78, 20, TR("Selezione"), HR("Nome@16|Classe@8|Descrizione@50|Custom"));
 | |
|   TString_array& files = sheet.rows_array();
 | |
|   TFilename first = path.name(); first.ext(""); // Riga su cui posizionarsi
 | |
|   bool ok = list_custom_files(ext, classe, files);
 | |
| 
 | |
|   if (ok)
 | |
|   {
 | |
|     if (first.full()) // Cerco la prima riga da selezionare se possibile
 | |
|     {
 | |
|       FOR_EACH_ARRAY_ROW(files, i, row)
 | |
|       {
 | |
|         if (first.compare(row->get(0), -1, true) == 0) // Ho trovato la selezione corrente
 | |
|         {
 | |
|           sheet.select(i);
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     ok = sheet.run() == K_ENTER;
 | |
|     if (ok)
 | |
|     {
 | |
|       path = sheet.row(-1).get(0);
 | |
|       path.ext(ext);
 | |
|       ok = path.custom_path();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return ok;
 | |
| }
 | |
| 
 | |
| ///////////////////////////////////////////////////////////
 | |
| // TCursor_parser
 | |
| ///////////////////////////////////////////////////////////
 | |
| 
 | |
| class TCursor_parser
 | |
| {
 | |
|   istream& _instr;
 | |
|   TArray& _column;
 | |
| 
 | |
|   TString _pushed;
 | |
|   TString _token;
 | |
|   
 | |
|   TRelation* _relation;
 | |
|   TCursor* _cursor;
 | |
| 
 | |
| protected:
 | |
|   const TString& pop();
 | |
|   const TString& line();
 | |
|   void push();
 | |
|   void add_column_info(const char* table, const TRectype& rec);
 | |
| 
 | |
|   void parse_sortexpr(TToken_string& se);
 | |
|   void parse_filter(TToken_string& filter);
 | |
|   void parse_select(TToken_string& filter);
 | |
|   void parse_region(TRectype& rec);
 | |
|   void parse_join_param(TRelation* rel, const TString& j, int to);
 | |
|   void parse_join();
 | |
|   void parse_sortedjoin();
 | |
| 
 | |
| public:
 | |
|   TRelation* get_relation() { return _relation; }
 | |
|   TCursor* get_cursor() { return _cursor; }
 | |
| 
 | |
|   TCursor_parser(istream& instr, TArray& column);
 | |
| };
 | |
| 
 | |
| const TString& TCursor_parser::pop()
 | |
| {
 | |
|   if (_pushed.not_empty())
 | |
|   {
 | |
|     _token = _pushed;
 | |
|     _pushed.cut(0);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     _token.cut(0);  
 | |
|     eatwhite(_instr);
 | |
|     char instring = '\0';
 | |
| 
 | |
|     while (_instr)
 | |
|     {
 | |
|       char c; 
 | |
|       _instr.get(c);
 | |
|       if (c == EOF)
 | |
|         break;
 | |
|       if (instring > ' ') // Sono dentro ad una stringa
 | |
|       {
 | |
|         if (c == '\n')
 | |
|           break;
 | |
|         if (c == instring)
 | |
|           instring = '\0';
 | |
|         _token << c;
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         if (c == '"' || c == '\'')
 | |
|         {
 | |
|           instring = c;
 | |
|           _token << c;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|           if (isspace(c))
 | |
|             break;
 | |
|           _token << c;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return _token;
 | |
| }
 | |
| 
 | |
| const TString& TCursor_parser::line()
 | |
| {
 | |
|   if (_pushed.not_empty())
 | |
|     return pop();
 | |
|   char* buff = _token.get_buffer(256);
 | |
|   _instr.getline(buff, _token.size());
 | |
|   return _token;
 | |
| }
 | |
| 
 | |
| void TCursor_parser::push()
 | |
| {
 | |
|   CHECK(_pushed.empty(), "Repushing?");
 | |
|   _pushed = _token;
 | |
| }
 | |
| 
 | |
| void TCursor_parser::add_column_info(const char* table, const TRectype& rec)
 | |
| {
 | |
|   for (int i = 0; i < rec.items(); i++)
 | |
|   {
 | |
|     TRecordset_column_info* info = new TRecordset_column_info;
 | |
|     const char* name = rec.fieldname(i);
 | |
|     info->_name << table << '.' << name;
 | |
|     info->_type = rec.type(name);
 | |
|     switch (info->_type)
 | |
|     {
 | |
|     case _datefld: info->_width = 10; break;
 | |
|     case _memofld: info->_width = 50; break;
 | |
|     default      : info->_width = rec.length(name); break;
 | |
|     }
 | |
|     _column.add(info);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void TCursor_parser::parse_sortexpr(TToken_string& se)
 | |
| {
 | |
|   const char sep = se.separator();
 | |
|   se.separator(' ');
 | |
|   _instr.getline(se.get_buffer(), se.size());
 | |
|   se.strip_double_spaces();
 | |
|   se.replace(' ', sep);
 | |
|   se.separator(sep);
 | |
| 
 | |
|   // Trasforma i nomi dei files in numeri se necessario
 | |
|   if (se.find('.') > 0)
 | |
|   {
 | |
|     TToken_string fld(16, '.');
 | |
|     TString16 name;
 | |
|     for (int i = 0; se.get(i, fld); i++)
 | |
|     {
 | |
|       if (!isdigit(fld[0]) && fld.find('.') > 0)
 | |
|       {
 | |
|         fld.get(0, name);
 | |
|         const int num = ::table2logic(name);
 | |
|         if (num != 0)
 | |
|         {
 | |
|           fld.add(num, 0);
 | |
|           se.add(fld, i);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void TCursor_parser::parse_filter(TToken_string& filter)
 | |
| {
 | |
|   const TString& str = pop();
 | |
|   while (str.find('=') > 0)
 | |
|   {
 | |
|     filter.add(str);
 | |
|     pop();
 | |
|   }
 | |
|   push();
 | |
| }
 | |
| 
 | |
| void TCursor_parser::parse_select(TToken_string& filter)
 | |
| {
 | |
|   filter = line();
 | |
|   filter.trim();
 | |
| }
 | |
| 
 | |
| 
 | |
| void TCursor_parser::parse_region(TRectype& rec)
 | |
| {
 | |
|   TString16 field;
 | |
|   TString80 value;
 | |
|   while (true)
 | |
|   {
 | |
|     const TString& ass = pop();
 | |
|     const int equal = ass.find('=');
 | |
|     if (equal > 0)
 | |
|     {
 | |
|       field = ass.left(equal);
 | |
|       value = ass.mid(equal+1);
 | |
|       value.trim();
 | |
|       if (value[0] == '"' || value[0] == '\'')
 | |
|       {
 | |
|         value.rtrim(1);
 | |
|         value.ltrim(1);
 | |
|       }
 | |
|       rec.put(field, value);       
 | |
|     }
 | |
|     else
 | |
|       break;
 | |
|   }
 | |
|   push();
 | |
| }
 | |
| 
 | |
| 
 | |
| void TCursor_parser::parse_join_param(TRelation* rel, const TString& j, int to)
 | |
| {
 | |
|   int key = 1;
 | |
|   const TString& tok = pop();
 | |
|   if (tok.starts_with("KE"))
 | |
|   {
 | |
|     pop();
 | |
|     key = atoi(tok);
 | |
|   }
 | |
|   else 
 | |
|     push();
 | |
| 
 | |
|   int alias = 0;
 | |
|   pop();
 | |
|   if (tok.starts_with("AL"))
 | |
|   {
 | |
|     pop();
 | |
|     alias = atoi(tok);
 | |
|   }
 | |
|   else 
 | |
|     push();
 | |
| 
 | |
|   TToken_string exp(80);
 | |
|   pop();
 | |
|   if (tok == "INTO")
 | |
|   {
 | |
|     parse_filter(exp);
 | |
|   }
 | |
|   if (exp.empty())
 | |
|     yesnofatal_box("JOIN senza espressioni INTO");
 | |
| 
 | |
|   const int logicnum = table2logic(j);
 | |
|   switch (logicnum)
 | |
|   {
 | |
|   case LF_TABGEN:
 | |
|   case LF_TABCOM:
 | |
|   case LF_TAB:
 | |
|   case LF_TABMOD:
 | |
|     rel->add(j, exp, key, to, alias);          // join table
 | |
|     break;
 | |
|   default:
 | |
|     rel->add(logicnum, exp, key, to, alias);   // join file
 | |
|     break;
 | |
|   } 
 | |
| 
 | |
|   TString16 tabname;
 | |
|   if (alias > 0)
 | |
|     tabname << alias << '@'; 
 | |
|   else
 | |
|     tabname = j;
 | |
|   const TRectype& rec = rel->curr(logicnum);
 | |
|   add_column_info(tabname, rec);    
 | |
| }
 | |
| 
 | |
| void TCursor_parser::parse_join()
 | |
| {
 | |
|   const TString j = pop();           // File or table
 | |
| 
 | |
|   int to = 0;
 | |
|   const TString& tok = pop();
 | |
|   if (tok == "TO")         // TO keyword
 | |
|   {
 | |
|     pop();
 | |
|     to = table2logic(tok);
 | |
|   }
 | |
|   else 
 | |
|     push();
 | |
|   
 | |
|   parse_join_param(_relation, j, to);
 | |
| }
 | |
| 
 | |
| void TCursor_parser::parse_sortedjoin()
 | |
| {
 | |
|   TToken_string filter,sortexp;
 | |
|   const TString j = pop();           // File or table
 | |
|   const TString& tok = pop(); 
 | |
|   if (tok == "BY" )
 | |
|     parse_sortexpr(sortexp);
 | |
|   else 
 | |
|     push();
 | |
|  
 | |
|   pop();
 | |
|   if (tok.starts_with("FI") || tok.starts_with("SE")) 
 | |
|   {
 | |
|     parse_select(filter);
 | |
|   } 
 | |
|   else 
 | |
|     push();
 | |
|   
 | |
|   TRelation* sortrel = new TRelation(table2logic(j)); 
 | |
|   while (true)
 | |
|   {
 | |
|     pop();
 | |
|     if (tok.empty() || tok.starts_with("JO"))
 | |
|     {
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     if (tok.starts_with("US"))         // USING keyword
 | |
|     {
 | |
|       const TString subj = pop();      // File or table
 | |
|       parse_join_param(sortrel, subj, table2logic(j));
 | |
|     } 
 | |
|   } 
 | |
| 
 | |
|   int to = 0;
 | |
|   pop();
 | |
|   if (tok == "TO")         // TO keyword
 | |
|   {
 | |
|     pop();
 | |
|     to = table2logic(tok);
 | |
|   }
 | |
|   else 
 | |
|     push();
 | |
| 
 | |
|   int key = 1;
 | |
|   pop();
 | |
|   if (tok.starts_with("KE"))
 | |
|   {
 | |
|     pop();
 | |
|     key = atoi(tok);
 | |
|   }
 | |
|   else 
 | |
|     push();
 | |
| 
 | |
|   int alias = 0;
 | |
|   pop();
 | |
|   if (tok.starts_with("AL"))
 | |
|   {
 | |
|     pop();
 | |
|     alias = atoi(tok);
 | |
|   }
 | |
|   else 
 | |
|     push();
 | |
| 
 | |
|   TToken_string exp(80);
 | |
|   if (pop() == "INTO")
 | |
|   {
 | |
|     pop();
 | |
|     while (tok.find('=') > 0)
 | |
|     {
 | |
|       exp.add(tok);
 | |
|       pop();
 | |
|     }
 | |
|   }
 | |
|   push();
 | |
| 
 | |
|   TSortedfile *sf= new TSortedfile(atoi(j),sortrel,sortexp,filter,key);
 | |
|   _relation->add((TLocalisamfile *)sf, exp, key, to, alias, false);         // join table
 | |
| 
 | |
|   TString16 tabname = j;
 | |
|   if (alias > 0)
 | |
|     tabname.cut(0) << alias << '@'; 
 | |
|   add_column_info(tabname, sf->curr());    
 | |
| }
 | |
| 
 | |
| TCursor_parser::TCursor_parser(istream& instr, TArray& col) 
 | |
|                 : _instr(instr), _column(col), _relation(NULL), _cursor(NULL)
 | |
| {
 | |
|   _column.destroy();
 | |
|   const TString& tok = pop();
 | |
|   if (!tok.starts_with("US"))
 | |
|     push();
 | |
| 
 | |
|   pop();
 | |
|   if (tok.blank())
 | |
|     return;
 | |
| 
 | |
|   TString table(tok); table.upper();
 | |
|   if (table.ends_with(".DBF"))
 | |
|   {
 | |
|     TFilename dbf = table;
 | |
|     if (table[0] == '$' || table[0] == '%')
 | |
|     {
 | |
|       if (table[0] == '%')
 | |
|         dbf = firm2dir(0);
 | |
|       else
 | |
|         dbf = firm2dir(prefix().get_codditta());
 | |
|       dbf.add(table.mid(1));
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       if (dbf.is_relative_path())
 | |
|       {
 | |
|         if (!dbf.custom_path())
 | |
|         {
 | |
|           dbf.tempdir();
 | |
|           dbf.add(table);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     dbf.lower();
 | |
|     TExternisamfile* eif = new TExternisamfile(dbf, true);
 | |
|     _relation = new TRelation(eif);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     int logicnum = table2logic(tok);
 | |
|     if (logicnum == LF_MAG && tok == "MAG")  // Faccio prevalere la tabella MAG sul file MAG
 | |
|       logicnum = LF_TAB;
 | |
| 
 | |
|     if (logicnum == LF_TAB || logicnum == LF_TABCOM || logicnum == LF_TABMOD)
 | |
|       _relation = new TRelation(tok);
 | |
|     else
 | |
|       _relation = new TRelation(logicnum);
 | |
|   }
 | |
|   pop();
 | |
| 	
 | |
|   add_column_info(table, _relation->curr());
 | |
| 
 | |
|   int key = 1;  // key number
 | |
|   if (tok.starts_with("KE"))
 | |
|   {
 | |
|     pop();
 | |
|     key = atoi(tok);
 | |
|   }
 | |
|   else
 | |
|     push();
 | |
| 
 | |
|   pop();
 | |
|   
 | |
| 	TToken_string filter;
 | |
| 
 | |
| 	if (tok.starts_with("FI") || tok.starts_with("SE"))
 | |
|     parse_select(filter);
 | |
|   else
 | |
|     push();
 | |
| 
 | |
|   pop();
 | |
|   if (tok.starts_with("BY"))  // "sort BY": user-defined sort
 | |
|   { 
 | |
|     TToken_string ordexpr(256);
 | |
|     parse_sortexpr(ordexpr);
 | |
|     _cursor = new TSorted_cursor(_relation, ordexpr, "", key);
 | |
|   }
 | |
|   else
 | |
|     push();
 | |
| 
 | |
| 
 | |
|   if (_cursor == NULL)
 | |
|     _cursor = new TCursor(_relation, "", key);
 | |
| 
 | |
|   _relation->lfile().zero();  // Azzera correttamente tabelle normali e di modulo!
 | |
|   TRectype rec_start(_relation->curr());
 | |
|   TRectype rec_stop(rec_start);
 | |
| 
 | |
|   TString_array fields;
 | |
| 	pop();
 | |
| 	while (tok.starts_with("DI"))
 | |
|   {
 | |
| 		pop();
 | |
| 		pop();
 | |
| 		TString field;
 | |
| 
 | |
| 		if (tok.find(".") < 0)
 | |
| 			field <<  table << ".";
 | |
| 		field << tok;
 | |
| 		fields.add(field);
 | |
| 		pop();
 | |
|   }
 | |
| 	push();
 | |
|   while (true)
 | |
|   {
 | |
|     pop();
 | |
|     if (tok.starts_with("FR"))
 | |
|       parse_region(rec_start); else
 | |
|     if (tok.starts_with("TO"))
 | |
|       parse_region(rec_stop); else
 | |
|     if (tok.starts_with("JO"))
 | |
|       parse_join(); else
 | |
|     if (tok.starts_with("SO"))
 | |
|       parse_sortedjoin(); 
 | |
|     else
 | |
|       break;
 | |
|   }
 | |
|   push(); 
 | |
| 
 | |
|   if (!rec_start.empty() || !rec_stop.empty())
 | |
|     _cursor->setregion(rec_start, rec_stop);
 | |
| 	if (!filter.empty())
 | |
| 		_cursor->setfilter(filter);
 | |
|   if (fields.items() > 0)
 | |
|   {
 | |
|     FOR_EACH_ARRAY_ITEM_BACK(_column, i, obj)
 | |
|     {
 | |
|       TRecordset_column_info* info = (TRecordset_column_info*)obj;
 | |
| 			
 | |
| 			if (fields.find(info->_name) == -1)
 | |
| 				_column.destroy(i, true);
 | |
|     }
 | |
|   }
 | |
|   if (_relation->items() == 0) // Non ci sono anche tabelle collegate
 | |
|   {
 | |
|     FOR_EACH_ARRAY_ITEM(_column, i, obj)
 | |
|     {
 | |
|       TRecordset_column_info* info = (TRecordset_column_info*)obj;
 | |
|       const int arrow = info->_name.find('.');
 | |
|       if (arrow > 0)
 | |
|         info->_name = info->_name.mid(arrow+1);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| ///////////////////////////////////////////////////////////
 | |
| // TISAM_recordset
 | |
| ///////////////////////////////////////////////////////////
 | |
| 
 | |
| const TVariant& TISAM_recordset::get_field(int logic, const char* fldname) const
 | |
| {
 | |
|   const int idx = relation()->log2ind(logic);
 | |
|   if (idx < 0)
 | |
|     return NULL_VARIANT;
 | |
| 
 | |
|   TString80 name = fldname;
 | |
|   TString16 subfield;
 | |
|   int from = 1, to = 0;
 | |
| 
 | |
|   const int open_bracket = name.find('[');
 | |
|   if (open_bracket > 0)
 | |
|   {
 | |
|     TToken_string range(name.mid(open_bracket+1), ',');
 | |
|     from = range.get_int();
 | |
|     to   = range.get_int();
 | |
|     name.cut(open_bracket);
 | |
|   }
 | |
| 
 | |
|   const int colon = name.find(':');
 | |
| 
 | |
|   if (colon > 0)
 | |
|   {
 | |
|     subfield = name.mid(colon+1);
 | |
|     name.cut(colon);
 | |
|   }
 | |
| 
 | |
|   const TRectype& rec = relation()->file(idx).curr();
 | |
|   const TFieldtypes ft = rec.type(name);
 | |
| 
 | |
|   if (ft == _nullfld)
 | |
|   {
 | |
|     switch (logic) // Proviamo la magia dei campi virtuali
 | |
|     {
 | |
|     case LF_DOC     : subfield = name; name = "G1";  break; 
 | |
|     case LF_RIGHEDOC: subfield = name; name = "RG1"; break;
 | |
|     default: return NULL_VARIANT;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   TVariant& var = get_tmp_var();
 | |
|   switch (ft)
 | |
|   {
 | |
|   case _boolfld: var.set(rec.get_bool(name)); break;
 | |
|   case _datefld: var.set(rec.get_date(name)); break;
 | |
|   case _realfld: var.set(rec.get_real(name)); break;
 | |
|   case _intfld : 
 | |
|   case _longfld:
 | |
|   case _wordfld: var.set(rec.get_long(name)); break;
 | |
|   case _intzerofld : // Non usare il convertitore degll interi
 | |
|   case _longzerofld: // in modo da preservare gli zeri iniziali
 | |
|   default          : var.set(rec.get(name)); break;
 | |
|   }
 | |
| 
 | |
|   if (subfield.not_empty())
 | |
|   {
 | |
|     subfield << '=';
 | |
|     const TString& str = var.as_string();
 | |
|     int s = str.find(subfield);
 | |
|     if (s == 0 || (s > 0 && str[s-1] < ' '))
 | |
|     {
 | |
|       s += subfield.len();
 | |
|       const int e = str.find('\n', s);
 | |
|       var.set(str.sub(s, e));
 | |
|     }
 | |
|     else
 | |
|       var.set_null();
 | |
|   }
 | |
| 
 | |
|   if (to >= from && !var.is_empty())
 | |
|     var.set(var.as_string().sub(from-1, to));
 | |
| 
 | |
|   return var;
 | |
| }
 | |
| 
 | |
| const TVariant& TISAM_recordset::get(size_t c) const
 | |
| {
 | |
|   const TRecordset_column_info* info = (const TRecordset_column_info*)_column.objptr(c);
 | |
|   if (info != NULL)
 | |
|   {
 | |
|     int logic = 0;
 | |
|     const char* field = info->_name;
 | |
|     const int dot = info->_name.find('.');
 | |
|     if (dot > 0)
 | |
|     {
 | |
|       logic = table2logic(info->_name.left(dot));
 | |
|       field += dot+1;
 | |
|     }
 | |
|     return get_field(logic, field);
 | |
|   }
 | |
|   return NULL_VARIANT;
 | |
| }
 | |
| 
 | |
| const TVariant& TISAM_recordset::get(const char* name) const
 | |
| {
 | |
|   if (*name == '#')
 | |
|     return get_var(name);
 | |
| 
 | |
|   const TFixed_string fldname(name);
 | |
|   
 | |
|   int table_end = fldname.find('.');
 | |
|   int field_start = table_end+1;
 | |
|   if (table_end < 0)
 | |
|   {
 | |
|     table_end = fldname.find('-');
 | |
|     if (table_end > 0)
 | |
|       field_start = table_end+2;
 | |
|   }
 | |
|   int logic = 0;
 | |
|   const char* field = name;
 | |
|   if (table_end > 0)
 | |
|   {
 | |
|     const TString& table = fldname.left(table_end);
 | |
|     logic = table2logic(table);
 | |
|     field += field_start;
 | |
|   }
 | |
|   return get_field(logic, field);
 | |
| }
 | |
| 
 | |
| const TRecordset_column_info& TISAM_recordset::column_info(size_t i) const
 | |
| {
 | |
|   return (const TRecordset_column_info&)_column[i];
 | |
| }
 | |
| 
 | |
| TCursor* TISAM_recordset::cursor() const
 | |
| {
 | |
|   if (_cursor == NULL && _use.full())
 | |
|   {
 | |
|     TString use; parsed_text(use);
 | |
|     TParagraph_string msg(use, 64);
 | |
|     TPerformance_profiler prof(msg.get(0));
 | |
|     TISAM_recordset* my = (TISAM_recordset*)this;
 | |
| 
 | |
|     istrstream instr(use.get_buffer(), use.len()+1);  //"barata" x aggiungere il carattere finale
 | |
|     TCursor_parser parser(instr, my->_column);
 | |
| 
 | |
|     my->_relation = parser.get_relation();
 | |
|     my->_cursor = parser.get_cursor();
 | |
| 
 | |
|     if (_cursor != NULL)
 | |
|     {
 | |
|       set_custom_filter(*_cursor);
 | |
|       const TRecnotype items = _cursor->items();
 | |
| 
 | |
|       _cursor->freeze();
 | |
| 			if (items > 0)
 | |
| 				*_cursor = 0L;
 | |
|     }
 | |
|   }
 | |
|   return _cursor;
 | |
| }
 | |
| 
 | |
| TRelation* TISAM_recordset::relation() const
 | |
| {
 | |
|   cursor();
 | |
|   return _relation;
 | |
| }
 | |
| 
 | |
| TRecnotype TISAM_recordset::current_row() const
 | |
| {
 | |
|   TCursor* c = cursor();
 | |
|   return c != NULL ? c->pos() : -1;
 | |
| }
 | |
| 
 | |
| TRecnotype TISAM_recordset::items() const 
 | |
| {
 | |
|   TCursor* c = cursor();
 | |
|   return c != NULL ? c->items() : 0;
 | |
| }
 | |
| 
 | |
| unsigned int TISAM_recordset::columns() const 
 | |
| { 
 | |
|   cursor();
 | |
|   return _column.items(); 
 | |
| }
 | |
| 
 | |
| 
 | |
| bool TISAM_recordset::move_to(TRecnotype pos)
 | |
| {
 | |
|   TCursor* c = cursor();
 | |
|   bool ok = c != NULL && pos >= 0;
 | |
|   if (ok)
 | |
|   {
 | |
|     *c = pos;
 | |
|     ok = pos >= 0 && pos < c->items();
 | |
|   }
 | |
|   return ok;
 | |
| }
 | |
| 
 | |
| void TISAM_recordset::reset()
 | |
| {
 | |
|   _column.destroy();
 | |
|   requery();
 | |
| }
 | |
| 
 | |
| void TISAM_recordset::requery()
 | |
| {
 | |
|   if (_cursor != NULL)
 | |
|   {
 | |
|     delete _cursor; 
 | |
|     _cursor = NULL;
 | |
|   }
 | |
|   if (_relation != NULL)
 | |
|   {
 | |
|     delete _relation; 
 | |
|     _relation = NULL;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void TISAM_recordset::set(const char* use)
 | |
| {
 | |
|   reset();
 | |
|   _use = use;
 | |
|   find_and_reset_vars(); 
 | |
| }
 | |
| 
 | |
| const TString& TISAM_recordset::driver_version() const
 | |
| {
 | |
|   TString& tmp = get_tmp_string(20);
 | |
|   DB_version(tmp.get_buffer(), tmp.size());
 | |
|   return tmp;
 | |
| }
 | |
| 
 | |
| TISAM_recordset::TISAM_recordset(const char* use) 
 | |
|                : _relation(NULL), _cursor(NULL)
 | |
| { 
 | |
|   set(use); 
 | |
| }
 | |
| 
 | |
| TISAM_recordset::~TISAM_recordset()
 | |
| {
 | |
|   requery();
 | |
| }
 | |
| 
 | |
| ///////////////////////////////////////////////////////////
 | |
| // TRecordset_sheet
 | |
| ///////////////////////////////////////////////////////////
 | |
| 
 | |
| void TRecordset_sheet::get_row(long r, TToken_string& row)
 | |
| {
 | |
|   row.separator('\t');
 | |
|   row.cut(0);
 | |
|   if (_query.move_to(r))
 | |
|   {
 | |
|     TString str;
 | |
|     unsigned int cols = _query.sheet_head().items();
 | |
|     if (cols == 0)
 | |
|       cols = _query.columns();
 | |
|     for (unsigned int c = 0; c < cols; c++)
 | |
|     {
 | |
|       _query.get(c).as_string(str);
 | |
|       row.add(str);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| long TRecordset_sheet::get_items() const
 | |
| {
 | |
|   return _query.items();
 | |
| }
 | |
| 
 | |
| TRecordset_sheet::TRecordset_sheet(TRecordset& query, const char* title, byte buttons) 
 | |
|                 : TSheet(-1, -1, -2, -5, title, query.sheet_head(), buttons, 1), _query(query)
 | |
| {
 | |
| }
 |