Commit 0b2bdebb authored by platypro's avatar platypro

Added "Periodic table" example.

In doing so I added static data storage for exercises and mustache support. Through special properties make properties that are executed at write time.
parent c1e08d68
......@@ -7,3 +7,6 @@
[submodule "backend/deps/onion"]
path = backend/deps/onion
url = https://github.com/davidmoreno/onion
[submodule "backend/deps/libpmustache"]
path = backend/deps/libpmustache
url = https://gitlab.com/platypro/libpmustache.git
......@@ -2,7 +2,7 @@ Quiz Grinder TODO
=================
General
* [ ] Command-line interface for backend
* [X] Command-line interface for backend
Generation Modes
* [X] Random
......@@ -10,18 +10,18 @@ Generation Modes
Quiz export types
* [X] Network quiz
* [ ] raw JSON export
* [X] raw JSON export
* [ ] PDF export
* [ ] Exam export (Many of one quiz, with QR codes)
* [ ] Party quiz
Widgets
* [X] TextView
* [ ] SVGView
* [X] SVGView
* [X] NumberInput
* [ ] TextInput
* [X] TextInput
* [ ] ChoiceInput
* Custom widget support???
* [ ] Answer options
Docs
* [ ] Building and deploying
......
......@@ -30,7 +30,8 @@ set(SRCS
src/daemon/daemon.c
src/daemon/http.c
src/pcg_basic.c
${PLATYPRO_BASE_LIST_SRCS}
${DEPS_SRC}
)
add_executable(quizgrinder ${SRCS})
......@@ -40,6 +41,7 @@ install(DIRECTORY
res/exercise
res/script
res/pset
res/template
DESTINATION ${APP_DATA_PATH})
install(DIRECTORY DESTINATION ${APP_VAR_PATH})
......
add_subdirectory(wjelement)
add_subdirectory(lua)
add_subdirectory(libpmustache)
# Onion build settings
SET(ONION_USE_SSL false)
......@@ -18,5 +19,6 @@ SET(ONION_USE_BINDINGS_CPP false)
SET(ONION_POLLER default)
add_subdirectory(onion)
set(DEPS_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/wjelement/include ${CMAKE_CURRENT_SOURCE_DIR}/onion/src PARENT_SCOPE)
set(DEPS_LIBS wjelement Lua onion PARENT_SCOPE)
set(DEPS_SRC ${PMUSTACHE_PROVIDER_WJE_SRCS} PARENT_SCOPE)
set(DEPS_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/mustache-c/src/ ${CMAKE_CURRENT_SOURCE_DIR}/wjelement/include ${CMAKE_CURRENT_SOURCE_DIR}/onion/src ${PMUSTACHE_INCLUDE_DIRS} PARENT_SCOPE)
set(DEPS_LIBS wjelement Lua onion pmustache PARENT_SCOPE)
Subproject commit af7977f368164ea7d64ae3e5723a1b6476f86270
......@@ -6,5 +6,5 @@
"max_1" : 20,
"max_2" : 20,
"format" : "natural"
}
},
}
{
"name" : "Periodic Table",
"calculator" : "banned",
"options" : {
"missing" : "number",
},
"static" : [
{"n":"Hydrogen","s":"H","a":1,"w":1.008},{"n":"Helium","s":"He","a":2,"w":4.0026},{"n":"Lithium","s":"Li","a":3,"w":6.94},{"n":"Beryllium","s":"Be","a":4,"w":9.01218},{"n":"Boron","s":"B","a":5,"w":10.81},{"n":"Carbon","s":"C","a":6,"w":12.011},{"n":"Nitrogen","s":"N","a":7,"w":14.007},{"n":"Oxygen","s":"O","a":8,"w":15.999},{"n":"Fluorine","s":"F","a":9,"w":18.9984},{"n":"Neon","s":"Ne","a":10,"w":20.1798},{"n":"Sodium","s":"Na","a":11,"w":22.9898},{"n":"Magnesium","s":"Mg","a":12,"w":24.305},{"n":"Aluminium","s":"Al","a":13,"w":26.9815},{"n":"Silicon","s":"Si","a":14,"w":28.085},{"n":"Phosphorus","s":"P","a":15,"w":30.9738},{"n":"Sulfur","s":"S","a":16,"w":32.06},{"n":"Chlorine","s":"Cl","a":17,"w":35.45},{"n":"Argon","s":"Ar","a":18,"w":39.9481},{"n":"Potassium","s":"K","a":19,"w":39.0983},{"n":"Calcium","s":"Ca","a":20,"w":40.0784},{"n":"Scandium","s":"Sc","a":21,"w":44.9559},{"n":"Titanium","s":"Ti","a":22,"w":47.8671},{"n":"Vanadium","s":"V","a":23,"w":50.9415},{"n":"Chromium","s":"Cr","a":24,"w":51.9962},{"n":"Manganese","s":"Mn","a":25,"w":54.938},{"n":"Iron","s":"Fe","a":26,"w":55.8452},{"n":"Cobalt","s":"Co","a":27,"w":58.9332},{"n":"Nickel","s":"Ni","a":28,"w":58.6934},{"n":"Copper","s":"Cu","a":29,"w":63.5463},{"n":"Zinc","s":"Zn","a":30,"w":65.382},{"n":"Gallium","s":"Ga","a":31,"w":69.7231},{"n":"Germanium","s":"Ge","a":32,"w":72.6308},{"n":"Arsenic","s":"As","a":33,"w":74.9216},{"n":"Selenium","s":"Se","a":34,"w":78.9718},{"n":"Bromine","s":"Br","a":35,"w":79.904},{"n":"Krypton","s":"Kr","a":36,"w":83.7982},{"n":"Rubidium","s":"Rb","a":37,"w":85.4678},{"n":"Strontium","s":"Sr","a":38,"w":87.621},{"n":"Yttrium","s":"Y","a":39,"w":88.9058},{"n":"Zirconium","s":"Zr","a":40,"w":91.2242},{"n":"Niobium","s":"Nb","a":41,"w":92.9064},{"n":"Molybdenum","s":"Mo","a":42,"w":95.951},{"n":"Technetium","s":"Tc","a":43,"w":98},{"n":"Ruthenium","s":"Ru","a":44,"w":101.072},{"n":"Rhodium","s":"Rh","a":45,"w":102.906},{"n":"Palladium","s":"Pd","a":46,"w":106.421},{"n":"Silver","s":"Ag","a":47,"w":107.868},{"n":"Cadmium","s":"Cd","a":48,"w":112.414},{"n":"Indium","s":"In","a":49,"w":114.818},{"n":"Tin","s":"Sn","a":50,"w":118.711},{"n":"Antimony","s":"Sb","a":51,"w":121.76},{"n":"Tellurium","s":"Te","a":52,"w":127.603},{"n":"Iodine","s":"I","a":53,"w":126.904},{"n":"Xenon","s":"Xe","a":54,"w":131.294},{"n":"Cesium","s":"Cs","a":55,"w":132.905},{"n":"Barium","s":"Ba","a":56,"w":137.328},{"n":"Lanthanum","s":"La","a":57,"w":138.905},{"n":"Cerium","s":"Ce","a":58,"w":140.116},{"n":"Praseodymium","s":"Pr","a":59,"w":140.908},{"n":"Neodymium","s":"Nd","a":60,"w":144.242},{"n":"Promethium","s":"Pm","a":61,"w":145},{"n":"Samarium","s":"Sm","a":62,"w":150.362},{"n":"Europium","s":"Eu","a":63,"w":151.964},{"n":"Gadolinium","s":"Gd","a":64,"w":157.253},{"n":"Terbium","s":"Tb","a":65,"w":158.925},{"n":"Dysprosium","s":"Dy","a":66,"w":162.5},{"n":"Holmium","s":"Ho","a":67,"w":164.93},{"n":"Erbium","s":"Er","a":68,"w":167.259},{"n":"Thulium","s":"Tm","a":69,"w":168.934},{"n":"Ytterbium","s":"Yb","a":70,"w":173.045},{"n":"Lutetium","s":"Lu","a":71,"w":174.967},{"n":"Hafnium","s":"Hf","a":72,"w":178.492},{"n":"Tantalum","s":"Ta","a":73,"w":180.948},{"n":"Tungsten","s":"W","a":74,"w":183.841},{"n":"Rhenium","s":"Re","a":75,"w":186.207},{"n":"Osmium","s":"Os","a":76,"w":190.233},{"n":"Iridium","s":"Ir","a":77,"w":192.217},{"n":"Platinum","s":"Pt","a":78,"w":195.085},{"n":"Gold","s":"Au","a":79,"w":196.967},{"n":"Mercury","s":"Hg","a":80,"w":200.592},{"n":"Thallium","s":"Tl","a":81,"w":204.38},{"n":"Lead","s":"Pb","a":82,"w":207.21},{"n":"Bismuth","s":"Bi","a":83,"w":208.98},{"n":"Polonium","s":"Po","a":84,"w":209},{"n":"Astatine","s":"At","a":85,"w":210},{"n":"Radon","s":"Rn","a":86,"w":222},{"n":"Francium","s":"Fr","a":87,"w":223},{"n":"Radium","s":"Ra","a":88,"w":226},{"n":"Actinium","s":"Ac","a":89,"w":227},{"n":"Thorium","s":"Th","a":90,"w":232.038},{"n":"Protactinium","s":"Pa","a":91,"w":231.036},{"n":"Uranium","s":"U","a":92,"w":238.029},{"n":"Neptunium","s":"Np","a":93,"w":237},{"n":"Plutonium","s":"Pu","a":94,"w":244},{"n":"Americium","s":"Am","a":95,"w":243},{"n":"Curium","s":"Cm","a":96,"w":247},{"n":"Berkelium","s":"Bk","a":97,"w":247},{"n":"Californium","s":"Cf","a":98,"w":251},{"n":"Einsteinium","s":"Es","a":99,"w":252},{"n":"Fermium","s":"Fm","a":100,"w":257},{"n":"Mendelevium","s":"Md","a":101,"w":258},{"n":"Nobelium","s":"No","a":102,"w":259},{"n":"Lawrencium","s":"Lr","a":103,"w":266},{"n":"Rutherfordium","s":"Rf","a":104,"w":267},{"n":"Dubnium","s":"Db","a":105,"w":268},{"n":"Seaborgium","s":"Sg","a":106,"w":269},{"n":"Bohrium","s":"Bh","a":107,"w":270},{"n":"Hassium","s":"Hs","a":108,"w":269},{"n":"Meitnerium","s":"Mt","a":109,"w":278},{"n":"Darmstadtium","s":"Ds","a":110,"w":281},{"n":"Roentgenium","s":"Rg","a":111,"w":282},{"n":"Copernicium","s":"Cn","a":112,"w":285},{"n":"Nihonium","s":"Nh","a":113,"w":286},{"n":"Flerovium","s":"Fl","a":114,"w":289},{"n":"Moscovium","s":"Mc","a":115,"w":289},{"n":"Livermorium","s":"Lv","a":116,"w":293},{"n":"Tennessine","s":"Ts","a":117,"w":294},{"n":"Oganesson","s":"Og","a":118,"w":294},{"n":"Ununennium","s":"Uue","a":119,"w":315}
]
}
{
"name" : "The Elements",
"questions" : [
{
"id":"ptable",
},
{
"id":"ptable",
"options":
{
"missing":"name"
}
}
]
}
......@@ -3,8 +3,8 @@ local b = math.random(options["max_2"])
local ans
function doWidget(ans, name, av, bv)
createWidget("numberInput", tostring(ans),
{hint=tostring(av) .. name .. tostring(bv) .. "="})
createWidget("numberInput", ans,
{hint=string.format("%d%s%d=", av, name, bv)})
end
function round(num, numDecimalPlaces)
......
thisElement = getStatic(string.format("[%d]", math.random( 0, getStaticCount(nil) - 1 )))
if options["missing"] == "number" then
qtext = "What is the atomic number of " .. thisElement.n .. "?"
answer = math.floor(thisElement.a)
thisElement.a = "???"
elseif options["missing"] == "name" then
qtext = "What is the name of this element?"
answer = thisElement.n
thisElement.n = "???"
else
error("Invalid option for missing ptable field!")
end
createWidget("textView", nil, {text = qtext})
createWidget("svgView", nil, {
src = mkTemplate("default.svg", thisElement)
})
if options["missing"] == "number" then
createWidget("numberInput", answer, {})
else
createWidget("textInput", answer, {})
end
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
height="100"
width="100">
<rect x="0" y="0"
height="100"
width="100"
id="border"
fill="#fff48d"
stroke="black"
stroke-width="3" />
<text x="2" y="14"
text-anchor= "start"
font-size= "12"
line-height= "12">
{{a}}
</text>
<text x="98" y="14"
text-anchor= "end"
font-size= "12"
line-height= "12">
{{w}}
</text>
<text x="50" y="60"
text-anchor= "middle"
font-size= "36"
line-height= "36">
{{s}}
</text>
<text x="50" y="90"
text-anchor= "middle"
font-size= "12"
line-height= "12">
{{n}}
</text>
</svg>
......@@ -30,7 +30,7 @@ USER** getUser(PG_STATE* state, UID_T uid)
USER** bucket = &state->http.users[basebucket];
USER** result = NULL;
while(!result)//bucket->uid != uid || result->next)
while(!result)
{
if(*bucket)
{
......@@ -94,25 +94,35 @@ void writePset(PG_STATE* state, WJWriter response)
WJWCloseArray(response);
}
#define SCP_GET_QUESTION(u) ((u)->questionAt)
#define SCP_GET_DOC(u) (SCP_GET_QUESTION(u)->doc)
bool user_writeQuestion(USER* u, WJWriter w)
bool user_nextQuestion(PG_STATE* state, USER* u, WJWriter w)
{
//Generate question
if(u->questionAt) exercise_destroyQuestion(u->questionAt);
if(!u->questionsLeft)
{
writeError(w, "No questions left! Please request a new quiz");
return false;
}
//if(u->questionsLeft)
u->questionAt = exercise_mkQuestion(state, u->pset);
if(u->questionAt)
{
WJWUInt32(KEYCOUNT(KEY_HINT), WJEArray(SCP_GET_DOC(u), KEYLIST(KEY_HINT), WJE_GET)->count, w);
WJWString(KEY_CALCULATOR, SCP_GET_QUESTION(u)->calculator, true, w);
WJWString(KEY_NAME, SCP_GET_QUESTION(u)->name, true, w);
WJWBoolean(KEY_SOLUTION,
WJEArray(SCP_GET_DOC(u), KEY_SOLUTION, WJE_GET) ? TRUE : FALSE
, w);
WJEWriteDocument(WJEArray(SCP_GET_DOC(u), KEYLIST(KEY_WIDGET), WJE_GET), w, KEYLIST(KEY_WIDGET));
u->questionsLeft--;
u->hintAt = u->questionAt->hints->child;
WJWUInt32(KEYCOUNT(KEY_HINT), u->questionAt->hints->count, w);
WJElement exdoc = u->questionAt->eref->exercise->doc;
WJWString(KEY_CALCULATOR, WJEString(exdoc, KEY_CALCULATOR, WJE_GET,""), true, w);
WJWString(KEY_NAME, WJEString(exdoc, KEY_NAME, WJE_GET,""), true, w);
WJWBoolean(KEY_SOLUTION, u->questionAt->solutions->count ? TRUE : FALSE, w);
exercise_writeWidgets(state, u->questionAt, w);
return true;
}
else
{
writeError(w, "No questions left! Please request a new quiz");
writeError(w, "Server error when generating question!");
return false;
}
}
......@@ -168,33 +178,22 @@ bool session_common_process(PG_STATE* state, WJElement request, WJWriter respons
char* pset = WJEString(request, C_KEY(KEY_PSET), WJE_GET, NULL);
SCP_ASSERT(pset, "Missing problem set!");
if(u->questionTop)
exercise_destroyQuiz(u->questionTop);
uint32_t numQuestions = 5;
QUESTION* elem = exercise_genQuiz(state, pset, &numQuestions);
SCP_ASSERT(elem, "Invalid problem set!");
u->questionAt = u->questionTop = elem;
u->hintAt = WJEArray(SCP_GET_DOC(u), KEYLIST(KEY_HINT), WJE_GET)->child;
WJWUInt32(KEYCOUNT(KEY_QUESTION), 5 - numQuestions, response);
if(giveFirst && user_writeQuestion(u, response))
u->questionsLeft = 5;
u->pset = exercise_getSet(state, pset);
SCP_ASSERT(u->pset, "Invalid problem set!");
WJWUInt32(KEYCOUNT(KEY_QUESTION), u->questionsLeft, response);
if(giveFirst && user_nextQuestion(state, u, response))
WJWString(KEY_ACTION, ACTION_QUIZ_NEW, true, response);
}
}
else if(!strcmp(action, ACTION_QUESTION_NEXT))
{
USER* u;
if((u = getUserAndAssert(request, state, response)))
{
SCP_ASSERT(u->questionAt, "No questions available!");
u->questionAt = u->questionAt->next;
if(user_writeQuestion(u, response))
{
u->hintAt = WJEArray(SCP_GET_DOC(u), KEYLIST(KEY_HINT), WJE_GET)->child;
WJWString(KEY_ACTION, ACTION_QUESTION_NEXT, true, response);
}
if((u = getUserAndAssert(request, state, response))
&& user_nextQuestion(state, u, response))
{
WJWString(KEY_ACTION, ACTION_QUESTION_NEXT, true, response);
}
}
else if(!strcmp(action, ACTION_QUESTION_HINT))
......@@ -209,7 +208,7 @@ bool session_common_process(PG_STATE* state, WJElement request, WJWriter respons
u->hintAt = u->hintAt->next;
}
else
WJEWriteDocument(WJEArray(SCP_GET_DOC(u), KEY_SOLUTION, WJE_GET), response, KEY_SOLUTION);
WJEWriteDocument(u->questionAt->solutions, response, KEY_SOLUTION);
}
}
else if(!strcmp(action, ACTION_QUESTION_CHECK))
......@@ -219,7 +218,7 @@ bool session_common_process(PG_STATE* state, WJElement request, WJWriter respons
{
WJElement usolution_orig = WJEArray(request, KEY_SOLUTION, WJE_GET);
WJElement usolution, usolution_next;
WJElement solution = WJEArray(SCP_GET_DOC(u), KEY_SOLUTION, WJE_GET)->child;
WJElement solution = u->questionAt->solutions->child;
uint8_t success = solution->parent->count;
SCP_ASSERT(usolution_orig, "No answer given!");
......@@ -289,7 +288,7 @@ void session_common_cleanup(PG_STATE* state)
while(*user)
{
USER* user_next = (*user)->next;
exercise_destroyQuiz((*user)->questionTop);
exercise_destroyQuestion((*user)->questionAt);
free(*user);
(*user) = user_next;
}
......
......@@ -36,6 +36,8 @@
typedef uint32_t UID_T;
struct PG_State;
struct ProblemSet;
struct Question;
typedef struct User
{
......@@ -43,8 +45,9 @@ typedef struct User
UID_T uid;
time_t touchTime;
struct Question* questionTop;
struct ProblemSet* pset;
struct Question* questionAt;
uint32_t questionsLeft;
WJElement hintAt;
} USER;
......
......@@ -15,6 +15,9 @@
#include <string.h>
#include <time.h>
#include <libgen.h>
#include <alloca.h>
#include <pmustache/template.h>
#include <pmustache/provider.wje.h>
#include "quizgrinder.h"
#include "paths.h"
......@@ -99,6 +102,7 @@ char* exercise_load(PG_STATE* state, char* filename)
exer=exerFind;
break;
}
exerFind = exerFind->next;
}
if(!exer)
......@@ -172,96 +176,162 @@ char* exercise_loadOne(PG_STATE* state, char* set)
return set;
}
QUESTION* exercise_genQuiz(PG_STATE* state, char* name, uint32_t* numQuestions)
PROBLEMSET* exercise_getSet(PG_STATE* state, char* name)
{
QUESTION* result = NULL;
QUESTION* at = NULL;
PROBLEMSET* pset = NULL, *pi = state->exercise.problemSets;
PROBLEMSET* pi = state->exercise.problemSets;
while(pi)
{
if(!strcmp(pi->name, name))
{
pset = pi;
break;
return pi;
}
pi = pi->next;
}
if(!pset) return NULL;
return NULL;
}
time_t t;
srand((unsigned) time(&t));
while(*numQuestions)
QUESTION* exercise_mkQuestion(PG_STATE* state, PROBLEMSET* pset)
{
QUESTION* result = calloc(1, sizeof(QUESTION));
uint32_t exnum = rand() % pset->numExercises;
EXERCISEREF* exercise = &pset->exercises[exnum];
result->eref = exercise;
result->solutions = WJEArray(NULL, NULL, WJE_NEW);
result->hints = WJEArray(NULL, NULL, WJE_NEW);
if(!script_genQuiz(state, result, exercise))
{
WJElement doc = WJEObject(NULL, NULL, WJE_NEW);
uint32_t exnum = rand() % pset->numExercises;
EXERCISEREF* exercise = &pset->exercises[exnum];
script_genQuiz(state, doc, exercise);
exercise_destroyQuestion(result);
result = NULL;
}
else if(!result->widgets)
{
printf("Question %s has no widgets!!\n", exercise->exercise->id);
exercise_destroyQuestion(result);
return NULL;
}
return result;
}
if(!WJEGet(doc, KEYLIST(KEY_WIDGET), NULL))
{
printf("Question %s has no widgets!!\n", exercise->exercise->id);
WJECloseDocument(doc);
continue;
}
if(!result)
{
result = calloc(1, sizeof(QUESTION));
at = result;
}
else
size_t writeWJW(char *data, size_t length, void *userdata)
{
WJWriter w = (WJWriter) userdata;
return (WJWStringN(NULL, data, length, FALSE, w) ? length : 0);
}
bool expandSpecialProperty(struct PG_State* state, QUESTION* q, SPECIALPROPERTY* sp, WJWriter w)
{
switch(sp->type)
{
case STYPE_TEMPLATE:
{
at->next = calloc(1, sizeof(QUESTION));
at = at->next;
}
TEMPLATECONTAINER* tc = state->exercise.templates;
while(tc)
{
if(!strcmp(tc->filename, sp->template.src))
break;
tc = tc->next;
}
at->doc = doc;
at->calculator =
WJEString(exercise->exercise->doc, KEY_CALCULATOR, WJE_GET, NULL);
at->name =
WJEString(exercise->exercise->doc, KEY_NAME, WJE_GET, NULL);
(*numQuestions) --;
if(!tc)
{
tc = calloc(1, sizeof(TEMPLATECONTAINER));
if(!tc) return false;
size_t flen = strlen(sp->template.src);
tc->filename = malloc(flen + 1);
if(!tc->filename) return false;
memcpy(tc->filename, sp->template.src, flen);
size_t l = strlen(APP_TEMPLATE_PATH) + strlen(q->eref->exercise->id) + strlen(sp->template.src) + 3;
char* filename = alloca(l);
snprintf(filename, l, "%s/%s.%s", APP_TEMPLATE_PATH, q->eref->exercise->id, sp->template.src);
char* src = mustache_eatFile(filename);
if(!src)
{
printf("Could not load template %s", filename);
free(tc);
return false;
}
tc->template = mustache_mkIndex(src, 0);
tc->next = state->exercise.templates;
state->exercise.templates = tc;
}
PMUS_CONTEXT ctx = {.provider=WJEMustacheProvider, .write=writeWJW, .template=tc->template};
mustache_generate(&ctx, w, sp->template.subs);
}
}
return result;
return true;
}
bool exercise_writeQuiz(QUESTION* q, char* file)
bool exercise_writeWidgets(struct PG_State* state, QUESTION* question, WJWriter w)
{
FILE* f = fopen(file, "w");
if(!f) return false;
WJWriter w = WJWOpenFILEDocument(true, f);
WJWOpenArray(NULL, w);
while(q)
WJWOpenArray(KEYLIST(KEY_WIDGET), w);
WIDGET* elem = question->widgets;
while(elem)
{
WJWOpenObject(NULL, w);
WJWString(KEY_CALCULATOR, q->calculator, true, w);
WJWString(KEY_NAME, q->name, true, w);
WJElement d = q->doc->child;
while(d)
WJWUInt32("id", elem->id, w);
WJWString("type", elem->type, TRUE, w);
WJWOpenObject("options", w);
WJElement child = elem->options->child;
while(child)
{
WJEWriteDocument(d, w, d->name);
d = d->next;
if(*child->name == '_')
{
SPECIALPROPERTY* sp = question->special;
while(sp)
{
if(sp->id == WJEUInt32(child, NULL, WJE_GET, 0))
{
WJWString(child->name + 1, "", false, w);
WJWString(NULL,
expandSpecialProperty(state, question, sp, w) ? "" : "ERROR", true, w);
break;
}
sp = sp->next;
}
}
else WJEWriteDocument(child, w, child->name);
child = child->next;
}
WJWCloseObject(w);
q = q->next;
WJWCloseObject(w);
elem = elem->next;
}
WJWCloseArray(w);
return true;
}
void exercise_destroyQuiz(QUESTION* quiz)
bool exercise_destroyQuestion(QUESTION* quiz)
{
QUESTION* q = quiz;
while(q)
WJECloseDocument(quiz->solutions);
WJECloseDocument(quiz->hints);
WIDGET* widget = quiz->widgets;
while(widget)
{
QUESTION* q_next = q->next;
WJECloseDocument(q->doc);
free(q);
q = q_next;
WIDGET* widget_next = widget->next;
WJECloseDocument(widget->options);
free(widget);
widget = widget_next;
}
SPECIALPROPERTY* prop = quiz->special;
while(prop)
{
SPECIALPROPERTY* prop_next = prop->next;
free(prop);
prop = prop_next;
}
free(quiz);
return true;
}
void exercise_cleanup(PG_STATE* state)
......
......@@ -10,6 +10,7 @@
#define INCLUDE_EXERCISE_H
#include <wjelement.h>
#include <pmustache/template.h>
struct PG_State;
......@@ -26,6 +27,8 @@ typedef enum CalcType
#define CALCTYPE_SCIENTIFIC_ "Scientific"
#define CALCTYPE_GRAPHING_ "Graphing"
#define CALCT
typedef struct Exercise
{
struct Exercise* next;
......@@ -51,27 +54,69 @@ typedef struct ProblemSet
} PROBLEMSET;
typedef struct Question
typedef enum SpecialType
{
struct Question* next;
STYPE_TEMPLATE,
} SPECIALTYPE;
struct TemplateProperty
{
char* src;
WJElement subs;
};
typedef struct SpecialProperty
{
struct SpecialProperty* next;
uint32_t id;
SPECIALTYPE type;
union {
struct TemplateProperty template;
};
} SPECIALPROPERTY;
typedef struct Widget
{
struct Widget* next;
WJElement doc;
char* calculator;
char* name;
uint32_t id;
char* type;
WJElement options;
} WIDGET;
typedef struct Question
{
EXERCISEREF* eref;
struct Widget* widgets;
struct Widget* widget_last;
WJElement hints;
WJElement solutions;
struct SpecialProperty* special;
} QUESTION;
typedef struct TemplateContainer
{
struct TemplateContainer* next;
char* filename;
PMUS_TEMPLATE* template;
} TEMPLATECONTAINER;
typedef struct PG_Exercise
{
struct ProblemSet* problemSets;
struct Exercise* stash;
TEMPLATECONTAINER* templates;
} PG_EXERCISE;
extern bool exercise_loadAll(struct PG_State* state);
extern char* exercise_loadOne(struct PG_State* state, char* set);
extern QUESTION* exercise_genQuiz(struct PG_State* state, char* name, uint32_t* numQuestions);
extern bool exercise_writeQuiz(QUESTION* quiz, char* path);
extern void exercise_destroyQuiz(QUESTION* quiz);
extern PROBLEMSET* exercise_getSet(struct PG_State* state, char* name);
extern QUESTION* exercise_mkQuestion(struct PG_State* state, PROBLEMSET* pset);
extern bool exercise_writeWidgets(struct PG_State* state, QUESTION* question, WJWriter w);
extern bool exercise_destroyQuestion(QUESTION* question);
extern void exercise_cleanup(struct PG_State* state);
#endif /*INCLUDE_EXERCISE_H*/
......