1999-10-20 15:27:08 +00:00
// Classe per la gestione/parsing questionari
# include "questionnaire.h"
// In alcuni casi la parentesi finale non viene messa perch<63> 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<70> sapere perch<63> 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 <20> no_tag
// La cosa pi<70> 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<63> che fa parte di <INPUT ....>
// se invece non contiene nessun <INPUT allora si tratta del testo della domanda
// e lo memorizza cos<6F> com'<27>
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 <20> molto debole perch<63> si basa su
// di un numero fisso di possibili risposte (MAXANSWERS)
// Quindi <20> 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) <20> 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 <20> : 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 ;
2000-03-02 15:26:30 +00:00
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 ;
1999-10-20 15:27:08 +00:00
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 ( ) )
{
2000-03-02 15:26:30 +00:00
cout < < " <P><STRONG> " < < _questions [ i ] . text < < " </STRONG></P> " < < endl ; // Testo della domanda
1999-10-20 15:27:08 +00:00
for ( j = 0 ; j < MAXANSWERS ; j + + )
{
Answer_struct & ans = _questions [ i ] . answers [ j ] ;
2000-03-02 15:26:30 +00:00
const bool is_right = ans . user_answer = = ans . corrector_answer ;
const bool wrong_checked = ( ( ans . user_answer = = ' V ' ) & & ( ans . corrector_answer = = ' F ' ) ) ? TRUE : FALSE ;
1999-10-20 15:27:08 +00:00
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.
2000-03-02 15:26:30 +00:00
if ( wrong_checked )
cout < < " <font color= \" #FF0000 \" > " ;
else
cout < < " <font color= \" #00800 \" > " ;
1999-10-20 15:27:08 +00:00
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 <20> 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 ;
}