我对 moodle 软件包很熟悉。但我有一个相反的问题。
有没有方法或工具可以将 xml moodle 问题库转换为 LaTeX?
编辑:xml 文件的示例如下
<?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>
答案1
这是一个使用 LuaXML 的解决方案。主库名为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
}
它的作用是使用 LuaXML 的 DOM 函数将 Moodle 元素和属性检索到 Lua 表中,然后将其转换为 Moodle 包使用的语法。
它可以从命令行使用,例如使用以下脚本moodletotex.lua
:
kpse.set_program_name "luatex"
local moodle = require "moodle-transform"
local text = io.read("*all")
print(moodle.process(text))
它可以与以下命令一起使用:
texlua moodletotex.lua < test.xml > output.tex
它会将 TeX 代码保存到output.tex
文件中:
\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}
您还可以制作一个简单的 LaTeX 包,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)
}
}%
它可以像这样使用:
\documentclass{article}
\usepackage{moodleimport}
\begin{document}
\importmoodle{test.xml}
\end{document}
您需要使用 来编译该文档lualatex
。
以下是论文的结果:
答案2
@michal.h21 的解决方案非常巧妙。作为 LaTeX 包的当前维护者moodle
,我一直在寻找这样的解决方案,以便将其测试套件提升到另一个层次。
如果它可以帮助某人,我刚刚发布了一个非常温和的 Python 解决方案这里。它仍处于“进行中”状态,欢迎大家贡献力量(我近期不打算从事这项工作)。
这个 Python 脚本似乎可以干净地转换 OP 的 XML 文件。它也可以处理图片。
答案3
我实际上正在从事由 @mgk 发起的项目,叉:
- 我已经纠正了一些与图像文件相关的错误(并非总是正确保存)
- 我已经“支持”了“计算简单”问题类型,通过将其转换为“简答”类型,以便能够“编译”生成的代码。
- 我设置了几个选项来在单独的文件中生成代码
- ...
我肯定会在接下来的几周对代码进行重要的重构,但如果有人做过类似的事情,我将不胜感激。@projetmbc 你提出了重构,但你最终做了什么吗?