*/
/***** Write heading of list of imported questions *****/
Tbl_StartTableWideMarginPadding (2);
TsI_WriteHeadingListImportedQst ();
/***** For each question... *****/
for (QuestionElem = TestElem->FirstChild;
QuestionElem != NULL;
QuestionElem = QuestionElem->NextBrother)
{
if (!strcmp (QuestionElem->TagName,"question"))
{
/***** Create test question *****/
Tst_QstConstructor ();
/* Get type of questions (in mandatory attribute "type") */
AnswerTypeFound = false;
for (Attribute = QuestionElem->FirstAttribute;
Attribute != NULL;
Attribute = Attribute->Next)
if (!strcmp (Attribute->AttributeName,"type"))
{
Gbl.Test.AnswerType = TsI_ConvertFromStrAnsTypXMLToAnsTyp (Attribute->Content);
AnswerTypeFound = true;
break; // Only first attribute "type"
}
if (!AnswerTypeFound)
Lay_ShowErrorAndExit ("Wrong type of answer.");
/* Get tags */
for (TagsElem = QuestionElem->FirstChild, Gbl.Test.Tags.Num = 0;
TagsElem != NULL;
TagsElem = TagsElem->NextBrother)
if (!strcmp (TagsElem->TagName,"tags"))
{
for (TagElem = TagsElem->FirstChild;
TagElem != NULL && Gbl.Test.Tags.Num < Tst_MAX_TAGS_PER_QUESTION;
TagElem = TagElem->NextBrother)
if (!strcmp (TagElem->TagName,"tag"))
{
if (TagElem->Content)
{
Str_Copy (Gbl.Test.Tags.Txt[Gbl.Test.Tags.Num],
TagElem->Content,
Tst_MAX_BYTES_TAG);
Gbl.Test.Tags.Num++;
}
}
break; // Only first element "tags"
}
/* Get stem (mandatory) */
for (StemElem = QuestionElem->FirstChild;
StemElem != NULL;
StemElem = StemElem->NextBrother)
if (!strcmp (StemElem->TagName,"stem"))
{
if (StemElem->Content)
{
/* Convert stem from text to HTML (in database stem is stored in HTML) */
Str_Copy (Stem,StemElem->Content,
Cns_MAX_BYTES_TEXT);
Str_ChangeFormat (Str_FROM_TEXT,Str_TO_HTML,
Stem,Cns_MAX_BYTES_TEXT,true);
Gbl.Test.Stem.Text = Stem;
Gbl.Test.Stem.Length = strlen (Stem);
}
break; // Only first element "stem"
}
/* Get feedback (optional) */
for (FeedbackElem = QuestionElem->FirstChild;
FeedbackElem != NULL;
FeedbackElem = FeedbackElem->NextBrother)
if (!strcmp (FeedbackElem->TagName,"feedback"))
{
if (FeedbackElem->Content)
{
/* Convert feedback from text to HTML (in database feedback is stored in HTML) */
Str_Copy (Feedback,FeedbackElem->Content,
Cns_MAX_BYTES_TEXT);
Str_ChangeFormat (Str_FROM_TEXT,Str_TO_HTML,
Feedback,Cns_MAX_BYTES_TEXT,true);
Gbl.Test.Feedback.Text = Feedback;
Gbl.Test.Feedback.Length = strlen (Feedback);
}
break; // Only first element "feedback"
}
/* Get shuffle. By default, shuffle is false. */
Gbl.Test.Shuffle = false;
for (AnswerElem = QuestionElem->FirstChild;
AnswerElem != NULL;
AnswerElem = AnswerElem->NextBrother)
if (!strcmp (AnswerElem->TagName,"answer"))
{
if (Gbl.Test.AnswerType == Tst_ANS_UNIQUE_CHOICE ||
Gbl.Test.AnswerType == Tst_ANS_MULTIPLE_CHOICE)
/* Get whether shuffle answers (in attribute "shuffle") */
for (Attribute = AnswerElem->FirstAttribute;
Attribute != NULL;
Attribute = Attribute->Next)
if (!strcmp (Attribute->AttributeName,"shuffle"))
{
Gbl.Test.Shuffle = XML_GetAttributteYesNoFromXMLTree (Attribute);
break; // Only first attribute "shuffle"
}
break; // Only first element "answer"
}
/* Get answer (mandatory) */
TsI_GetAnswerFromXML (AnswerElem);
/* Make sure that tags, text and answer are not empty */
if (Tst_CheckIfQstFormatIsCorrectAndCountNumOptions ())
{
/* Check if question already exists in database */
QuestionExists = TsI_CheckIfQuestionExistsInDB ();
/* Write row with this imported question */
TsI_WriteRowImportedQst (StemElem,FeedbackElem,QuestionExists);
/***** If a new question ==> insert question, tags and answer in the database *****/
if (!QuestionExists)
{
Gbl.Test.QstCod = -1L;
Tst_InsertOrUpdateQstTagsAnsIntoDB ();
}
}
/***** Destroy test question *****/
Tst_QstDestructor ();
}
}
Tbl_EndTable ();
}
else // TestElem not found
Ale_ShowAlert (Ale_ERROR,"Root element <test> not found.");
/***** End table *****/
Box_EndBox ();
/***** Free XML tree *****/
XML_FreeTree (RootElem);
}
/*****************************************************************************/
/***** Convert a string with the type of answer in XML to type of answer *****/
/*****************************************************************************/
static Tst_AnswerType_t TsI_ConvertFromStrAnsTypXMLToAnsTyp (const char *StrAnsTypeXML)
{
extern const char *Tst_StrAnswerTypesXML[Tst_NUM_ANS_TYPES];
Tst_AnswerType_t AnsType;
if (StrAnsTypeXML != NULL)
for (AnsType = (Tst_AnswerType_t) 0;
AnsType < Tst_NUM_ANS_TYPES;
AnsType++)
// comparison must be case insensitive, because users can edit XML
if (!strcasecmp (StrAnsTypeXML,Tst_StrAnswerTypesXML[AnsType]))
return AnsType;
Lay_ShowErrorAndExit ("Wrong type of answer.");
return (Tst_AnswerType_t) 0; // Not reached
}
/*****************************************************************************/
/**************** Get answer inside an XML question elements *****************/
/*****************************************************************************/
static bool TsI_CheckIfQuestionExistsInDB (void)
{
extern const char *Tst_StrAnswerTypesDB[Tst_NUM_ANS_TYPES];
MYSQL_RES *mysql_res_qst;
MYSQL_RES *mysql_res_ans;
MYSQL_ROW row;
bool IdenticalQuestionFound = false;
bool IdenticalAnswers;
unsigned NumQst;
unsigned NumQstsWithThisStem;
unsigned NumOpt;
unsigned NumOptsExistingQstInDB;
long QstCod;
unsigned i;
/***** Check if stem exists *****/
NumQstsWithThisStem =
(unsigned) DB_QuerySELECT (&mysql_res_qst,"can not check"
" if a question exists",
"SELECT QstCod FROM tst_questions"
" WHERE CrsCod=%ld AND AnsType='%s' AND Stem='%s'",
Gbl.Hierarchy.Crs.CrsCod,
Tst_StrAnswerTypesDB[Gbl.Test.AnswerType],
Gbl.Test.Stem.Text);
if (NumQstsWithThisStem) // There are questions in database with the same stem that the one of this question
{
/***** Check if the answer exists in any of the questions with the same stem *****/
/* For each question with the same stem */
for (NumQst = 0;
!IdenticalQuestionFound && NumQst < NumQstsWithThisStem;
NumQst++)
{
row = mysql_fetch_row (mysql_res_qst);
if ((QstCod = Str_ConvertStrCodToLongCod (row[0])) < 0)
Lay_ShowErrorAndExit ("Wrong code of question.");
/* Get answers from this question */
NumOptsExistingQstInDB =
(unsigned) DB_QuerySELECT (&mysql_res_ans,"can not get the answer"
" of a question",
"SELECT Answer FROM tst_answers"
" WHERE QstCod=%ld ORDER BY AnsInd",
QstCod);
switch (Gbl.Test.AnswerType)
{
case Tst_ANS_INT:
row = mysql_fetch_row (mysql_res_ans);
IdenticalQuestionFound = (Tst_GetIntAnsFromStr (row[0]) == Gbl.Test.Answer.Integer);
break;
case Tst_ANS_FLOAT:
for (IdenticalAnswers = true, i = 0;
IdenticalAnswers && i < 2;
i++)
{
row = mysql_fetch_row (mysql_res_ans);
IdenticalAnswers = (Tst_GetFloatAnsFromStr (row[0]) == Gbl.Test.Answer.FloatingPoint[i]);
}
IdenticalQuestionFound = IdenticalAnswers;
break;
case Tst_ANS_TRUE_FALSE:
row = mysql_fetch_row (mysql_res_ans);
IdenticalQuestionFound = (Str_ConvertToUpperLetter (row[0][0]) == Gbl.Test.Answer.TF);
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
case Tst_ANS_TEXT:
if (NumOptsExistingQstInDB == Gbl.Test.Answer.NumOptions)
{
for (IdenticalAnswers = true, NumOpt = 0;
IdenticalAnswers && NumOpt < NumOptsExistingQstInDB;
NumOpt++)
{
row = mysql_fetch_row (mysql_res_ans);
if (strcasecmp (row[0],Gbl.Test.Answer.Options[NumOpt].Text))
IdenticalAnswers = false;
}
}
else // Different number of answers (options)
IdenticalAnswers = false;
IdenticalQuestionFound = IdenticalAnswers;
break;
default:
break;
}
/* Free structure that stores the query result for answers */
DB_FreeMySQLResult (&mysql_res_ans);
}
}
else // Stem does not exist
IdenticalQuestionFound = false;
/* Free structure that stores the query result for questions */
DB_FreeMySQLResult (&mysql_res_qst);
return IdenticalQuestionFound;
}
/*****************************************************************************/
/**************** Get answer inside an XML question elements *****************/
/*****************************************************************************/
// Answer is mandatory
static void TsI_GetAnswerFromXML (struct XMLElement *AnswerElem)
{
struct XMLElement *OptionElem;
struct XMLElement *TextElem;
struct XMLElement *FeedbackElem;
struct XMLElement *LowerUpperElem;
struct XMLAttribute *Attribute;
unsigned NumOpt;
switch (Gbl.Test.AnswerType)
{
case Tst_ANS_INT:
if (!Tst_AllocateTextChoiceAnswer (0))
/* Abort on error */
Ale_ShowAlertsAndExit ();
if (AnswerElem->Content)
Str_Copy (Gbl.Test.Answer.Options[0].Text,AnswerElem->Content,
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
break;
case Tst_ANS_FLOAT:
if (!Tst_AllocateTextChoiceAnswer (0))
/* Abort on error */
Ale_ShowAlertsAndExit ();
if (!Tst_AllocateTextChoiceAnswer (1))
/* Abort on error */
Ale_ShowAlertsAndExit ();
for (LowerUpperElem = AnswerElem->FirstChild;
LowerUpperElem != NULL;
LowerUpperElem = LowerUpperElem->NextBrother)
if (!strcmp (LowerUpperElem->TagName,"lower"))
{
if (LowerUpperElem->Content)
Str_Copy (Gbl.Test.Answer.Options[0].Text,
LowerUpperElem->Content,
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
break; // Only first element "lower"
}
for (LowerUpperElem = AnswerElem->FirstChild;
LowerUpperElem != NULL;
LowerUpperElem = LowerUpperElem->NextBrother)
if (!strcmp (LowerUpperElem->TagName,"upper"))
{
if (LowerUpperElem->Content)
Str_Copy (Gbl.Test.Answer.Options[1].Text,
LowerUpperElem->Content,
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
break; // Only first element "upper"
}
break;
case Tst_ANS_TRUE_FALSE:
// Comparisons must be case insensitive, because users can edit XML
if (!AnswerElem->Content)
Gbl.Test.Answer.TF = ' ';
else if (!strcasecmp (AnswerElem->Content,"true") ||
!strcasecmp (AnswerElem->Content,"T") ||
!strcasecmp (AnswerElem->Content,"yes") ||
!strcasecmp (AnswerElem->Content,"Y"))
Gbl.Test.Answer.TF = 'T';
else if (!strcasecmp (AnswerElem->Content,"false") ||
!strcasecmp (AnswerElem->Content,"F") ||
!strcasecmp (AnswerElem->Content,"no") ||
!strcasecmp (AnswerElem->Content,"N"))
Gbl.Test.Answer.TF = 'F';
else
Gbl.Test.Answer.TF = ' ';
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
case Tst_ANS_TEXT:
/* Get options */
for (OptionElem = AnswerElem->FirstChild, NumOpt = 0;
OptionElem != NULL && NumOpt < Tst_MAX_OPTIONS_PER_QUESTION;
OptionElem = OptionElem->NextBrother, NumOpt++)
if (!strcmp (OptionElem->TagName,"option"))
{
if (!Tst_AllocateTextChoiceAnswer (NumOpt))
/* Abort on error */
Ale_ShowAlertsAndExit ();
for (TextElem = OptionElem->FirstChild;
TextElem != NULL;
TextElem = TextElem->NextBrother)
if (!strcmp (TextElem->TagName,"text"))
{
if (TextElem->Content)
{
Str_Copy (Gbl.Test.Answer.Options[NumOpt].Text,
TextElem->Content,
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
/* Convert answer from text to HTML (in database answer text is stored in HTML) */
Str_ChangeFormat (Str_FROM_TEXT,Str_TO_HTML,
Gbl.Test.Answer.Options[NumOpt].Text,Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,true);
}
break; // Only first element "text"
}
for (FeedbackElem = OptionElem->FirstChild;
FeedbackElem != NULL;
FeedbackElem = FeedbackElem->NextBrother)
if (!strcmp (FeedbackElem->TagName,"feedback"))
{
if (FeedbackElem->Content)
{
Str_Copy (Gbl.Test.Answer.Options[NumOpt].Feedback,
FeedbackElem->Content,
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
/* Convert feedback from text to HTML (in database answer feedback is stored in HTML) */
Str_ChangeFormat (Str_FROM_TEXT,Str_TO_HTML,
Gbl.Test.Answer.Options[NumOpt].Feedback,Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,true);
}
break; // Only first element "feedback"
}
if (Gbl.Test.AnswerType == Tst_ANS_TEXT)
Gbl.Test.Answer.Options[NumOpt].Correct = true;
else
/* Check if option is correct or wrong */
for (Attribute = OptionElem->FirstAttribute;
Attribute != NULL;
Attribute = Attribute->Next)
if (!strcmp (Attribute->AttributeName,"correct"))
{
Gbl.Test.Answer.Options[NumOpt].Correct = XML_GetAttributteYesNoFromXMLTree (Attribute);
break; // Only first attribute "correct"
}
}
break;
default:
break;
}
}
/*****************************************************************************/
/************* Write heading of list of imported test questions **************/
/*****************************************************************************/
static void TsI_WriteHeadingListImportedQst (void)
{
extern const char *Txt_No_INDEX;
extern const char *Txt_Tags;
extern const char *Txt_Type;
extern const char *Txt_Shuffle;
extern const char *Txt_Question;
/***** Write the heading *****/
Tbl_StartRow ();
fprintf (Gbl.F.Out," | "
""
"%s"
" | "
""
"%s"
" | "
""
"%s"
" | "
""
"%s"
" | "
""
"%s"
" | ",
Txt_No_INDEX,
Txt_Tags,
Txt_Type,
Txt_Shuffle,
Txt_Question);
Tbl_EndRow ();
}
/*****************************************************************************/
/**************** Write a row with one imported test question ****************/
/*****************************************************************************/
static void TsI_WriteRowImportedQst (struct XMLElement *StemElem,
struct XMLElement *FeedbackElem,
bool QuestionExists)
{
extern const char *Txt_Existing_question;
extern const char *Txt_New_question;
extern const char *Txt_no_tags;
extern const char *Txt_TST_STR_ANSWER_TYPES[Tst_NUM_ANS_TYPES];
extern const char *Txt_TST_Answer_given_by_the_teachers;
static unsigned NumQst = 0;
static unsigned NumNonExistingQst = 0;
const char *Stem = (StemElem != NULL) ? StemElem->Content :
"";
const char *Feedback = (FeedbackElem != NULL) ? FeedbackElem->Content :
"";
unsigned NumTag;
unsigned NumOpt;
char *AnswerText;
size_t AnswerTextLength;
char *AnswerFeedback;
size_t AnswerFeedbackLength;
const char *ClassData = QuestionExists ? "DAT_SMALL_LIGHT" :
"DAT_SMALL";
const char *ClassStem = QuestionExists ? "TEST_EDI_LIGHT" :
"TEST_EDI";
Gbl.RowEvenOdd = NumQst % 2;
NumQst++;
/***** Put icon to indicate that a question does not exist in database *****/
Tbl_StartRow ();
fprintf (Gbl.F.Out,""
"",
Gbl.RowEvenOdd,
Cfg_URL_ICON_PUBLIC,
QuestionExists ? "tr16x16.gif" :
"check-circle.svg",
QuestionExists ? Txt_Existing_question :
Txt_New_question,
QuestionExists ? Txt_Existing_question :
Txt_New_question);
Tbl_EndCell ();
/***** Write number of question *****/
fprintf (Gbl.F.Out," | ",
ClassData,Gbl.RowEvenOdd);
if (!QuestionExists)
fprintf (Gbl.F.Out,"%u ",++NumNonExistingQst);
Tbl_EndCell ();
/***** Write the question tags *****/
fprintf (Gbl.F.Out," | ",
Gbl.RowEvenOdd);
if (Gbl.Test.Tags.Num)
{
/***** Write the tags *****/
Tbl_StartTable ();
for (NumTag = 0;
NumTag < Gbl.Test.Tags.Num;
NumTag++)
{
Tbl_StartRow ();
fprintf (Gbl.F.Out," | "
" • ",
ClassData);
Tbl_EndCell ();
fprintf (Gbl.F.Out," | "
"%s",
ClassData,Gbl.Test.Tags.Txt[NumTag]);
Tbl_EndCell ();
Tbl_EndRow ();
}
Tbl_EndTable ();
}
else // no tags for this question
fprintf (Gbl.F.Out," (%s) ",
ClassData,Txt_no_tags);
Tbl_EndCell ();
/***** Write the question type *****/
fprintf (Gbl.F.Out," | "
"%s ",
ClassData,Gbl.RowEvenOdd,
Txt_TST_STR_ANSWER_TYPES[Gbl.Test.AnswerType]);
Tbl_EndCell ();
/***** Write if shuffle is enabled *****/
fprintf (Gbl.F.Out," | ",
Gbl.RowEvenOdd);
if (Gbl.Test.AnswerType == Tst_ANS_UNIQUE_CHOICE ||
Gbl.Test.AnswerType == Tst_ANS_MULTIPLE_CHOICE)
/* Put an icon that indicates whether shuffle is enabled or not */
if (Gbl.Test.Shuffle)
fprintf (Gbl.F.Out,"",
Cfg_URL_ICON_PUBLIC,
Txt_TST_Answer_given_by_the_teachers,
Txt_TST_Answer_given_by_the_teachers,
QuestionExists ? "ICO_HIDDEN " :
"");
Tbl_EndCell ();
/***** Write the stem and the answers *****/
fprintf (Gbl.F.Out," | ",
Gbl.RowEvenOdd);
Tst_WriteQstStem (Stem,ClassStem);
Tst_WriteQstFeedback (Feedback,"TEST_EDI_LIGHT");
switch (Gbl.Test.AnswerType)
{
case Tst_ANS_INT:
fprintf (Gbl.F.Out,"(%ld)",
ClassStem,Gbl.Test.Answer.Integer);
break;
case Tst_ANS_FLOAT:
fprintf (Gbl.F.Out,"([%lg; %lg])",
ClassStem,Gbl.Test.Answer.FloatingPoint[0],Gbl.Test.Answer.FloatingPoint[1]);
break;
case Tst_ANS_TRUE_FALSE:
fprintf (Gbl.F.Out,"(",ClassStem);
Tst_WriteAnsTF (Gbl.Test.Answer.TF);
fprintf (Gbl.F.Out,")");
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
case Tst_ANS_TEXT:
Tbl_StartTable ();
for (NumOpt = 0;
NumOpt < Gbl.Test.Answer.NumOptions;
NumOpt++)
{
/* Convert the answer, that is in HTML, to rigorous HTML */
AnswerTextLength = strlen (Gbl.Test.Answer.Options[NumOpt].Text) *
Str_MAX_BYTES_PER_CHAR;
if ((AnswerText = (char *) malloc (AnswerTextLength + 1)) == NULL)
Lay_NotEnoughMemoryExit ();
Str_Copy (AnswerText,Gbl.Test.Answer.Options[NumOpt].Text,
AnswerTextLength);
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
AnswerText,AnswerTextLength,false);
/* Convert the feedback, that is in HTML, to rigorous HTML */
AnswerFeedbackLength = 0;
AnswerFeedback = NULL;
if (Gbl.Test.Answer.Options[NumOpt].Feedback)
if (Gbl.Test.Answer.Options[NumOpt].Feedback[0])
{
AnswerFeedbackLength = strlen (Gbl.Test.Answer.Options[NumOpt].Feedback) *
Str_MAX_BYTES_PER_CHAR;
if ((AnswerFeedback = (char *) malloc (AnswerFeedbackLength + 1)) == NULL)
Lay_NotEnoughMemoryExit ();
Str_Copy (AnswerFeedback,
Gbl.Test.Answer.Options[NumOpt].Feedback,
AnswerFeedbackLength);
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
AnswerFeedback,AnswerFeedbackLength,false);
}
/* Put an icon that indicates whether the answer is correct or wrong */
Tbl_StartRow ();
fprintf (Gbl.F.Out," | ",
Gbl.RowEvenOdd);
if (Gbl.Test.Answer.Options[NumOpt].Correct)
fprintf (Gbl.F.Out,"",
Cfg_URL_ICON_PUBLIC,
Txt_TST_Answer_given_by_the_teachers,
Txt_TST_Answer_given_by_the_teachers,
QuestionExists ? "ICO_HIDDEN " :
"");
Tbl_EndCell ();
/* Write the number of option */
fprintf (Gbl.F.Out," | "
"%c) ",
ClassData,'a' + (char) NumOpt);
Tbl_EndCell ();
/* Write the text and the feedback of the answer */
fprintf (Gbl.F.Out," | "
" "
"%s"
" ",
ClassStem,AnswerText);
if (AnswerFeedbackLength)
fprintf (Gbl.F.Out,""
"%s"
" ",
AnswerFeedback);
Tbl_EndCell ();
Tbl_EndRow ();
/* Free memory allocated for the answer and the feedback */
free ((void *) AnswerText);
if (AnswerFeedbackLength)
free ((void *) AnswerFeedback);
}
Tbl_EndTable ();
break;
default:
break;
}
Tbl_EndCell ();
Tbl_EndRow ();
}
|