swad-core/swad_test_import.c

1074 lines
38 KiB
C
Raw Normal View History

2014-12-01 23:55:08 +01:00
// swad_test_import.c: import and export self-assessment tests using XML files
/*
SWAD (Shared Workspace At a Distance),
is a web platform developed at the University of Granada (Spain),
and used to support university teaching.
This file is part of SWAD core.
2021-02-09 12:43:45 +01:00
Copyright (C) 1999-2021 Antonio Ca<EFBFBD>as Vargas
2014-12-01 23:55:08 +01:00
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*****************************************************************************/
/*********************************** Headers *********************************/
/*****************************************************************************/
#include <stdlib.h> // For exit, system, malloc, free, etc
#include <string.h> // For string functions
#include <sys/stat.h> // For mkdir
#include <sys/types.h> // For mkdir
2017-06-10 21:38:10 +02:00
#include "swad_box.h"
2014-12-01 23:55:08 +01:00
#include "swad_database.h"
#include "swad_error.h"
2018-11-09 20:47:39 +01:00
#include "swad_form.h"
2014-12-01 23:55:08 +01:00
#include "swad_global.h"
2019-10-23 19:05:05 +02:00
#include "swad_HTML.h"
2014-12-01 23:55:08 +01:00
#include "swad_parameter.h"
#include "swad_test.h"
#include "swad_xml.h"
/*****************************************************************************/
/***************************** Public constants ******************************/
/*****************************************************************************/
/*****************************************************************************/
/**************************** Private constants ******************************/
/*****************************************************************************/
/*****************************************************************************/
2019-11-21 16:47:07 +01:00
/******************************* Private types *******************************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
/*****************************************************************************/
/************** External global variables from others modules ****************/
/*****************************************************************************/
extern struct Globals Gbl;
/*****************************************************************************/
2019-11-21 16:47:07 +01:00
/************************* Private global variables **************************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
/*****************************************************************************/
2019-11-21 16:47:07 +01:00
/***************************** Private prototypes ****************************/
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
2020-10-14 00:59:24 +02:00
static void TsI_PutParamsExportQsts (void *Test);
2020-03-17 00:35:11 +01:00
static void TsI_PutCreateXMLParam (void);
2015-12-13 21:30:28 +01:00
2020-03-27 18:56:00 +01:00
static void TsI_ExportQuestion (struct Tst_Question *Question,FILE *FileXML);
2020-03-17 00:35:11 +01:00
static void TsI_GetAndWriteTagsXML (long QstCod,FILE *FileXML);
2020-04-04 02:07:54 +02:00
static void TsI_WriteAnswersOfAQstXML (const struct Tst_Question *Question,
2020-03-18 18:49:45 +01:00
FILE *FileXML);
2014-12-01 23:55:08 +01:00
static void TsI_ReadQuestionsFromXMLFileAndStoreInDB (const char *FileNameXML);
static void TsI_ImportQuestionsFromXMLBuffer (const char *XMLBuffer);
static Tst_AnswerType_t TsI_ConvertFromStrAnsTypXMLToAnsTyp (const char *StrAnsTypeXML);
2020-03-18 01:57:08 +01:00
static void TsI_GetAnswerFromXML (struct XMLElement *AnswerElem,
2020-03-19 20:57:54 +01:00
struct Tst_Question *Question);
2014-12-01 23:55:08 +01:00
static void TsI_WriteHeadingListImportedQst (void);
static void TsI_WriteRowImportedQst (struct XMLElement *StemElem,
struct XMLElement *FeedbackElem,
2020-03-17 14:47:58 +01:00
const struct Tst_Question *Question,
2014-12-01 23:55:08 +01:00
bool QuestionExists);
/*****************************************************************************/
/**************** Put a link (form) to export test questions *****************/
/*****************************************************************************/
2020-05-17 18:45:27 +02:00
void TsI_PutIconToExportQuestions (struct Tst_Test *Test)
2014-12-01 23:55:08 +01:00
{
extern const char *Txt_Export_questions;
2015-12-13 21:30:28 +01:00
/***** Put a link to create a file with questions *****/
2020-05-17 18:45:27 +02:00
Lay_PutContextualLinkOnlyIcon (ActLstTstQst,NULL,
2020-03-27 14:56:54 +01:00
TsI_PutParamsExportQsts,Test,
2019-01-12 03:00:59 +01:00
"file-import.svg",
Txt_Export_questions);
2015-12-13 21:30:28 +01:00
}
/*****************************************************************************/
/****************** Put params to export test questions **********************/
/*****************************************************************************/
2014-12-01 23:55:08 +01:00
2020-10-14 00:59:24 +02:00
static void TsI_PutParamsExportQsts (void *Test)
2015-12-13 21:30:28 +01:00
{
2020-10-14 00:59:24 +02:00
if (Test)
2020-03-26 02:54:30 +01:00
{
2020-10-14 00:59:24 +02:00
Tst_PutParamsEditQst (Test);
2020-03-26 02:54:30 +01:00
Par_PutHiddenParamChar ("OnlyThisQst",'N');
2020-10-14 00:59:24 +02:00
Par_PutHiddenParamUnsigned (NULL,"Order",(unsigned) (((struct Tst_Test *) Test)->SelectedOrder));
2020-03-26 02:54:30 +01:00
TsI_PutCreateXMLParam ();
}
2020-03-17 00:35:11 +01:00
}
/*****************************************************************************/
/************************ Parameter to create XML file ***********************/
/*****************************************************************************/
static void TsI_PutCreateXMLParam (void)
{
2014-12-01 23:55:08 +01:00
Par_PutHiddenParamChar ("CreateXML",'Y');
}
2020-03-17 00:35:11 +01:00
bool TsI_GetCreateXMLParamFromForm (void)
{
return Par_GetParToBool ("CreateXML");
}
2014-12-01 23:55:08 +01:00
/*****************************************************************************/
/*************** Put a link (form) to import test questions ******************/
/*****************************************************************************/
2020-05-17 18:45:27 +02:00
void TsI_PutIconToImportQuestions (void)
2014-12-01 23:55:08 +01:00
{
extern const char *Txt_Import_questions;
/***** Put a link to create a file with questions *****/
2020-05-17 18:45:27 +02:00
Lay_PutContextualLinkOnlyIcon (ActReqImpTstQst,NULL,
2020-03-26 02:54:30 +01:00
NULL,NULL,
2019-01-12 03:00:59 +01:00
"file-export.svg",
Txt_Import_questions);
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/*********** Show form to import test questions from an XML file *************/
/*****************************************************************************/
void TsI_ShowFormImportQstsFromXML (void)
{
2016-11-13 20:18:49 +01:00
extern const char *Hlp_ASSESSMENT_Tests;
2019-02-22 21:47:50 +01:00
extern const char *The_ClassFormInBox[The_NUM_THEMES];
2016-03-21 13:08:18 +01:00
extern const char *Txt_Import_questions;
extern const char *Txt_You_need_an_XML_file_containing_a_list_of_questions;
2014-12-01 23:55:08 +01:00
extern const char *Txt_XML_file;
2019-10-26 02:19:42 +02:00
/***** Begin box *****/
2020-03-26 02:54:30 +01:00
Box_BoxBegin (NULL,Txt_Import_questions,
NULL,NULL,
2017-06-12 15:03:29 +02:00
Hlp_ASSESSMENT_Tests,Box_NOT_CLOSABLE);
2014-12-01 23:55:08 +01:00
/***** Write help message *****/
2019-02-16 19:29:27 +01:00
Ale_ShowAlert (Ale_INFO,Txt_You_need_an_XML_file_containing_a_list_of_questions);
2014-12-01 23:55:08 +01:00
/***** Write a form to import questions *****/
Frm_BeginForm (ActImpTstQst);
2019-11-02 23:40:52 +01:00
HTM_LABEL_Begin ("class=\"%s\"",The_ClassFormInBox[Gbl.Prefs.Theme]);
2020-01-11 15:22:02 +01:00
HTM_TxtColonNBSP (Txt_XML_file);
2020-04-27 03:16:55 +02:00
HTM_INPUT_FILE (Fil_NAME_OF_PARAM_FILENAME_ORG,".xml",
HTM_SUBMIT_ON_CHANGE,
NULL);
2019-11-02 12:59:31 +01:00
HTM_LABEL_End ();
2018-11-09 20:47:39 +01:00
Frm_EndForm ();
2016-03-21 13:08:18 +01:00
2017-06-12 14:16:33 +02:00
/***** End box *****/
2019-10-25 22:48:34 +02:00
Box_BoxEnd ();
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/*** Create the XML file with test questions and put a link to download it ***/
/*****************************************************************************/
2020-03-24 01:50:39 +01:00
void TsI_CreateXML (unsigned NumQsts,MYSQL_RES *mysql_res)
2014-12-01 23:55:08 +01:00
{
2019-02-22 21:47:50 +01:00
extern const char *The_ClassFormOutBoxBold[The_NUM_THEMES];
2014-12-01 23:55:08 +01:00
extern const char *Txt_NEW_LINE;
extern const char *Txt_XML_file;
2017-01-28 15:58:46 +01:00
char PathPubFile[PATH_MAX + 1];
2020-03-17 00:35:11 +01:00
FILE *FileXML;
2020-03-24 01:50:39 +01:00
unsigned NumQst;
2020-03-27 18:56:00 +01:00
struct Tst_Question Question;
2014-12-01 23:55:08 +01:00
MYSQL_ROW row;
2016-04-04 02:12:06 +02:00
/***** Create a temporary public directory
used to download the XML file *****/
Brw_CreateDirDownloadTmp ();
2014-12-01 23:55:08 +01:00
/***** Create public XML file with the questions *****/
snprintf (PathPubFile,sizeof (PathPubFile),"%s/%s/%s/test.xml",
2019-03-20 01:36:36 +01:00
Cfg_PATH_FILE_BROWSER_TMP_PUBLIC,
2019-03-20 14:36:26 +01:00
Gbl.FileBrowser.TmpPubDir.L,
Gbl.FileBrowser.TmpPubDir.R);
2020-03-17 00:35:11 +01:00
if ((FileXML = fopen (PathPubFile,"wb")) == NULL)
Err_ShowErrorAndExit ("Can not open target file.");
2014-12-01 23:55:08 +01:00
/***** Begin XML file *****/
2020-03-17 00:35:11 +01:00
XML_WriteStartFile (FileXML,"test",false);
fprintf (FileXML,"%s",Txt_NEW_LINE);
2014-12-01 23:55:08 +01:00
/***** Write rows *****/
2020-03-24 01:50:39 +01:00
for (NumQst = 0;
NumQst < NumQsts;
NumQst++)
2014-12-01 23:55:08 +01:00
{
2020-03-27 18:56:00 +01:00
/* Create test question */
Tst_QstConstructor (&Question);
2020-03-17 00:35:11 +01:00
/* Get question code (row[0]) */
row = mysql_fetch_row (mysql_res);
if ((Question.QstCod = Str_ConvertStrCodToLongCod (row[0])) <= 0)
Err_WrongQuestionExit ();
2020-03-17 00:35:11 +01:00
2020-03-27 18:56:00 +01:00
TsI_ExportQuestion (&Question,FileXML);
/* Destroy test question */
Tst_QstDestructor (&Question);
2020-03-17 00:35:11 +01:00
}
/***** End XML file *****/
XML_WriteEndFile (FileXML,"test");
/***** Close the XML file *****/
fclose (FileXML);
/***** Return to start of query result *****/
mysql_data_seek (mysql_res,0);
/***** Write the link to XML file *****/
HTM_A_Begin ("href=\"%s/%s/%s/test.xml\" class=\"%s\" target=\"_blank\"",
Cfg_URL_FILE_BROWSER_TMP_PUBLIC,
Gbl.FileBrowser.TmpPubDir.L,
Gbl.FileBrowser.TmpPubDir.R,
The_ClassFormOutBoxBold[Gbl.Prefs.Theme]);
Ico_PutIconTextLink ("file.svg",
Txt_XML_file);
HTM_A_End ();
}
/*****************************************************************************/
/****************** Write one question into the XML file *********************/
/*****************************************************************************/
2020-03-27 18:56:00 +01:00
static void TsI_ExportQuestion (struct Tst_Question *Question,FILE *FileXML)
2020-03-17 00:35:11 +01:00
{
extern const char *Tst_StrAnswerTypesXML[Tst_NUM_ANS_TYPES];
extern const char *Txt_NEW_LINE;
2020-04-04 19:20:50 +02:00
if (Tst_GetQstDataFromDB (Question))
2020-03-17 00:35:11 +01:00
{
2020-04-04 02:07:54 +02:00
/***** Write the answer type *****/
2020-03-17 00:35:11 +01:00
fprintf (FileXML,"<question type=\"%s\">%s",
2020-03-27 18:56:00 +01:00
Tst_StrAnswerTypesXML[Question->Answer.Type],Txt_NEW_LINE);
2014-12-01 23:55:08 +01:00
2020-03-17 00:35:11 +01:00
/***** Write the question tags *****/
fprintf (FileXML,"<tags>%s",Txt_NEW_LINE);
2020-03-27 18:56:00 +01:00
TsI_GetAndWriteTagsXML (Question->QstCod,FileXML);
2020-03-17 00:35:11 +01:00
fprintf (FileXML,"</tags>%s",Txt_NEW_LINE);
2014-12-01 23:55:08 +01:00
2020-04-04 02:07:54 +02:00
/***** Write the stem, that is in HTML format *****/
2020-03-17 00:35:11 +01:00
fprintf (FileXML,"<stem>%s</stem>%s",
2020-04-04 19:20:50 +02:00
Question->Stem,Txt_NEW_LINE);
2014-12-01 23:55:08 +01:00
2020-04-04 02:07:54 +02:00
/***** Write the feedback, that is in HTML format *****/
2020-04-04 19:20:50 +02:00
if (Question->Feedback[0])
2020-04-04 02:07:54 +02:00
fprintf (FileXML,"<feedback>%s</feedback>%s",
2020-04-04 19:20:50 +02:00
Question->Feedback,Txt_NEW_LINE);
2014-12-01 23:55:08 +01:00
2020-03-17 00:35:11 +01:00
/***** Write the answers of this question.
2020-04-04 02:07:54 +02:00
Shuffle can be enabled or disabled *****/
2020-03-17 00:35:11 +01:00
fprintf (FileXML,"<answer");
2020-03-27 18:56:00 +01:00
if (Question->Answer.Type == Tst_ANS_UNIQUE_CHOICE ||
Question->Answer.Type == Tst_ANS_MULTIPLE_CHOICE)
2020-03-17 00:35:11 +01:00
fprintf (FileXML," shuffle=\"%s\"",
2020-04-04 02:07:54 +02:00
Question->Answer.Shuffle ? "yes" :
"no");
2020-03-17 00:35:11 +01:00
fprintf (FileXML,">");
2020-03-27 18:56:00 +01:00
TsI_WriteAnswersOfAQstXML (Question,FileXML);
2020-03-17 00:35:11 +01:00
fprintf (FileXML,"</answer>%s",Txt_NEW_LINE);
2014-12-01 23:55:08 +01:00
2020-03-17 00:35:11 +01:00
/***** End question *****/
fprintf (FileXML,"</question>%s%s",
2016-04-04 02:12:06 +02:00
Txt_NEW_LINE,Txt_NEW_LINE);
2014-12-01 23:55:08 +01:00
}
}
/*****************************************************************************/
/************* Get and write tags of a question into the XML file ************/
/*****************************************************************************/
2020-03-17 00:35:11 +01:00
static void TsI_GetAndWriteTagsXML (long QstCod,FILE *FileXML)
2014-12-01 23:55:08 +01:00
{
extern const char *Txt_NEW_LINE;
unsigned NumTags;
unsigned NumTag;
2014-12-01 23:55:08 +01:00
MYSQL_RES *mysql_res;
MYSQL_ROW row;
if ((NumTags = Tst_GetTagsQst (QstCod,&mysql_res))) // Result: TagTxt
2014-12-01 23:55:08 +01:00
/***** Write the tags *****/
for (NumTag = 1;
NumTag <= NumTags;
NumTag++)
2014-12-01 23:55:08 +01:00
{
row = mysql_fetch_row (mysql_res);
2020-03-17 00:35:11 +01:00
fprintf (FileXML,"<tag>%s</tag>%s",
2014-12-01 23:55:08 +01:00
row[0],Txt_NEW_LINE);
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
/**************** Get and write the answers of a test question ***************/
/*****************************************************************************/
2020-04-04 02:07:54 +02:00
static void TsI_WriteAnswersOfAQstXML (const struct Tst_Question *Question,
2020-03-18 18:49:45 +01:00
FILE *FileXML)
2014-12-01 23:55:08 +01:00
{
extern const char *Txt_NEW_LINE;
unsigned NumOpt;
2020-03-18 01:57:08 +01:00
/***** Write answers *****/
2020-03-25 01:36:22 +01:00
switch (Question->Answer.Type)
2014-12-01 23:55:08 +01:00
{
case Tst_ANS_INT:
2020-04-04 02:07:54 +02:00
fprintf (FileXML,"%ld",Question->Answer.Integer);
2014-12-01 23:55:08 +01:00
break;
case Tst_ANS_FLOAT:
2020-03-17 00:35:11 +01:00
fprintf (FileXML,"%s"
"<lower>%.15lg</lower>%s"
"<upper>%.15lg</upper>%s",
2014-12-01 23:55:08 +01:00
Txt_NEW_LINE,
2020-04-04 02:07:54 +02:00
Question->Answer.FloatingPoint[0],Txt_NEW_LINE,
Question->Answer.FloatingPoint[1],Txt_NEW_LINE);
2014-12-01 23:55:08 +01:00
break;
case Tst_ANS_TRUE_FALSE:
2020-03-17 00:35:11 +01:00
fprintf (FileXML,"%s",
2020-04-04 02:07:54 +02:00
Question->Answer.TF == 'T' ? "true" :
"false");
2014-12-01 23:55:08 +01:00
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
case Tst_ANS_TEXT:
2020-03-17 00:35:11 +01:00
fprintf (FileXML,"%s",Txt_NEW_LINE);
2014-12-01 23:55:08 +01:00
for (NumOpt = 0;
2020-03-25 01:36:22 +01:00
NumOpt < Question->Answer.NumOptions;
2014-12-01 23:55:08 +01:00
NumOpt++)
{
/* Begin answer */
2020-03-17 00:35:11 +01:00
fprintf (FileXML,"<option");
2019-05-17 09:19:49 +02:00
2020-04-04 02:07:54 +02:00
/* Write whether the answer is correct or not */
2020-03-25 01:36:22 +01:00
if (Question->Answer.Type != Tst_ANS_TEXT)
2020-03-17 00:35:11 +01:00
fprintf (FileXML," correct=\"%s\"",
2020-04-04 02:07:54 +02:00
Question->Answer.Options[NumOpt].Correct ? "yes" :
"no");
2019-05-17 09:19:49 +02:00
2020-03-17 00:35:11 +01:00
fprintf (FileXML,">%s",Txt_NEW_LINE);
2019-05-17 09:19:49 +02:00
2020-04-04 02:07:54 +02:00
/* Write the answer, that is in HTML */
2020-03-17 00:35:11 +01:00
fprintf (FileXML,"<text>%s</text>%s",
2020-04-04 02:07:54 +02:00
Question->Answer.Options[NumOpt].Text,Txt_NEW_LINE);
2016-04-06 14:41:47 +02:00
2020-04-04 02:07:54 +02:00
/* Write the feedback */
if (Question->Answer.Options[NumOpt].Feedback)
if (Question->Answer.Options[NumOpt].Feedback[0])
2020-03-17 00:35:11 +01:00
fprintf (FileXML,"<feedback>%s</feedback>%s",
2020-04-04 02:07:54 +02:00
Question->Answer.Options[NumOpt].Feedback,Txt_NEW_LINE);
2019-05-17 09:19:49 +02:00
/* End answer */
2020-03-17 00:35:11 +01:00
fprintf (FileXML,"</option>%s",
2014-12-01 23:55:08 +01:00
Txt_NEW_LINE);
}
break;
default:
break;
}
}
/*****************************************************************************/
/************ Get questions from XML and store them in database **************/
/*****************************************************************************/
void TsI_ImportQstsFromXML (void)
{
2015-01-14 00:32:23 +01:00
extern const char *Txt_The_file_is_not_X;
2016-04-01 01:59:27 +02:00
struct Param *Param;
2017-01-15 18:02:52 +01:00
char FileNameXMLSrc[PATH_MAX + 1];
char FileNameXMLTmp[PATH_MAX + 1]; // Full name (including path and .xml) of the destination temporary file
char MIMEType[Brw_MAX_BYTES_MIME_TYPE + 1];
2016-03-28 19:30:37 +02:00
bool WrongType = false;
2014-12-01 23:55:08 +01:00
/***** Creates directory if not exists *****/
2019-03-20 01:36:36 +01:00
Fil_CreateDirIfNotExists (Cfg_PATH_TEST_PRIVATE);
2014-12-01 23:55:08 +01:00
/***** First of all, copy in disk the file received from stdin (really from Gbl.F.Tmp) *****/
2016-04-04 12:13:37 +02:00
Param = Fil_StartReceptionOfFile (Fil_NAME_OF_PARAM_FILENAME_ORG,
FileNameXMLSrc,MIMEType);
2014-12-01 23:55:08 +01:00
2016-04-07 01:16:34 +02:00
/* Check if the file type is XML */
2014-12-01 23:55:08 +01:00
if (strcmp (MIMEType,"text/xml"))
if (strcmp (MIMEType,"application/xml"))
if (strcmp (MIMEType,"application/octet-stream"))
if (strcmp (MIMEType,"application/octetstream"))
if (strcmp (MIMEType,"application/octet"))
2016-03-28 19:30:37 +02:00
WrongType = true;
2014-12-01 23:55:08 +01:00
2016-03-28 19:30:37 +02:00
if (WrongType)
2019-02-16 19:29:27 +01:00
Ale_ShowAlert (Ale_WARNING,Txt_The_file_is_not_X,
"xml");
2016-03-28 19:30:37 +02:00
else
2014-12-01 23:55:08 +01:00
{
/* End the reception of XML in a temporary file */
snprintf (FileNameXMLTmp,sizeof (FileNameXMLTmp),"%s/%s.xml",
2019-03-20 01:36:36 +01:00
Cfg_PATH_TEST_PRIVATE,Gbl.UniqueNameEncrypted);
2016-04-01 01:59:27 +02:00
if (Fil_EndReceptionOfFile (FileNameXMLTmp,Param))
2014-12-01 23:55:08 +01:00
/***** Get questions from XML file and store them in database *****/
TsI_ReadQuestionsFromXMLFileAndStoreInDB (FileNameXMLTmp);
else
2019-02-16 19:29:27 +01:00
Ale_ShowAlert (Ale_WARNING,"Error copying file.");
2014-12-01 23:55:08 +01:00
}
}
/*****************************************************************************/
/********** Get questions from XML file and store them in database ***********/
/*****************************************************************************/
static void TsI_ReadQuestionsFromXMLFileAndStoreInDB (const char *FileNameXML)
{
2020-03-17 00:35:11 +01:00
FILE *FileXML;
2014-12-01 23:55:08 +01:00
char *XMLBuffer;
unsigned long FileSize;
/***** Open file *****/
2020-03-17 00:35:11 +01:00
if ((FileXML = fopen (FileNameXML,"rb")) == NULL)
Err_ShowErrorAndExit ("Can not open XML file.");
2014-12-01 23:55:08 +01:00
/***** Compute file size *****/
2020-03-17 00:35:11 +01:00
fseek (FileXML,0L,SEEK_END);
FileSize = (unsigned long) ftell (FileXML);
fseek (FileXML,0L,SEEK_SET);
2014-12-01 23:55:08 +01:00
/***** Allocate memory for XML buffer *****/
if ((XMLBuffer = malloc (FileSize + 1)) == NULL)
Err_NotEnoughMemoryExit ();
2014-12-01 23:55:08 +01:00
else
{
/***** Read file contents into XML buffer *****/
2020-03-17 00:35:11 +01:00
if (fread (XMLBuffer,sizeof (char),(size_t) FileSize,FileXML))
2014-12-01 23:55:08 +01:00
XMLBuffer[FileSize] = '\0';
else
XMLBuffer[0] = '\0';
/***** Import questions from XML buffer *****/
TsI_ImportQuestionsFromXMLBuffer (XMLBuffer);
free (XMLBuffer);
}
/***** Close file *****/
2020-03-17 00:35:11 +01:00
fclose (FileXML);
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/******************** Import questions from XML buffer ***********************/
/*****************************************************************************/
static void TsI_ImportQuestionsFromXMLBuffer (const char *XMLBuffer)
{
2016-11-13 20:18:49 +01:00
extern const char *Hlp_ASSESSMENT_Tests;
2014-12-01 23:55:08 +01:00
extern const char *Txt_XML_file_content;
extern const char *Txt_Imported_questions;
struct XMLElement *RootElem;
struct XMLElement *TestElem = NULL;
struct XMLElement *QuestionElem;
struct XMLElement *TagsElem;
struct XMLElement *TagElem;
struct XMLElement *StemElem;
struct XMLElement *FeedbackElem;
struct XMLElement *AnswerElem;
struct XMLAttribute *Attribute;
2020-03-17 14:47:58 +01:00
struct Tst_Question Question;
2020-03-25 01:36:22 +01:00
bool QuestionExists;
bool AnswerTypeFound;
2014-12-01 23:55:08 +01:00
/***** Allocate and get XML tree *****/
XML_GetTree (XMLBuffer,&RootElem);
2019-10-26 02:19:42 +02:00
/***** Begin box *****/
2020-03-26 02:54:30 +01:00
Box_BoxBegin (NULL,Txt_Imported_questions,
NULL,NULL,
2017-06-12 15:03:29 +02:00
Hlp_ASSESSMENT_Tests,Box_NOT_CLOSABLE);
2016-11-13 20:18:49 +01:00
2014-12-01 23:55:08 +01:00
/***** Print XML tree *****/
2019-10-24 00:04:40 +02:00
HTM_DIV_Begin ("class=\"TEST_FILE_CONTENT\"");
2019-10-31 17:42:05 +01:00
HTM_TEXTAREA_Begin ("title=\"%s\" cols=\"60\" rows=\"5\""
" spellcheck=\"false\" readonly",
Txt_XML_file_content);
2014-12-01 23:55:08 +01:00
XML_PrintTree (RootElem);
2019-10-31 17:42:05 +01:00
HTM_TEXTAREA_End ();
2019-10-23 20:07:56 +02:00
HTM_DIV_End ();
2014-12-01 23:55:08 +01:00
/***** Get questions from XML tree and print them *****/
/* Go to <test> element */
if (RootElem->FirstChild)
{
TestElem = RootElem->FirstChild;
if (strcmp (TestElem->TagName,"test")) // <test> must be at level 1
TestElem = NULL;
}
2016-11-13 20:18:49 +01:00
if (TestElem)
2014-12-01 23:55:08 +01:00
{
2016-11-13 20:18:49 +01:00
/* Current element is <test> */
/***** Write heading of list of imported questions *****/
2020-05-17 22:16:39 +02:00
HTM_TABLE_BeginWideMarginPadding (5);
2016-11-13 20:18:49 +01:00
TsI_WriteHeadingListImportedQst ();
/***** For each question... *****/
for (QuestionElem = TestElem->FirstChild;
QuestionElem != NULL;
QuestionElem = QuestionElem->NextBrother)
{
if (!strcmp (QuestionElem->TagName,"question"))
{
/***** Create test question *****/
2020-03-19 20:57:54 +01:00
Tst_QstConstructor (&Question);
2016-11-13 20:18:49 +01:00
2020-03-18 18:49:45 +01:00
/* Get answer type (in mandatory attribute "type") */
2016-11-13 20:18:49 +01:00
AnswerTypeFound = false;
for (Attribute = QuestionElem->FirstAttribute;
Attribute != NULL;
Attribute = Attribute->Next)
if (!strcmp (Attribute->AttributeName,"type"))
{
2020-03-19 20:57:54 +01:00
Question.Answer.Type = TsI_ConvertFromStrAnsTypXMLToAnsTyp (Attribute->Content);
2016-11-13 20:18:49 +01:00
AnswerTypeFound = true;
break; // Only first attribute "type"
}
2020-03-18 18:49:45 +01:00
if (AnswerTypeFound)
{
/* Get tags */
2020-03-25 01:36:22 +01:00
for (TagsElem = QuestionElem->FirstChild, Question.Tags.Num = 0;
2020-03-18 18:49:45 +01:00
TagsElem != NULL;
TagsElem = TagsElem->NextBrother)
if (!strcmp (TagsElem->TagName,"tags"))
{
for (TagElem = TagsElem->FirstChild;
2020-05-17 17:11:04 +02:00
TagElem != NULL && Question.Tags.Num < Tag_MAX_TAGS_PER_QUESTION;
2020-03-18 18:49:45 +01:00
TagElem = TagElem->NextBrother)
if (!strcmp (TagElem->TagName,"tag"))
2016-11-13 20:18:49 +01:00
{
2020-03-18 18:49:45 +01:00
if (TagElem->Content)
{
2020-03-25 01:36:22 +01:00
Str_Copy (Question.Tags.Txt[Question.Tags.Num],
2020-03-18 18:49:45 +01:00
TagElem->Content,
sizeof (Question.Tags.Txt[Question.Tags.Num]) - 1);
2020-03-25 01:36:22 +01:00
Question.Tags.Num++;
2020-03-18 18:49:45 +01:00
}
2016-11-13 20:18:49 +01:00
}
2020-03-18 18:49:45 +01:00
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 (Question.Stem,StemElem->Content,Cns_MAX_BYTES_TEXT);
2020-03-18 18:49:45 +01:00
Str_ChangeFormat (Str_FROM_TEXT,Str_TO_HTML,
2020-04-04 19:20:50 +02:00
Question.Stem,Cns_MAX_BYTES_TEXT,true);
2016-11-13 20:18:49 +01:00
}
2020-03-18 18:49:45 +01:00
break; // Only first element "stem"
}
2016-11-13 20:18:49 +01:00
2020-03-18 18:49:45 +01:00
/* Get feedback (optional) */
for (FeedbackElem = QuestionElem->FirstChild;
FeedbackElem != NULL;
FeedbackElem = FeedbackElem->NextBrother)
if (!strcmp (FeedbackElem->TagName,"feedback"))
2016-11-13 20:18:49 +01:00
{
2020-03-18 18:49:45 +01:00
if (FeedbackElem->Content)
{
/* Convert feedback from text to HTML (in database feedback is stored in HTML) */
Str_Copy (Question.Feedback,FeedbackElem->Content,Cns_MAX_BYTES_TEXT);
2020-03-18 18:49:45 +01:00
Str_ChangeFormat (Str_FROM_TEXT,Str_TO_HTML,
2020-04-04 19:20:50 +02:00
Question.Feedback,Cns_MAX_BYTES_TEXT,true);
2020-03-18 18:49:45 +01:00
}
break; // Only first element "feedback"
2016-11-13 20:18:49 +01:00
}
2020-03-18 18:49:45 +01:00
/* Get shuffle. By default, shuffle is false. */
2020-04-03 19:13:00 +02:00
Question.Answer.Shuffle = false;
2020-03-18 18:49:45 +01:00
for (AnswerElem = QuestionElem->FirstChild;
AnswerElem != NULL;
AnswerElem = AnswerElem->NextBrother)
if (!strcmp (AnswerElem->TagName,"answer"))
2016-11-13 20:18:49 +01:00
{
2020-03-19 20:57:54 +01:00
if (Question.Answer.Type == Tst_ANS_UNIQUE_CHOICE ||
Question.Answer.Type == Tst_ANS_MULTIPLE_CHOICE)
2020-03-18 18:49:45 +01:00
/* Get whether shuffle answers (in attribute "shuffle") */
for (Attribute = AnswerElem->FirstAttribute;
Attribute != NULL;
Attribute = Attribute->Next)
if (!strcmp (Attribute->AttributeName,"shuffle"))
{
2020-04-03 19:13:00 +02:00
Question.Answer.Shuffle = XML_GetAttributteYesNoFromXMLTree (Attribute);
2020-03-18 18:49:45 +01:00
break; // Only first attribute "shuffle"
}
break; // Only first element "answer"
2016-11-13 20:18:49 +01:00
}
2020-03-18 18:49:45 +01:00
/* Get answer (mandatory) */
2020-03-19 20:57:54 +01:00
TsI_GetAnswerFromXML (AnswerElem,&Question);
2020-03-18 18:49:45 +01:00
/* Make sure that tags, text and answer are not empty */
2020-03-25 01:36:22 +01:00
if (Tst_CheckIfQstFormatIsCorrectAndCountNumOptions (&Question))
2016-11-13 20:18:49 +01:00
{
2020-03-18 18:49:45 +01:00
/* Check if question already exists in database */
2020-03-19 20:57:54 +01:00
QuestionExists = Tst_CheckIfQuestionExistsInDB (&Question);
2020-03-18 18:49:45 +01:00
/* Write row with this imported question */
TsI_WriteRowImportedQst (StemElem,FeedbackElem,
2020-03-25 01:36:22 +01:00
&Question,QuestionExists);
2020-03-18 18:49:45 +01:00
/***** If a new question ==> insert question, tags and answer in the database *****/
if (!QuestionExists)
2020-03-25 01:36:22 +01:00
{
Question.QstCod = -1L;
Tst_InsertOrUpdateQstTagsAnsIntoDB (&Question);
if (Question.QstCod <= 0)
Err_ShowErrorAndExit ("Can not create question.");
2020-03-25 01:36:22 +01:00
}
2016-11-13 20:18:49 +01:00
}
}
2020-03-18 18:49:45 +01:00
else // Answer type not found
Err_WrongAnswerExit ();
2016-11-13 20:18:49 +01:00
/***** Destroy test question *****/
2020-03-19 20:57:54 +01:00
Tst_QstDestructor (&Question);
2016-11-13 20:18:49 +01:00
}
}
2019-10-23 19:05:05 +02:00
HTM_TABLE_End ();
2014-12-01 23:55:08 +01:00
}
2016-11-13 20:18:49 +01:00
else // TestElem not found
2019-02-16 19:29:27 +01:00
Ale_ShowAlert (Ale_ERROR,"Root element &lt;test&gt; not found.");
2014-12-01 23:55:08 +01:00
2016-11-13 20:18:49 +01:00
/***** End table *****/
2019-10-25 22:48:34 +02:00
Box_BoxEnd ();
2014-12-01 23:55:08 +01:00
/***** 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)
2019-12-15 20:02:34 +01:00
for (AnsType = (Tst_AnswerType_t) 0;
AnsType <= (Tst_AnswerType_t) (Tst_NUM_ANS_TYPES - 1);
2014-12-01 23:55:08 +01:00
AnsType++)
// comparison must be case insensitive, because users can edit XML
if (!strcasecmp (StrAnsTypeXML,Tst_StrAnswerTypesXML[AnsType]))
return AnsType;
Err_WrongAnswerExit ();
2014-12-01 23:55:08 +01:00
return (Tst_AnswerType_t) 0; // Not reached
}
/*****************************************************************************/
/**************** Get answer inside an XML question elements *****************/
/*****************************************************************************/
// Answer is mandatory
2020-03-18 01:57:08 +01:00
static void TsI_GetAnswerFromXML (struct XMLElement *AnswerElem,
2020-03-19 20:57:54 +01:00
struct Tst_Question *Question)
2014-12-01 23:55:08 +01:00
{
struct XMLElement *OptionElem;
struct XMLElement *TextElem;
struct XMLElement *FeedbackElem;
struct XMLElement *LowerUpperElem;
struct XMLAttribute *Attribute;
unsigned NumOpt;
2020-03-19 20:57:54 +01:00
switch (Question->Answer.Type)
2014-12-01 23:55:08 +01:00
{
case Tst_ANS_INT:
2020-03-19 20:57:54 +01:00
if (!Tst_AllocateTextChoiceAnswer (Question,0))
2019-03-09 20:12:44 +01:00
/* Abort on error */
Ale_ShowAlertsAndExit ();
2014-12-01 23:55:08 +01:00
if (AnswerElem->Content)
2020-03-19 20:57:54 +01:00
Str_Copy (Question->Answer.Options[0].Text,AnswerElem->Content,
2017-01-15 18:02:52 +01:00
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
2014-12-01 23:55:08 +01:00
break;
case Tst_ANS_FLOAT:
2020-03-19 20:57:54 +01:00
if (!Tst_AllocateTextChoiceAnswer (Question,0))
2019-03-09 20:12:44 +01:00
/* Abort on error */
Ale_ShowAlertsAndExit ();
2020-03-19 20:57:54 +01:00
if (!Tst_AllocateTextChoiceAnswer (Question,1))
2019-03-09 20:12:44 +01:00
/* Abort on error */
Ale_ShowAlertsAndExit ();
2014-12-01 23:55:08 +01:00
for (LowerUpperElem = AnswerElem->FirstChild;
LowerUpperElem != NULL;
LowerUpperElem = LowerUpperElem->NextBrother)
if (!strcmp (LowerUpperElem->TagName,"lower"))
{
if (LowerUpperElem->Content)
2020-03-19 20:57:54 +01:00
Str_Copy (Question->Answer.Options[0].Text,
2017-01-17 03:10:43 +01:00
LowerUpperElem->Content,
2017-01-15 18:02:52 +01:00
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
2014-12-01 23:55:08 +01:00
break; // Only first element "lower"
}
for (LowerUpperElem = AnswerElem->FirstChild;
LowerUpperElem != NULL;
LowerUpperElem = LowerUpperElem->NextBrother)
if (!strcmp (LowerUpperElem->TagName,"upper"))
{
if (LowerUpperElem->Content)
2020-03-19 20:57:54 +01:00
Str_Copy (Question->Answer.Options[1].Text,
2017-01-17 03:10:43 +01:00
LowerUpperElem->Content,
2017-01-15 18:02:52 +01:00
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
2014-12-01 23:55:08 +01:00
break; // Only first element "upper"
}
break;
case Tst_ANS_TRUE_FALSE:
// Comparisons must be case insensitive, because users can edit XML
if (!AnswerElem->Content)
2020-03-19 20:57:54 +01:00
Question->Answer.TF = ' ';
2014-12-01 23:55:08 +01:00
else if (!strcasecmp (AnswerElem->Content,"true") ||
!strcasecmp (AnswerElem->Content,"T") ||
!strcasecmp (AnswerElem->Content,"yes") ||
!strcasecmp (AnswerElem->Content,"Y"))
2020-03-19 20:57:54 +01:00
Question->Answer.TF = 'T';
2014-12-01 23:55:08 +01:00
else if (!strcasecmp (AnswerElem->Content,"false") ||
!strcasecmp (AnswerElem->Content,"F") ||
!strcasecmp (AnswerElem->Content,"no") ||
!strcasecmp (AnswerElem->Content,"N"))
2020-03-19 20:57:54 +01:00
Question->Answer.TF = 'F';
2014-12-01 23:55:08 +01:00
else
2020-03-19 20:57:54 +01:00
Question->Answer.TF = ' ';
2014-12-01 23:55:08 +01:00
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"))
{
2020-03-19 20:57:54 +01:00
if (!Tst_AllocateTextChoiceAnswer (Question,NumOpt))
2019-03-09 20:12:44 +01:00
/* Abort on error */
Ale_ShowAlertsAndExit ();
2014-12-01 23:55:08 +01:00
for (TextElem = OptionElem->FirstChild;
TextElem != NULL;
TextElem = TextElem->NextBrother)
if (!strcmp (TextElem->TagName,"text"))
{
if (TextElem->Content)
{
2020-03-19 20:57:54 +01:00
Str_Copy (Question->Answer.Options[NumOpt].Text,
2017-01-15 18:02:52 +01:00
TextElem->Content,
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
2014-12-01 23:55:08 +01:00
/* Convert answer from text to HTML (in database answer text is stored in HTML) */
Str_ChangeFormat (Str_FROM_TEXT,Str_TO_HTML,
2020-03-19 20:57:54 +01:00
Question->Answer.Options[NumOpt].Text,
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,true);
2014-12-01 23:55:08 +01:00
}
break; // Only first element "text"
}
for (FeedbackElem = OptionElem->FirstChild;
FeedbackElem != NULL;
FeedbackElem = FeedbackElem->NextBrother)
if (!strcmp (FeedbackElem->TagName,"feedback"))
{
if (FeedbackElem->Content)
{
2020-03-19 20:57:54 +01:00
Str_Copy (Question->Answer.Options[NumOpt].Feedback,
2017-01-15 18:02:52 +01:00
FeedbackElem->Content,
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
2014-12-01 23:55:08 +01:00
/* Convert feedback from text to HTML (in database answer feedback is stored in HTML) */
Str_ChangeFormat (Str_FROM_TEXT,Str_TO_HTML,
2020-03-19 20:57:54 +01:00
Question->Answer.Options[NumOpt].Feedback,
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK,true);
2014-12-01 23:55:08 +01:00
}
break; // Only first element "feedback"
}
2020-03-19 20:57:54 +01:00
if (Question->Answer.Type == Tst_ANS_TEXT)
Question->Answer.Options[NumOpt].Correct = true;
2014-12-01 23:55:08 +01:00
else
/* Check if option is correct or wrong */
for (Attribute = OptionElem->FirstAttribute;
Attribute != NULL;
Attribute = Attribute->Next)
if (!strcmp (Attribute->AttributeName,"correct"))
{
2020-03-19 20:57:54 +01:00
Question->Answer.Options[NumOpt].Correct = XML_GetAttributteYesNoFromXMLTree (Attribute);
2014-12-01 23:55:08 +01:00
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 *****/
2019-10-23 19:05:05 +02:00
HTM_TR_Begin (NULL);
2019-10-12 00:07:52 +02:00
2019-10-23 19:05:05 +02:00
HTM_TH_Empty (1);
2019-10-12 00:07:52 +02:00
2019-10-23 19:05:05 +02:00
HTM_TH (1,1,"CT",Txt_No_INDEX);
HTM_TH (1,1,"CT",Txt_Tags);
HTM_TH (1,1,"CT",Txt_Type);
HTM_TH (1,1,"CT",Txt_Shuffle);
HTM_TH (1,1,"LT",Txt_Question);
2019-10-12 00:07:52 +02:00
2019-10-23 19:05:05 +02:00
HTM_TR_End ();
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/**************** Write a row with one imported test question ****************/
/*****************************************************************************/
static void TsI_WriteRowImportedQst (struct XMLElement *StemElem,
struct XMLElement *FeedbackElem,
2020-03-17 14:47:58 +01:00
const struct Tst_Question *Question,
2014-12-01 23:55:08 +01:00
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];
2018-12-09 13:11:20 +01:00
extern const char *Txt_TST_Answer_given_by_the_teachers;
2014-12-01 23:55:08 +01:00
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";
2020-06-17 20:20:16 +02:00
const char *ClassStem = QuestionExists ? "TEST_TXT_LIGHT" :
"TEST_TXT";
2014-12-01 23:55:08 +01:00
Gbl.RowEvenOdd = NumQst % 2;
NumQst++;
2019-10-23 19:05:05 +02:00
HTM_TR_Begin (NULL);
2019-10-10 10:41:00 +02:00
/***** Put icon to indicate that a question does not exist in database *****/
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"BT%u CT\"",Gbl.RowEvenOdd);
2019-10-29 21:41:54 +01:00
Ico_PutIcon (QuestionExists ? "tr16x16.gif" :
"check-circle.svg",
QuestionExists ? Txt_Existing_question :
Txt_New_question,
"CONTEXT_ICO_16x16");
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
/***** Write number of question *****/
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"%s CT COLOR%u\"",ClassData,Gbl.RowEvenOdd);
2014-12-01 23:55:08 +01:00
if (!QuestionExists)
2019-11-11 10:59:24 +01:00
HTM_TxtF ("%u&nbsp;",++NumNonExistingQst);
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
/***** Write the question tags *****/
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"LT COLOR%u\"",Gbl.RowEvenOdd);
2020-03-25 01:36:22 +01:00
if (Question->Tags.Num)
2014-12-01 23:55:08 +01:00
{
/***** Write the tags *****/
2019-10-23 19:05:05 +02:00
HTM_TABLE_Begin (NULL);
2014-12-01 23:55:08 +01:00
for (NumTag = 0;
2020-03-25 01:36:22 +01:00
NumTag < Question->Tags.Num;
2014-12-01 23:55:08 +01:00
NumTag++)
2019-10-04 14:42:59 +02:00
{
2019-10-23 19:05:05 +02:00
HTM_TR_Begin (NULL);
2019-10-07 21:15:14 +02:00
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"%s LT\"",ClassData);
2019-11-11 10:59:24 +01:00
HTM_TxtF ("&nbsp;%s&nbsp;","&#8226;");
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2019-10-07 21:15:14 +02:00
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"%s LT\"",ClassData);
2020-03-25 01:36:22 +01:00
HTM_Txt (Question->Tags.Txt[NumTag]);
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2019-10-07 21:15:14 +02:00
2019-10-23 19:05:05 +02:00
HTM_TR_End ();
2019-10-04 14:42:59 +02:00
}
2019-10-23 19:05:05 +02:00
HTM_TABLE_End ();
2014-12-01 23:55:08 +01:00
}
else // no tags for this question
2019-11-07 10:24:00 +01:00
{
HTM_SPAN_Begin ("class=\"%s\"",ClassData);
2019-11-11 00:15:44 +01:00
HTM_TxtF ("&nbsp;(%s)&nbsp;",Txt_no_tags);
2019-11-07 10:24:00 +01:00
HTM_SPAN_End ();
}
2014-12-01 23:55:08 +01:00
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
/***** Write the question type *****/
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"%s CT COLOR%u\"",ClassData,Gbl.RowEvenOdd);
2020-03-19 20:57:54 +01:00
HTM_TxtF ("%s&nbsp;",Txt_TST_STR_ANSWER_TYPES[Question->Answer.Type]);
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
/***** Write if shuffle is enabled *****/
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"CT COLOR%u\"",Gbl.RowEvenOdd);
2020-03-19 20:57:54 +01:00
if (Question->Answer.Type == Tst_ANS_UNIQUE_CHOICE ||
Question->Answer.Type == Tst_ANS_MULTIPLE_CHOICE)
2014-12-01 23:55:08 +01:00
/* Put an icon that indicates whether shuffle is enabled or not */
2020-04-03 19:13:00 +02:00
if (Question->Answer.Shuffle)
2019-10-29 21:41:54 +01:00
Ico_PutIcon ("check.svg",Txt_TST_Answer_given_by_the_teachers,
QuestionExists ? "ICO_HIDDEN ICO16x16" :
"ICO16x16");
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
/***** Write the stem and the answers *****/
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"LT COLOR%u\"",Gbl.RowEvenOdd);
2020-02-17 15:30:55 +01:00
Tst_WriteQstStem (Stem,ClassStem,
true); // Visible
2020-06-17 20:20:16 +02:00
Tst_WriteQstFeedback (Feedback,"TEST_TXT_LIGHT");
2020-03-19 20:57:54 +01:00
switch (Question->Answer.Type)
2014-12-01 23:55:08 +01:00
{
case Tst_ANS_INT:
2019-11-07 10:24:00 +01:00
HTM_SPAN_Begin ("class=\"%s\"",ClassStem);
2020-03-19 20:57:54 +01:00
HTM_TxtF ("(%ld)",Question->Answer.Integer);
2019-11-07 10:24:00 +01:00
HTM_SPAN_End ();
2014-12-01 23:55:08 +01:00
break;
case Tst_ANS_FLOAT:
2019-11-07 10:24:00 +01:00
HTM_SPAN_Begin ("class=\"%s\"",ClassStem);
2020-01-11 15:22:02 +01:00
HTM_Txt ("([");
2020-03-19 20:57:54 +01:00
HTM_Double (Question->Answer.FloatingPoint[0]);
2020-01-11 15:22:02 +01:00
HTM_Txt ("; ");
2020-03-19 20:57:54 +01:00
HTM_Double (Question->Answer.FloatingPoint[1]);
2020-01-11 15:22:02 +01:00
HTM_Txt ("])");
2019-11-07 10:24:00 +01:00
HTM_SPAN_End ();
2014-12-01 23:55:08 +01:00
break;
case Tst_ANS_TRUE_FALSE:
2019-11-07 10:24:00 +01:00
HTM_SPAN_Begin ("class=\"%s\"",ClassStem);
2019-11-10 13:51:07 +01:00
HTM_Txt ("(");
2020-03-19 20:57:54 +01:00
Tst_WriteAnsTF (Question->Answer.TF);
2019-11-10 13:51:07 +01:00
HTM_Txt (")");
2019-11-07 10:24:00 +01:00
HTM_SPAN_End ();
2014-12-01 23:55:08 +01:00
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
case Tst_ANS_TEXT:
2019-10-23 19:05:05 +02:00
HTM_TABLE_Begin (NULL);
2014-12-01 23:55:08 +01:00
for (NumOpt = 0;
2020-03-19 20:57:54 +01:00
NumOpt < Question->Answer.NumOptions;
2014-12-01 23:55:08 +01:00
NumOpt++)
{
/* Convert the answer, that is in HTML, to rigorous HTML */
2020-03-19 20:57:54 +01:00
AnswerTextLength = strlen (Question->Answer.Options[NumOpt].Text) *
2017-03-08 03:48:23 +01:00
Str_MAX_BYTES_PER_CHAR;
if ((AnswerText = malloc (AnswerTextLength + 1)) == NULL)
Err_NotEnoughMemoryExit ();
2020-03-19 20:57:54 +01:00
Str_Copy (AnswerText,Question->Answer.Options[NumOpt].Text,
2017-01-15 18:02:52 +01:00
AnswerTextLength);
2014-12-01 23:55:08 +01:00
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;
2020-03-19 20:57:54 +01:00
if (Question->Answer.Options[NumOpt].Feedback)
if (Question->Answer.Options[NumOpt].Feedback[0])
2014-12-01 23:55:08 +01:00
{
2020-03-19 20:57:54 +01:00
AnswerFeedbackLength = strlen (Question->Answer.Options[NumOpt].Feedback) *
2017-03-08 03:48:23 +01:00
Str_MAX_BYTES_PER_CHAR;
if ((AnswerFeedback = malloc (AnswerFeedbackLength + 1)) == NULL)
Err_NotEnoughMemoryExit ();
2017-01-15 18:02:52 +01:00
Str_Copy (AnswerFeedback,
2020-03-19 20:57:54 +01:00
Question->Answer.Options[NumOpt].Feedback,
2017-01-15 18:02:52 +01:00
AnswerFeedbackLength);
2014-12-01 23:55:08 +01:00
Str_ChangeFormat (Str_FROM_HTML,Str_TO_RIGOROUS_HTML,
AnswerFeedback,AnswerFeedbackLength,false);
}
2019-10-23 19:05:05 +02:00
HTM_TR_Begin (NULL);
2019-10-10 10:41:00 +02:00
/* Put an icon that indicates whether the answer is correct or wrong */
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"BT%u\"",Gbl.RowEvenOdd);
2020-03-19 20:57:54 +01:00
if (Question->Answer.Options[NumOpt].Correct)
2019-10-29 21:41:54 +01:00
Ico_PutIcon ("check.svg",Txt_TST_Answer_given_by_the_teachers,
QuestionExists ? "ICO_HIDDEN CONTEXT_ICO_16x16" :
"CONTEXT_ICO_16x16");
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
/* Write the number of option */
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"%s LT\"",ClassData);
2019-11-11 00:15:44 +01:00
HTM_TxtF ("%c)&nbsp;",'a' + (char) NumOpt);
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2014-12-01 23:55:08 +01:00
/* Write the text and the feedback of the answer */
2019-10-23 19:05:05 +02:00
HTM_TD_Begin ("class=\"LT\"");
2019-10-24 00:04:40 +02:00
HTM_DIV_Begin ("class=\"%s\"",ClassStem);
2019-11-10 12:36:37 +01:00
HTM_Txt (AnswerText);
2019-10-23 20:07:56 +02:00
HTM_DIV_End ();
2019-10-24 00:04:40 +02:00
2014-12-01 23:55:08 +01:00
if (AnswerFeedbackLength)
2019-10-23 20:07:56 +02:00
{
2020-06-17 20:20:16 +02:00
HTM_DIV_Begin ("class=\"TEST_TXT_LIGHT\"");
2019-11-10 12:36:37 +01:00
HTM_Txt (AnswerFeedback);
2019-10-23 20:07:56 +02:00
HTM_DIV_End ();
}
2019-10-24 00:04:40 +02:00
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
2019-10-10 10:41:00 +02:00
2019-10-23 19:05:05 +02:00
HTM_TR_End ();
2014-12-01 23:55:08 +01:00
/* Free memory allocated for the answer and the feedback */
2019-11-06 19:45:20 +01:00
free (AnswerText);
2014-12-01 23:55:08 +01:00
if (AnswerFeedbackLength)
2019-11-06 19:45:20 +01:00
free (AnswerFeedback);
2014-12-01 23:55:08 +01:00
}
2019-10-23 19:05:05 +02:00
HTM_TABLE_End ();
2014-12-01 23:55:08 +01:00
break;
default:
break;
}
2019-10-23 19:05:05 +02:00
HTM_TD_End ();
HTM_TR_End ();
2014-12-01 23:55:08 +01:00
}