
私は 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が始めたプロジェクトに取り組んでいます。フォーク:
- 画像ファイルに関連するいくつかのバグを修正しました(常に正しく保存されるわけではありませんでした)
- 生成されたコードを「コンパイル」できるように、「calculatedsimple」質問タイプを「shortanswer」タイプに変換して「サポート」しました。
- 別々のファイルにコードを生成するためのオプションをいくつか設定しました
- ...
今後数週間で、コードに重要なリファクタリングを行うことは間違いありませんが、同様のことを行っている方がいらっしゃれば、教えていただけると幸いです。@projetmbc さんはリファクタリングを提案されましたが、最終的に何か実行されましたか?