swad-core/swad_test_import.c

1184 lines
42 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.
2019-01-07 21:52:19 +01:00
Copyright (C) 1999-2019 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"
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 ******************************/
/*****************************************************************************/
/*****************************************************************************/
/******************************* Internal types ******************************/
/*****************************************************************************/
/*****************************************************************************/
/************** External global variables from others modules ****************/
/*****************************************************************************/
extern struct Globals Gbl;
/*****************************************************************************/
/************************* Internal global variables *************************/
/*****************************************************************************/
/*****************************************************************************/
/***************************** Internal prototypes ***************************/
/*****************************************************************************/
2015-12-13 21:30:28 +01:00
static void TsI_PutParamsExportQsts (void);
2014-12-01 23:55:08 +01:00
static void TsI_GetAndWriteTagsXML (long QstCod);
static void TsI_WriteAnswersOfAQstXML (long QstCod);
static void TsI_ReadQuestionsFromXMLFileAndStoreInDB (const char *FileNameXML);
static void TsI_ImportQuestionsFromXMLBuffer (const char *XMLBuffer);
static Tst_AnswerType_t TsI_ConvertFromStrAnsTypXMLToAnsTyp (const char *StrAnsTypeXML);
static bool TsI_CheckIfQuestionExistsInDB (void);
static void TsI_GetAnswerFromXML (struct XMLElement *AnswerElem);
static void TsI_WriteHeadingListImportedQst (void);
static void TsI_WriteRowImportedQst (struct XMLElement *StemElem,
struct XMLElement *FeedbackElem,
bool QuestionExists);
/*****************************************************************************/
/**************** Put a link (form) to export test questions *****************/
/*****************************************************************************/
void TsI_PutFormToExportQuestions (void)
{
extern const char *Txt_Export_questions;
2015-12-13 21:30:28 +01:00
/***** Put a link to create a file with questions *****/
2019-01-12 03:00:59 +01:00
Lay_PutContextualLinkIconText (ActLstTstQst,NULL,TsI_PutParamsExportQsts,
"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
2015-12-13 21:30:28 +01:00
static void TsI_PutParamsExportQsts (void)
{
2019-02-11 22:15:18 +01:00
Dat_WriteParamsIniEndDates ();
2014-12-01 23:55:08 +01:00
Tst_WriteParamEditQst ();
Par_PutHiddenParamChar ("OnlyThisQst",'N');
2017-01-29 12:42:19 +01:00
Par_PutHiddenParamUnsigned ("Order",(unsigned) Gbl.Test.SelectedOrder);
2014-12-01 23:55:08 +01:00
Par_PutHiddenParamChar ("CreateXML",'Y');
}
/*****************************************************************************/
/*************** Put a link (form) to import test questions ******************/
/*****************************************************************************/
void TsI_PutFormToImportQuestions (void)
{
extern const char *Txt_Import_questions;
/***** Put a link to create a file with questions *****/
2019-01-12 03:00:59 +01:00
Lay_PutContextualLinkIconText (ActReqImpTstQst,NULL,NULL,
"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;
2017-06-12 14:16:33 +02:00
/***** Start box *****/
2019-10-25 22:48:34 +02:00
Box_BoxBegin (NULL,Txt_Import_questions,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 *****/
2018-11-09 20:47:39 +01:00
Frm_StartForm (ActImpTstQst);
2016-03-21 13:08:18 +01:00
fprintf (Gbl.F.Out,"<label class=\"%s\">"
2016-12-20 02:18:50 +01:00
"%s:&nbsp;"
2016-04-11 15:32:59 +02:00
"<input type=\"file\" name=\"%s\" accept=\".xml\""
2016-12-20 02:18:50 +01:00
" onchange=\"document.getElementById('%s').submit();\" />"
"</label>",
2019-02-22 21:47:50 +01:00
The_ClassFormInBox[Gbl.Prefs.Theme],
2014-12-01 23:55:08 +01:00
Txt_XML_file,
2016-03-29 10:24:14 +02:00
Fil_NAME_OF_PARAM_FILENAME_ORG,
Gbl.Form.Id);
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 ***/
/*****************************************************************************/
void TsI_CreateXML (unsigned long NumRows,MYSQL_RES *mysql_res)
{
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 *Tst_StrAnswerTypesXML[Tst_NUM_ANS_TYPES];
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];
2014-12-01 23:55:08 +01:00
unsigned long NumRow;
MYSQL_ROW row;
long QstCod;
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 *****/
2018-10-18 02:02:32 +02:00
snprintf (PathPubFile,sizeof (PathPubFile),
2019-03-20 14:36:26 +01:00
"%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);
2014-12-01 23:55:08 +01:00
if ((Gbl.Test.XML.FileXML = fopen (PathPubFile,"wb")) == NULL)
Lay_ShowErrorAndExit ("Can not open target file.");
/***** Start XML file *****/
XML_WriteStartFile (Gbl.Test.XML.FileXML,"test",false);
fprintf (Gbl.Test.XML.FileXML,"%s",Txt_NEW_LINE);
/***** Write rows *****/
for (NumRow = 0;
NumRow < NumRows;
NumRow++)
{
row = mysql_fetch_row (mysql_res);
2016-04-04 02:12:06 +02:00
/*
2019-03-18 15:42:22 +01:00
row[0] QstCod
row[1] UNIX_TIMESTAMP(EditTime)
row[2] AnsType
row[3] Shuffle
row[4] Stem
row[5] Feedback
row[6] MedCod
row[7] NumHits
row[8] NumHitsNotBlank
row[9] Score
2016-04-04 02:12:06 +02:00
*/
2014-12-01 23:55:08 +01:00
/* row[0] holds the code of the question */
if ((QstCod = Str_ConvertStrCodToLongCod (row[0])) < 0)
Lay_ShowErrorAndExit ("Wrong code of question.");
/* Write the question type (row[2]) */
Gbl.Test.AnswerType = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[2]);
fprintf (Gbl.Test.XML.FileXML,"<question type=\"%s\">%s",
Tst_StrAnswerTypesXML[Gbl.Test.AnswerType],Txt_NEW_LINE);
/* Write the question tags */
fprintf (Gbl.Test.XML.FileXML,"<tags>%s",Txt_NEW_LINE);
TsI_GetAndWriteTagsXML (QstCod);
fprintf (Gbl.Test.XML.FileXML,"</tags>%s",Txt_NEW_LINE);
/* Write the stem (row[4]), that is in HTML format */
fprintf (Gbl.Test.XML.FileXML,"<stem>%s</stem>%s",
row[4],Txt_NEW_LINE);
2016-04-07 01:16:34 +02:00
/* Write the feedback (row[5]), that is in HTML format */
if (row[5])
if (row[5][0])
2014-12-01 23:55:08 +01:00
fprintf (Gbl.Test.XML.FileXML,"<feedback>%s</feedback>%s",
2016-04-07 01:16:34 +02:00
row[5],Txt_NEW_LINE);
2014-12-01 23:55:08 +01:00
/* Write the answers of this question.
Shuffle can be enabled or disabled (row[3]) */
fprintf (Gbl.Test.XML.FileXML,"<answer");
if (Gbl.Test.AnswerType == Tst_ANS_UNIQUE_CHOICE ||
Gbl.Test.AnswerType == Tst_ANS_MULTIPLE_CHOICE)
fprintf (Gbl.Test.XML.FileXML," shuffle=\"%s\"",
2016-09-07 18:48:10 +02:00
(row[3][0] == 'Y') ? "yes" :
"no");
2014-12-01 23:55:08 +01:00
fprintf (Gbl.Test.XML.FileXML,">");
TsI_WriteAnswersOfAQstXML (QstCod);
fprintf (Gbl.Test.XML.FileXML,"</answer>%s",Txt_NEW_LINE);
/* End question */
2016-04-04 02:12:06 +02:00
fprintf (Gbl.Test.XML.FileXML,"</question>%s%s",
Txt_NEW_LINE,Txt_NEW_LINE);
2014-12-01 23:55:08 +01:00
}
/***** End XML file *****/
XML_WriteEndFile (Gbl.Test.XML.FileXML,"test");
/***** Close the XML file *****/
fclose (Gbl.Test.XML.FileXML);
/***** Return to start of query result *****/
2016-12-11 21:02:22 +01:00
mysql_data_seek (mysql_res,0);
2014-12-01 23:55:08 +01:00
/***** Write the link to XML file *****/
2019-03-20 14:36:26 +01:00
fprintf (Gbl.F.Out,"<a href=\"%s/%s/%s/test.xml\""
2019-03-20 01:36:36 +01:00
" class=\"%s\" target=\"_blank\">",
Cfg_URL_FILE_BROWSER_TMP_PUBLIC,
2019-03-20 14:36:26 +01:00
Gbl.FileBrowser.TmpPubDir.L,
Gbl.FileBrowser.TmpPubDir.R,
2019-02-22 21:47:50 +01:00
The_ClassFormOutBoxBold[Gbl.Prefs.Theme]);
2019-01-12 19:46:33 +01:00
Ico_PutIconTextLink ("file.svg",
Txt_XML_file);
2015-12-13 18:32:37 +01:00
fprintf (Gbl.F.Out,"</a>");
2014-12-01 23:55:08 +01:00
}
/*****************************************************************************/
/************* Get and write tags of a question into the XML file ************/
/*****************************************************************************/
static void TsI_GetAndWriteTagsXML (long QstCod)
{
extern const char *Txt_NEW_LINE;
unsigned long NumRow;
unsigned long NumRows;
MYSQL_RES *mysql_res;
MYSQL_ROW row;
if ((NumRows = Tst_GetTagsQst (QstCod,&mysql_res))) // Result: TagTxt
/***** Write the tags *****/
for (NumRow = 1;
NumRow <= NumRows;
NumRow++)
{
row = mysql_fetch_row (mysql_res);
fprintf (Gbl.Test.XML.FileXML,"<tag>%s</tag>%s",
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 ***************/
/*****************************************************************************/
static void TsI_WriteAnswersOfAQstXML (long QstCod)
{
extern const char *Txt_NEW_LINE;
unsigned NumOpt;
unsigned i;
MYSQL_RES *mysql_res;
MYSQL_ROW row;
double FloatNum[2];
Gbl.Test.Answer.NumOptions = Tst_GetAnswersQst (QstCod,&mysql_res,false); // Result: AnsInd,Answer,Correct
2016-04-06 14:41:47 +02:00
/*
2019-03-02 21:49:11 +01:00
row[0] AnsInd
row[1] Answer
row[2] Feedback
2019-03-18 15:42:22 +01:00
row[3] MedCod
row[4] Correct
2016-04-06 14:41:47 +02:00
*/
2014-12-01 23:55:08 +01:00
/***** Write the answers *****/
switch (Gbl.Test.AnswerType)
{
case Tst_ANS_INT:
Tst_CheckIfNumberOfAnswersIsOne ();
row = mysql_fetch_row (mysql_res);
fprintf (Gbl.Test.XML.FileXML,"%ld",
Tst_GetIntAnsFromStr (row[1]));
break;
case Tst_ANS_FLOAT:
if (Gbl.Test.Answer.NumOptions != 2)
Lay_ShowErrorAndExit ("Wrong float range.");
for (i = 0;
i < 2;
i++)
{
row = mysql_fetch_row (mysql_res);
FloatNum[i] = Tst_GetFloatAnsFromStr (row[1]);
}
fprintf (Gbl.Test.XML.FileXML,"%s"
"<lower>%lg</lower>%s"
"<upper>%lg</upper>%s",
Txt_NEW_LINE,
FloatNum[0],Txt_NEW_LINE,
FloatNum[1],Txt_NEW_LINE);
break;
case Tst_ANS_TRUE_FALSE:
Tst_CheckIfNumberOfAnswersIsOne ();
row = mysql_fetch_row (mysql_res);
fprintf (Gbl.Test.XML.FileXML,"%s",
row[1][0] == 'T' ? "true" :
"false");
break;
case Tst_ANS_UNIQUE_CHOICE:
case Tst_ANS_MULTIPLE_CHOICE:
case Tst_ANS_TEXT:
fprintf (Gbl.Test.XML.FileXML,"%s",Txt_NEW_LINE);
for (NumOpt = 0;
NumOpt < Gbl.Test.Answer.NumOptions;
NumOpt++)
{
row = mysql_fetch_row (mysql_res);
2019-05-17 09:19:49 +02:00
/* Start answer */
2014-12-01 23:55:08 +01:00
fprintf (Gbl.Test.XML.FileXML,"<option");
2019-05-17 09:19:49 +02:00
/* Write whether the answer is correct or not (row[4]) */
2014-12-01 23:55:08 +01:00
if (Gbl.Test.AnswerType != Tst_ANS_TEXT)
fprintf (Gbl.Test.XML.FileXML," correct=\"%s\"",
2019-05-17 09:19:49 +02:00
(row[4][0] == 'Y') ? "yes" :
2016-09-07 18:48:10 +02:00
"no");
2019-05-17 09:19:49 +02:00
fprintf (Gbl.Test.XML.FileXML,">%s",Txt_NEW_LINE);
/* Write the answer (row[1]), that is in HTML */
fprintf (Gbl.Test.XML.FileXML,"<text>%s</text>%s",
2014-12-01 23:55:08 +01:00
row[1],Txt_NEW_LINE);
2016-04-06 14:41:47 +02:00
/* Write the feedback (row[2]) */
if (row[2])
if (row[2][0])
2014-12-01 23:55:08 +01:00
fprintf (Gbl.Test.XML.FileXML,"<feedback>%s</feedback>%s",
2016-04-06 14:41:47 +02:00
row[2],Txt_NEW_LINE);
2019-05-17 09:19:49 +02:00
/* End answer */
2014-12-01 23:55:08 +01:00
fprintf (Gbl.Test.XML.FileXML,"</option>%s",
Txt_NEW_LINE);
}
break;
default:
break;
}
/***** Free structure that stores the query result *****/
DB_FreeMySQLResult (&mysql_res);
}
/*****************************************************************************/
/************ 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 */
2018-10-18 02:02:32 +02:00
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)
{
char *XMLBuffer;
unsigned long FileSize;
/***** Open file *****/
if ((Gbl.Test.XML.FileXML = fopen (FileNameXML,"rb")) == NULL)
Lay_ShowErrorAndExit ("Can not open XML file.");
/***** Compute file size *****/
fseek (Gbl.Test.XML.FileXML,0L,SEEK_END);
FileSize = (unsigned long) ftell (Gbl.Test.XML.FileXML);
fseek (Gbl.Test.XML.FileXML,0L,SEEK_SET);
/***** Allocate memory for XML buffer *****/
2018-10-08 12:37:29 +02:00
if ((XMLBuffer = (char *) malloc (FileSize + 1)) == NULL)
2018-10-18 20:06:54 +02:00
Lay_NotEnoughMemoryExit ();
2014-12-01 23:55:08 +01:00
else
{
/***** Read file contents into XML buffer *****/
if (fread ((void *) XMLBuffer,sizeof (char),(size_t) FileSize,Gbl.Test.XML.FileXML))
XMLBuffer[FileSize] = '\0';
else
XMLBuffer[0] = '\0';
/***** Import questions from XML buffer *****/
TsI_ImportQuestionsFromXMLBuffer (XMLBuffer);
free (XMLBuffer);
}
/***** Close file *****/
fclose (Gbl.Test.XML.FileXML);
}
/*****************************************************************************/
/******************** 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;
bool AnswerTypeFound;
bool QuestionExists;
2017-01-17 03:10:43 +01:00
char Stem[Cns_MAX_BYTES_TEXT + 1];
char Feedback[Cns_MAX_BYTES_TEXT + 1];
2014-12-01 23:55:08 +01:00
/***** Allocate and get XML tree *****/
XML_GetTree (XMLBuffer,&RootElem);
2017-06-12 14:16:33 +02:00
/***** Start box *****/
2019-10-25 22:48:34 +02:00
Box_BoxBegin (NULL,Txt_Imported_questions,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-23 21:12:40 +02:00
fprintf (Gbl.F.Out,"<textarea title=\"%s\" cols=\"60\" rows=\"5\""
2016-11-13 20:54:06 +01:00
" spellcheck=\"false\" readonly>",
Txt_XML_file_content);
2014-12-01 23:55:08 +01:00
XML_PrintTree (RootElem);
2019-10-23 20:07:56 +02:00
fprintf (Gbl.F.Out,"</textarea>");
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 *****/
2019-10-23 19:05:05 +02:00
HTM_TABLE_BeginWideMarginPadding (2);
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 *****/
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)
{
2017-01-15 18:02:52 +01:00
Str_Copy (Gbl.Test.Tags.Txt[Gbl.Test.Tags.Num],
2017-01-17 03:10:43 +01:00
TagElem->Content,
Tst_MAX_BYTES_TAG);
2016-11-13 20:18:49 +01:00
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) */
2017-01-17 03:10:43 +01:00
Str_Copy (Stem,StemElem->Content,
Cns_MAX_BYTES_TEXT);
2016-11-13 20:18:49 +01:00
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) */
2017-01-15 18:02:52 +01:00
Str_Copy (Feedback,FeedbackElem->Content,
Cns_MAX_BYTES_TEXT);
2016-11-13 20:18:49 +01:00
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 ();
}
}
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)
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 *****/
2018-11-02 01:23:05 +01:00
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'",
2019-04-04 10:45:15 +02:00
Gbl.Hierarchy.Crs.CrsCod,
2018-11-02 01:23:05 +01:00
Tst_StrAnswerTypesDB[Gbl.Test.AnswerType],
Gbl.Test.Stem.Text);
2014-12-01 23:55:08 +01:00
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 */
2018-11-02 01:23:05 +01:00
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);
2014-12-01 23:55:08 +01:00
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))
2019-03-09 20:12:44 +01:00
/* Abort on error */
Ale_ShowAlertsAndExit ();
2014-12-01 23:55:08 +01:00
if (AnswerElem->Content)
2017-01-15 18:02:52 +01:00
Str_Copy (Gbl.Test.Answer.Options[0].Text,AnswerElem->Content,
Tst_MAX_BYTES_ANSWER_OR_FEEDBACK);
2014-12-01 23:55:08 +01:00
break;
case Tst_ANS_FLOAT:
if (!Tst_AllocateTextChoiceAnswer (0))
2019-03-09 20:12:44 +01:00
/* Abort on error */
Ale_ShowAlertsAndExit ();
2014-12-01 23:55:08 +01:00
if (!Tst_AllocateTextChoiceAnswer (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)
2017-01-17 03:10:43 +01:00
Str_Copy (Gbl.Test.Answer.Options[0].Text,
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)
2017-01-17 03:10:43 +01:00
Str_Copy (Gbl.Test.Answer.Options[1].Text,
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)
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))
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)
{
2017-01-15 18:02:52 +01:00
Str_Copy (Gbl.Test.Answer.Options[NumOpt].Text,
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,
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)
{
2017-01-15 18:02:52 +01:00
Str_Copy (Gbl.Test.Answer.Options[NumOpt].Feedback,
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,
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 *****/
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,
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";
const char *ClassStem = QuestionExists ? "TEST_EDI_LIGHT" :
"TEST_EDI";
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-09 23:49:29 +02:00
fprintf (Gbl.F.Out,"<img src=\"%s/%s\""
2015-07-22 18:59:44 +02:00
" alt=\"%s\" title=\"%s\""
2019-10-07 21:15:14 +02:00
" class=\"CONTEXT_ICO_16x16\" />",
2019-03-20 01:36:36 +01:00
Cfg_URL_ICON_PUBLIC,
2019-01-11 03:45:13 +01:00
QuestionExists ? "tr16x16.gif" :
"check-circle.svg",
2015-07-22 18:59:44 +02:00
QuestionExists ? Txt_Existing_question :
Txt_New_question,
2014-12-01 23:55:08 +01:00
QuestionExists ? Txt_Existing_question :
Txt_New_question);
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)
fprintf (Gbl.F.Out,"%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);
2016-04-06 22:27:33 +02:00
if (Gbl.Test.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;
2016-04-06 22:27:33 +02:00
NumTag < Gbl.Test.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-10-09 23:49:29 +02:00
fprintf (Gbl.F.Out,"&nbsp;&#8226;&nbsp;");
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);
2019-10-09 23:49:29 +02:00
fprintf (Gbl.F.Out,"%s",Gbl.Test.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
fprintf (Gbl.F.Out,"<span class=\"%s\">&nbsp;(%s)&nbsp;</span>",
ClassData,Txt_no_tags);
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);
2019-10-09 23:49:29 +02:00
fprintf (Gbl.F.Out,"%s&nbsp;",Txt_TST_STR_ANSWER_TYPES[Gbl.Test.AnswerType]);
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);
2014-12-01 23:55:08 +01:00
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)
2019-01-11 04:04:12 +01:00
fprintf (Gbl.F.Out,"<img src=\"%s/check.svg\""
2015-07-22 18:59:44 +02:00
" alt=\"%s\" title=\"%s\""
2019-01-11 04:04:12 +01:00
" class=\"%sICO16x16\" />",
2019-03-20 01:36:36 +01:00
Cfg_URL_ICON_PUBLIC,
2018-12-09 13:11:20 +01:00
Txt_TST_Answer_given_by_the_teachers,
2019-01-11 04:04:12 +01:00
Txt_TST_Answer_given_by_the_teachers,
QuestionExists ? "ICO_HIDDEN " :
"");
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);
2014-12-01 23:55:08 +01:00
Tst_WriteQstStem (Stem,ClassStem);
Tst_WriteQstFeedback (Feedback,"TEST_EDI_LIGHT");
switch (Gbl.Test.AnswerType)
{
case Tst_ANS_INT:
2016-01-18 00:33:52 +01:00
fprintf (Gbl.F.Out,"<span class=\"%s\">(%ld)</span>",
2014-12-01 23:55:08 +01:00
ClassStem,Gbl.Test.Answer.Integer);
break;
case Tst_ANS_FLOAT:
2016-01-18 00:33:52 +01:00
fprintf (Gbl.F.Out,"<span class=\"%s\">([%lg; %lg])</span>",
2014-12-01 23:55:08 +01:00
ClassStem,Gbl.Test.Answer.FloatingPoint[0],Gbl.Test.Answer.FloatingPoint[1]);
break;
case Tst_ANS_TRUE_FALSE:
2016-01-18 00:33:52 +01:00
fprintf (Gbl.F.Out,"<span class=\"%s\">(",ClassStem);
2014-12-01 23:55:08 +01:00
Tst_WriteAnsTF (Gbl.Test.Answer.TF);
2016-01-18 00:33:52 +01:00
fprintf (Gbl.F.Out,")</span>");
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;
NumOpt < Gbl.Test.Answer.NumOptions;
NumOpt++)
{
/* Convert the answer, that is in HTML, to rigorous HTML */
AnswerTextLength = strlen (Gbl.Test.Answer.Options[NumOpt].Text) *
2017-03-08 03:48:23 +01:00
Str_MAX_BYTES_PER_CHAR;
2018-10-08 12:37:29 +02:00
if ((AnswerText = (char *) malloc (AnswerTextLength + 1)) == NULL)
2018-10-18 20:06:54 +02:00
Lay_NotEnoughMemoryExit ();
2017-01-15 18:02:52 +01:00
Str_Copy (AnswerText,Gbl.Test.Answer.Options[NumOpt].Text,
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;
if (Gbl.Test.Answer.Options[NumOpt].Feedback)
if (Gbl.Test.Answer.Options[NumOpt].Feedback[0])
{
AnswerFeedbackLength = strlen (Gbl.Test.Answer.Options[NumOpt].Feedback) *
2017-03-08 03:48:23 +01:00
Str_MAX_BYTES_PER_CHAR;
2018-10-08 12:37:29 +02:00
if ((AnswerFeedback = (char *) malloc (AnswerFeedbackLength + 1)) == NULL)
2018-10-18 20:06:54 +02:00
Lay_NotEnoughMemoryExit ();
2017-01-15 18:02:52 +01:00
Str_Copy (AnswerFeedback,
Gbl.Test.Answer.Options[NumOpt].Feedback,
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);
2014-12-01 23:55:08 +01:00
if (Gbl.Test.Answer.Options[NumOpt].Correct)
2019-01-12 19:46:33 +01:00
fprintf (Gbl.F.Out,"<img src=\"%s/check.svg\""
2015-07-22 18:59:44 +02:00
" alt=\"%s\" title=\"%s\""
2019-01-12 19:46:33 +01:00
" class=\"%sCONTEXT_ICO_16x16\" />",
2019-03-20 01:36:36 +01:00
Cfg_URL_ICON_PUBLIC,
2018-12-09 13:11:20 +01:00
Txt_TST_Answer_given_by_the_teachers,
2019-01-12 19:46:33 +01:00
Txt_TST_Answer_given_by_the_teachers,
QuestionExists ? "ICO_HIDDEN " :
"");
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-10-09 23:49:29 +02:00
fprintf (Gbl.F.Out,"%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-10-23 21:12:40 +02:00
fprintf (Gbl.F.Out,"%s",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
{
2019-10-24 00:04:40 +02:00
HTM_DIV_Begin ("class=\"TEST_EDI_LIGHT\"");
2019-10-23 21:12:40 +02:00
fprintf (Gbl.F.Out,"%s",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 */
free ((void *) AnswerText);
if (AnswerFeedbackLength)
free ((void *) AnswerFeedback);
}
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
}