diff options
author | Karl Berry <karl@freefriends.org> | 2016-01-12 23:02:18 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2016-01-12 23:02:18 +0000 |
commit | 40e1a1e6a0ab09c8088183f91b4fe3752c207cd2 (patch) | |
tree | 83bc1a18dbe88c42382833162ac350b57ecc566b /Master/texmf-dist/source/latex/moodle/moodle.dtx | |
parent | 29594b03973a1e16771ef0f22c06175799117bc7 (diff) |
moodle (12jan16)
git-svn-id: svn://tug.org/texlive/trunk@39367 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/source/latex/moodle/moodle.dtx')
-rw-r--r-- | Master/texmf-dist/source/latex/moodle/moodle.dtx | 3316 |
1 files changed, 3316 insertions, 0 deletions
diff --git a/Master/texmf-dist/source/latex/moodle/moodle.dtx b/Master/texmf-dist/source/latex/moodle/moodle.dtx new file mode 100644 index 00000000000..02b43f79238 --- /dev/null +++ b/Master/texmf-dist/source/latex/moodle/moodle.dtx @@ -0,0 +1,3316 @@ +% \iffalse meta-comment +% +% moodle.dtx +% Copyright 2016 by Anders O.F. Hendrickson (anders.hendrickson@snc.edu) +% +% This work may be distributed and/or modified under the +% conditions of the LaTeX Project Public License, either version 1.3 +% of this license or (at your option) any later version. +% The latest version of this license is in +% http://www.latex-project.org/lppl.txt +% and version 1.3 or later is part of all distributions of LaTeX +% version 2005/12/01 or later. +% +% This work has the LPPL maintenance status `maintained'. +% +% The Current Maintainer of this work is Anders O.F. Hendrickson. +% +% This work consists of the files moodle.dtx and moodle.ins +% and the derived files moodle.sty and getitems.sty. +% +% \fi +% +% \iffalse +%<*driver> +\ProvidesFile{moodle.dtx} +%</driver> +%<package>\NeedsTeXFormat{LaTeX2e}[1999/12/01] +%<package>\ProvidesPackage{moodle} +%<*package> + [2016/01/11 v0.5 Moodle quiz XML generation] +%</package> +% +%<*driver> +\documentclass{ltxdoc} +\usepackage{moodle} +\usepackage{amssymb} +\usepackage{fancyvrb} +\EnableCrossrefs +\CodelineIndex +\OnlyDescription +\RecordChanges +\begin{document} + \DocInput{moodle.dtx} + \PrintChanges + %\PrintIndex +\end{document} +%</driver> +% \fi +% +% \CheckSum{3827} +% +% \CharacterTable +% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z +% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z +% Digits \0\1\2\3\4\5\6\7\8\9 +% Exclamation \! Double quote \" Hash (number) \# +% Dollar \$ Percent \% Ampersand \& +% Acute accent \' Left paren \( Right paren \) +% Asterisk \* Plus \+ Comma \, +% Minus \- Point \. Solidus \/ +% Colon \: Semicolon \; Less than \< +% Equals \= Greater than \> Question mark \? +% Commercial at \@ Left bracket \[ Backslash \\ +% Right bracket \] Circumflex \^ Underscore \_ +% Grave accent \` Left brace \{ Vertical bar \| +% Right brace \} Tilde \~} +% +% +% \changes{v0.5}{2016/01/05}{Initial version} +% +% \GetFileInfo{moodle.sty} +% +% \DoNotIndex{\newcommand,\newenvironment,\def} +% +% +% \title{The \textsf{moodle} package: \\ +% generating Moodle quizzes via \LaTeX% +% \thanks{This document +% corresponds to \textsf{moodle.sty}~\fileversion, dated \filedate.}} +% \author{Anders Hendrickson\\ St.~Norbert College, De~Pere, WI, USA \\ \url{anders.hendrickson@snc.edu}} +% \date{January 11, 2016} +% +% \maketitle +% +% \providecommand\TikZ{Ti\emph{k}Z} +% +% \section{Motivation} +% +% The acronym Moodle stands for ``Modular Object-Oriented Dynamic Learning Environment.'' +% It is an open source learning management system employed by many universities, colleges, +% and high schools to provide digital access to course materials, such as +% notes, video lectures, forums, and the like; see +% \url{https://moodle.com/moodle-lms/} for more information. +% One of the many useful +% features of Moodle is that mathematical and scientific notation can be entered in +% \LaTeX\ or \TeX\ code, which will be typeset either through a built-in \TeX\ filter +% or by invoking MathJax. +% +% For instructors who want to give students frequent feedback, +% but lack the time to do so, a particularly valuable module in Moodle +% is the \emph{quiz}. A Moodle quiz can consist of several different types of +% questions---not only multiple choice or true/false questions, but also +% questions requiring a short phrase or numerical answer, and even essay +% questions. All but the essay questions are automatically graded by the +% system, and the instructor has full control over how often the quiz may be +% attempted, its duration, and so forth. Feedback can be tailored to specific +% mistakes the student makes. +% +% All these features make Moodle quizzes very useful tools for instructors +% who have access to them. +% Unfortunately, the primary way to create or edit a Moodle quiz +% is through a web-based interface that can be slow to operate. +% To users of \LaTeX, accustomed to the speed of typing source code on +% a keyboard alone, the agonizing slowness of switching between mouse and keyboard +% to navigate a web form with its myriad dropdown boxes, radio buttons, +% compounded with a perceptible time lag as one's Moodle server responds to requests, +% can produce a very frustrating experience. Moreover, editing is entirely +% impossible without network access. +% +% Once the quiz is written, there is no easy way to view and proofread all the +% information of which it is made. Each question is edited on a separate webpage, +% which is so full of options that it cannot be viewed on a single screen. +% An instructor has to spend much time checking over the newly created quiz in +% order to be confident there are no errors. +% +% Added to all this is the frustration of managing graphics. +% If a question requires an image---say, asking a calculus student to interpret +% the graph of a function---the image must first be produced as a standalone file +% (e.g., in JPG or PNG format), uploaded to Moodle, and then chosen in a web-based +% HTML editor. Great is the vexation of the instructor who decided to alter a +% question, as there are more and more possibilities of error whenever multiple +% files must be kept synchronized. +% +% Users of \LaTeX\ are also accustomed to the speed and flexibility that +% comes from defining their own macros, which may be as brief as writing +% |\R| instead of |\mathbb{R}| +% or as complex as macros that generate entire paragraphs of text. +% The Moodle editor, by contrast, requires you to type |\mathbb{R}| +% every single time you want $\mathbb{R}$. +% +% Finally, there is the question of archiving and reusing one's work. +% Much, much work goes into creating Moodle quizzes, which then reside +% on a Moodle server somewhere in the cloud in a format neither easily browsable +% nor easily modifiable. +% +% \LaTeX\ itself has the power to solve all these difficulties: +% it is swift to edit and swifter to compile a \LaTeX\ document, +% and the PDF may be previewed onscreen or printed out for ease of proofreading. +% Mathematical graphics can be integrated within the main file through \TikZ, +% and of course \LaTeX\ macros can be customized. +% Using the present \textsf{moodle.sty} package, +% a quiz author can type a quiz using familiar \LaTeX\ syntax and document +% structure. Upon compilation, \LaTeX\ will generate both a well-organized +% PDF that is easy to proofread and an XML file that can be uploaded directly +% to Moodle. The entire process is far faster than using Moodle's own +% web-based editor, makes it easier to catch one's mistakes, +% and the ultimate source code of one's work is a human-readable |.tex| file +% that can be archived, browsed, and edited offline. +% +% \section{Workflow} +% The process of creating a quiz in Moodle using this package is as follows: +% \begin{enumerate} +% \item Write a \LaTeX\ document using |\usepackage{moodle}| as described below. +% \item Compile the document to PDF using pdf\LaTeX. This will also produce the file +% \meta{jobname}|-moodle.xml|. +% \item Open Moodle, navigate to the desired course, +% and under ``Question bank'' select ``Import.'' +% \item Select ``Moodle XML format,'' choose the XML file to upload, and press ``Import.'' +% \item After Moodle verifies that the questions have been imported correctly, +% you may add them to your quizzes. +% \end{enumerate} +% +% \section{Usage}\label{sect:usage} +% \subsection{Example Document} +% +% The following pages presume the reader already has some familiarity with creating +% and editing Moodle quizzes through the web interface. +% The |xkeyval| package is used to provide a key-value interface. +% Here is a simple example document: +% \begin{verbatim} +% \documentclass[12pt]{article} +% \usepackage{moodle} +% +% \begin{document} +% +% \begin{quiz}{My first quiz} +% \begin{numerical}[points=2]{Basic addition} +% What is $8+3$? +% \item 11 +% \end{numerical} +% +% \begin{shortanswer}[case sensitive=true]{Newton's name} +% What was Newton's first name? +% \item Isaac +% \item[fraction=0, feedback={No, silly!}] Fig +% \item{fraction=0} Sir +% \end{shortanswer} +% +% \begin{multi}[points=3]{A first derivative} +% What is the first derivative of $x^3$? +% \item $\frac{1}{4} x^4+C$ +% \item* $3x^2$ +% \item $51$ +% \end{multi} +% \end{quiz} +% +% \end{document} +% \end{verbatim} +% Key features to note in this first example are that a |quiz| environment +% contains several question environments. +% Each question takes a name as a mandatory argument, +% and it may also take optional key-value arguments within brackets. +% The question environments resemble list environments +% such as |itemize| or |enumerate|, in that answers are set off by +% |\item|'s, but the question itself is the text that occurs before +% the first |\item|. +% +% \DescribeMacro{draft} +% If the package option |draft| is invoked, by calling |\usepackage[draft]{moodle}|, +% then no XML file will be generated. This is especially useful while editing a quiz +% containing graphics, so as to avoid the time spent converting image files. +% +% \newpage +% \subsection{Quiz and Question Environments} +% +% \DescribeMacro{quiz} +% A |.tex| document to generate Moodle quizzes contains one or more +% |quiz| environments, within which various question environments are nested. +% The required argument to the |quiz| environment names the ``question bank'' +% to which the questions inside will belong after being uploaded to moodle. +% \begin{center} +% |\begin{quiz}|\oarg{options}\marg{question bank name}% +% \end{center} +% There are no |quiz|-specific options, +% but any \meta{options} set with |\begin{quiz}| +% will be inherited by all questions contained within that |quiz| environment. +% +% \DescribeMacro{\moodleset} +% Options may also be set at any time with |\moodleset|\marg{options}; +% these changes are local to \TeX-groups. +% \bigskip +% +% The syntax for each question environment is +% \begin{quote} +% |\begin|\marg{question type}\oarg{question options}\marg{question name} \\ +% \rule{2em}{0pt}\meta{question text} \\ +% \rule{2em}{0pt}|\item| \meta{item} \\ +% \rule{2em}{0pt}\quad$\vdots$ \\ +% \rule{2em}{0pt}|\item| \meta{item} \\ +% |\end|\marg{question type} +% \end{quote} +% The meaning of the \meta{item}s varies depending on the question type, +% but they usually are answers to the question. +% Details will be given below. +% +% The following key-value options may be set for all questions: +% +% \DescribeMacro{points}\DescribeMacro{default grade} +% By default, each question is worth 1 point on the quiz. +% This may be changed with the |points| key or its synonym, |default grade|; +% for example, |points=2| makes that question worth two points. +% +% \DescribeMacro{penalty} +% The |penalty| is the fraction of points that is taken off for each wrong attempt; +% it may be set to any value between 0 and 1. +% The default is |penalty=0.10|. +% +% \DescribeMacro{fraction} +% In most question types, it is possible to designate some answers as being +% worth partial credit---that is, some fraction of a completely correct answer. +% The |fraction| key may be set to any integer from |0| (entirely wrong) to |100| (entirely correct). +% +% \DescribeMacro{feedback} +% The |feedback| key sets text that will appear to the student after completing the quiz. +% For example, one might set +% \begin{center} +% |feedback={A question like this will appear on the exam.}| +% \end{center} +% The desired feedback should be included in braces. +% +% Two kinds of feedback can be given. If the |feedback| key is set for a +% question, then that feedback will appear to each student regardless of the student's answer. +% Answer-specific feedback (perhaps explaining a common mistake) +% may also be given by setting the |feedback| key \emph{at the individual answer}. +% +% \newpage +% \subsection{Question Types} +% +% We next discuss the various question types supported by \textsf{moodle.sty} +% and the options that may be set. +% +% \subsubsection{Multiple Choice} +% +% \DescribeMacro{multi} +% The syntax for a classic multiple choice question, +% with only one correct answer, is as follows: +% \begin{quote} +% |\begin{multi}|\oarg{question options}\marg{question name} \\ +% \rule{2em}{0pt}\meta{question text} \\ +% \rule{2em}{0pt}|\item*| \meta{correct answer} \\ +% \rule{2em}{0pt}|\item|\oarg{options} \meta{wrong answer} \\ +% \rule{2em}{0pt}\quad$\vdots$ \\ +% \rule{2em}{0pt}|\item|\oarg{options} \meta{wrong answer} \\ +% |\end{multi}| +% \end{quote} +% The correct answer is designated by the asterisk |*| after the |\item|; +% it need not appear first in the list. +% +% +% \DescribeMacro{shuffle} +% The boolean key |shuffle| determines whether Moodle will +% rearrange the possible answers in a random order. +% Setting |shuffle=false| will guarantee that the answer appear +% in the order they were typed; the default is |shuffle=true|. +% +% \DescribeMacro{numbering} +% Moodle offers different options for numbering the possible answers. +% You may set the |numbering| key to any of the following values, +% which mirror the usual \LaTeX\ syntax: +% |alph|, |Alph|, |arabic|, |roman|, |Roman|, and |none|. +% Calling |numbering=none| produces an unnumbered list of answers. +% The Moodle syntax of |abc|, |ABC|, |123|, |iii|, and |IIII| is also acceptable, +% but note that it requires \emph{four} |I|'s to obtain upper-case Roman numerals this way. +% +% \DescribeMacro{fraction} +% The |fraction| key can be used to designate some wrong answers +% as being worth partial credit. For example, a question might read thus: +% \begin{verbatim} +% \begin{multi}{my question} +% Compute $\int 4x^3\,dx$. +% \item* $x^4+C$ +% \item[fraction=50] $x^4$ +% \item $12x^2$ +% \end{multi} +% \end{verbatim} +% Thus the asterisk |*| is shorthand for |fraction=100|, +% whereas a bare |\item| sets |fraction=0|. +% +% \DescribeMacro{single}\DescribeMacro{multiple} +% By default, the |multi| environment produces +% a multiple choice question operates with only one correct answer; +% this is called |single| mode, and on Moodle it appears with radio buttons. +% It is also possible to write questions with more than one correct answer, +% asking the user to check all correct answers. +% To do this, use the key |multiple| or |single=false|. +% +% The worth of each correct answers in |multiple| mode may be set by |fraction|, +% but Moodle will require that all the fractions add up to at least 100. +% If you simply designate each correct answer with |\item*|, then \textsf{moodle.sty} +% will divide the 100\% equally among those answers. +% For example, the following two examples produce equivalent results: +% \begin{verbatim} +% \begin{multi}[multiple]{my question} +% Which numbers are prime? +% \item* 5 +% \item 6 +% \item* 7 +% \item 8 +% \end{multi} +% \end{verbatim} +% \begin{verbatim} +% \begin{multi}[multiple]{my question} +% Which numbers are prime? +% \item[fraction=50] 5 +% \item 6 +% \item[fraction=50] 7 +% \item 8 +% \end{multi} +% \end{verbatim} +% +% \subsubsection{Numerical} +% +% A numerical question in Moodle requires the student +% to input a real number in decimal form. +% Its typical format is +% \begin{quote} +% |\begin{numerical}|\oarg{question options}\marg{question name} \\ +% \rule{2em}{0pt}\meta{question text} \\ +% \rule{2em}{0pt}|\item|\oarg{options} \meta{correct answer} \\ +% |\end{numerical}| +% \end{quote} +% If there is more than one correct answer, additional |\item|'s may be included. +% Because this is not a multiple choice question, there is no need to provide +% incorrect answers. There may nevertheless be reasons to include incorrect answers. +% For example, partially correct answers may be specified by setting the |fraction| key. +% Feedback for a common mistake may be given by including the incorrect answer like this: +% \begin{quote}\footnotesize +% |\item[fraction=0,feedback={You forgot to antidifferentiate!}]| \meta{incorrect answer} +% \end{quote} +% +% \newpage +% \subsubsection{Short Answer} +% A short answer question resembles a numerical question: the student is to fill +% in a text box with a missing word or phrase. +% \begin{quote} +% |\begin{shortanswer}|\oarg{question options}\marg{question name} \\ +% \rule{2em}{0pt}\meta{question text} \\ +% \rule{2em}{0pt}|\item|\oarg{options} \meta{correct answer} \\ +% \rule{2em}{0pt}\quad$\vdots$ \\ +% \rule{2em}{0pt}|\item|\oarg{options} \meta{correct answer} \\ +% |\end{shortanswer}| +% \end{quote} +% You can make the text box appear as part of the question with the +% control sequence |\blank|. For example, +% your question might read +% \begin{verbatim} +% \begin{shortanswer}{Leibniz} +% Newton's rival was Gottfried Wilhelm \blank. +% \item Leibniz +% \item Leibniz. +% \end{shortanswer} +% \end{verbatim} +% Note that as the blank occurred at the end of a sentence, +% we included two answers, +% lest students get the question wrong merely by +% including or omitting a period. +% +% \DescribeMacro{case sensitive}\DescribeMacro{usecase} +% By default, Moodle ignores the distinction between upper- and lower-case letters +% when grading a short answer question. +% You can make a question case-sensitive with the key |case sensitive| +% or its shorter synonym |usecase|. +% +% \subsubsection{Essay Questions} +% Instructors may ask essay questions on a Moodle quiz, +% although Moodle's software is not up to the task of grading them! +% Instead each essay question answer must be graded manually by the +% instructor or a teaching assistant. +% \begin{quote} +% |\begin{essay}|\oarg{question options}\marg{question name} \\ +% \rule{2em}{0pt}\meta{question text} \\ +% \rule{2em}{0pt}|\item|\oarg{options} \meta{notes for grader} \\ +% \rule{2em}{0pt}\quad$\vdots$ \\ +% \rule{2em}{0pt}|\item|\oarg{options} \meta{notes for grader} \\ +% |\end{essay}| +% \end{quote} +% Instead of containing answers, the |\item| tags for the |essay| question +% contain notes that will appear to whoever is grading the question manually. +% +% \DescribeMacro{response required} +% Although Moodle cannot grade the content of an essay question, +% it can at least determine whether the question has been left blank. +% If the |response required| key is set, Moodle will insist that the student +% enter something in the blank before accepting the quiz as completed. +% +% \newpage +% \DescribeMacro{response format} +% Moodle offers five different ways for students to enter and/or upload their +% answers to an essay question. You may choose one of these five options: +% \begin{description} +% \item[{\tt html}] An editor with the ability to format HTML responses +% including markup for italics, boldface, etc. This is the default. +% \item[{\tt file}] A file picker allowing the student to upload a file, such as +% a PDF or DOC file, containing the essay. +% \item[{\tt html+file}] The same HTML editor as above, but with the ability +% to upload files as well. This permits some students to type answers +% directly into the web form, and others to compose their essays in +% another program first. +% \item[{\tt text}] This editor allows only for entering plain text without any markup. +% \item[{\tt monospaced}] This yields a plain text editor, without any markup, +% and with a fixed-width font. This could be useful for entering code snippets, for example. +% \end{description} +% +% \DescribeMacro{response field lines} +% The key |response field lines| controls the height of the input box. +% The default is |response field lines=15|. +% +% \DescribeMacro{attachments allowed} +% The |attachments allowed| key controls \emph{how many} attachments a student is +% allowed to upload. Permissible values are |0|, |1|, |2|, |3|, or |unlimited|. +% +% \DescribeMacro{attachments required} +% You may also require the student to upload a certain number of attachments +% by setting |attachments required| to |0|, |1|, |2|, or |3|. +% +% \DescribeMacro{response template} +% Finally, you may preload the essay question with a template that the student +% will edit and/or type over, with the key |response template=|\marg{template}. +% The \meta{template} should be enclosed in braces. +% +% \subsection{Matching Questions} +% +% A matching question offers a series of subquestions +% and a set of possible answers from which to choose. +% If there are $m$ questions and $n$ possible answers, +% a matching question will look like this: +% +% \begin{quote} +% |\begin{matching}|\oarg{question options}\marg{question name} \\ +% \rule{2em}{0pt}\meta{question text} \\ +% \rule{2em}{0pt}|\item|\oarg{options} \meta{question 1} |\answer| \meta{answer 1}\\ +% \rule{2em}{0pt}|\item|\oarg{options} \meta{question 2} |\answer| \meta{answer 2}\\ +% \rule{2em}{0pt}\quad$\vdots$ \\ +% \rule{2em}{0pt}|\item|\oarg{options} \meta{question $m$} |\answer| \meta{answer $m$}\\ +% \rule{2em}{0pt}|\item|\oarg{options} |\answer| \meta{answer $m+1$}\\ +% \rule{2em}{0pt}\quad$\vdots$ \\ +% \rule{2em}{0pt}|\item|\oarg{options} |\answer| \meta{answer $n$}\\ +% |\end{matching}| +% \end{quote} +% Answers $1$ through $m$ correspond to questions $1$ through $m$; +% answers $m+1$ through $n$ are ``decoy'' answers. +% If multiple questions should have the same answer, +% be sure your typed answer match exactly, so that Moodle will not +% create duplicate copies of the same answer! +% +% \DescribeMacro{shuffle} +% The |matching| question accepts the option of |shuffle| to randomly +% permute the questions and answers; by default |shuffle=true|. +% +% \DescribeMacro{drag and drop}\DescribeMacro{dd} +% Moodle has two formats for matching questions. +% The older matching question offered a dropdown box +% for choosing the answer to each question; +% the newer ``drag and drop matching'' format +% shows all questions in one column, +% all answers in a second column, and allows students to drag the correct +% answer to the question using a mouse. +% To enable drag-and-drop matching, use the key `|drag and drop|' or `|dd|' for +% short. The default is |dd=false|. If you choose the older format, +% then due to the limitations of dropdown boxes, no \LaTeX\ or HTML code can be used +% in the answers. +% +% \subsection{Cloze Questions} +% +% A ``cloze question'' has one or more subquestions embedded within a passage of text. +% For example, you might ask students to fill in several missing words within +% a sentence, or calculate several coefficients of a polynomial. +% To encode cloze questions in \LaTeX\ using this package is easy: +% you simply nest one or more |multi|, |shortanswer|, or |numerical| environments +% within a |cloze| environment, as in the following example: +% \begin{verbatim} +% \begin{cloze}{my cloze question} \\ +% Thanks to calculus, invented by Isaac +% \begin{shortanswer} +% \item Newton +% \end{shortanswer}, +% we know that the derivative of $x^2$ is +% \begin{multi}[horizontal] +% \item $2x$ +% \item* $\frac{1}{3} x^3 + C$ +% \item $0$ +% \end{multi} +% and that $\int_0^2 x^2\,dx$ equals +% \begin{numerical} +% \item[tolerance=0.0004] 2.667 +% \end{numerical}. +% Thanks, Isaac! +% \end{cloze} +% \end{verbatim} +% Note that when used as a subquestion within a cloze question, +% |\begin{multi}| is \emph{not} followed by name in braces; +% the same is true for the |shortanswer| and |numerical| +% environments. +% +% \DescribeMacro{vertical}\DescribeMacro{horizontal} +% Within a cloze question, by default a multiple choice question is implemented +% as an inline dropdown box. This is visually compact, but it also prevents the +% use of mathematical or HTML formatting. +% Adding the option |vertical| displays the subquestion as a vertical column +% of radio buttons instead; likewise the option |horizontal| creates a horizontal +% row of radio buttons. +% +% \section{Conversion to HTML} +% +% Questions should be typed as usual for \LaTeX, +% including |\$| to obtain dollar signs, |$|'s for math shifts, +% |$$|'s for display math, et cetera. +% The package \textsf{moodle.sty} automatically converts +% this \LaTeX\ code into HTML for web display. +% +% The following table lists \LaTeX\ commands and environments that +% will be converted to HTML: +% \begin{center} +% \begin{tabular}{lll} +% |~| & |\#| & |\$| \\ +% |\&| & |\\| & |\S| \\ +% |\{| & |\}| & {\tt\catcode`\ =12\relax\catcode`\\=12\relax\ }\\ +% |\emph| & |\textbf| & |\par| \\ +% |\ldots| & |\dots| & |\relax| \\ +% |\euro| & |\texteuro| \\ +% |\begin{center}| \\ +% |\begin{enumerate}| \\ +% |\begin{itemize}| \\ +% |\begin{tikzpicture}| \\ +% \end{tabular} +% \end{center} +% Single and double quotation marks and the diacritical commands |\^|, |\'|, |\`|, |\"|, |\~|, and |\H| are also handled, +% as are the characters |\aa|, |\ae|, |\o|, |\ss|, |\l|, and their capitalizations. +% +% In addition, |<| and |>| will be converted to |<| and |>| \emph{within math mode only}. +% If they should be typed outside of math mode, they will be passed as typed to +% the HTML, and probably interpreted by students' browsers as HTML tags +% or other unpredicated results. +% +% Be aware that \emph{\textsf{moodle.sty} does not know how to convert any +% other \TeX\ or \LaTeX\ commands to HTML.} +% If other sequences are used, they may be passed verbatim to the XML file +% or may cause unpredicted results. +% +% If you think of another \LaTeX\ command that should be changed to an HTML equivalent, +% please contact the author at \url{anders.hendrickson@snc.edu} +% so that it may be added to a future revision of the package. +% +% \section{Graphics} +% The \textsf{moodle.sty} package can handle two kinds of graphics seamlessly. +% External graphics files may be included with the |\includegraphics| command +% from the |graphicx| package, +% and graphics may be generated internally using \TikZ. In either case, the +% graphics will be embedded in base-64 encoding directly within +% the Moodle~XML produced. This prevents the hassle of managing separate +% graphics files on the Moodle server, as Moodle will store the picture +% within the question in the question bank. +% +% \DescribeMacro{\includegraphics}\DescribeMacro{height}\DescribeMacro{width} +% When using |\includegraphics|, the only options currently supported +% are |height| and |width|. Attempts to use other |\includegraphics| options, +% such as |scale| or |angle|, will affect the PDF but not the XML output. +% The dimensions set by |height| and |width| are \TeX\ dimensions such as |4in| or |2.3cm|. +% In order to prepare the image for web viewing, this package converts those +% dimensions to pixels using a default of +% 103 pixels per inch.\footnote{This +% number was selected because an image with +% |<IMG HEIGHT=103 WIDTH=103 SRC="...">| showed up as almost exactly 1 inch +% tall and 1 inch wide on several of this author's devices and browsers +% as of January 2016.} +% \DescribeMacro{ppi} +% That value may be changed by setting the |ppi| key (e.g., |ppi=72|); +% this is probably best done for the entire document with a |\moodleset| command, +% rather than image-by-image. +% +% The mechanism used is somewhat fragile and relies upon one \TikZ\ library and three free external programs. +% \begin{enumerate} +% \item \textsf{moodle.sty} invokes |\usetikzlibrary{external}|, +% so that each |tikzpicture| environment is compiled to a freestanding PDF file. +% \item GhostScript (\url{www.ghostscript.com}) is used to convert the PDF output +% from \TikZ\ into a PNG raster graphics file. +% The default command line is presumed to be |gswin64c.exe| +% (if |\ifwindows| from the |ifplatform| package returns true) +% or |gs| (if |\ifwindows| returns false). +% If your system requires a different command line to invoke Ghostscript, +% \DescribeMacro{\ghostscriptcommand} +% you may change it by invoking |\ghostscriptcommand|\marg{executable filename}. +% \item When external graphics files such as JPG or GIF are included, +% the open-source ImageMagick software (\url{www.imagemagick.org}) +% converts each file to PNG format. +% The command line for ImageMagick is the nondescript word |convert|, +% \DescribeMacro{\imagemagickcommand} +% but may be changed by invoking |\imagemagickcommand|\marg{executable filename}. +% \item Finally, the OpenSSL toolkit (\url{www.openssl.org}) is used to base64-encode +% the PNG file so that it can be included directly in the text of the XML file. +% The command line is presumed to be |openssl|, but can be changed with +% \DescribeMacro{\opensslcommand} +% |\opensslcommand|\marg{executable filename}. +% \end{enumerate} +% +% Please note the following vital points to make the graphics handling work: +% \begin{itemize} +% \item As of now, graphics are only supported when compiling directly to a PDF +% with |pdflatex|. Including PS graphics or using \TikZ\ with the DVI$\to$PS workflow is not +% yet supported. +% \item Filenames should not contains spaces or special characters like |_| or |\|. +% \item You must have Ghostscript, ImageMagick, and OpenSSL installed on your system +% to fully use the graphics-handling capabilities of \textsf{moodle.sty}. +% \item \LaTeX\ must be able to call system commands; that is, |\write18| must be enabled. +% For Mik\TeX, this means adding |--enable-write18| to the command line of |pdflatex|; +% for \TeX Live, this means adding |--shell-escape=true|. +% \end{itemize} +% +% \newpage +% \section{Known Limitations and Call for Bug Reports} +% Some features of Moodle quizzes have not yet been implemented in \textsf{moodle.sty}. +% Here is a non-exhaustive list. +% \begin{itemize} +% \item Moodle's feature of designating feedback for correct, +% partially correct, and incorrect answers. +% \item Calculated questions; that is, automatically generated numerical questions +% using randomly chosen numbers. +% \item Hints +% \item Keywords +% \item So-called ``description'' questions. +% \item Shuffled answers in multiple choice cloze subquestions. +% \end{itemize} +% I have used Version 0.5 for one semester's teaching, +% but if other users adopt this package, I fully expect them to find bugs. +% \emph{Please} send all bugs you find to \url{anders.hendrickson@snc.edu}, +% so that I can fix them for subsequent versions. +% +% \section{Compatibility} +% This package has been written for and tested with the implementation of +% Moodle 2.9 run by Moodlerooms for St.~Norbert College in January 2016. +% Future versions of this package will probably include some support for +% specifying your version of Moodle in the |.tex| file to help ensure compatability. +% +% As the ultimate purpose of this package is the generation of XML files, +% future versions of \textsf{moodle.sty} will attempt to maintain backwards +% compatability with earlier versions of regarding the XML output, apart from +% bug fixes. +% Backwards compatibility of the PDF output is not yet guaranteed, however, +% in case the author or users discover better ways for the PDF to display +% the underlying XML data to be proofread. +% +% In other words, compiling your current |.tex| file with a future version +% of \textsf{moodle.sty} should produce the same XML file it does now +% (apart from bug fixes), +% but it might produce a more informative, and hence different, +% PDF output. +% +% +% \section{Unrelated Tip: Quality of Moodle \TeX\ Images} +% This has nothing to do with |moodle.sty|, but is a Frequently Asked Question +% in is own right. +% On some servers, at least, Moodle's default ``\TeX\ Filter'' for +% displaying mathematical notation is of abysmally poor quality, rending mathematics +% as low-resolution PNG's. One solution that has worked for me is to go +% to ``Course Administration $\to$ Filters,'' turn ``\TeX\ Notation'' \emph{off}, +% but turn ``MathJax'' \emph{on}. This forces \TeX\ code to be rendered by MathJax +% instead of Moodle, producing much higher-quality results. +% +% \StopEventually{} +% +% \section{Implementation} +% \subsection{Packages, Options, and Utilities} +% \begin{macrocode} +\newif\ifmoodle@draftmode +\newif\ifmoodle@tikzloaded + +%%DECLARATION OF OPTIONS +\DeclareOption{draft}{\moodle@draftmodetrue} + +\moodle@draftmodefalse +\moodle@tikzloadedfalse + +\ProcessOptions + +\RequirePackage{environ} %To be able to take environment body as a macro argument +\RequirePackage{xkeyval} %For key-handling +\RequirePackage{amssymb} %For \checkmark symbol +\RequirePackage{trimspaces} %To remove extra spaces from strings +\RequirePackage{etex} %Expansion control, detokenization, etc. +\RequirePackage{etoolbox}%List management +\RequirePackage{xpatch} %To patch commands easily in HTML mode +\RequirePackage{array} %For formatting tables in the LaTeX mode of Clozes +\RequirePackage{ifplatform} % To choose Ghostscript commands +\RequirePackage{ifpdf} % Needed to know whether we can convert output from PDF to PNG + +\RequirePackage{getitems} %To gather the header and items + +\let\xa=\expandafter +\def\@star{*}% +\def\@hundred{100}% +\def\@moodle@empty{}% +\def\@relax{\relax}% +% \end{macrocode} +% As the package involves a fair bit of file processing, +% we automate the naming of auxiliary files. +% \begin{macrocode} +\def\jobnamewithsuffixtomacro#1#2{% + \filenamewithsuffixtomacro{#1}{\jobname}{#2}% +} +\def\@jn@quote{"}% +\def\filenamewithsuffixtomacro#1#2#3{% + % #1 = macro to create + % #2 = filename to add suffix to + % #3 = suffix to add + \edef\jn@suffix{#3}% + \def\jn@macro{#1}% + \xa\testforquote#2\@jn@rdelim +} +\def\testforquote#1#2\@jn@rdelim{% + \def\jn@test@i{#1}% + \ifx\jn@test@i\@jn@quote + % Involves quotes + \edef\jn@next{"\jn@stripquotes#1#2\jn@suffix"}% + \else + \edef\jn@next{#1#2\jn@suffix}% + \fi + \xa\xdef\jn@macro{\jn@next}% +} +\def\jn@stripquotes"#1"{#1}% + + +\jobnamewithsuffixtomacro{\outputfilename}{-moodle.xml} +% \end{macrocode} +% Next, we create macros to open and close the Moodle XML file +% we will be writing. +% \begin{macrocode} +\newwrite\moodle@outfile +\def\openmoodleout{% + \immediate\openout\moodle@outfile=\outputfilename\relax + \writetomoodle{<?xml version="1.0" encoding="UTF-8"?>}% + \writetomoodle{<quiz>}% + \writetomoodle{ }% +}% +\def\closemoodleout{% + \writetomoodle{ }% + \writetomoodle{</quiz>}% + \immediate\closeout\moodle@outfile +}% +% \end{macrocode} +% To both make this |.sty| file and the XML output more readable, +% we create a mechanism for writing to the output file with indents. +% The macro |\calculateindent|\marg{$n$} globally defines +% |\moodle@indent| to be a string of \meta{$n$} |\otherspace|'s. +% \begin{macrocode} +\def\calculateindent#1{% + \bgroup + \count0=\number#1\relax + \gdef\moodle@indent{}% + \calculateindent@int + \egroup +}% +\def\calculateindent@int{% + \ifnum\count0>0\relax + \g@addto@macro{\moodle@indent}{\otherspace}% + \advance\count0 by -1\relax + \expandafter + \calculateindent@int + \fi +}% +% \end{macrocode} +% Now the command |\writetomoodle|\oarg{n}\marg{stuff} adds the line +% ``\meta{stuff}'' to the XML file +% preceded by an indent of \meta{n} spaces. +% Note that this command |\edef|'s its second argument. +% \begin{macrocode} +\newcommand\writetomoodle[2][0]{% + \edef\test@ii{#2}% + \ifnum#1=0\relax + \immediate\write\moodle@outfile{\test@ii}% + \else + \calculateindent{#1}% + \immediate\write\moodle@outfile{\moodle@indent\trim@pre@space{\test@ii}}% + \fi +}% +% \end{macrocode} +% We now create the main |quiz| environment that will contain +% the questions we write. It outputs to XML as a |<question type="category">| tag. +% \begin{macrocode} +\newenvironment{quiz}[2][]% + {\setkeys{moodle}{#1}% + \@moodle@ifgeneratexml{% + \openmoodleout% + \setcategory{#2}% + }{}% + \subsection*{#2}% + \begin{enumerate}% + } + {\end{enumerate}% + \@moodle@ifgeneratexml{\closemoodleout}{}} + +{\catcode`\$=12\catcode`\ =12% +\gdef\setcategory#1{% +\writetomoodle{<question type="category">}% +\writetomoodle{ <category>}% +\writetomoodle{ <text>$module$/#1</text>}% +\writetomoodle{ </category>}% +\writetomoodle{</question>}% +\writetomoodle{ }% +}}% +% \end{macrocode} +% The next utility takes a single macro control sequence |#1|, +% and allows that macro's current value to persist +% after the next |\egroup|, |}|, or |\endgroup|. +% \begin{macrocode} +\def\passvalueaftergroup#1{% + \xa\xa\xa\gdef\xa\xa\csname moodle@remember@\string#1\endcsname\xa{\xa\def\xa#1\xa{#1}}% + \xa\aftergroup\csname moodle@remember@\string#1\endcsname +} +% \end{macrocode} +% +% \subsubsection{Main Switch: to create XML or not} +% +% \begin{macrocode} +\long\def\@moodle@ifgeneratexml#1#2{% + % If we are generating XML, do #1; otherwise do #2. + \tikzifexternalizing{% + % This run of LaTeX is currently ONLY generating a Tikz image + % to be saved in an external file. We do NOT want to waste time + % generating XML, and moreover trying to do so would cause errors + % because of file dependencies. + #2% + }{% + \ifmoodle@draftmode + #2% + \else + #1% + \fi + }% +} +% \end{macrocode} +% +% \subsection{Key-Value Pairs for Quiz Questions} +% +% The various options are set using key-value syntax of |xkeyval|. +% \begin{macrocode} +\def\moodleset#1{\setkeys{moodle}{#1}}% +% \end{macrocode} +% We first define some macros that will help us write other macros. +% Calling |\generate@moodle@write@code|\marg{name}|<|\meta{HTML tag}|>|\marg{text to write} +% creates a macro |\moodle@write|\meta{name}, taking no parameters, +% which writes the code |<|\meta{HTML tag}|>...</|\meta{HTML tag}|>| +% to the output XML file. +% +% The ordinary version |\generate@moodle@write@code| passes its output text |#3| +% through the HTMLizer, producing HTML code, while the starred version +% |\generate@moodle@write@code*| passes |#3| verbatim as text. +% +% For example, +% |\generate@moodle@write@code{excuse}<EXC>{\theexcuse}| +% would expand to +% \begin{Verbatim}[gobble=4,frame=single] +% \gdef\moodle@writeexcuse{% +% \xa\def\xa\test@iii\xa{\theexcuse}% +% \ifx\test@iii\@moodle@empty +% \writetomoodle[2]{ <EXC format="html"><text/></EXC>}% +% \else +% \xa\converttohtmlmacro\xa\moodle@htmltowrite\xa{\theexcuse}% +% \writetomoodle[2]{ <EXC format="html">}% +% \writetomoodle[4]{ <text><![CDATA[<p>\moodle@htmltowrite</p>]]></text>}% +% \writetomoodle[2]{ </EXC>}% +% \fi +% }% +% \end{Verbatim} +% but +% |\generate@moodle@write@code*{excuse}<EXC>{\theexcuse}| +% would expand only to +% \begin{Verbatim}[gobble=4,frame=single] +% \gdef\moodle@writeexcuse{% +% \writetomoodle[2]{ <EXC>\theexcuse</EXC>}% +% } +% \end{Verbatim} +% \begin{macrocode} +\def\generate@moodle@write@code{% + \@ifnextchar*\generate@moodle@write@data\generate@moodle@write@html +}% + +\def\generate@moodle@write@html#1<#2>#3{% + % #1 = NAME for \moodle@writeNAME + % #2 = HTML tag + % #3 = what, exactly, to write + \xa\gdef\csname moodle@write#1\endcsname{% + \xa\def\xa\test@iii\xa{#3}% + \ifx\test@iii\@moodle@empty + \writetomoodle[2]{ <#2 format="html"><text/></#2>}% + \else + \xa\converttohtmlmacro\xa\moodle@htmltowrite\xa{#3}% + \writetomoodle[2]{ <#2 format="html">}% + \writetomoodle[4]{ <text><![CDATA[<p>\moodle@htmltowrite</p>]]></text>}% + \writetomoodle[2]{ </#2>}% + \fi + }% +}% + + +\def\generate@moodle@write@data*#1<#2>#3{% + % #1 = NAME for \moodle@writeNAME + % #2 = HTML tag + % #3 = what, exactly, to write + \xa\gdef\csname moodle@write#1\endcsname{% + \writetomoodle[2]{ <#2>#3</#2>}% + }% +}% + +% \end{macrocode} +% \subsubsection{Keys for all question types} +% \begin{macrocode} +%% QUESTIONNAME + \define@cmdkey{moodle}[moodle@]{questionname}{}% + \gdef\moodle@writequestionname{% + \writetomoodle[2]{<name>}% + \writetomoodle[4]{ <text>\moodle@questionname</text>}% + \writetomoodle[2]{</name>}% + }% + +%% QUESTIONTEXT + %I tried to use questiontext as a key, but it doesn't seem to work. + %The trouble is that xkeyval has trouble parsing a key with a \par token followed by a comma within brackets, + %like \setkeys{moodle}{questiontext={ABC\par [D,E]}} + %It's not worth trying to fix. + + \long\def\questiontext#1{% + %\converttohtmlmacro\myoutput{#1}% + %\let\moodle@questiontext=\myoutput% + \def\moodle@questiontext{#1}% + }% + \generate@moodle@write@code{questiontext}<questiontext>{\moodle@questiontext}%{% + +%% PENALTY FOR WRONG ATTEMPT + \define@cmdkey{moodle}[moodle@]{penalty}[0.1000000]{}% + \generate@moodle@write@code*{penalty}<penalty>{\moodle@penalty}% + \moodleset{penalty}% + +%% FEEDBACK + % Moodle allows for feedback tailored to each question, + % and feedback tailored to each right or wrong answer. + % We shall use the key 'feedback' to record both kinds of feedback, + % relying on TeX's grouping mechanism to keep them apart. + % When it comes time to write them to XML, + % \moodle@writegeneralfeedback uses the HTML tag <generalfeedback> + % whereas \moodle@writefeedback uses the tag <feedback>. + % Note that the general feedback is NOT inherited by each answer! + \define@cmdkey{moodle}[moodle@]{feedback}[]{}% + \generate@moodle@write@code{generalfeedback}<generalfeedback>{\moodle@feedback}% + \generate@moodle@write@code{feedback}<feedback>{\moodle@feedback}% + \moodleset{feedback}% + +%% DEFAULT GRADE + %The next line creates \moodle@defaultgrade, + %which is how many points the quiz question is worth. + %Key calls like [default grade=7] set \moodle@defaultgrade. + \define@cmdkey{moodle}[moodle@]{default grade}[1.0]{}% + %Next, makes 'points' a synonym for 'default grade' + \define@key{moodle}{points}[1.0]{\xa\def\csname moodle@default grade\endcsname{#1}} + \generate@moodle@write@code*{defaultgrade}<defaultgrade>{\csname moodle@default grade\endcsname}% + \moodleset{default grade=1.0} %This sets the default. + +%% HIDDEN + \define@boolkey{moodle}[moodle@]{hidden}[true]{}% + \generate@moodle@write@code*{hidden}<hidden>{\ifmoodle@hidden 1\else 0\fi}% + \moodleset{hidden=false}% + +\def\moodle@writecommondata{% + \moodle@writequestionname% + \moodle@writequestiontext% + \moodle@writedefaultgrade% + \moodle@writegeneralfeedback% + \moodle@writepenalty% + \moodle@writehidden% +}% + +% TO DO: 'tags' (i.e., keywords) + +% \end{macrocode} +% \subsubsection{Keys for all answers} +% \begin{macrocode} +%% FRACTION -- how much this answer is worth out of 100 percent + \define@cmdkey{moodle}[moodle@]{fraction}[100]{}% + %We do not create \moodle@writefraction, because the fraction occurs in + %the XML within the answer tag, like <answer fraction="75">. + \moodleset{fraction=100} %This sets the default. +% \end{macrocode} +% \subsubsection{Keys for multiple choice questions} +% \begin{macrocode} + +%% SINGLE and MULTIPLE -- for multichoice, is there 1 right answer or more than 1? + \define@boolkey{moodle}[moodle@]{single}[true]{}% + \generate@moodle@write@code*{single}<single>{\ifmoodle@single true\else false\fi}% + \moodleset{single}% + %The key 'multiple' is an antonym to 'single'. + \define@boolkey{moodle}[moodle@]{multiple}[true]{\ifmoodle@multiple\moodle@singlefalse\else\moodle@singletrue\fi}% + +%% SHUFFLE ANSWERS + \define@boolkey{moodle}[moodle@]{shuffle}[true]{}% + \generate@moodle@write@code*{shuffle}<shuffleanswers>{\ifmoodle@shuffle 1\else 0\fi}% + \moodleset{shuffle=true}% + +%% TO DO: CORRECTFEEDBACK +%% TO DO: PARTIALLYCORRECTFEEDBACK +%% TO DO: INCORRECTFEEDBACK +%% TO DO: NUMCORRECT key + +%% NUMBERING -- for numbering of multichoice questions + \define@choicekey{moodle}{numbering}% + {alpha,alph,Alpha,Alph,arabic,roman,Roman,% + abc,ABC,123,iii,IIII,none}[abc]{% + \def\moodle@numbering{#1}% + \def\test@@i{#1}% + \ifx\test@@i\@moodle@alpha + \def\moodle@numbering{abc}\fi + \ifx\test@@i\@moodle@alph + \def\moodle@numbering{abc}\fi + \ifx\test@@i\@moodle@Alpha + \def\moodle@numbering{ABC}\fi + \ifx\test@@i\@moodle@Alph + \def\moodle@numbering{ABC}\fi + \ifx\test@@i\@moodle@arabic + \def\moodle@numbering{123}\fi + \ifx\test@@i\@moodle@roman + \def\moodle@numbering{iii}\fi + \ifx\test@@i\@moodle@Roman + \def\moodle@numbering{IIII}\fi + }% + %'answer numbering' will be a synonym to 'numbering' + \define@key{moodle}{answer numbering}[abc]{\setkeys{moodle}{numbering={#1}}}% + \generate@moodle@write@code*{answernumbering}<answernumbering>{\moodle@numbering}% + %N.B. if we did not set the default here, then \moodle@numbering would be undefined, causing problems. + \moodleset{answer numbering=abc}% + + \def\@moodle@alpha{alpha}% + \def\@moodle@Alpha{Alpha}% + \def\@moodle@alph{alph}% + \def\@moodle@Alph{Alph}% + \def\@moodle@arabic{arabic}% + \def\@moodle@roman{roman}% + \def\@moodle@Roman{Roman}% + \def\@moodle@abc{abc}% + \def\@moodle@ABC{ABC}% + \def\@moodle@arabicnumbers{123}% + \def\@moodle@iii{iii}% + \def\@moodle@IIII{IIII}% + \def\@moodle@none{none}% + \def\moodle@obeynumberingstyle{% + \ifx\moodle@numbering\@moodle@abc + \renewcommand\theenumii{\alph{enumii}}% + \fi + \ifx\moodle@numbering\@moodle@ABC + \renewcommand\theenumii{\Alph{enumii}}% + \fi + \ifx\moodle@numbering\@moodle@arabicnumbers + \renewcommand\theenumii{\arabic{enumii}}% + \fi + \ifx\moodle@numbering\@moodle@iii + \renewcommand\theenumii{\roman{enumii}}% + \fi + \ifx\moodle@numbering\@moodle@IIII + \renewcommand\theenumii{\Roman{enumii}}% + \fi + \ifx\moodle@numbering\@moodle@none + \renewcommand\labelenumii{$\bullet$~}% + \fi + } + %TO DO: * In the PDF, how should 'none' in a multi look different from + % short answer or numerical options? + % * Instead of \theenumi and \labelenumi, + % use \@enumdepth to automatically set the correct depth. + +%% DISPLAY MODE -- affects Cloze multiple choice questions only. + % 0 = inline, 1 = vertical, 2 = horizontal + \def\moodle@multi@mode{0}% + \define@key{moodle}{inline}[]{\def\moodle@multi@mode{0}}% + \define@key{moodle}{vertical}[]{\def\moodle@multi@mode{1}}% + \define@key{moodle}{horizontal}[]{\def\moodle@multi@mode{2}}% +% \end{macrocode} +% \subsubsection{Keys for numerical questions} +% \begin{macrocode} +%% TOLERANCE + \define@cmdkey{moodle}[moodle@]{tolerance}[0]{}% + \moodleset{tolerance=0}% + %There is no \moodle@writetolerance, because in the XML the + %tolerance is given within the answer tag, + %like <answer fraction=100 tolerance=0.03>. + +% TO DO: implement unit-handling for numerical questions! +% \end{macrocode} +% \subsubsection{Keys for short answer questions} +% \begin{macrocode} +%% CASE SENSITIVE + \define@boolkey{moodle}[moodle@]{case sensitive}[true]{}% + \generate@moodle@write@code*{usecase}<usecase>{\csname ifmoodle@case sensitive\endcsname 1\else 0\fi}% + % We make 'usecase' a synonym for 'case sensitive'. + \define@boolkey{moodle}[moodle@]{usecase}[true]{\ifmoodle@usecase\csname moodle@case sensitivetrue\endcsname\else\csname moodle@case sensitivefalse\endcsname\fi}% + \moodleset{case sensitive=false}% +% \end{macrocode} +% \subsubsection{Keys for matching questions} +% \begin{macrocode} +%% DRAG-AND-DROP FORMAT + \define@boolkey{moodle}[moodle@]{draganddrop}[true]{}% + % We make 'dd' and 'dragdrop' and 'drag and drop' synonyms for 'draganddrop'. + \define@boolkey{moodle}[moodle@]{dd}[true]{\ifmoodle@dd\moodle@draganddroptrue\else\moodle@draganddropfalse\fi}% + \define@boolkey{moodle}[moodle@]{drag and drop}[true]{\moodle@ddsynonym}% + \def\moodle@ddsynonym{% + \csname ifmoodle@drag and drop\endcsname + \moodle@draganddroptrue + \else + \moodle@draganddropfalse + \fi + } + \moodleset{draganddrop=false}% +% \end{macrocode} +% \subsubsection{Keys for essay questions} +% \begin{macrocode} +%% EDITOR + \def\@moodle@html{html}% + \def\@moodle@htmlfile{html+file}% + \def\@moodle@text{text}% + \def\@moodle@plain{plain}% + \def\@moodle@monospaced{monospaced}% + \def\@moodle@file{file}% + \def\@moodle@noinline{noinline}% + \define@choicekey{moodle}{response format}% + {html,html+file,text,monospaced,file}[html]% + {\def\test@i{#1}% + \ifx\test@i\@moodle@html + % HTML Editor + \def\moodle@responseformat{editor}% + \fi + \ifx\test@i\@moodle@htmlfile + % HTML Editor with File Picker + \def\moodle@responseformat{editorfilepicker}% + \fi + \ifx\test@i\@moodle@text + % Plain text + \def\moodle@responseformat{plain}% + \fi + \ifx\test@i\@moodle@plain + % Plain text + \def\moodle@responseformat{plain}% + \fi + \ifx\test@i\@moodle@monospaced + % Plain text, monospaced font + \def\moodle@responseformat{monospaced}% + \fi + \ifx\test@i\@moodle@file + % No inline text (i.e., attachments only) + \def\moodle@responseformat{noinline}% + \fi + \ifx\test@i\@moodle@noinline + % No inline text (i.e., attachments only) + \def\moodle@responseformat{noinline}% + \fi + }% + \generate@moodle@write@code*{responseformat}<responseformat>{\moodle@responseformat}% + \moodleset{response format=html}% + %N.B. if we did not set a default, then \moodle@responseformat would be undefined, causing problems. + +%% RESPONSE REQUIRED + \define@boolkey{moodle}[moodle@]{response required}[true]{}% + % TO DO: Make synonym 'required' + \generate@moodle@write@code*{responserequired}<responserequired>{\csname ifmoodle@response required\endcsname 1\else 0\fi}% + \moodleset{response required=false}% + +%% RESPONSEFIELDLINES + \define@cmdkey{moodle}[moodle@]{response field lines}[15]{}% + \generate@moodle@write@code*{responsefieldlines}<responsefieldlines>{\csname moodle@response field lines\endcsname}% + %Make synonyms 'input box size' or 'height' or 'lines'? + \moodleset{response field lines=15}% N.B. if we do not set a default, then \moodle@responseformat will be undefined, causing problems. + +%% ATTACHMENTS ALLOWED + \def\@moodle@unlimited{unlimited}% + \define@choicekey{moodle}{attachments allowed}{0,1,2,3,unlimited}[1]{% + \def\test@i{#1}% + \ifx\test@i\@moodle@unlimited + \def\moodle@attachmentsallowed{-1}% + \else + \def\moodle@attachmentsallowed{#1}% + \fi + } + \generate@moodle@write@code*{attachmentsallowed}<attachments>{\moodle@attachmentsallowed} + \moodleset{attachments allowed=0}% + +%% ATTACHMENTS REQUIRED + \define@choicekey{moodle}{attachments required}{0,1,2,3}[1]{\def\moodle@attachmentsrequired{#1}}% + \generate@moodle@write@code*{attachmentsrequired}<attachmentsrequired>{\moodle@attachmentsrequired} + \moodleset{attachments required=0}% + +%% RESPONSE TEMPLATE + \define@key{moodle}{template}{\long\def\moodle@responsetemplate{#1}}% + \generate@moodle@write@code{responsetemplate}<responsetemplate>{\moodle@responsetemplate} + \moodleset{template={}}% +% \end{macrocode} +% \subsubsection{Hint tags} +% The following are not yet fully implemented. +% \begin{macrocode} +%% SHOWNUMCORRECT + \define@boolkey{moodle}[moodle@]{shownumcorrect}[true]{}% + \gdef\moodle@writeshownumcorrect{% + \if\moodle@shownumcorrect + \writetomoodle[4]{ <shownumcorrect/>}% + \fi + }% + \moodleset{shownumcorrect=false}% + +%% CLEARWRONG + \define@boolkey{moodle}[moodle@]{clearwrong}[true]{}% + \gdef\moodle@writeclearwrong{% + \if\moodle@clearwrong + \writetomoodle[4]{ <clearwrong/>}% + \fi + }% + \moodleset{clearwrong=false}% + +% TO DO: Implement hints +% \end{macrocode} +% +% \subsection{Answer handling} +% +% \begin{macrocode} +%The Answers XML depends heavily on the question type. +%Each type of question defines how it obtains answers from the LaTeX input, +%how it typesets those in a PDF or DVI, and how it writes them as XML code. +%It will write that XML to the macro \moodle@answers@xml, +%which them gets written to the file when \moodle@writeanswers +%is invoked. + +\def\moodle@answers@xml{}% +\gdef\moodle@writeanswers{% + \writetomoodle{\moodle@answers@xml}% +}% + +\newcommand\addto@xml[3][0]{% + % #1 = spaces to indent (default=0) + % #2 = macro containing XML code (possibly empty) + % #3 = XML text to be appended to that macro (will be \edef'd) + \calculateindent{#1}% + \edef\xml@to@add{\moodle@indent\trim@pre@space{#3}}% + \ifx#2\@moodle@empty + \edef\newxml{\noexpand#2\xml@to@add}% + \else + \edef\newxml{\noexpand#2^^J\xml@to@add}% + \fi + \xa\xa\xa\def\xa\xa\xa#2\xa\xa\xa{\newxml}% +}% +% \end{macrocode} +% +% \subsubsection{Not yet implemented} +% +% \begin{macrocode} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TRUE/FALSE QUESTIONS %%%%%%%%%%%%%%%%% + +% TODO: implement the \writetruefalsequestion +% and a front-end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% DESCRIPTION 'QUESTIONS' %%%%%%%%%%%%% + +% TODO: implement the \writedescription and a suitable front-end. +% Should this be \begin{description}...\end{description}, +% or should \begin{quiz}...\end{quiz} just scoop up all +% text outside question environments and package it in descriptions? + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% CALCULATED %%%%%%%%%%%%%%%%%%%%%%%%%% + +% TODO: I don't think I really want to handle this. Not now. + +% \end{macrocode} + +% \subsection{Front Ends} +% This section creates the user interface for the various question types. +% First, we define a generic command to create +% a front-end environment for a Moodle question type. +% In order to function, the following macros must be hard-coded: +% \begin{itemize} +% \item |\moodle@|\meta{type}|@latexprocessing|: +% Loops through the saved |\item|'s to typeset them in LaTeX, +% usually inside an itemize or enumerate environment. +% \item |\save|\meta{type}|answer#1|: +% Processes the text of a single |\item| to save the information to memory, +% usually inside |\moodle@answers@xml|. +% \item |\write|\meta{type}|question|: +% Writes the information, hitherto saved only in macros, +% into the XML file. +% \end{itemize} +% For example, to create the `shortanswer' question type, +% we shall call +% \begin{Verbatim}[gobble=5,frame=single] +% \moodle@makefrontend{shortanswer} +% \def\moodle@shortanswer@latexprocessing{...} +% \def\saveshortansweranswer#1{...} +% \def\writeshortanswerquestion{...} +% \end{Verbatim} +% +% \begin{macrocode} +\def\moodle@makefrontend#1{% + \NewEnviron{#1}[2][]{% + \bgroup + \setkeys{moodle}{##1,questionname={##2}}% + \expandafter\gatheritems\xa{\BODY}% + \let\moodle@questionheader=\gatheredheader + %First, the LaTeX processing + \item \textbf{\moodle@questionname}\par + \noindent + \moodle@questionheader + \csname moodle@#1@latexprocessing\endcsname + %Now, writing information to XML + \@moodle@ifgeneratexml{% + \xa\questiontext\xa{\moodle@questionheader}% Save the question text. + \bgroup + \gdef\moodle@answers@xml{}% + \setkeys{moodle}{feedback={}}% + \xa\loopthroughitemswithcommand\xa{\csname save#1answer\endcsname}% + \passvalueaftergroup{\moodle@answers@xml}% + \egroup + \csname write#1question\endcsname + }{}% + \egroup + }% +} +% \end{macrocode} +% +% \subsubsection{Essay Question Front-End} +% The essay question is the only question type whose front end +% is not yet created by |\moodle@makefrontend|. +% This is because of what it must do with its |\item|'s. +% +% \begin{macrocode} +\def\moodle@essay@latexprocessing{% + % Moodle cannot automatically grade an essay, + % but if the user puts \item's in, we can list them in an itemize as notes. + \ifnum\c@numgathereditems>0\relax + \par\noindent Notes: (not included in XML) + \begin{itemize} \setlength\itemsep{0pt}\setlength\parskip{0pt}% + \loopthroughitemswithcommand{\moodle@print@essay@answer}% + \end{itemize}% + \fi +} + +\NewEnviron{essay}[2][]{% + \bgroup + \setkeys{moodle}{#1,questionname={#2}}% + \expandafter\gatheritems\expandafter{\BODY}% + \let\moodle@questionheader=\gatheredheader + %First, the LaTeX processing. + \item \textbf{\moodle@questionname}\par + \noindent + \moodle@questionheader + \csname moodle@essay@latexprocessing\endcsname + %Now, writing information to memory. + \@moodle@ifgeneratexml{% + \xa\questiontext\xa{\moodle@questionheader}% Save the question text. + \bgroup + \gdef\moodle@answers@xml{}% + % + \ifnum\c@numgathereditems=0\relax + \addto@xml[2]\moodle@answers@xml{<graderinfo format="html"><text/></graderinfo>}% + \else + \addto@xml[2]\moodle@answers@xml{<graderinfo format="html"><text><![CDATA[}% + \ifnum\c@numgathereditems>1\relax + \addto@xml[4]\moodle@answers@xml{<ul>}% + \fi + \loopthroughitemswithcommand{\moodle@savegraderinfo}% + \ifnum\c@numgathereditems>1\relax + \addto@xml[4]\moodle@answers@xml{</ul>}% + \fi + \addto@xml[2]\moodle@answers@xml{]]></text></graderinfo>}% + \fi + % + \passvalueaftergroup{\moodle@answers@xml}% + \egroup + \writeessayquestion + }{}% + \egroup +}% + +%%%% TO DO +%%%% To make essay work will be tough. +%%%% Every line from \ifnum\c@numgathereditems=0\relax through its \else and \fi, +%%%% with the exception of +%%%% \xa\loopthroughitemswithcommand\xa{\csname save#1answer\endcsname}% +%%%% , does not exist in our current \moodle@makefrontend code. +%%%% How can we cope? +%%%% +%%%% Idea: change \moodle@makefrontend so that +%%%% 1. if \c@numgathereditems=0, we don't do anything. +%%%% 2. it calls a preamble and postamble around the \loopthroughitemswithcommand. +%%%% Like this: +%%%% +%%%% \@moodle@ifgeneratexml{% +%%%% \xa\questiontext\xa{\moodle@questionheader}% Save the question text. +%%%% \bgroup +%%%% \gdef\moodle@answers@xml{}% +%%%% \setkeys{moodle}{feedback={}}% +%%%% \@ifundefined{moodle@#1@answers@preamble}{}{}% +%%%% \csname moodle@#1@answers@preamble\endcsname +%%%% \ifnum\c@numgathereditems=0\relax +%%%% \relax +%%%% \else +%%%% \xa\loopthroughitemswithcommand\xa{\csname save#1answer\endcsname}% +%%%% \fi +%%%% \@ifundefined{moodle@#1@answers@postamble}{}{}% +%%%% \csname moodle@#1@answers@postamble\endcsname +%%%% \passvalueaftergroup{\moodle@answers@xml}% +%%%% \egroup +%%%% \csname write#1question\endcsname +%%%% }{}% +%%%% The \@ifundefined lines should automatically define the +%%%% \...@preamble \...@postamble macros to be \relax if they don't exist already. + +\gdef\writeessayquestion{% + \writetomoodle{<question type="essay">}% + \moodle@writecommondata% + \moodle@writeresponserequired% + \moodle@writeresponseformat% + \moodle@writeresponsefieldlines% + \moodle@writeattachmentsallowed% + \moodle@writeattachmentsrequired% + \moodle@writeanswers% The 'answers' XML really contains the grader info. + \moodle@writeresponsetemplate% + \writetomoodle{</question>}% +}% + +\def\moodle@print@essay@answer#1{% + \item #1% +}% + + +\def\moodle@savegraderinfo#1{% + \bgroup + \moodle@savegraderinfo@int#1\moodle@answer@rdelim + \passvalueaftergroup{\moodle@answers@xml}% + \egroup +}% +\newcommand\moodle@savegraderinfo@int[1][]{% + \setkeys{moodle}{fraction=0,#1}% + \moodle@savegraderinfo@int@int% +}% +\def\moodle@savegraderinfo@int@int#1\moodle@answer@rdelim{% + \xa\converttohtmlmacro\xa\moodle@answertext@html\xa{#1}% + %\trim@spaces@in\moodle@answertext + \ifnum\c@numgathereditems>1\relax + \addto@xml[6]{\moodle@answers@xml}{<li>\moodle@answertext@html</li>}% + \else + \addto@xml[4]{\moodle@answers@xml}{\moodle@answertext@html}% + \fi +}% +% \end{macrocode} +% +% \subsubsection{Short Answer Question Front-End} +% +% \begin{macrocode} +\def\blank{\rule{1in}{0.5pt}}% +% TO DO: Make an optional argument for width? This wouldn't affect Moodle, +% only the appearance in the PDF. It doesn't seem worth it. + +% \NewEnviron{shortanswer}[2][]{% +% \bgroup +% \setkeys{moodle}{#1,questionname={#2}}% +% \expandafter\gatheritems\xa{\BODY}% +% \let\moodle@questionheader=\gatheredheader +% %First, the LaTeX processing. +% \bigskip\par\noindent +% \fbox{\textbf{\moodle@questionname}}\par +% \noindent +% \moodle@questionheader +% \csname moodle@shortanswer@latexprocessing\endcsname +% %Now, writing information to memory. +% \@moodle@ifgeneratexml{% +% \xa\questiontext\xa{\moodle@questionheader}% Save the question text. +% \bgroup +% \gdef\moodle@answers@xml{}% +% \setkeys{moodle}{feedback={}}% +% \xa\loopthroughitemswithcommand\xa{\csname saveshortansweranswer\endcsname}% +% \passvalueaftergroup{\moodle@answers@xml}% +% \egroup +% \csname writeshortanswerquestion\endcsname +% }{}% +% \egroup +% }% + +\moodle@makefrontend{shortanswer}% + +% LATEX PROCESSING +\def\moodle@shortanswer@latexprocessing{% + \begin{itemize} \setlength\itemsep{0pt}\setlength\parskip{0pt}% + \loopthroughitemswithcommand{\moodle@print@shortanswer@answer}% + \end{itemize}% +} + + \def\moodle@print@shortanswer@answer#1{% + \moodle@print@shortanswer@answer@int#1\@rdelim + }% + \newcommand\moodle@print@shortanswer@answer@int[1][]{% + \setkeys{moodle}{#1}% + \moodle@print@shortanswer@answer@int@int% + }% + \def\moodle@print@shortanswer@answer@int@int#1\@rdelim{% + \ifx\moodle@fraction\@hundred + \item #1\quad$\checkmark$% + \else + \item #1\quad (\moodle@fraction\%)% + \fi + }% + +% SAVING ANSWERS TO MEMORY +\def\saveshortansweranswer#1{% + \bgroup + \saveshortansweranswer@int#1\moodle@answer@rdelim + \passvalueaftergroup{\moodle@answers@xml}% + \egroup +}% + \newcommand\saveshortansweranswer@int[1][]{% + \setkeys{moodle}{fraction=100,#1}% %%%%%% DEFAULT VALUE IS 100% + \saveshortansweranswer@int@int% + }% + \def\saveshortansweranswer@int@int#1\moodle@answer@rdelim{% + \def\moodle@answertext{#1}% + \addto@xml[2]{\moodle@answers@xml}{<answer fraction="\moodle@fraction" format="plain_text">}% + \addto@xml[4]{\moodle@answers@xml}{ <text>\moodle@answertext</text>}% + \ifx\moodle@feedback\@empty\relax\else + \addto@xml[4]{\moodle@answers@xml}{ <feedback format="html"><text><![CDATA[<p>\moodle@feedback</p>]]></text></feedback>}% + \fi + \addto@xml[2]{\moodle@answers@xml}{</answer>}% + }% + +% WRITING QUESTION TO XML FILE +\gdef\writeshortanswerquestion{% + \writetomoodle{<question type="shortanswer">}% + \moodle@writecommondata% + \moodle@writeusecase% + \moodle@writeanswers% + \writetomoodle{</question>}% +}% +% \end{macrocode} +% +% \subsubsection{Numerical Question Front-End} +% +% \begin{macrocode} +\moodle@makefrontend{numerical}% + +% LATEX PROCESSING +\def\moodle@numerical@latexprocessing{% + \begin{itemize} \setlength\itemsep{0pt}\setlength\parskip{0pt}% + \loopthroughitemswithcommand{\moodle@print@numerical@answer}% + \end{itemize}% +} + + \def\moodle@print@numerical@answer#1{% + \moodle@print@numerical@answer@int#1\@rdelim + }% + \newcommand\moodle@print@numerical@answer@int[1][]{% + \setkeys{moodle}{#1}% + \moodle@print@numerical@answer@int@int% + }% + \def\moodle@print@numerical@answer@int@int#1\@rdelim{% + \ifdim0pt=\moodle@tolerance pt\relax + \def\moodle@numericalprint@tolerance{}% + \else + \edef\moodle@numericalprint@tolerance{\noexpand\pm\moodle@tolerance}% + \fi + \ifx\moodle@fraction\@hundred + \item $#1\moodle@numericalprint@tolerance\quad\checkmark$% + \else + \item $#1\moodle@numericalprint@tolerance$\quad (\moodle@fraction\%)% + \fi + }% + +% SAVING ANSWERS TO MEMORY +\def\savenumericalanswer#1{% + \bgroup + \savenumericalanswer@int#1\moodle@answer@rdelim + \passvalueaftergroup{\moodle@answers@xml}% + \egroup +}% + \newcommand\savenumericalanswer@int[1][]{% + \setkeys{moodle}{fraction=100,#1}% %%%%%% DEFAULT VALUE IS 100% + \savenumericalanswer@int@int% + }% + \def\savenumericalanswer@int@int#1\moodle@answer@rdelim{% + \def\moodle@answertext{#1}% + \addto@xml[2]{\moodle@answers@xml}{<answer fraction="\moodle@fraction" format="plain_text">}% + \addto@xml[4]{\moodle@answers@xml}{ <text>\moodle@answertext</text>}% + \addto@xml[4]{\moodle@answers@xml}{ <tolerance>\moodle@tolerance</tolerance>}% + \ifx\moodle@feedback\@empty\relax\else + \addto@xml[4]{\moodle@answers@xml}{ <feedback format="html"><text><![CDATA[<p>\moodle@feedback</p>]]></text></feedback>}% + \fi + \addto@xml[2]{\moodle@answers@xml}{</answer>}% + }% + + +% WRITING QUESTION TO XML FILE +\gdef\writenumericalquestion{% + \writetomoodle{<question type="numerical">}% + \moodle@writecommondata% + \moodle@writeanswers% + \writetomoodle{</question>}% +}% +% \end{macrocode} +% +% \subsubsection{Multiple Choice Question Front-End} +% +% \begin{macrocode} +%Multiple choice has the structure +% \begin{multi}[options]{name}% +% What is 5+7? +% \item 13 +% \item* 12 +% \item 11 +% \end{multi}% + +\moodle@makefrontend{multi} + +% LATEX PROCESSING +\def\moodle@multi@latexprocessing{% + \moodle@countcorrectanswers + \begin{enumerate}\moodle@obeynumberingstyle + %\renewcommand{\theenumi}{\alph{enumi}}% + \setlength\itemsep{0pt}\setlength\parskip{0pt}% + \loopthroughitemswithcommand{\moodle@print@multichoice@answer}% + \end{enumerate}% +} + \def\moodle@print@multichoice@answer#1{% + \moodle@print@multichoice@answer@int#1\@rdelim + }% + \newcommand\moodle@print@multichoice@answer@int[1][]{% + \setkeys{moodle}{fraction=0,#1}% + \moodle@print@multichoice@answer@int@int + }% + \def\moodle@print@multichoice@answer@int@int#1#2\@rdelim{% + \def\test@i{#1}% + \ifmoodle@single + \ifx\test@i\@star + \item #2$~\checkmark$% + \else + \item #1#2% + \fi + \else + \ifx\test@i\@star + \item #2% + \setkeys{moodle}{fraction=\moodle@autopoints}% + \else + \item #1#2% + \fi + $~(\moodle@fraction\%)$ + \fi + }% + +% COMMON UTILITY: COUNTING CORRECT ANSWERS + \newcounter{moodle@numcorrectanswers}% + \newlength{\moodle@pointspercorrect}% + \def\moodle@countcorrectanswers{% + \setcounter{moodle@numcorrectanswers}{0}% + \global\setlength{\moodle@pointspercorrect}{100pt}% + \loopthroughitemswithcommand{\moodle@countcorrectanswers@a}% + \ifnum0=\c@moodle@numcorrectanswers\relax + \PackageError{moodle}{No correct answers given for multiple choice question.}{Please mark at least one answer correct.}% + \gdef\moodle@autopoints{0}% + \else + \global\divide\moodle@pointspercorrect by \c@moodle@numcorrectanswers\relax + \edef\moodle@autopoints{\strip@pt\moodle@pointspercorrect}% + \fi + } + \def\moodle@countcorrectanswers@a#1{% + %The grouping is to keep key answer-specific key changes local. + \bgroup + \moodle@countcorrectanswers@b#1\moodle@answer@rdelim + \egroup + }% + \newcommand\moodle@countcorrectanswers@b[1][]{% + \setkeys{moodle}{fraction=0,#1}% + \moodle@countcorrectanswers@c% + }% + \def\moodle@countcorrectanswers@c#1#2\moodle@answer@rdelim{% + \def\test@i{#1}% + \ifx\test@i\@star + \stepcounter{moodle@numcorrectanswers}% + \else + \global\addtolength{\moodle@pointspercorrect}{-\moodle@fraction pt}% + \fi + }% + \def\moodle@setautopoints#1pt{% + \gdef\moodle@autopoints{#1}% + } + +% TODO: What if rounding errors make the percentages not add to 100%? Will Moodle complain? +% TODO: Verify that negative scores are handled correctly. +% TODO: Put these macros in same order as other sections'. + +% SAVING ANSWERS TO MEMORY +\def\savemultianswer#1{% + \bgroup + \savemultianswer@int#1\moodle@answer@rdelim + \passvalueaftergroup{\moodle@answers@xml}% + \egroup +}% + \newcommand\savemultianswer@int[1][]{% + \setkeys{moodle}{fraction=0,#1}% + \savemultianswer@int@int% + }% + \def\savemultianswer@int@int#1#2\moodle@answer@rdelim{% + \def\test@i{#1}% + \ifx\test@i\@star + \ifmoodle@single + \setkeys{moodle}{fraction=100}% + \else + \setkeys{moodle}{fraction=\moodle@autopoints}% + \fi + \def\moodle@answertext{#2}% + \else + \def\moodle@answertext{#1#2}% + \fi + \trim@spaces@in\moodle@answertext + \addto@xml[2]{\moodle@answers@xml}{<answer fraction="\moodle@fraction" format="html">}% + \xa\converttohtmlmacro\xa\moodle@answertext@html\xa{\moodle@answertext}% + \addto@xml[4]{\moodle@answers@xml}{ <text><![CDATA[<p>\moodle@answertext@html</p>]]></text>}% + \ifx\moodle@feedback\@empty\relax\else + \addto@xml[4]{\moodle@answers@xml}{ <feedback format="html"><text><![CDATA[<p>\moodle@feedback</p>]]></text></feedback>}% + \fi + \addto@xml[2]{\moodle@answers@xml}{</answer>}% + }% + +% WRITING QUESTION TO XML FILE +\gdef\writemultiquestion{% + \writetomoodle{<question type="multichoice">}% + \moodle@writecommondata% + \moodle@writesingle% + \moodle@writeshuffle% + \moodle@writeanswernumbering% + \moodle@writeanswers% + \writetomoodle{</question>}% +}% +% \end{macrocode} +% +% \subsubsection{Matching Question Front-End} +% +% \begin{macrocode} +\let\answer=\hfill + +\moodle@makefrontend{matching} + +% LATEX PROCESSING +\def\moodle@matching@latexprocessing{% + \bgroup + \let\answer=\hfill + \begin{enumerate}\renewcommand{\theenumi}{\alph{enumi}}\setlength\itemsep{0pt}\setlength\parskip{0pt}% + \loopthroughitemswithcommand{\moodle@print@matching@answer}% + \end{enumerate}% + \egroup +} + \long\def\moodle@print@matching@answer#1{% + \moodle@print@matching@answer@int#1\@rdelim + }% + \newcommand\moodle@print@matching@answer@int[1][]{% + \moodle@print@matching@answer@int@int\relax + }% + \long\def\moodle@print@matching@answer@int@int#1\answer#2\@rdelim{% + \item #1\hfill #2% + }% + + +% SAVING ANSWERS TO MEMORY +\long\def\savematchinganswer#1{% + \bgroup + \savematchinganswer@int#1\moodle@answer@rdelim% + \passvalueaftergroup{\moodle@answers@xml}% + \egroup +}% + \newcommand\savematchinganswer@int[1][]{% + \setkeys{moodle}{#1}% + \xa\savematchinganswer@int@int\space% + }% + \long\def\savematchinganswer@int@int#1\answer#2\moodle@answer@rdelim{% + % Note that #1 may simply be \relax. + \def\moodle@subquestiontext{#1}% + \def\moodle@subanswertext{#2}% + \trim@spaces@in\moodle@subquestiontext + \xa\converttohtmlmacro\xa\moodle@subquestiontext@htmlized\xa{\moodle@subquestiontext}% + \trim@spaces@in\moodle@subanswertext + \ifmoodle@draganddrop + \xa\converttohtmlmacro\xa\moodle@subanswertext@htmlized\xa{\moodle@subanswertext}% + \fi + \addto@xml[2]{\moodle@answers@xml}{<subquestion format="html">}% + \ifx\moodle@subquestiontext\@empty + \addto@xml[4]{\moodle@answers@xml}{ <text></text>}% + \else + \addto@xml[4]{\moodle@answers@xml}{ <text><![CDATA[<p>\moodle@subquestiontext@htmlized</p>]]></text>}% + \fi + \ifmoodle@draganddrop +% \show\moodle@subanswertext@htmlized + \addto@xml[4]{\moodle@answers@xml}{ <answer format="html"><text><![CDATA[<p>\moodle@subanswertext@htmlized</p>]]></text></answer>}% + \else +% \show\moodle@subanswertext + \addto@xml[4]{\moodle@answers@xml}{ <answer><text>\moodle@subanswertext</text></answer>}% + \fi + \ifx\moodle@feedback\@empty\relax\else + \addto@xml[4]{\moodle@answers@xml}{ <feedback format="html"><text><![CDATA[<p>\moodle@feedback</p>]]></text></feedback>}% + \fi + \addto@xml[2]{\moodle@answers@xml}{</subquestion>}% + }% + +% WRITING QUESTION TO XML FILE +\gdef\writematchingquestion{% + \ifmoodle@draganddrop + \writetomoodle{<question type="ddmatch">}% + \else + \writetomoodle{<question type="matching">}% + \fi + \moodle@writecommondata% + \moodle@writesingle% + \moodle@writeshuffle% + \moodle@writeanswernumbering% + \moodle@writeanswers% + \writetomoodle{</question>}% +% \gdef\moodle@answ +}% +% \end{macrocode} +% +% \subsection{Cloze Questions} +% Because cloze questions are so complicated, they get their own section of code. +% The cloze strategy is as follows. +% +% All subquestions show up as part of the question body text. +% For each type of subquestion, we have a cloze-version environment +% that actually has 2 versions, depending on whether we are doing LaTeX or XML processing. +% So the main environment is quite typical: +% \begin{enumerate} +% \item Process the body as LaTeX. +% During this run, a |\begin{multi}| etc.~will be processed for display onscreen. +% \item Then save the body as the questiontext for XML. +% During this run, a |\begin{multi}| etc.~will be parsed and turned into +% cloze code as part of the XML questiontext. +% \end{enumerate} +% \begin{macrocode} +% LATEX PROCESSING +% SAVING ANSWERS TO MEMORY + +\NewEnviron{cloze}[2][]{% + \bgroup + \setkeys{moodle}{default grade=1}% + \setkeys{moodle}{#1,questionname={#2}}% + % A cloze question won't have any \item's in it, so we just use \BODY. + \moodle@enableclozeenvironments + %First, the LaTeX processing. + \item \textbf{\moodle@questionname}\par + \noindent + \BODY + %Now, writing information to memory. + \@moodle@ifgeneratexml{% + \xa\questiontext\xa{\BODY}% Save the question text as HTML. + \writeclozequestion + }{}% + \egroup% +} + +\def\moodle@enableclozeenvironments{% + \let\multi=\clozemulti + \let\endmulti=\endclozemulti + \let\numerical=\clozenumerical + \let\endnumerical=\endclozenumerical + \let\shortanswer=\clozeshortanswer + \let\endshortanswer=\endclozeshortanswer +} + +% WRITING QUESTION TO XML FILE +\gdef\writeclozequestion{% + \writetomoodle{<question type="cloze">}% + \moodle@writecommondata% + \writetomoodle{</question>}% +}% +% \end{macrocode} +% +% \subsubsection{Cloze Multiple Choice Questions} +% +% \begin{macrocode} +% TODO: Guard against [single=false] option + +\NewEnviron{clozemulti}[1][]{% + \bgroup + \setkeys{moodle}{default grade=1}% + \setkeys{moodle}{#1}% + \expandafter\gatheritems\xa{\BODY}% + \let\moodle@questionheader=\gatheredheader + \ifhtmlizer@active + %HTML version + \def\moodle@clozemulti@output{}% + \xa\g@addto@macro\xa\moodle@clozemulti@output\xa{\moodle@questionheader}% + \def\clozemulti@coding{}% + \edef\clozemulti@coding{\otherlbrace\csname moodle@default grade\endcsname:}% + \ifcase\moodle@multi@mode\relax + % Case 0: dropdown box style + \g@addto@macro{\clozemulti@coding}{MULTICHOICE:}% + \or + % Case 1: vertical style + \g@addto@macro{\clozemulti@coding}{MULTICHOICE_V:}% + \else + % Case 2: horizontal radio buttons + \g@addto@macro{\clozemulti@coding}{MULTICHOICE_H:}% + \fi + \bgroup + \setkeys{moodle}{feedback={}}% + \loopthroughitemswithcommand{\saveclozemultichoiceanswer}% + \egroup + \xa\g@addto@macro\xa\clozemulti@coding\xa{\otherrbrace}% + \xa\g@addto@macro\xa\moodle@clozemulti@output\xa{\clozemulti@coding}% + \xa\gdef\xa\htmlize@afteraction@hook\xa{\moodle@clozemulti@output}% + \def\endclozemulti@code{\htmlize@patchendenvironment}% + \else + %LaTeX version + \moodle@questionheader% %Any introductory text just continues to be typeset. + \def\cloze@multichoice@table@text{}% + \loopthroughitemswithcommand{\moodle@print@clozemultichoice@answer}% + \ifcase\moodle@multi@mode\relax + %Case 0: dropdown box style + \begin{tabular}[t]{|l|}\firsthline% (\firsthline is from the array package.) + \cloze@multichoice@table@text% + \end{tabular}% + \or + %Case 1: vertical style + \begin{itemize}\setlength\itemsep{0pt}\setlength\parskip{0pt}% + \cloze@multichoice@table@text% + \end{itemize}% + \else + %Case 2: horizontal radio buttons + \par{\cloze@multichoice@table@text}\par% + \fi + \def\endclozemulti@code{\relax}% + \fi + \passvalueaftergroup\endclozemulti@code% + \passvalueaftergroup\htmlize@afteraction@hook% + \egroup% +}[\endclozemulti@code]% + + +\def\moodle@print@clozemultichoice@answer#1{% + \moodle@print@clozemultichoice@answer@int#1\@rdelim% +}% +\newcommand\moodle@print@clozemultichoice@answer@int[1][]{% + \moodle@print@clozemultichoice@answer@int@int% +}% +\def\moodle@print@clozemultichoice@answer@int@int#1#2\@rdelim{% + % Case 0: "(answer) \\ \hline" + % Case 1: "\item (answer)" + % Case 2: "$\bullet~$(answer)\hfill" + \ifcase\moodle@multi@mode\relax + \relax% Case 0 + \or + \g@addto@macro\cloze@multichoice@table@text{\item}% Case 1 + \else + \g@addto@macro\cloze@multichoice@table@text{$\bullet~$}% Case 2 + \fi + \def\test@i{#1}% + \ifx\test@i\@star + \g@addto@macro\cloze@multichoice@table@text{#2$~\checkmark$}% + \else + \g@addto@macro\cloze@multichoice@table@text{#1#2}% + \fi + \ifcase\moodle@multi@mode\relax + \g@addto@macro{\cloze@multichoice@table@text}{\\ \hline}% Case 0 + \or + \relax% Case 1 + \else + \g@addto@macro{\cloze@multichoice@table@text}{\hfill}% Case 2 + \fi +}% + +\def\saveclozemultichoiceanswer#1{% + \bgroup + \saveclozemultichoiceanswer@int#1\moodle@answer@rdelim + \egroup +}% +\newcommand\saveclozemultichoiceanswer@int[1][]{% + \setkeys{moodle}{fraction=0,#1}% + \saveclozemultichoiceanswer@int@int% +}% +\def\saveclozemultichoiceanswer@int@int#1#2\moodle@answer@rdelim{% + \def\test@i{#1}% + \ifgatherbeginningofloop\else + \xa\gdef\xa\clozemulti@coding\xa{\clozemulti@coding\clozetilde}% separator between answers + \fi + \ifx\test@i\@star + \setkeys{moodle}{fraction=100}% + \def\moodle@answertext{#2}% + \else + \def\moodle@answertext{#1#2}% + \fi + \trim@spaces@in\moodle@answertext + \ifx\moodle@fraction\@hundred + \g@addto@macro\clozemulti@coding{=}% + \fi + \ifnum0<\moodle@fraction\relax + \ifnum\moodle@fraction<100\relax + \xdef\clozemulti@coding{\expandonce\clozemulti@coding\otherpercent\moodle@fraction\otherpercent}% + \fi + \fi + \xdef\clozemulti@coding{\expandonce\clozemulti@coding\expandonce\moodle@answertext}% + \ifx\moodle@feedback\@empty\else + \xdef\clozemulti@coding{\expandonce\clozemulti@coding\otherbackslash\otherhash\moodle@feedback}% + \fi +}% +% \end{macrocode} +% +% \subsubsection{Cloze Numerical Questions} +% +% \begin{macrocode} +\NewEnviron{clozenumerical}[1][]{% + \bgroup + \expandafter\gatheritems\expandafter{\BODY}% + \let\moodle@questionheader=\gatheredheader + \setkeys{moodle}{default grade=1}% + \setkeys{moodle}{#1}% + \ifhtmlizer@active + %HTML version + \def\moodle@clozenumerical@output{}% + \xa\g@addto@macro\xa\moodle@clozenumerical@output\xa{\moodle@questionheader}% + \def\clozenumerical@coding{}% + \edef\clozenumerical@coding{\otherlbrace\csname moodle@default grade\endcsname:NUMERICAL:}% + \bgroup + \setkeys{moodle}{feedback={}}% + \loopthroughitemswithcommand{\saveclozenumericalanswer}% + \egroup + \xa\g@addto@macro\xa\clozenumerical@coding\xa{\otherrbrace}% + \xa\g@addto@macro\xa\moodle@clozenumerical@output\xa{\clozenumerical@coding}% + \xa\gdef\xa\htmlize@afteraction@hook\xa{\moodle@clozenumerical@output}% + \def\endclozenumerical@code{\htmlize@patchendenvironment}% + \else + %LaTeX version + \moodle@questionheader% %Any introductory text just continues to be typeset. + \def\cloze@numerical@table@text{}% + \loopthroughitemswithcommand{\moodle@print@clozenumerical@answer}% + \begin{tabular}[t]{|ll|}\firsthline + \cloze@numerical@table@text\hline% + \end{tabular}% + \def\endclozenumerical@code{\relax}% + \fi + \passvalueaftergroup\endclozenumerical@code% + \passvalueaftergroup\htmlize@afteraction@hook% + \egroup +}[\endclozenumerical@code]% + +\def\moodle@print@clozenumerical@answer#1{% + \bgroup + \moodle@print@clozenumerical@answer@int#1\@rdelim + \egroup +}% +\newcommand\moodle@print@clozenumerical@answer@int[1][]{% + \setkeys{moodle}{#1}% + \moodle@print@clozenumerical@answer@int@int% +}% +\def\moodle@print@clozenumerical@answer@int@int#1\@rdelim{% + \ifx\moodle@fraction\@hundred + \def\moodle@clozenumericalprint@fraction{$\checkmark$}% + \else + \edef\moodle@clozenumericalprint@fraction{(\moodle@fraction\%)}% + \fi + \ifdim0pt=\moodle@tolerance pt\relax + \def\moodle@clozenumericalprint@tolerance{}% + \else + \edef\moodle@clozenumericalprint@tolerance{\noexpand\pm\moodle@tolerance}% + \fi + \xdef\moodle@clozenumericalprint@line{$#1\moodle@clozenumericalprint@tolerance$ & \moodle@clozenumericalprint@fraction}% + \xa\g@addto@macro\xa\cloze@numerical@table@text\xa{\moodle@clozenumericalprint@line \\}% +}% + + + +\def\saveclozenumericalanswer#1{% + \bgroup + \saveclozenumericalanswer@int#1\moodle@answer@rdelim + \egroup +}% +\newcommand\saveclozenumericalanswer@int[1][]{% + \setkeys{moodle}{fraction=100,#1}% %%%%%% DEFAULT VALUE IS 100% + \saveclozenumericalanswer@int@int% +}% +\def\saveclozenumericalanswer@int@int#1\moodle@answer@rdelim{% + \ifgatherbeginningofloop\else + \xa\gdef\xa\clozenumerical@coding\xa{\clozenumerical@coding\clozetilde}% separator between answers + \fi + \def\moodle@answertext{#1}% + \trim@spaces@in\moodle@answertext + \ifx\moodle@fraction\@hundred + \g@addto@macro\clozenumerical@coding{=}% + \fi + \ifnum0<\moodle@fraction\relax + \ifnum\moodle@fraction<100\relax + \xdef\clozenumerical@coding{\expandonce\clozenumerical@coding\otherpercent\moodle@fraction\otherpercent}% + \fi + \fi + \xdef\clozenumerical@coding{\expandonce\clozenumerical@coding\moodle@answertext:\moodle@tolerance}% + \ifx\moodle@feedback\@empty\else + \xdef\clozenumerical@coding{\expandonce\clozenumerical@coding\otherbackslash\otherhash\moodle@feedback}% + \fi +}% +% \end{macrocode} +% +% \subsubsection{Cloze Short Answer Questions} +% +% \begin{macrocode} +\NewEnviron{clozeshortanswer}[1][]{% + \bgroup + \expandafter\gatheritems\expandafter{\BODY}% + \let\moodle@questionheader=\gatheredheader + \setkeys{moodle}{default grade=1}% + \setkeys{moodle}{#1}% + %Because nesting conditionals built by \csname doesn't work well, + %we'll test '\ifmoodle@case sensitive' now and save the result in \count0. + \csname ifmoodle@case sensitive\endcsname + \count0=1\relax + \else + \count0=0\relax + \fi + \ifhtmlizer@active + %HTML version + \def\moodle@clozeshortanswer@output{}% + \xa\g@addto@macro\xa\moodle@clozeshortanswer@output\xa{\moodle@questionheader}% + \def\clozeshortanswer@coding{}% + \ifnum\count0=1\relax + \edef\clozeshortanswer@coding{\otherlbrace\csname moodle@default grade\endcsname:SHORTANSWER_C:}% + \else + \edef\clozeshortanswer@coding{\otherlbrace\csname moodle@default grade\endcsname:SHORTANSWER:}% + \fi + \bgroup + \setkeys{moodle}{feedback={}}% + \loopthroughitemswithcommand{\saveclozeshortansweranswer}% + \egroup + \xa\g@addto@macro\xa\clozeshortanswer@coding\xa{\otherrbrace}% + \xa\g@addto@macro\xa\moodle@clozeshortanswer@output\xa{\clozeshortanswer@coding}% + \xa\gdef\xa\htmlize@afteraction@hook\xa{\moodle@clozeshortanswer@output}% + \def\endclozeshortanswer@code{\htmlize@patchendenvironment}% + \else + %LaTeX version + \moodle@questionheader% %Any introductory text just continues to be typeset. + \def\cloze@shortanswer@table@text{}% + \loopthroughitemswithcommand{\moodle@print@clozeshortanswer@answer}% + \ifnum\count0=1\relax + \marginpar{\tiny\mbox{(Case-Sensitive)}}% + \fi + \begin{tabular}[t]{|ll|}\firsthline + \cloze@shortanswer@table@text\hline% + \end{tabular}% + \def\endclozeshortanswer@code{\relax}% + \fi + \passvalueaftergroup\endclozeshortanswer@code% + \passvalueaftergroup\htmlize@afteraction@hook% + \egroup +}[\endclozeshortanswer@code]% + +\def\moodle@print@clozeshortanswer@answer#1{% + \bgroup + \moodle@print@clozeshortanswer@answer@int#1\@rdelim + \egroup +}% +\newcommand\moodle@print@clozeshortanswer@answer@int[1][]{% + \setkeys{moodle}{#1}% + \moodle@print@clozeshortanswer@answer@int@int% +}% +\def\moodle@print@clozeshortanswer@answer@int@int#1\@rdelim{% + \ifx\moodle@fraction\@hundred + \def\moodle@clozeshortanswerprint@fraction{$\checkmark$}% + \else + \edef\moodle@clozeshortanswerprint@fraction{(\moodle@fraction\%)}% + \fi + \xdef\moodle@clozeshortanswerprint@line{#1 & \moodle@clozeshortanswerprint@fraction}% + \xa\g@addto@macro\xa\cloze@shortanswer@table@text\xa{\moodle@clozeshortanswerprint@line \\}% +}% + + + +\def\saveclozeshortansweranswer#1{% + \bgroup + \saveclozeshortansweranswer@int#1\moodle@answer@rdelim + \egroup +}% +\newcommand\saveclozeshortansweranswer@int[1][]{% + \setkeys{moodle}{fraction=100,#1}% %%%%%% DEFAULT VALUE IS 100% + \saveclozeshortansweranswer@int@int% +}% +\def\saveclozeshortansweranswer@int@int#1\moodle@answer@rdelim{% + \ifgatherbeginningofloop\else + \xa\gdef\xa\clozeshortanswer@coding\xa{\clozeshortanswer@coding\clozetilde}% separator between answers + \fi + \def\moodle@answertext{#1}% + \trim@spaces@in\moodle@answertext + \ifx\moodle@fraction\@hundred + \g@addto@macro\clozeshortanswer@coding{=}% + \fi + \ifnum0<\moodle@fraction\relax + \ifnum\moodle@fraction<100\relax + \xdef\clozeshortanswer@coding{\expandonce\clozeshortanswer@coding\otherpercent\moodle@fraction\otherpercent}% + \fi + \fi + \xdef\clozeshortanswer@coding{\expandonce\clozeshortanswer@coding\moodle@answertext}% + \ifx\moodle@feedback\@empty\else + \xdef\clozeshortanswer@coding{\expandonce\clozeshortanswer@coding\otherbackslash\otherhash\moodle@feedback}% + \fi +}% +% \end{macrocode} +% +% \section{Converting \LaTeX\ to HTML} +% A lot of work must now be done to convert the \LaTeX\ code +% of a question or answer into HTML code with embedded \TeX\ for math. +% +% \subsection{Catcode Setup} +% First, we create versions of the special characters with catcode 12, ``other.'' +% \begin{macrocode} +{\catcode`\#=12\gdef\otherhash{#}% + \catcode`\~=12\gdef\othertilde{~}% + \catcode`\&=12\gdef\otherampersand{&}% + \catcode`\^=12\gdef\othercaret{^}% + \catcode`\$=12\gdef\otherdollar{$}% + \catcode`\%=12\gdef\otherpercent{%}}% +{\catcode`\ =12\gdef\otherspace{ }}% +{\tt\catcode`\|=0\catcode`\\=12\relax|gdef|otherbackslash{\}}% +{\catcode`\[=1\catcode`\]=2\catcode`\{=12\catcode`\}=12% + \gdef\otherlbrace[{]\gdef\otherrbrace[}]] + + +\edef\@otherlbrace{\otherlbrace}% +\edef\@otherrbrace{\otherrbrace}% +\edef\@otherdollar{\otherdollar}% +\edef\@otherbackslash{\otherbackslash}% +\edef\@othertilde{\othertilde}% +% \end{macrocode} +% +% Next, we define commands to change catcodes to a suitable verbatim mode +% for transcription. +% +% \begin{macrocode} +{ \catcode`\[=1\relax + \catcode`\]=2\relax + \catcode`\|=0\relax + |gdef|verbcatcodesweirdest[ + |catcode`\{=12|relax + |catcode`\}=12|relax + |catcode`\\=12|relax + ]% +}% +\def\verbcatcodes{% + \catcode`\$=12\relax + \catcode`\&=12\relax + \catcode`\#=12\relax + \catcode`\^=12\relax + \catcode`\_=12\relax + \catcode`\~=12\relax + \makeatletter + \catcode`\%=12\relax + \catcode`\ =12\relax\catcode\newlinechar=12\verbcatcodesweirdest}% + +\def\normalcatcodes{% + \catcode`\\=0\relax + \catcode`\{=1\relax + \catcode`\}=2\relax + \catcode`\$=3\relax + \catcode`\&=4\relax + \catcode\endlinechar=5\relax + \catcode`\#=6\relax + \catcode`\^=7\relax + \catcode`\_=8\relax + \catcode`\ =10\relax + \makeatletter% We will be detokenizing and retokenizing internal control sequences, so we need this. + \catcode`\~=13\relax + \catcode`\%=14\relax}% + +\def\retokenizingcatcodes{% + %For rescanning previously scanned text, all true comments will already be gone, + %but % signs may have been inserted by Cloze questions, so we want to treat them as 'other.' + % + % TODO: #'s are more worrisome. + \normalcatcodes + \catcode`\%=12\relax +} +% \end{macrocode} +% +% \subsection{Detokenization and Retokenization} +% +% We will be processing a \TeX\ token list. +% Based on its content, sometimes we will want it to be detokenized to +% individual characters, but other times we want it retokenized so that +% \TeX's own parsing mechanism can gather up the parameters of macros. +% We use the e\TeX\ primitive command |\scantokens| to do this. +% +% The following code (catcodes, groupings and all) defines a |\scantokens@to@macro| macro. +% That will assemble and disassemble strings of tokens +% using any changing schemes of catcodes we desire. +% +% We define |^^A| to be |\gdef\stm@saved|, while |^^B| and |^^C| are substitutes for |{| and |}|, respectively. +% This permits us to define |\scantokens@to@macro| in a peculiar catcode regime. +% \begin{macrocode} +\begingroup + \catcode`\^^A=13\gdef^^A{\gdef\stm@saved}% + \catcode`\^^B=1\catcode`\^^C=2\relax + \long\gdef\scantokens@to@macro#1#2#3{% + % #1 = control sequence to be defined + % #2 = command to change catcodes, e.g. \verbcatcodes, + % and define any command sequences + % #3 = text to be retokenized and saved into #1. + \bgroup + \def\texttorescan{#3}% + \catcode`\^^A=13\catcode`\^^B=1\catcode`\^^C=2\relax + \xa\def\xa\arg\xa{\xa^^A\xa^^B\texttorescan^^C}% + #2% + \catcode\endlinechar=9\relax% + %\scantokens always sees an end-of-line character at its end and converts it to a space. + %The catcode change sets \scantokens to ignore end-of-line chars. + %In practice, we're always calling \scantokens on previously scanned text anyway, + %so we won't miss any real end-of-line chars, since they were already converted to spaces. + \xa\scantokens\xa{\arg}% + \egroup + \xa\def\xa#1\xa{\stm@saved}% + }% +\endgroup% + +\long\def\ultradetokenize@to@macro#1#2{% + \scantokens@to@macro#1{\verbcatcodes}{#2}% +}% +\def\retokenizenormal@to@macro#1#2{% + \scantokens@to@macro#1{\retokenizingcatcodes}{#2}% +}% +% \end{macrocode} +% +% \subsection{Level-Tracking} +% While parsing, we'll need to keep track of whether +% we are in math mode (and how many levels deep the math mode might be nested), +% and also how deeply nested in \TeX\ groups we are. +% \begin{macrocode} +\newcount\moodle@mathmodedepth +\moodle@mathmodedepth=0\relax +\def\moodle@ifmathmode#1#2{% + \ifnum\moodle@mathmodedepth>0\relax + #1% + \else + #2% + \fi +} + +\newcount\grouplevel + +\newif\ifhtmlizer@active +\htmlizer@activefalse +% \end{macrocode} +% +% \subsection{Separation} +% +% This code separates a string of tokens into two parts. +% Its parameters, |#1#2|, consist of tokenized text, +% plus one terminal |\@htmlize@stop|. +% We are trying to break up the text into its first group and the remainder. +% This |\@htmlize@stop| is needed in case |#2| has the form ``|{...}|'', +% since we don't want \TeX\ to strip the braces off. +% Thus |\@htmlize@remainder| will definitely end in ``|\@htmlize@stop|''. +% \begin{macrocode} +\long\def\htmlize@grabblock#1#2\htmlize@rdelim@ii{% + \long\def\htmlize@blockinbraces{#1}% + \long\def\htmlize@remainder{#2}% +}% +% \end{macrocode} +% +% The next line defines the macro |\@htmlize@stop@detokenized| to contain +% the string of tokens |\@htmlize@stop|, all of category code 12 (other) or 10 (letter). +% We'll need this for comparison purposes later. +% \begin{macrocode} +\ultradetokenize@to@macro\@htmlize@stop@detokenized{\@htmlize@stop}% +% \end{macrocode} +% +% The next line creates the macro |\htmlize@remove@stopcode|, +% which removes the characters ``|\@htmlize@stop |'' from the end of a +% detokenized sequence. +% Its syntax when called is simply |\htmlize@remove@stopcode |\meta{material}, +% with no delimiters, since the ``|\@htmlize@stop |'' is itself the delimiter. +% \begin{macrocode} +\xa\def\xa\htmlize@remove@stopcode\xa#\xa1\@htmlize@stop@detokenized{#1}% +% \end{macrocode} +% +% \subsection{Main Code: the HTMLizer} +% +% \begin{macrocode} +\newif\ifhtmlize@actioncs +\newif\ifhtmlize@expandcs +\newif\ifhtmlize@passcs + +\long\def\@@begin@cs{\begin}% +\def\@@htmlize@stop{\@htmlize@stop}% + +\long\def\converttohtmlmacro#1#2{% + \grouplevel=0\relax + \def\htmlize@output{}% + \htmlizer@activetrue% + \converttohtml@int{#2}% + \htmlizer@activefalse% + \let#1=\htmlize@output\relax +} + +\long\def\converttohtml@int#1{% + \advance\grouplevel by 1\relax + \bgroup + \ultradetokenize@to@macro\htmlize@texttoscan{#1}% + \xa\htmlize@recursive@i\htmlize@texttoscan\@htmlize@stop\@htmlize@stop\@htmlize@stop\htmlize@rdelim@i + \egroup + \advance\grouplevel by -1\relax +}% + +\def\@lt{<}% +\def\@gt{>}% + +\long\def\htmlize@recursive@i#1#2#3\htmlize@rdelim@i{% + % #1#2#3 is a sequence of tokens. All should be categories 11 (letter) or 12 (other). + % It terminates with the control sequences \@htmlize@stop\@htmlize@stop\@htmlize@stop. + %\long\def\ds{(#1|#2|#3)}\show\ds + \def\test@i{#1}% + \def\test@ii{#2}% + \ifx\test@i\@@htmlize@stop + \let\htmlize@next@i=\relax + \else + \ifx\test@i\@otherlbrace% + \xa\g@addto@macro\xa\htmlize@output\xa{\otherlbrace}% + \bgroup + \normalcatcodes + %We need to rescan the input as TeX code, + % so TeX can automatically pull off the first group in braces. + % First, let's get rid of the terminal \@htmlize@stop codes. + {\def\@htmlize@stop{}\xdef\htmlize@scrap{#1#2#3}}% + \let\htmlize@text@to@rescan=\htmlize@scrap% + % Next, we retokenize the code. + \xa\retokenizenormal@to@macro\xa\htmlize@rescanned\xa{\htmlize@text@to@rescan}% + % Now break it up into two pieces. + \xa\htmlize@grabblock\htmlize@rescanned\@htmlize@stop\htmlize@rdelim@ii% + % The first piece, \htmlize@blockinbraces, will be passed as a unit to \converttohtml@int. + % The second part, \htmlize@remainder, will continue at this depth of grouping. + % Therefore we'll detokenize \htmlize@remainder here. + \xa\ultradetokenize@to@macro\xa\htmlize@remainder@detokenized\xa{\htmlize@remainder}% + \edef\htmlize@remainder@detokenized{\xa\htmlize@remove@stopcode\htmlize@remainder@detokenized}% + % + % Now build \htmlize@next@i. + % When done, should look like + % \converttohtml@int{\htmlize@blockinbraces}% + % \g@addto@macro\htmlize@output{\otherrbrace}% + % \htmlize@recursive@i\htmlize@remainder@detokenized\@htmlize@stop\@htmlize@stop\@htmlize@stop\htmlize@rdelim@i% + % but with all three arguments expanded. + % Note that we are running + \gdef\htmlize@scrap{\converttohtml@int}% + \xa\g@addto@macro\xa\htmlize@scrap\xa{\xa{\htmlize@blockinbraces}}% + \g@addto@macro\htmlize@scrap{\g@addto@macro\htmlize@output}% + \xa\g@addto@macro\xa\htmlize@scrap\xa{\xa{\otherrbrace}}% + \g@addto@macro\htmlize@scrap{\htmlize@recursive@i}% + \xa\g@addto@macro\xa\htmlize@scrap\xa{\htmlize@remainder@detokenized\@htmlize@stop\@htmlize@stop\@htmlize@stop\htmlize@rdelim@i}% + % Okay, that's done. It's stored in a global macro. + % Now we get it out of this group. + \egroup + \let\htmlize@next@i=\htmlize@scrap + \else + \ifx\test@i\@otherdollar% + % Math shift character. + \ifx\test@ii\@otherdollar + % Double dollar sign, so we're entering display math mode. + % We grab everything between $$...$$, sanitize it, and add it verbatim to + % our output. + \htmlize@displaymathshift@replace#1#2#3\htmlize@rdelim@iii% + \else + % Single dollar sign, so we're entering inline math mode. + % We grab everything between $$...$$, sanitize it, and add it verbatim to + % our output. + \htmlize@inlinemathshift@replace#1#2#3\htmlize@rdelim@iii% + \fi + % Now we resume work. + % The \htmlize@xxxxxxmathshift@replace macro stored the remaining text in \htmlize@remaining@text. + % Note that since we never detokenized and retokenized #1#2#3, + % \htmlize@remaining@text still includes the terminating \@htmlize@stop\@htmlize@stop\@htmlize@stop. + \def\htmlize@next@i{\xa\htmlize@recursive@i\htmlize@remaining@text\htmlize@rdelim@i}% + \else + \ifx\test@i\@otherbackslash% + % Control sequence. Oh boy. + % There are three possible things to do: + % 1. Retokenize everything, so we get a token list. + % Expand this control sequence, the first one in the list, + % to obtain a new token list. Then resume processing that list. + % Examples: \def\emph#1{<EM>#1</EM>}, \def\rec#1{\frac{1}{#1}}, \def\inv{^{-1}} + % \& --> & \# --> #; etc. + % Environments: \begin{center}...\end{center} --> <CENTER>...</CENTER> + % 2. Retokenize everything, so we get a token list. + % Let this first command (with its parameters) ACT. + % This may involve work in TeX's stomach (e.g., with counters) + % or with external files (e.g., image processing). + % The command may directly add material to \htmlize@output, + % but it should not typeset anything and should vanish from the + % input stream when it is done. + % When it's done, we somehow need to detokenize and resume + % processing the remainder of the input stream. + % Only commands explicitly crafted (or modified) to work + % with moodle.sty can possibly do all this! + % Examples: (modified) \includegraphics + % Environments: \begin{clozemulti}, \begin{enumerate} + % 3. Ignore that it's a command. Pass it right on as a character + % sequence to \htmlize@output. + % Examples: \alpha, \frac + % Environments: \begin{array} + % + % #2 is only for items on a specific list. + % #1 is anything that runs in TeX's mouth. + % We could keep a list and give users a way to add to it. + % I could also try expanding macros, using \ifcsmacro from etoolbox.sty + % + % The first step is to figure out what control sequence we're dealing with. + % First, let's get rid of the terminal \@htmlize@stop codes. + {\def\@htmlize@stop{}\xdef\htmlize@scrap{#1#2#3}}% + \let\htmlize@text@to@rescan=\htmlize@scrap% + % Next, we retokenize the code. + \xa\retokenizenormal@to@macro\xa\htmlize@rescanned\xa{\htmlize@text@to@rescan}% + % Now break it up into two pieces. + \xa\htmlize@grabblock\htmlize@rescanned\@htmlize@stop\htmlize@rdelim@ii% + \let\@htmlize@cs\htmlize@blockinbraces% + \edef\htmlize@cs@string{\xa\string\@htmlize@cs}% + % The first piece, \htmlize@blockinbraces, will contain the single token in \@htmlize@cs. + % We'll need to keep the second part, \htmlize@remainder, since it probably + % contains arguments to the cs in \@htmlize@cs. + %\edef\ds{Encountered '\xa\string\@htmlize@cs'}\show\ds + % + % N.B. that \@htmlize@cs is a macro *containing* a single control sequence. + % This is good for testing with \ifx. + % \htmlize@cs@string contains the cs as a string, e.g., the characters "\emph". + % + \ifx\@htmlize@cs\@@begin@cs + %This is a \begin. Begin environment-handling routine. + % + % Grab the first {...} from \htmlize@remainder, which is the argument + % to \begin. + \xa\htmlize@grabblock\htmlize@remainder\@htmlize@stop\htmlize@rdelim@ii% + \let\htmlize@envname=\htmlize@blockinbraces% + %We do not need the rest, so we won't pay any attention to the new + %content of \htmlize@remainder. + % + %Now environments are non-expandable, + %so there are only two possibilities: action or pass. + \xa\ifinlist\xa{\htmlize@envname}{\htmlize@env@actionlist}% + {% Action environment! + %\bgroup + %\def\ds{Encountered active environment \string\begin\{{\htmlize@envname}\}}\show\ds + \def\htmlize@next@i{\xa\htmlize@do@actionenv\htmlize@rescanned\@htmlize@stop\htmlize@actionsequence@rdelim}% + %The \bgroup is to active the environments. + %The matching \egroup is found in \htmlize@do@actionenv. + }{%An environment to pass to the HTML + %We just pass the backslash from "\begin" and move on. + \g@addto@macro\htmlize@output{#1}% + \def\htmlize@next@i{\htmlize@recursive@i#2#3\htmlize@rdelim@i}% + }% + \else% + %This is not an environment. Begin macro-handling routine. + \htmlize@actioncsfalse + \htmlize@expandcsfalse + \htmlize@passcsfalse + \xa\ifinlist\xa{\htmlize@cs@string}{\htmlize@cs@actionlist}% + {%Action sequence! + \htmlize@actioncstrue}% + {% Not action sequence! + \xa\ifinlist\xa{\htmlize@cs@string}{\htmlize@cs@expandlist}% + {%CS to be expanded! + \htmlize@expandcstrue% + }% + {%CS to be transcribed to XML + \htmlize@passcstrue% + }% + }% + %Now exactly one of \ifhtmlize@actioncs, \ifhtmlize@expandcs, and \ifhtmlize@passcs is true. + \ifhtmlize@actioncs + % It's an action-sequence. + %\edef\ds{Must let \xa\string\@htmlize@cs\ act!}\show\ds + %\show\htmlize@rescanned + \def\htmlize@next@i{\xa\htmlize@do@actioncs\htmlize@rescanned\@htmlize@stop\htmlize@actionsequence@rdelim}% + %\show\htmlize@rescanned + % Note that \htmlize@do@actioncs should patch the command to have it + % restart the scanning in time. + \else + \ifhtmlize@expandcs + % control sequence to be expanded + %\edef\ds{Must expand \xa\string\@htmlize@cs}\show\ds + \bgroup + \htmlize@redefine@expansionmacros + %The \expandafters first expand \htmlize@rescanned, + %and then expand its first token just once. + \xa\xa\xa\gdef\xa\xa\xa\htmlize@scrap\xa\xa\xa{\htmlize@rescanned}% + \egroup + \xa\ultradetokenize@to@macro\xa\htmlize@remaining@text\xa{\htmlize@scrap}% + \def\htmlize@next@i{\xa\htmlize@recursive@i\htmlize@remaining@text\@htmlize@stop\@htmlize@stop\@htmlize@stop\htmlize@rdelim@i}% + \else + % control sequence to be transcribed to XML. + %\edef\ds{Must pass on \xa\string\@htmlize@cs}\show\ds + \g@addto@macro\htmlize@output{#1}% + \def\htmlize@next@i{\htmlize@recursive@i#2#3\htmlize@rdelim@i}% + \fi + \fi + \fi + \else% + \ifx\test@i\@othertilde% + % The ~ becomes non-breaking space + \g@addto@macro\htmlize@output{\otherampersand nbsp;}% + \def\htmlize@next@i{\htmlize@recursive@i#2#3\htmlize@rdelim@i}% + \else + \ifx\test@i\@lsinglequote% + \ifx\test@ii\@lsinglequote% + % Double left quote + \g@addto@macro\htmlize@output{\otherampersand ldquo;}% + \def\htmlize@next@i{\htmlize@recursive@i#3\htmlize@rdelim@i}% + \else + \g@addto@macro\htmlize@output{\otherampersand lsquo;}% + \def\htmlize@next@i{\htmlize@recursive@i#2#3\htmlize@rdelim@i}% + \fi + \else + \ifx\test@i\@rsinglequote% + \ifx\test@ii\@rsinglequote% + %Double right quote + \g@addto@macro\htmlize@output{\otherampersand rdquo;}% + \def\htmlize@next@i{\htmlize@recursive@i#3\htmlize@rdelim@i}% + \else + \g@addto@macro\htmlize@output{\otherampersand rsquo;}% + \def\htmlize@next@i{\htmlize@recursive@i#2#3\htmlize@rdelim@i}% + \fi + \else + \ifx\test@i\@doublequote + \g@addto@macro\htmlize@output{\otherampersand rdquo;}% + \def\htmlize@next@i{\htmlize@recursive@i#2#3\htmlize@rdelim@i}% + \else + \ifx\test@i\@lt + \moodle@ifmathmode{\g@addto@macro\htmlize@output{\otherampersand lt;}}% + {\g@addto@macro\htmlize@output{<}}% + \def\htmlize@next@i{\htmlize@recursive@i#2#3\htmlize@rdelim@i}% + \else + \ifx\test@i\@gt + \moodle@ifmathmode{\g@addto@macro\htmlize@output{\otherampersand gt;}}% + {\g@addto@macro\htmlize@output{>}}% + \def\htmlize@next@i{\htmlize@recursive@i#2#3\htmlize@rdelim@i}% + \else + % Default case: write first token to output, call self on remaining tokens. + \g@addto@macro\htmlize@output{#1}% + \def\htmlize@next@i{\htmlize@recursive@i#2#3\htmlize@rdelim@i}% + \fi + \fi + \fi + \fi + \fi + \fi + \fi + \fi + \fi + \fi + \htmlize@next@i +}% + +\def\@lsinglequote{`}% +\def\@rsinglequote{'}% +\def\@doublequote{"}% +% \end{macrocode} +% +% \subsection{Math Mode handling} +% +% In the following, note that the |\|\meta{*}|mathrightdelim|'s gobble an argument. +% This is so ``|$a$ is...|" can turn into +% ``\ldots |a\|\meta{*}|mathrightdelim{} is...|" +% and preserve a trailing space. +% \begin{macrocode} +\edef\inlinemathleftdelim{\otherbackslash(}% +\def\inlinemathrightdelim#1{\advancemathmodecounter{-1}% + \g@addto@macro\htmlize@output{\otherbackslash)}}% +\edef\displaymathleftdelim{<CENTER>\otherbackslash[}% +\def\displaymathrightdelim#1{\advancemathmodecounter{-1}% + \g@addto@macro\htmlize@output{\otherbackslash]</CENTER>}}% +{\catcode`\$=12\relax% + \gdef\htmlize@inlinemathshift@replace$#1$#2\htmlize@rdelim@iii{% + %\def\ds{inline math shift has '#1' and '#2'}\show\ds + \xa\g@addto@macro\xa\htmlize@output\xa{\inlinemathleftdelim}% + \advancemathmodecounter{1}% + \def\mathtext{#1}% + \def\aftertext{#2}% + \xdef\htmlize@remaining@text{\expandonce\mathtext% + \otherbackslash inlinemathrightdelim{}% + \expandonce\aftertext}% + %\show\htmlize@remaining@text + }% + \gdef\htmlize@displaymathshift@replace$$#1$$#2\htmlize@rdelim@iii{% + \xa\g@addto@macro\xa\htmlize@output\xa{\displaymathleftdelim}% + \advancemathmodecounter{1}% + \def\mathtext{#1}% + \def\aftertext{#2}% + \xdef\htmlize@remaining@text{\expandonce\mathtext% + \otherbackslash displaymathrightdelim{}% + \expandonce\aftertext}% + }% +} +% \end{macrocode} +% +% \subsection{Engines for Control Sequences} +% +% There are three kinds of control sequences that need special handling: +% \begin{enumerate} +% \item Action environments +% \item Action command sequences +% \item Expansion macros +% \end{enumerate} +% +% \subsubsection{Engine for running action environments} +% \begin{macrocode} +\long\def\htmlize@do@actionenv#1#2\@htmlize@stop\htmlize@actionsequence@rdelim{% + \bgroup %The corresponding \egroup is given in \htmlize@proceedwiththerest, + %to localize the changes to the environment definitions. + \htmlize@activate@environments + \gdef\htmlize@afteraction@hook{}% + #1#2\@htmlize@stop\htmlize@actionsequence@rdelim% +} + +\def\htmlize@patchendenvironment{\swaptotrueendenvironment{\xa\htmlize@proceedwiththerest\htmlize@afteraction@hook}}% + +\def\swaptotrueendenvironment#1#2\if@ignore\@ignorefalse\ignorespaces\fi{#2\if@ignore\@ignorefalse\ignorespaces\fi#1}% + + +\long\def\htmlize@record@environment#1{% + \listadd{\htmlize@env@actionlist}{#1}% +} +\long\def\html@newenvironment#1#2{% + \listadd{\htmlize@env@actionlist}{#1}% + \g@addto@macro\htmlize@activate@environments{% + \xa\let\csname #1\endcsname\relax% + \xa\let\csname end#1\endcsname\relax% + \NewEnviron{#1}{% + #2% + }[\htmlize@patchendenvironment]% + }% +} + + +\def\htmlize@activate@environments{}% +% \end{macrocode} +% +% \subsubsection{Engine for running action command sequences} +% +% The following automatically adds the ``engine'' to do the command +% and then resume processing the \LaTeX\ into HTML. +% It uses the |xpatch| package, which says it works with anything +% defined using |\newcommand| etc. and |\newenvironment| etc. +% \begin{macrocode} +\gdef\htmlize@afteraction@hook{}% + +\long\def\htmlize@do@actioncs#1#2\htmlize@actionsequence@rdelim{% + % #1#2 contains the current string to be rendered into HTML; + % N.B. it has been tokenized at this point, + % so TeX can process it directly. + % #1 = the command sequence we need to execute + % #2 = the rest of the string + % + % First, we patch the desired command so that, when it is over, + % it calls \htmlize@proceedwiththerest. + % We do this within the group, so as not to permanently change the command. + \bgroup + % The matching \egroup is issued in \htmlize@proceedwiththerest, + % so that the changes made by \htmlize@activate@css are localized to just the command itself. + \gdef\htmlize@afteraction@hook{}% + \htmlize@activate@css% + \def\test@i{#1}% + \ifx\test@i\@relax + \def#1{\xa\htmlize@proceedwiththerest\htmlize@afteraction@hook}% + \else + \xapptocmd#1{\xa\htmlize@proceedwiththerest\htmlize@afteraction@hook}{}{\PackageError{Could not patch the command \string#1!}}% + \fi + % Now we call that patched command. + #1#2\htmlize@actionsequence@rdelim% + %The matching \egroup now is built into the command #1. +} +\long\def\htmlize@proceedwiththerest#1\htmlize@actionsequence@rdelim{% + % The action cs has done its work. + % Now we gather up the remaining tokens, detokenize them, + % remove the \@htmlize@stop, and get back to work transcribing it. + \egroup %This \egroup matches the \bgroup that was issued either in \htmlize@do@actioncs or in \htmlize@do@actionenv + \ultradetokenize@to@macro\htmlize@remainder@detokenized{#1}% + %This will contain an extra \@htmlize@stop, so we remove it. + \xa\xa\xa\def\xa\xa\xa\htmlize@remainder@detokenized\xa\xa\xa{\xa\htmlize@remove@stopcode\htmlize@remainder@detokenized}% + %Now we get back to work transcribing the remainder. + \xa\htmlize@recursive@i\htmlize@remainder@detokenized\@htmlize@stop\@htmlize@stop\@htmlize@stop\htmlize@rdelim@i% +} + +\long\def\htmlize@record@action#1{% + \xa\listadd\xa\htmlize@cs@actionlist\xa{\string#1}% +} + +\def\htmlize@activate@css{}% +\def\html@action@def#1{% + \htmlize@record@action{#1}% + \xa\def\xa\htmlize@scrap\xa{\xa\let\xa#1\csname html@\string#1\endcsname}% + \xa\g@addto@macro\xa\htmlize@activate@css\xa{\htmlize@scrap}% + \xa\def\csname html@\string#1\endcsname% %And this \def\html@\oldcsname is follows by the remainder of the definition. +} +% \end{macrocode} +% +% \subsubsection{Engine for expansion control sequences} +% +% Calling |\htmlize@redefine@expansionmacros| will redefine +% the macros for us. It starts out empty. +% \begin{macrocode} +\def\htmlize@redefine@expansionmacros{}% +% \end{macrocode} +% If |\mymacro| needs no changes to be suited for expansion, +% you can simply call |\htmlize@record@expand{\mymacro}| +% or |\htmlregister{\mymacro}| +% to record that it should be expanded on its way to the HTML. +% Examples would be user-built macros such as |\inv|$\to$|^{-1}| +% or |\N|$\to$|\mathbb{N}|. +% \begin{macrocode} +\long\def\htmlize@record@expand#1{% + \xa\listadd\xa\htmlize@cs@expandlist\xa{\string#1}% +} +\let\htmlregister=\htmlize@record@expand +% \end{macrocode} +% On the other hand, if an alternate version of the macro is +% needed for HTML purposes, you can define its HTML version with +% |\html@def\mymacro...| +% Parameters are okay. +% An example would be +% |\html@def\emph#1{<EM>#1</EM>}|. +% \begin{macrocode} +\long\def\html@def#1{% + \htmlize@record@expand{#1}% + \xa\def\xa\htmlize@scrap\xa{\xa\let\xa#1\csname html@\string#1\endcsname}% + \xa\g@addto@macro\xa\htmlize@redefine@expansionmacros\xa{\htmlize@scrap}% + \xa\def\csname html@\string#1\endcsname% +} +% \end{macrocode} +% Note that when |\html@def| expands out, it ends with |\def\html@\oldcsname| +% which abuts directly on the remainder of the definition. +% +% \subsection{Specific Control Sequences for Action and Expansion} +% +% Now that we have that machinery in place, +% we define specific environments, action control sequences, and macros to +% expand to accomplish our purposes. +% +% \subsubsection{Action Environments} +% \begin{macrocode} +\htmlize@record@environment{clozemulti} +\htmlize@record@environment{multi} +\htmlize@record@environment{clozenumerical} +\htmlize@record@environment{numerical} +\htmlize@record@environment{clozeshortanswer} +\htmlize@record@environment{shortanswer} + +\html@newenvironment{center}{\xdef\htmlize@afteraction@hook{<CENTER>\expandonce\BODY</CENTER>}}% +\html@newenvironment{enumerate}{% + \xa\gatheritems\xa{\BODY}% + \gdef\htmlize@afteraction@hook{<OL>}% + \loopthroughitemswithcommand{\moodle@itemtoLI}% + \g@addto@macro\htmlize@afteraction@hook{</OL>}% +}% +\html@newenvironment{itemize}{% + \xa\gatheritems\xa{\BODY}% + \gdef\htmlize@afteraction@hook{<UL>}% + \loopthroughitemswithcommand{\moodle@itemtoLI}% + \g@addto@macro\htmlize@afteraction@hook{</UL>}% +}% +\def\moodle@itemtoLI#1{% + \g@addto@macro\htmlize@afteraction@hook{<LI>#1</LI>}% +}% + +% \end{macrocode} +% +% \subsubsection{Action Control Sequences} +% +% \begin{macrocode} +\def\advancemathmodecounter#1{% + \global\advance\moodle@mathmodedepth by #1\relax +} +\htmlize@record@action{\advancemathmodecounter}% + + + + +\htmlize@record@action{\relax}% + +\html@action@def\#{\g@addto@macro\htmlize@output{\otherhash}}% +\html@action@def\&{\g@addto@macro\htmlize@output{\otherampersand}}% +\html@action@def\\{\moodle@ifmathmode{\g@addto@macro\htmlize@output{\otherbackslash\otherbackslash}}% + {\g@addto@macro\htmlize@output{<BR/>}}}% +\html@action@def\{{% + \moodle@ifmathmode{\g@addto@macro\htmlize@output{\otherbackslash\otherlbrace}}% + {\g@addto@macro\htmlize@output{\otherlbrace}}% + }% +\html@action@def\}{% + \moodle@ifmathmode{\g@addto@macro\htmlize@output{\otherbackslash\otherrbrace}}% + {\g@addto@macro\htmlize@output{\otherrbrace}}% + }% +\html@action@def\ldots{% + \moodle@ifmathmode{\g@addto@macro\htmlize@output{\string\ldots}}% + {\g@addto@macro\htmlize@output{...}}% + }% +\html@action@def\dots{% + \moodle@ifmathmode{\g@addto@macro\htmlize@output{\string\dots}}% + {\g@addto@macro\htmlize@output{...}}% + }% +\html@action@def\ {% + \moodle@ifmathmode{\g@addto@macro\htmlize@output{\otherbackslash\otherspace}}% + {\g@addto@macro\htmlize@output{\otherspace}}% + }% +\html@action@def\${% + \g@addto@macro\htmlize@output{\otherdollar}% + }% + +\html@action@def\clozetilde{% + \xa\g@addto@macro\xa\htmlize@output\xa{\othertilde}% + }% + + + +%Diacritical marks over vowels +{\catcode`|=3\relax + \gdef\htmlize@vowels{a|e|i|o|u|A|E|I|O|U|}} +\def\htmlize@define@diacritic#1#2{% + \htmlize@record@action{#1}% + \g@addto@macro\htmlize@activate@css{% + \def#1##1{% + \ifinlist{##1}{\htmlize@vowels}% + {\g@addto@macro\htmlize@output{\otherampersand##1#2;}}% + {\xa\g@addto@macro\htmlize@output\xa{\string#1##1}}% + }% + }% +} +\htmlize@define@diacritic{\^}{circ}% +\htmlize@define@diacritic{\'}{acute}% +\htmlize@define@diacritic{\"}{uml}% +\htmlize@define@diacritic{\`}{grave}% + +%Hungarian long-umlaut diacritics +\def\@o{o}\def\@O{O}\def\@u{u}\def\@U{U}% +\html@action@def\H#1{% + \bgroup + \def\test@i{#1}% + \ifx\test@i\@O + \def\toadd{\otherampersand\otherhash337;}% + \else + \ifx\test@i\@o + \def\toadd{\otherampersand\otherhash337;}% + \else + \ifx\test@i\U + \def\toadd{\otherampersand\otherhash368;}% + \else + \ifx\test@i\u + \def\toadd{\otherampersand\otherhash369;}% + \else + \def\toadd{\otherbackslash\otherlbrace#1\otherrbrace}% + \fi + \fi + \fi + \fi + \xa\g@addto@macro\xa\htmlize@output\xa{\toadd}% + \egroup +}% + + +%Tilde over a, n, o +{\catcode`|=3\relax + \gdef\htmlize@tilde{A|N|O|a|n|o|}} +\html@action@def\~#1{% + \ifinlist{#1}{\htmlize@tilde}% + {\g@addto@macro\htmlize@output{\otherampersand#1tilde;}}% + {\xa\g@addto@macro\htmlize@output\xa{\string\~#1}}% + }% +% \end{macrocode} +% +% \subsubsection{Command sequences for Expansion} +% +% \begin{macrocode} +\html@def\underline#1{<SPAN STYLE="text-decoration: underline;">#1</SPAN>} +\html@def\emph#1{<EM>#1</EM>}% +\html@def\textbf#1{<B>#1</B>}% +\html@def\blank{____________}% +\html@def\par{</P><P>}% +\html@def\aa{\å}% +\html@def\AA{\Å}% +\html@def\ae{\æ}% +\html@def\AE{\Æ}% +\html@def\S{\§}% +\html@def\euro{\€}% +\html@def\texteuro{\€}% +\html@def\o{\ø}% +\html@def\O{\Ø}% +\html@def\ss{\ß}% +\html@def\l{\ł}% +\html@def\L{\Ł}% + +% AH CUSTOM MACROS TO EXPAND --- remove these before publishing! +\htmlize@record@expand{\inv}% +\htmlize@record@expand{\rec}% + +\htmlize@record@action\inlinemathrightdelim +\htmlize@record@action\displaymathrightdelim + + +% \end{macrocode} +% +% \subsection{Graphics via {\tt\string\includegraphics}} +% +% \subsubsection{External program command lines} +% We first set up commands for the external programs. +% \begin{macrocode} +\def\htmlize@setexecutable#1{% + % Defines macro #1 to be #2 in a verbatim mode suitable for filenames + \def\htmlize@executable@macro{#1}% + \bgroup\catcode`\|=0\catcode`\\=12\relax% + \htmlize@setexecutable@int +} +\def\htmlize@setexecutable@int#1{% + \egroup + \expandafter\def\htmlize@executable@macro{#1}% +} + +\def\ghostscriptcommand{\htmlize@setexecutable\gs} +\def\opensslcommand{\htmlize@setexecutable\openssl} +\def\imagemagickcommand{\htmlize@setexecutable\htmlize@imagemagick@convert} + +\ifwindows% + \ghostscriptcommand{gswin64c.exe}% +\else% + \ghostscriptcommand{gs}% +\fi% + +\opensslcommand{openssl}% +\imagemagickcommand{convert}% +% \end{macrocode} +% +% \subsubsection{Graphics key-handling} +% Next, we get ready to handle keys like |height=4cm| or |width=3cm| or |ppi=72|. +% \begin{macrocode} +\define@cmdkeys{moodle@includegraphics}[moodle@graphics@]{ppi} +\define@cmdkey{moodle}[moodle@graphics@]{ppi}{}% This is so the ppi key can be set at the document, quiz, or question level. +\define@cmdkeys{Gin}{ppi}% This is so the original \includegraphics will not object to a key of ppi. +\setkeys{moodle@includegraphics}{ppi=103} + +\newdimen\moodle@graphics@temp@dimen +\newcount\moodle@graphics@height@pixels +\newcount\moodle@graphics@width@pixels +\def\moodle@graphics@dimentopixels#1#2{% + \moodle@graphics@temp@dimen=#2\relax + \moodle@graphics@temp@dimen=0.013837\moodle@graphics@temp@dimen + \xa\moodle@graphics@temp@dimen\xa=\moodle@graphics@ppi\moodle@graphics@temp@dimen + #1=\moodle@graphics@temp@dimen + \divide #1 by 65536\relax +} +\define@key{moodle@includegraphics}{height}[]{% + \moodle@graphics@dimentopixels{\moodle@graphics@height@pixels}{#1}% +} +\define@key{moodle@includegraphics}{width}[]{% + \moodle@graphics@dimentopixels{\moodle@graphics@width@pixels}{#1}% +} +\setkeys{moodle@includegraphics}{height=0pt,width=0pt} +% \end{macrocode} +% +% \subsubsection{Graphics conversion to HTML} +% \begin{macrocode} +\html@action@def\includegraphics{\moodle@includegraphics@int}% + % Note that \htmlize@do@actioncs will 'patch' this by putting + % '\xa\htmlize@proceedwiththerest\htmlize@afteraction@hook' + % at the end. We want those 3 tokens to occur instead after + % the graphics filename. +\def\moodle@includegraphics@int#1#2#3{\moodle@includegraphics@int@int}% + % This gobbles up those three spurious tokens, + % which we will re-insert after our work is done. +\newcommand\moodle@includegraphics@int@int[2][]{% + \bgroup% The grouping is to localize the changes caused by \setkeys. + \message{moodle.sty: Processing \string\includegraphics[#1]{#2} for HTML...^^J} + \setkeys*{moodle@includegraphics}{#1}% + % Height or width should be given in TeX dimensions like cm or pt or in, + % and are converted to pixels for web use using the ppi key. + % TO DO: Can we modify \includegraphics to accept height or width in + % pixels? + % TO DO: What about \includegraphics[scale=0.7] ? + % Other keys: keepaspectratio=true|false, angle (rotation), clip & trim + \ifnum\moodle@graphics@height@pixels=0\relax + \ifnum\moodle@graphics@width@pixels=0\relax + % No size specified. Default to height of 200 pixels. + \def\moodle@graphics@geometry{x200}% + \def\moodle@graphics@htmlgeometry{}% + \else + % Width only specified. + \edef\moodle@graphics@geometry{\number\moodle@graphics@width@pixels}% + \edef\moodle@graphics@htmlgeometry{width=\number\moodle@graphics@width@pixels}% + \fi + \else + \ifnum\moodle@graphics@width@pixels=0\relax + % Height only specified. The `x' is part of the syntax. + \edef\moodle@graphics@geometry{x\number\moodle@graphics@height@pixels}% + \edef\moodle@graphics@htmlgeometry{height=\number\moodle@graphics@height@pixels}% + \else + % Height and width both specified. The `!' is part of the syntax. + \edef\moodle@graphics@geometry{\number\moodle@graphics@width@pixels x\number\moodle@graphics@height@pixels!}% + \edef\moodle@graphics@htmlgeometry{width=\number\moodle@graphics@width@pixels\otherspace height=\number\moodle@graphics@height@pixels}% + \fi + \fi + %First, convert it to PNG + \edef\cmdline{\htmlize@imagemagick@convert\otherspace #2 -resize \moodle@graphics@geometry\otherspace #2.png}% + \message{moodle.sty: Converting '#2' to PNG...^^J}% + \expandafter\immediate\expandafter\write18\expandafter{\cmdline}% + %Next, convert the PNG to base64 encoding + \def\cmdline{\openssl\otherspace enc -base64 -in #2.png -out #2.enc}% + \message{moodle.sty: Converting '#2.png' to base64...^^J}% + \expandafter\immediate\expandafter\write18\expandafter{\cmdline}% + %Now, save that base64 encoding in a TeX macro + \def\moodle@newpic@baselxiv{}% + \message{moodle.sty: Reading base64 file '#2.enc'...^^J}% + \openin\baseLXIVdatafile=#2.enc\relax + \savebaselxivdata@recursive + \closein\baseLXIVdatafile + \xa\global\xa\let\csname picbaselxiv@graphics@#2\endcsname=\moodle@newpic@baselxiv% + \edef\htmlize@imagetag{<IMG \moodle@graphics@htmlgeometry\otherspace SRC="data:image/png;base64,\csname picbaselxiv@graphics@#2\endcsname">}% + \xa\g@addto@macro\xa\htmlize@output\xa{\htmlize@imagetag}% + \message{moodle.sty: <IMG> tag inserted.^^J}% + \egroup + % Now we re-insert the code to get the HTMLizing going again. + \xa\htmlize@proceedwiththerest\htmlize@afteraction@hook +} +% \end{macrocode} +% The following code accomplishes the reading of an |.enc| file into memory. +% It is also used by the \TikZ\ code below. +% \begin{macrocode} +\newread\baseLXIVdatafile +\def\savebaselxivdata@recursive{% + \ifeof\baseLXIVdatafile + \let\baselxiv@next=\relax + \else + \read\baseLXIVdatafile to \datalinein + %\message{<<\datalinein>>^^J} + \ifx\datalinein\@moodle@par + \let\baselxiv@next=\relax + \else + %We add tokens manually, rather than with \g@addto@macro, to save time. + \xa\xa\xa\gdef\xa\xa\xa\moodle@newpic@baselxiv\xa\xa\xa{\xa\moodle@newpic@baselxiv\datalinein^^J}% + \let\baselxiv@next=\savebaselxivdata@recursive + \fi + \fi + \baselxiv@next +} +% \end{macrocode} +% +% \subsection{\TikZ\ Picture Handling} +% If the user is not using the \TikZ\ package, there is no need to waste time +% loading it. Without \TikZ\ loaded, however, many of the following commands +% are undefined. +% Our solution is to wait until |\AtBeginDocument| and then test whether +% \TikZ\ is loaded. If so, we make the appropriate definitions. +% \begin{macrocode} +\newif\ifmoodle@tikzloaded +\moodle@tikzloadedfalse +\AtBeginDocument{ + \ifx\tikzpicture\@undefined + \moodle@tikzloadedfalse + \else + \moodle@tikzloadedtrue + \fi + \ifmoodle@tikzloaded + \usetikzlibrary{external} + \tikzexternalize% + \tikzset{external/force remake}% + + \ifpdf + \def\pdftopng{\edef\gscmdline{\gs\otherspace -dBATCH -dNOPAUSE -sDEVICE=pngalpha -sOutputFile=\tikzexternalrealjob-tikztemp-\the\numconvertedpictures.png -r200 \tikzexternalrealjob-tikztemp-\the\numconvertedpictures.pdf}% + \expandafter\immediate\expandafter\write18\expandafter{\gscmdline}}% + \else + \def\pstopng{\edef\gscmdline{\gs\otherspace -dBATCH -dNOPAUSE -sDEVICE=pngalpha -sOutputFile=\tikzexternalrealjob-tikztemp-\the\numconvertedpictures.png -r200 \tikzexternalrealjob-tikztemp-\the\numconvertedpictures.ps}% + \expandafter\immediate\expandafter\write18\expandafter{\gscmdline}}% + \fi + \def\pngtobaselxiv{\edef\opensslcmdline{\openssl\otherspace enc -base64 -in \tikzexternalrealjob-tikztemp-\the\numconvertedpictures.png -out \tikzexternalrealjob-tikztemp-\the\numconvertedpictures.enc}% + \expandafter\immediate\expandafter\write18\expandafter{\opensslcmdline}}% + + \let\moodle@oldtikzpicture=\tikzpicture + + %The following code lets us run things *before* the normal \begin{tikzpicture}. + \renewenvironment{tikzpicture}{% + \global\advance\numconvertedpictures by 1\relax + %\jobnamewithsuffixtomacro{\htmlize@picbasename}{-tikztemp-\the\numconvertedpictures}% + %\xa\tikzsetnextfilename\xa{\htmlize@picbasename}% + \tikzsetnextfilename{\tikzexternalrealjob-tikztemp-\the\numconvertedpictures}% + \moodle@oldtikzpicture% + }{}% + % However, the tikz externalize library does *not* run \end{tikzpicture}. + % In order to run commands after the tikz picture is done compiling, we need to + % use a hook into \tikzexternal@closeenvironments. + + \g@addto@macro{\tikzexternal@closeenvironments}{% + \moodle@endtikzpicture@hook + } + \def\moodle@endtikzpicture@hook{% + \@moodle@ifgeneratexml{% + \ifpdf + \message{moodle.sty: Converting picture '\tikzexternalrealjob-tikztemp-\the\numconvertedpictures.pdf' to PNG...^^J}% + \pdftopng + \else + \pstopng + \fi + \message{moodle.sty: Converting '\tikzexternalrealjob-tikztemp-\the\numconvertedpictures.png' to base64...^^J}% + \pngtobaselxiv + \message{moodle.sty: Reading base64 file '\tikzexternalrealjob-tikztemp-\the\numconvertedpictures.enc'...^^J}% + \savebaselxivdata + \message{moodle.sty: base64 data saved.^^J}% + }{}% + } + \tikzset{external/optimize=true} + \tikzset{external/optimize command away={\VerbatimInput}{1}} + % + % The HTMLizer version of the tikzpicture environment, + % which writes an <IMG> tag to the XML file. + \htmlize@record@environment{tikzpicture} + \g@addto@macro\htmlize@activate@environments{% + \let\tikzpicture\relax\let\endtikzpicture\relax + \NewEnviron{tikzpicture}[1][]{% + \global\advance\numpicturesread by 1\relax + \edef\htmlize@imagetag{<IMG SRC="data:image/png;base64,\csname picbaselxiv@\the\numpicturesread\endcsname">}% + \xa\g@addto@macro\xa\htmlize@output\xa{\htmlize@imagetag}% + }[\htmlize@patchendenvironment]% + }% + \else + %TikZ not loaded. Provide dummy definitions for commands. + \long\def\tikzifexternalizing#1#2{#2}% + \fi +} + +\newcount\numconvertedpictures +\numconvertedpictures=0\relax +\newcount\numpicturesread +\numpicturesread=0\relax + +\def\savebaselxivdata{% + \def\moodle@newpic@baselxiv{}% + \openin\baseLXIVdatafile=\tikzexternalrealjob-tikztemp-\the\numconvertedpictures.enc\relax + \savebaselxivdata@recursive + \closein\baseLXIVdatafile + \xa\global\xa\let\csname picbaselxiv@\the\numconvertedpictures\endcsname=\moodle@newpic@baselxiv% +} + +\def\@moodle@par{\par}% +% \end{macrocode} +% Finally, we clean up our mess by deleting the temporary PDF, PNG, and ENC +% files we created. +% \begin{macrocode} +\AtEndDocument{% + \ifmoodle@tikzloaded + \@moodle@ifgeneratexml{% + \ifwindows + \immediate\write18{del \tikzexternalrealjob-tikztemp-*.*}% + \else + \immediate\write18{rm \tikzexternalrealjob-tikztemp-*.*}% + \fi + }{}% + \fi +} + +% TO DO: +% * sizing options for TikZ pictures? +% * In cloze multi, how to handle HTML or TeX in answers? In particular, what about ~? +% \end{macrocode} +% +% \Finale +\endinput |