",
			   NULL };
// Implementazione metodi della classe
void Questionnaire::reset()
{
  int i,j; // Ma porca Eva, si può sapere perchè le specifiche ANSI vietano di definire variabili nel' inizializzazione di un for!?!?!?
  _title = "";
  _loaded = _parsed = _computed = FALSE;
  _score  = ZERO;
  _max_score = 0;
  for (i = 0; i < MAXQUESTIONS; i++) 
  {
    Question_struct& qq = _questions[i];
    qq.text = ""; // Azzera il testo della domanda
    for (j = 0 ; j < MAXANSWERS; j++) 
      {
	Answer_struct& aa = qq.answers[j];
	aa.text = "";   // Azzera il testo della risposta
	aa.user_answer = 'F';
	aa.corrector_answer = 'F';
	aa.p = ZERO; // Azzera il peso di questo item
      }
  }
}
// Abbozzo di semplice parser html ad uso e consumo AGA, per modificare
// gli html dei questionari "al volo"
void Questionnaire::parse_html(const char* name)
{
  char c;
  bool in_form, in_paragraph, in_table;
  bracket_state bstate = none;
  String current_string_tag, paragraph;
  valid_tags current_tag = no_tag;
  short progressivo_domande, progressivo_risposte;
  
  progressivo_domande = progressivo_risposte = 0;
  //reset();
  ifstream html(name);
  if (html.bad())  {
    cout << "
Errore: impossibile leggere il file: " << name << "
" << endl;
    return;
  }
  in_table = in_form = in_paragraph = FALSE;
  // 2 parse or not 2 parse... This is the question...
  // Note: this is a VERY VERY simple parser, designed only to find questions
  // and answers in html files like test_a.htm. 
  // Si suppone che il file html sia sintatticamente corretto.
  while (!html.eof())
  {
    html.get(c);
    switch (c)
    {
    case '<': bstate = open_bracket; break;
    case '>': if (bstate == open_bracket) bstate = close_bracket; break;
    default: break;
    }
    if (bstate != none) // Compose current tag
      current_string_tag += c;
    if (bstate == open_bracket)
      continue;
    if (bstate == close_bracket)
    {
      // Traduce il tag da lettera a numero.
      // Se non lo trova, tiene l'ultimo valido.
      current_string_tag.upcase();
      short i;
      for ( i = 0; tag_list[i] != NULL; i++)
	if (current_string_tag.index(tag_list[i]) >= 0) // Tag was recognized.
	  break;
      i++; // Il primo elemento è no_tag
      // La cosa più giusta sarebbe avere una pila di tag riconosciuti e farne il pop
      if ((valid_tags) i != ignored_tag)
	current_tag = (valid_tags) i;
      if (current_tag == input_tag && in_paragraph)
	{
	  paragraph += current_string_tag;
	  current_tag = paragraph_tag_begin;
	}
      current_string_tag = "";
      bstate = none; // Reset bracket state
      continue;
    }
    // Che razza di tag sei?
    switch (current_tag)
    {
    case title_tag_begin: // Match 
      _title += c;
      break;
    case title_tag_end: // Match  
      current_tag = no_tag; 
      break;
    case form_tag_begin: // Match 
	{ 
	  paragraph += c; // Compone tutto il paragrafo tra 
 e 
	  in_paragraph = TRUE; 
	}
      break;
    case paragraph_tag_end: // Match 
      if (in_form && !in_table)
	{ 
	  current_tag = no_tag;
	  in_paragraph = FALSE;
	  // Controlla se il paragrafo contiene eventuali 
	  // se invece non contiene nessun 
= 0) // 
	    { 	
	      const int end_pos = paragraph.index('>', start_pos); //
	      paragraph.del(start_pos, end_pos - start_pos + 1);
	      _questions[progressivo_domande].answers[progressivo_risposte++].text = paragraph;
	      // Ogni MAXANSWERS cambia la domanda
	      // Questo tipo di logica è molto debole perchè si basa su
	      // di un numero fisso di possibili risposte (MAXANSWERS)
	      // Quindi è necessario che esse siano sempre in tal numero (MAXANSWERS appunto)
	      
	      if (progressivo_risposte == MAXANSWERS)
		{
		  progressivo_risposte = 0;
		  progressivo_domande++;
		  if (progressivo_domande == MAXQUESTIONS) // Non eccedere MAXQUESTIONS
		    progressivo_domande--;
		}
	    }
	  else
	    _questions[progressivo_domande].text = paragraph;
	  paragraph = "";
	}
      break;
    case table_tag_begin:
      in_table = TRUE;
      break;
    case table_tag_end:
      in_table = FALSE;
      break;
    default: 
      break; // Ignore any other tag...(ignored_tag or no_tag)
    }
  }
  _parsed = TRUE;
}
// S1 contiene il correttore, S2 contiene le risposte fornite
bool Questionnaire::load(const String& s1, const String& s2)
{
  reset();
  _loaded = !s1.empty() && !s2.empty();
  if (!_loaded) {
    cout << "Errore: impossibile caricare il correttore o le risposte fornite
" << endl;
    return FALSE;
  }
  // Il formato delle stringhe da passare in S1 ed S2  (correttore e risposte) è siffatto:
  // Q_=;Q_=; ...etc
  // I valori delle risposte possono essre V (vero) oppure F (falso); se indicato un blank
  // lo si tratta come risposta non presente, ovvero per identificare la fine delle risposte
  // possibili per ogni domanda; 
  // serve per riconoscere quando fermare il test per ogni risposta data in calc_score().
  // Esempio:  Q_1=VVFV;Q_2=VFFV;Q_3=FFVF
  // Attenzione, le risposte possibili sono sempre 5, ma nel correttore ne vengono indicate solo 4;
  // questo perche' la quinta viene presa in considerazione solo nel caso le precedenti
  // siano tutte V o tutte F. La quinta domanda corrisponde a "nessuna delle precedenti"
  // In questa funzione si carica la matrice correttore con i punteggi possibili per le
  // varie risposte fornite; ecco gli esempi possibili con i vari punteggi:
  // VVVV --->    +1/4, +1/4, +1/4, +1/4, -1
  // FFFF --->    -1/4, -1/4, -1/4, -1/4, +1
  // VFFF --->    +1,   -1/3, -1/3, -1/3,  0
  // VVFF --->    +1/2, +1/2, -1/2, -1/2,  0
  // VVVF --->    +1/3, +1/3, +1/3, -1,    0
  // Ecc. ecc. per tutte le varie combinazioni possibili. In pratica il concetto è: 1 punto
  // totale per ogni domanda, tale punto va suddiviso a seconda delle risposte corrette. Nel
  // caso di risposte tutte sbagliate o tutte giuste entra in gioco anche la domanda nr. 5
  // Quindi per un questionario con 20 domande avremo un massimo di 20 punti ed un minimo
  // assoluto di -20.
  String  separator, risposte;
  String rs[MAXQUESTIONS], qs[2], ns[2];
  int numr, numi;
  for (int x = 0; x < 2 ; x++) {
    const bool parse_corrector = x == 0;
    risposte = parse_corrector ? s1 : s2;
    trim(risposte);
    separator = ";";
    numr = split(risposte, rs, MAXQUESTIONS, separator); 
    for (int i=0; i < numr; i++) {
      separator = "=";
      numi = split(rs[i], qs, 2, separator);
      // qs[0] contiene Q_
      // qs[1] contiene VVVV FFFF VFFV etc
      if (numi == 2) {
	if (parse_corrector)
	  _max_score++;
	separator = "_";
	split(qs[0],ns,2,separator);
	const int qn = atoi(ns[1]) -1;
	String& qs1 = qs[1];
	qs1.upcase();
      
	const int r_vere  = qs1.freq('V');           // Numero di risposte vere
	const int r_false = MAXANSWERS - r_vere - 1; // Numero di risposte false
	Rational p_v(1, r_vere != 0 ? r_vere : 1);   // 1/4, 1/3, 1/2, 1
	Rational p_f(1, r_false != 0 ? r_false : 1); // -1/4, -1/3, -1/2. -1
	p_f.negate(); // Cambia segno per i punti in negativo
	const int l = qs1.length();
	
	if (parse_corrector && r_vere == 0)
	  _questions[qn].answers[MAXANSWERS-1].corrector_answer = 'V';
      
	for (int j = 0; j < MAXANSWERS; j++) {
	  Answer_struct& ans = _questions[qn].answers[j];
	  if (parse_corrector)  {
	    if (jCorrettore " << endl;
  for (int j = 0; j < MAXQUESTIONS; j++) {
  cout << "Domanda " << j+1 << ": ";
  for (int k = 0; k < MAXANSWERS; k++) {
    cout << _questions[j].answers[k].corrector_answer << "__";
    //Rational& rr = _questions[j].answers[k].p;
    //cout << rr.numerator() << "/" << rr.denominator() << ";";
    }
    cout << " " << endl;
  }
  _loaded = FALSE;
#endif
 
  return _loaded;
}
void Questionnaire::dump_html(const String& corso, const int modulo, const char testnum)
{
  if (!_parsed) {
    String nome;
    nome = FAD_ROOT;
    nome += corso;
    nome += "/documenti/M";
    nome += itoa(modulo);
    nome += "/test_";
    nome += testnum;
    nome += ".htm";
    parse_html(nome);
  }
  if (_parsed && _loaded)
    {
      int i,j;
      Rational punteggio;
      
      punteggio = calc_score();
      cout << "" << endl;
      cout << "" << _title << " " << endl;
      cout << " " << endl;
      cout << "Il punteggio ottenuto nel test corrisponde a: " << dtoa((double)punteggio) << " .";
      cout << "
Ricordiamo che il punteggio avrebbe potuto assumere un valore compreso fra -" << itoa(_max_score) << " e " << itoa(_max_score) <<" " << endl;
      /*if (testnum == 'z')
      {
      	String command;
      	command = "SELECT * FROM VERIFICHE WHERE loginname='";
  			command += application._utente;
  			command += "' AND testnum='";
  			command += prevtest;
  			command += "'";
  			application._db->ExecCommandOk(command);
      }
      */	
      cout << "
Le caselle selezionate nella colonna di sinistra identificano le risposte corrette, mentre la colonna di destra riporta le risposte da voi fornite
" << endl;
      cout << "Le risposte evidenziate in rosso corrispondono a risposte sbagliate da voi indicate come esatte.
" <Le risposte evidenziate in verde corrispondono a risposte esatte che voi non avete individuato." <Le risposte in nero corrispondono a risposte esatte da voi indicate come tali." <Di conseguenza le risposte in rosso e quelle in verde rappresentano l'insieme completo dei vostri errori." < " << endl;
      cout << "" << endl;
    }
  else
    cout << "Errore: per eseguire il dump dei tests è necessario caricare il correttore, le risposte e specificare il corso, il modulo ed il numero di test.
" << endl;
}
Rational  Questionnaire::calc_score()
{
  if (_loaded && !_computed)    
    {
      // Calcolo punteggio: per ogni risposta corrispondente a quella del correttore
      // si ottiene un punto.
      _computed = TRUE;
      for (int i=0; i < MAXQUESTIONS; i++) 
	{
	  for (int j=0; j < MAXANSWERS; j++) {     
	    Answer_struct& ans = _questions[i].answers[j];
	    if (((Rational&)ans.p).numerator() != 0)
	      if (ans.user_answer == 'V') // Per ogni risposta vera fornita, aggiunge il punteggio fornito dal correttore
		_score += ((Rational&) ans.p);
	  }
	}      
    }
  return _score;
}