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;
|
|
}
|