
Ich bin mit dem Paket Moodle vertraut. Ich habe jedoch eine Frage in die entgegengesetzte Richtung.
Gibt es eine Methode oder ein Tool zum Konvertieren von XML-Moodle-Fragendatenbanken in LaTeX?
Bearbeiten: Ein Beispiel für eine XML-Datei ist wie unten
<?xml version="1.0" encoding="UTF-8"?>
<quiz>
<!-- question: 0 -->
<question type="category">
<category>
<text>$course$/top/Default for my course</text>
</category>
<info format="moodle_auto_format">
<text>The default category for questions shared in context 'my course'.</text>
</info>
<idnumber></idnumber>
</question>
<!-- question: 137829 -->
<question type="essay">
<name>
<text>Vector space problem</text>
</name>
<questiontext format="html">
<text><![CDATA[<p dir="ltr" style="text-align: left;">Prove that a vector space cannot be written as a union of two proper subspaces.<br></p>]]></text>
</questiontext>
<generalfeedback format="html">
<text><![CDATA[If we replace 'two' by 'three" the assertion may not be true.<br>]]></text>
</generalfeedback>
<defaultgrade>7.0000000</defaultgrade>
<penalty>0.0000000</penalty>
<hidden>0</hidden>
<idnumber></idnumber>
<responseformat>editorfilepicker</responseformat>
<responserequired>0</responserequired>
<responsefieldlines>15</responsefieldlines>
<attachments>-1</attachments>
<attachmentsrequired>0</attachmentsrequired>
<maxbytes>0</maxbytes>
<filetypeslist>.odt,.pdf</filetypeslist>
<graderinfo format="html">
<text><![CDATA[The assertion is true for groups as well.<br>]]></text>
</graderinfo>
<responsetemplate format="html">
<text></text>
</responsetemplate>
</question>
<!-- question: 128217 -->
<question type="multichoice">
<name>
<text>Querstion 2</text>
</name>
<questiontext format="html">
<text><![CDATA[<p dir="ltr" style="text-align: left;">The number of the subgroups of the alternating group \( A_4 \) is:<br></p>]]></text>
</questiontext>
<generalfeedback format="html">
<text><![CDATA[\( A_4 \) is the subgroup of the symmetric group \( S_4 \) consisting of even permutations.<br>]]></text>
</generalfeedback>
<defaultgrade>10.0000000</defaultgrade>
<penalty>0.3333333</penalty>
<hidden>0</hidden>
<idnumber></idnumber>
<single>true</single>
<shuffleanswers>true</shuffleanswers>
<answernumbering>abc</answernumbering>
<showstandardinstruction>0</showstandardinstruction>
<correctfeedback format="html">
<text>Your answer is correct.</text>
</correctfeedback>
<partiallycorrectfeedback format="html">
<text>Your answer is partially correct.</text>
</partiallycorrectfeedback>
<incorrectfeedback format="html">
<text>Your answer is incorrect.</text>
</incorrectfeedback>
<shownumcorrect/>
<answer fraction="0" format="html">
<text><![CDATA[<p dir="ltr" style="text-align: left;">8<br></p>]]></text>
<feedback format="html">
<text><![CDATA[<p dir="ltr" style="text-align: left;">oops!<br></p>]]></text>
</feedback>
</answer>
<answer fraction="0" format="html">
<text><![CDATA[<p dir="ltr" style="text-align: left;">9<br></p>]]></text>
<feedback format="html">
<text><![CDATA[<p dir="ltr" style="text-align: left;">oops!<br></p>]]></text>
</feedback>
</answer>
<answer fraction="100" format="html">
<text><![CDATA[<p dir="ltr" style="text-align: left;">10<br></p>]]></text>
<feedback format="html">
<text><![CDATA[<p dir="ltr" style="text-align: left;">Yes!<br></p>]]></text>
</feedback>
</answer>
<answer fraction="0" format="html">
<text><![CDATA[<p dir="ltr" style="text-align: left;">11<br></p>]]></text>
<feedback format="html">
<text><![CDATA[<p dir="ltr" style="text-align: left;">oops!<br></p>]]></text>
</feedback>
</answer>
</question>
<!-- question: 128220 -->
<question type="multichoice">
<name>
<text>Question 6</text>
</name>
<questiontext format="html">
<text><![CDATA[What is \( \lim_{n\to\infty}(1+\frac{1}{n})^n \)?<br>]]></text>
</questiontext>
<generalfeedback format="html">
<text></text>
</generalfeedback>
<defaultgrade>3.0000000</defaultgrade>
<penalty>0.3333333</penalty>
<hidden>0</hidden>
<idnumber></idnumber>
<single>true</single>
<shuffleanswers>true</shuffleanswers>
<answernumbering>abc</answernumbering>
<showstandardinstruction>0</showstandardinstruction>
<correctfeedback format="html">
<text>Your answer is correct.</text>
</correctfeedback>
<partiallycorrectfeedback format="html">
<text>Your answer is partially correct.</text>
</partiallycorrectfeedback>
<incorrectfeedback format="html">
<text>Your answer is incorrect.</text>
</incorrectfeedback>
<shownumcorrect/>
<answer fraction="100" format="html">
<text><![CDATA[<p dir="ltr" style="text-align: left;">e<br></p>]]></text>
<feedback format="html">
<text><![CDATA[<p dir="ltr" style="text-align: left;">Yes.<br></p>]]></text>
</feedback>
</answer>
<answer fraction="100" format="html">
<text><![CDATA[<p dir="ltr" style="text-align: left;">exp(1)<br></p>]]></text>
<feedback format="html">
<text></text>
</feedback>
</answer>
<answer fraction="80" format="html">
<text><![CDATA[<p dir="ltr" style="text-align: left;">2.7<br></p>]]></text>
<feedback format="html">
<text><![CDATA[<p dir="ltr" style="text-align: left;">Good.<br></p>]]></text>
</feedback>
</answer>
<answer fraction="50" format="html">
<text><![CDATA[<p dir="ltr" style="text-align: left;">2.9<br></p>]]></text>
<feedback format="html">
<text>near!</text>
</feedback>
</answer>
</question>
<!-- question: 128219 -->
<question type="numerical">
<name>
<text>Question 5</text>
</name>
<questiontext format="html">
<text><![CDATA[<p dir="ltr" style="text-align: left;">What is the approximate value of \( \int_1^2\frac{1}{x}dx? \)<br></p>]]></text>
</questiontext>
<generalfeedback format="html">
<text></text>
</generalfeedback>
<defaultgrade>7.0000000</defaultgrade>
<penalty>0.3333333</penalty>
<hidden>0</hidden>
<idnumber></idnumber>
<answer fraction="100" format="moodle_auto_format">
<text>0.693147</text>
<feedback format="html">
<text></text>
</feedback>
<tolerance>0.001</tolerance>
</answer>
<answer fraction="50" format="moodle_auto_format">
<text>0.693147</text>
<feedback format="html">
<text></text>
</feedback>
<tolerance>0.01</tolerance>
</answer>
<unitgradingtype>0</unitgradingtype>
<unitpenalty>0.1000000</unitpenalty>
<showunits>3</showunits>
<unitsleft>0</unitsleft>
</question>
<!-- question: 128218 -->
<question type="shortanswer">
<name>
<text>Question 3</text>
</name>
<questiontext format="html">
<text><![CDATA[<p dir="ltr" style="text-align: left;">What is the number of the subgroups of the alternating group \( A_4 \)?<br></p>]]></text>
</questiontext>
<generalfeedback format="html">
<text><![CDATA[<p dir="ltr" style="text-align: left;">You should specify a number.<br></p>]]></text>
</generalfeedback>
<defaultgrade>2.0000000</defaultgrade>
<penalty>0.3333333</penalty>
<hidden>0</hidden>
<idnumber></idnumber>
<usecase>0</usecase>
<answer fraction="100" format="moodle_auto_format">
<text>10</text>
<feedback format="html">
<text></text>
</feedback>
</answer>
<answer fraction="100" format="moodle_auto_format">
<text>ten</text>
<feedback format="html">
<text></text>
</feedback>
</answer>
<answer fraction="100" format="moodle_auto_format">
<text>dix</text>
<feedback format="html">
<text></text>
</feedback>
</answer>
</question>
<!-- question: 128216 -->
<question type="truefalse">
<name>
<text>Question 1</text>
</name>
<questiontext format="html">
<text><![CDATA[<p dir="ltr" style="text-align: left;">The vector space \( \mathbb{R}^3 \) has just four subspaces.<br></p>]]></text>
</questiontext>
<generalfeedback format="html">
<text><![CDATA[<p dir="ltr" style="text-align: left;">General feedback: Subspace means subvector space.<br></p>]]></text>
</generalfeedback>
<defaultgrade>1.0000000</defaultgrade>
<penalty>1.0000000</penalty>
<hidden>0</hidden>
<idnumber></idnumber>
<answer fraction="0" format="moodle_auto_format">
<text>true</text>
<feedback format="html">
<text><![CDATA[<p>This space has infinitely many subspace!</p><br>]]></text>
</feedback>
</answer>
<answer fraction="100" format="moodle_auto_format">
<text>false</text>
<feedback format="html">
<text><![CDATA[<p>Bravo.<br></p>]]></text>
</feedback>
</answer>
</question>
</quiz>
Antwort1
Hier ist eine Lösung, die LuaXML verwendet. Die Hauptbibliothek heißt moodle-transform.lua
:
local transform = require "luaxml-transform"
local domobject = require "luaxml-domobject"
local transformer = transform.new()
transformer:add_action("p", "@<.>\n\n", {verbatim=true})
transformer:add_action("div", "@<.>\n", {verbatim=true})
transformer:add_action("*", "@<.>", {verbatim=true})
-- ********************
-- helper functions
-- ********************
local function get_cdata(element, selector)
local t = {}
local selector = selector or "text"
for _, el in ipairs(element:query_selector(selector)) do
for _, child in ipairs(el:get_children()) do
if child._type == "CDATA" then
local handle_greater = child._text:gsub("<%s", "< "):gsub(">%s", "> ")
-- we pack everything in <div>, to force verbatim processing
t[#t+1] = transformer:parse_xml("<div>" .. handle_greater .. "</div>")
-- for x,y in pairs(child) do print(x,y) end
else
t[#t+1] = child:get_text()
end
end
end
-- remove spurious spaces
for k,v in ipairs(t) do t[k] = v:gsub("^%s*", ""):gsub("%s*$", "") end
return table.concat(t, " ")
end
local function number_to_boolean(el)
local number = el:get_text()
if number == "1" then return true end
return false
end
-- map from XML elements to Moodle fields
local basic_data_mapping = {
penalty = "penalty",
defaultgrade = "default grade",
tags = "tags",
generalfeedback = {"feedback", get_cdata},
name = {"name", get_cdata},
questiontext = {"question", get_cdata}
}
function add_mapping(basic, additional)
local t = {}
for k, v in pairs(basic) do t[k] = v end
for k,v in pairs(additional) do t[k] = v end
return t
end
local function map(data, el, mapping)
local name = el._name
local map = mapping[name]
if map then
if type(map) == "table" then
local newname = map[1]
local fn = map[2]
data[newname] = fn(el)
else
data[map] = el:get_text()
end
end
end
local function get_basic_data(question, mapping)
local mapping = mapping or basic_data_mapping
local data = {}
-- data.name = get_cdata(question, "name text")
-- data.question = get_cdata(question, "questiontext text")
for _, el in ipairs(question:get_children()) do
if el:is_element() then
map(data, el, mapping)
end
end
return data
end
local function get_answers(question)
-- process particular answers
local answers = {}
for _, answer in ipairs(question:query_selector("answer")) do
local fraction = answer:get_attribute("fraction")
local item = get_cdata(answer, "answer > text")
local feedback = get_cdata(answer, "feedback > text")
local tolerance = get_cdata(answer, "tolerance")
if feedback == "" then feedback = nil end
if tolerance == "" then tolerance = nil end
answers[#answers+1] = {item = item, feedback = feedback, fraction = fraction, tolerance = tolerance}
end
return answers
end
local function make_field(field, value)
if value then
return string.format("%s=%s", field, value)
end
return nil
end
local basic_question_fields = {"points", "default grade", "penalty", "fraction", "feedback", "tags"}
local function add_question_fields(fields)
local t = {}
for _, v in ipairs(basic_question_fields) do t[#t+1] = v end
for _, v in ipairs(fields) do t[#t+1] = v end
return t
end
local function make_begin(name, data, fields)
local expanded_fields = {}
local fields = fields or basic_question_fields
for _, field in ipairs(fields) do
local value = data[field]
if value and value ~= "" then
if value:match("[%s]") then value = "{" .. value .. "}" end
expanded_fields[#expanded_fields+1] = make_field(field, value)
end
end
return string.format("\\begin{%s}[%s]{%s}", name, table.concat(expanded_fields, ","), data.name)
end
local function format_answers(answers, extra_fields, mark_fraction)
local extra_fields = extra_fields or {}
local t = {}
for _, v in ipairs(answers) do
local options = {}
options[1] = make_field("fraction", v.fraction)
local item = "\\item"
if mark_fraction then
if v.fraction == "100" then item = item .. "*" end
end
options[#options+1] = make_field("feedback", v.feedback)
for _,y in ipairs(extra_fields) do options[#options+1] = make_field(y, v[y]) end
t[#t+1] = item .. "[" .. table.concat(options, ",") .. "]" .. v.item
end
return table.concat(t, "\n")
end
local function make_end(environment)
return "\\end{" .. environment .. "}"
end
-- ********************
-- question handlers
-- ********************
local function handle_category(question)
local category = get_cdata(question, "category text") or ""
-- category = category:gsub("%$course%$%/", "")
-- get top level of the category hiearchy
category = category:match("[^%/]+$")
-- local info = get_cdata(question, "info text") or ""
-- it seems that category info isn't supported inside quiz environment
local info = ""
return string.format("\\setcategory{%s}\n%s\n", category, info)
end
multichoice_begin = add_question_fields {"single","numbering","shuffle", "fraction",}
local multichoice_mapping = add_mapping(basic_data_mapping,
{
single = "single",
shuffleanswers = {"shuffle", number_to_boolean} ,
answernumbering = "numbering",
}
)
local function handle_multichoice(question)
local data = get_basic_data(question, multichoice_mapping)
data.answers = get_answers(question)
local result = {}
result[#result+1] = make_begin("multi", data, multichoice_begin)
result[#result+1] = data.question
result[#result+1] = format_answers(data.answers)
result[#result+1] = make_end("multi")
return table.concat(result, "\n")-- data.question
end
local numerical_mapping = add_mapping(basic_data_mapping,
{
tolerance = "tolerance",
})
local numeric_begin = add_question_fields {"tolerance"}
local function handle_numerical(question)
local result = {}
local data = get_basic_data(question, numerical_mapping)
data.answers = get_answers(question)
result[#result+1] = make_begin("numerical", data, numeric_begin)
result[#result+1] = data.question
result[#result+1] = format_answers(data.answers,{"tolerance"})
result[#result+1] = make_end("numerical")
return table.concat(result, "\n")
end
local truefalse_mapping = add_mapping(basic_data_mapping,
{})
local function handle_truefalse(question)
local result = {}
local data = get_basic_data(question, truefalse_mapping)
data.answers = get_answers(question)
result[#result+1] = make_begin("truefalse", data)
result[#result+1] = data.question
result[#result+1] = format_answers(data.answers, nil, true)
result[#result+1] = make_end("truefalse")
return table.concat(result, "\n")
end
local function handle_shortanswer(question)
local result = {}
local data = get_basic_data(question)
data.answers = get_answers(question)
result[#result+1] = make_begin("shortanswer", data)
result[#result+1] = data.question
result[#result+1] = format_answers(data.answers)
result[#result+1] = make_end("shortanswer")
return table.concat(result, "\n")
end
local function handle_essay(question)
local result = {}
local data = get_basic_data(question)
data.answers = get_answers(question)
result[#result+1] = make_begin("essay", data)
result[#result+1] = data.question
result[#result+1] = format_answers(data.answers)
result[#result+1] = make_end("essay")
return table.concat(result, "\n")
end
local actions = {
category = handle_category,
multichoice = handle_multichoice,
numerical = handle_numerical,
truefalse = handle_truefalse,
shortanswer = handle_shortanswer,
essay = handle_essay,
}
local function transform(dom)
-- buffer for tranformed LaTeX code
local result = {"\\begin{quiz}{Unnamed}"}
-- loop over all questions, and convert them to LaTeX
for _, question in ipairs(dom:query_selector("question")) do
-- we have a special handler for each question type
local qtype = question:get_attribute("type")
-- find action for the current question in the table of actions
local action = actions[qtype]
if action then
-- transform current question to LaTeX
result[#result+1] = action(question)
end
end
result[#result+1] = "\\end{quiz}"
return table.concat(result, "\n")
end
local function process(text)
local dom = domobject.parse(text)
return transform(dom)
end
local function process_file(filename)
local f = io.open(filename, "r")
if f then
local text = f:read("*all")
f:close()
return process(text)
end
end
return {
transform = transform,
process = process,
process_file = process_file
}
Dabei werden die DOM-Funktionen von LuaXML verwendet, um Moodle-Elemente und -Attribute in eine Lua-Tabelle abzurufen, die dann in die vom Moodle-Paket verwendete Syntax umgewandelt wird.
Es kann von der Befehlszeile aus verwendet werden, beispielsweise mit dem folgenden Skript moodletotex.lua
:
kpse.set_program_name "luatex"
local moodle = require "moodle-transform"
local text = io.read("*all")
print(moodle.process(text))
Es kann mit diesem Befehl verwendet werden:
texlua moodletotex.lua < test.xml > output.tex
Dadurch wird TeX-Code in der output.tex
Datei gespeichert:
\begin{quiz}{Unnamed}
\setcategory{Default for my course}
\begin{multi}[default grade=10.0000000,penalty=0.3333333,feedback={\( A_4 \) is the subgroup of the symmetric group \( S_4 \) consisting of even permutations.},single=true,numbering=abc]{Querstion 2}
The number of the subgroups of the alternating group \( A_4 \) is:
\item[fraction=0,feedback=oops!]8
\item[fraction=0,feedback=oops!]9
\item[fraction=100,feedback=Yes!]10
\item[fraction=0,feedback=oops!]11
\end{multi}
\begin{multi}[default grade=3.0000000,penalty=0.3333333,single=true,numbering=abc]{Question 6}
What is \( \lim_{n\to\infty}(1+\frac{1}{n})^n \)?
\item[fraction=100,feedback=Yes.]e
\item[fraction=100]exp(1)
\item[fraction=80,feedback=Good.]2.7
\item[fraction=50,feedback=near!]2.9
\end{multi}
\begin{numerical}[default grade=7.0000000,penalty=0.3333333]{Question 5}
What is the approximate value of \( \int_1^2\frac{1}{x}dx? \)
\item[fraction=100,tolerance=0.001]0.693147
\item[fraction=50,tolerance=0.01]0.693147
\end{numerical}
\begin{shortanswer}[default grade=2.0000000,penalty=0.3333333,feedback={You should specify a number.}]{Question 3}
What is the number of the subgroups of the alternating group \( A_4 \)?
\item[fraction=100]10
\item[fraction=100]ten
\item[fraction=100]dix
\end{shortanswer}
\begin{truefalse}[default grade=1.0000000,penalty=1.0000000,feedback={General feedback: Subspace means subvector space.}]{Question 1}
The vector space \( \mathbb{R}^3 \) has just four subspaces.
\item[fraction=0,feedback=This space has infinitely many subspace!]true
\item*[fraction=100,feedback=Bravo.]false
\end{truefalse}
\end{quiz}
Sie können auch ein einfaches LaTeX-Paket erstellen moodleimport.sty
:
\ProvidesPackage{moodletotex}
\RequirePackage{moodle}
\RequirePackage{luacode}
\begin{luacode*}
moodleimport = require "moodle-transform"
function moodle_print(result)
for line in result:gmatch("([^\n]+)") do
tex.sprint(line)
end
end
\end{luacode*}
\newcommand\importmoodle[1]{%
\luaexec{%
local result = moodleimport.process_file("#1")
moodle_print(result)
}
}%
Es kann wie folgt verwendet werden:
\documentclass{article}
\usepackage{moodleimport}
\begin{document}
\importmoodle{test.xml}
\end{document}
Sie müssen dieses Dokument mit kompilieren lualatex
.
Hier ist ein Ergebnis für den Aufsatz:
Antwort2
Die von @michal.h21 erarbeitete Lösung ist sehr gut. Als aktueller Betreuer des LaTeX-Pakets moodle
habe ich nach einer Lösung wie dieser gesucht, um die Testsuite auf ein neues Niveau zu bringen.
Wenn es jemandem helfen kann, habe ich gerade eine sehr bescheidene Python-Lösung veröffentlichtHier. Es befindet sich noch in der Phase „in Arbeit“ und Beiträge sind willkommen (ich habe nicht vor, in naher Zukunft daran zu arbeiten).
Dieses Python-Skript scheint die XML-Datei des OP sauber zu konvertieren. Es ist auch für die Verarbeitung von Bildern gedacht.
Antwort3
Ich arbeite gerade an dem von @mgk gestarteten Projekt, in einemGabel:
- Ich habe einige Fehler im Zusammenhang mit Bilddateien behoben (wurden nicht immer richtig gespeichert)
- Ich habe den Fragetyp „calculatedsimple“ „unterstützt“, indem ich ihn in den Typ „shortanswer“ konvertiert habe, um den generierten Code „kompilieren“ zu können.
- Ich habe mehrere Optionen festgelegt, um den Code in getrennten Dateien zu generieren
- ...
Ich werde in den nächsten Wochen sicherlich eine umfassende Umgestaltung des Codes vornehmen, wäre aber dankbar für jede Nachricht, ob jemand bereits ähnliche Dinge getan hat. @projetmbc, Sie haben die Umgestaltung vorgeschlagen, aber haben Sie am Ende etwas unternommen?