Moodle-XML-Konvertierung nach LaTeX

Moodle-XML-Konvertierung nach LaTeX

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", "&lt; "):gsub(">%s", "&gt; ")
        -- 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.texDatei 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.

Dies ist das Ergebnis: Bildbeschreibung hier eingeben

Hier ist ein Ergebnis für den Aufsatz:

Bildbeschreibung hier eingeben

Antwort2

Die von @michal.h21 erarbeitete Lösung ist sehr gut. Als aktueller Betreuer des LaTeX-Pakets moodlehabe 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?

verwandte Informationen