// Classe per la gestione/parsing questionari #include "questionnaire.h" // In alcuni casi la parentesi finale non viene messa perchè possono esistere forme del tipo: //
oppure

oppure #define INPUT_TAG "", "", "", "", "", 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 current_tag = no_tag; in_form = FALSE; in_paragraph = FALSE; break; case paragraph_tag_begin: // Match

e

{ 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; // Senza il tag non visualizza i checkbox! for (i=0;i" << _questions[i].text << "

" << endl; // Testo della domanda for (j=0;j"; // Risposte del correttore cout << ""; // 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 << ""; else cout << ""; cout << _questions[i].answers[j].text; if (!is_right) cout << ""; cout << "

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