moodle xml 转换为 LaTeX

moodle xml 转换为 LaTeX

我对 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", "&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
}

它的作用是使用 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 你提出了重构,但你最终做了什么吗?

相关内容