Files correlati : Ricompilazione Demo : [ ] Commento : Modifiche di marco git-svn-id: svn://10.65.10.50/trunk@8858 c028cbd2-c16b-5b4b-a496-9718f37d4682
		
			
				
	
	
		
			411 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			411 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
		
			Executable File
		
	
	
	
	
| // Classe per la gestione/parsing questionari
 | |
| #include "questionnaire.h"
 | |
| 
 | |
| // In alcuni casi la parentesi finale non viene messa perchè possono esistere forme del tipo:
 | |
| // <FORM method= ..> oppure <p align ...> oppure <input type=...>
 | |
| #define INPUT_TAG          "<INPUT"
 | |
| 
 | |
| 
 | |
| 
 | |
| const char* tag_list[] = { "<TITLE>",
 | |
| 			   "</TITLE>",
 | |
| 			   "<FORM",
 | |
| 			   "</FORM",
 | |
| 			   "<P",
 | |
| 			   "</P>", 
 | |
| 			   "<INPUT",
 | |
| 			   "<TABLE",
 | |
| 			   "</TABLE>",
 | |
| 			   "<DIV",
 | |
| 			   "</DIV>",
 | |
| 			   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 << "<p>Errore: impossibile leggere il file: " << name << "</p>" << 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>
 | |
|       _title += c;
 | |
|       break;
 | |
|     case title_tag_end: // Match </TITLE>
 | |
|       current_tag = no_tag; 
 | |
|       break;
 | |
|     case form_tag_begin: // Match <FORM
 | |
|       current_tag = no_tag; 
 | |
|       in_form = TRUE; 
 | |
|       in_paragraph = FALSE;
 | |
|       break;
 | |
|     case form_tag_end: // Match </FORM>
 | |
|       current_tag = no_tag; 
 | |
|       in_form = FALSE; 
 | |
|       in_paragraph = FALSE;
 | |
|       break;
 | |
|     case paragraph_tag_begin: // Match <P
 | |
|       if (in_form && !in_table) 	// Ignora i paragrafi che non sono compresi tra <FORM> e </FORM>
 | |
| 	{ 
 | |
| 	  paragraph += c; // Compone tutto il paragrafo tra <P> e </P>
 | |
| 	  in_paragraph = TRUE; 
 | |
| 	}
 | |
|       break;
 | |
|     case paragraph_tag_end: // Match </P>
 | |
|       if (in_form && !in_table)
 | |
| 	{ 
 | |
| 	  current_tag = no_tag;
 | |
| 	  in_paragraph = FALSE;
 | |
| 	  // Controlla se il paragrafo contiene eventuali <INPUT 
 | |
| 	  // Per estrarre il testo deve prima togliere tutto ciò che fa parte di <INPUT  ....>
 | |
| 	  // se invece non contiene nessun <INPUT allora si tratta del testo della domanda
 | |
| 	  // e lo memorizza così com'è
 | |
| 	  const int start_pos = paragraph.index(INPUT_TAG);
 | |
| 	  if (start_pos >= 0) // <INPUT... >
 | |
| 	    { 	
 | |
| 	      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 << "<p>Errore: impossibile caricare il correttore o le risposte fornite</p>" << endl;
 | |
|     return FALSE;
 | |
|   }
 | |
|   // Il formato delle stringhe da passare in S1 ed S2  (correttore e risposte) è siffatto:
 | |
|   // Q_<numero domanda>=<V|F><V|F><V|F><V|F>;Q_<numero domanda>=<V|F><V|F><V|F><V|F>; ...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_<numero domanda>
 | |
|       // 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 (j<l) {
 | |
| 	      ans.p = qs1[j] == 'V' ? p_v : p_f;
 | |
| 	      ans.corrector_answer = qs1[j]; // Memorizza le risposte del correttore ed il "peso"
 | |
| 	    }
 | |
| 	    else
 | |
| 	      if (r_vere == 0)
 | |
| 		ans.p = p_v;
 | |
| 	      else
 | |
| 		if (r_false == 0)
 | |
| 		  ans.p = p_f;
 | |
| 	  }
 | |
| 	  else
 | |
| 	    if (j<l)
 | |
| 	      ans.user_answer = qs1[j]; // Memorizza le risposte dell'utente
 | |
| 	} 
 | |
|       }      
 | |
|       _loaded = TRUE;
 | |
|     }
 | |
|   }
 | |
|   // Debug Stuff
 | |
| #ifdef DBG
 | |
|   cout << "<H2>Correttore</H2><BR><BR>" << endl;
 | |
|   for (int j = 0; j < MAXQUESTIONS; j++) {
 | |
|   cout << "Domanda " << j+1 << ":<BR>";
 | |
|   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 << "<BR>" << 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 << "<CENTER>" << endl;
 | |
|       cout << "<H2>" << _title << "</H2>" << endl;
 | |
|       cout << "</CENTER>" << endl;
 | |
|       cout << "<P>Il punteggio ottenuto nel test corrisponde a: <STRONG>" << dtoa((double)punteggio) << "</STRONG>.";
 | |
|       cout << "<P>Ricordiamo che il punteggio avrebbe potuto assumere un valore compreso fra -" << itoa(_max_score) << " e " << itoa(_max_score) <<"<BR><BR><BR>" << 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 << "<P>Le caselle selezionate nella colonna di sinistra identificano le risposte corrette, mentre la colonna di destra riporta le risposte da voi fornite</P>" << endl;
 | |
|       cout << "<P>Le risposte evidenziate in rosso corrispondono a risposte sbagliate da voi indicate come esatte.</p>" <<endl;
 | |
|       cout << "<P>Le risposte evidenziate in verde corrispondono a risposte esatte che voi non avete individuato.</p>" <<endl;
 | |
|       cout << "<P>Le risposte in nero corrispondono a risposte esatte da voi indicate come tali.</p>" <<endl;
 | |
|       cout << "<P>Di conseguenza le risposte in rosso e quelle in verde rappresentano l'insieme completo dei vostri errori.</p>" <<endl;
 | |
|       cout << "<BR><BR>" << endl;
 | |
|       cout << "<FORM>" << endl; // Senza il tag <FORM> non visualizza i checkbox!
 | |
|       for (i=0;i<MAXQUESTIONS;i++)
 | |
| 	if (!_questions[i].text.empty())
 | |
| 	  {	  
 | |
| 	    cout << "<P><STRONG>" << _questions[i].text << "</STRONG></P>" << endl; // Testo della domanda
 | |
| 	    for (j=0;j<MAXANSWERS;j++)
 | |
| 	      {	  
 | |
| 		Answer_struct& ans = _questions[i].answers[j];
 | |
|     const bool is_right = ans.user_answer == ans.corrector_answer;
 | |
|     const bool wrong_checked = ((ans.user_answer == 'V') && (ans.corrector_answer == 'F'))? TRUE:FALSE;
 | |
| 		cout << "<P><INPUT TYPE=\"CHECKBOX\" NAME=\"C_" << i+1 << "_" << j+1;
 | |
| 		cout << "\" VALUE=\"ON\" ";
 | |
| 		if (ans.corrector_answer == 'V')
 | |
| 		  cout << "CHECKED ";
 | |
| 		cout << "onClick=\"return false;\">"; // Risposte del correttore
 | |
| 		cout << "<INPUT TYPE=\"CHECKBOX\" NAME=\"U_" << i+1 << "_" << j+1;
 | |
| 		cout << "\" VALUE=\"ON\" ";
 | |
| 		if (ans.user_answer == 'V')
 | |
| 		  cout << "CHECKED ";
 | |
| 		cout << "onClick=\"return false;\">"; // Risposte dell'utente
 | |
| 		if (!is_right) // Se la risposta fornita non corrisponde a quella del correttore, evidenzia in rosso la risposta data.
 | |
| 		  if (wrong_checked)
 | |
| 		  	cout << "<font color=\"#FF0000\">";
 | |
| 		  else
 | |
| 		  	cout << "<font color=\"#00800\">";
 | |
| 		cout << _questions[i].answers[j].text;
 | |
| 		if (!is_right)
 | |
| 		  cout << "</font>";
 | |
| 		cout << "</P>" << endl;
 | |
| 	      }
 | |
| 	  }
 | |
|       cout << "</FORM>" << endl;
 | |
|     }
 | |
|   else
 | |
|     cout << "<p>Errore: per eseguire il dump dei tests è necessario caricare il correttore, le risposte e specificare il corso, il modulo ed il numero di test.</p>" << 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;
 | |
| }
 |