4

I'm writing a little exam generation package in R that takes a list of questions, randomizes question order and responses, and creates a .pdf. One issue I have is that questions and their responses may be separated by a page break, which is obviously not ideal. I've found a manual workaround in encapsulating each \item within a minipage, but a manual solution won't work here.

Is it possible to modify the environment of enumerate or create a custom command that puts each \item in a minipage to keep questions from being split across pages? That way, I can include the code in the preamble of the RMarkdown document that generates the exam.

Essentially I want

\documentclass{article}

\begin{document}

\begin{enumerate}
\item
  Which bear is best?

  A. Honey bear\\
  B. Grizzly bear\\
  C. Black bear\\
  D. Teddy bear\\
  E. Polar bear\\
\item
  How much snow per year is the preferable amount?

  A. 90 inches\\
  B. 120+ inches\\
  C. 30 inches\\
  D. 60 inches\\
\end{enumerate}
\end{document}

to become

\documentclass{article}

\begin{document}

\begin{enumerate}
\begin{minipage}{\linewidth}
\item
  Which bear is best?

  A. Honey bear\\
  B. Grizzly bear\\
  C. Black bear\\
  D. Teddy bear\\
  E. Polar bear\\
\end{minipage}
\begin{minipage}{\linewidth}
\item
  How much snow per year is the preferable amount?

  A. 90 inches\\
  B. 120+ inches\\
  C. 30 inches\\
  D. 60 inches\\
\end{minipage}
\end{enumerate}
\end{document}

by putting something in the preamble.

2
  • Unrelated, but why not use a second level nested enumerate for the answers instead if manually numbering them?
    – leandriis
    Commented Nov 22, 2019 at 6:21
  • @leandriis Hm, I'm not sure I understand. The answer choices are generated dynamically when rendering an RMarkdown document. I guess the reason why is... it worked like this!
    – haff
    Commented Nov 22, 2019 at 6:32

4 Answers 4

2

Here's a LuaLaTeX-based solution. It sets up a Lua function, called add_minipage_wrappers, that acts as a preprocessor of sorts: It scans all input lines, kicks into high gear when it encounters a \begin{enumerate} instruction, encases \item groups in minipage environments, and falls back to near-dormancy after it encounters an \end{enumerate} instruction. The Lua function doesn't operate on \item directives contained in itemize or description list environments.

The only input requirements are as follows:

  1. the instructions \begin{enumerate} and \end{enumerate} occur at the beginning of lines
  2. no lines contain both a \begin{enumerate} and an \item instruction
  3. only one \item instruction on a given line
  4. enumerate environments aren't nested.

I trust that these input requirements aren't too burdensome.

Here's what the output looks like if the Lua function is active. To deactivate the function, just comment out (or delete) the lines

\AtBeginDocument{\directlua{luatexbase.add_to_callback (
   "process_input_buffer" , add_minipage_wrappers , "addminipagewrappers" )}}

enter image description here

% !TEX TS-program = lualatex
\documentclass{article}
\pagestyle{empty}
\usepackage[margin=5mm,textheight=0.5cm,paperheight=4.5cm]{geometry}

\usepackage{luacode} %% for 'luacode*' environment
\begin{luacode*}
local in_enumerate = false -- define a couple of flags (Boolean variables)
local in_item = false
function add_minipage_wrappers ( s )
  if s:find ( "^\\begin{enumerate}" ) then
     in_enumerate = true -- switch 'in_enumerate' to "true"
  elseif s:find ( "^\\end{enumerate}" ) then
     s = s:gsub ( "\\end{enumerate}" , "\\end{minipage} \\end{enumerate}" )
     in_enumerate = false
     in_item = false
  elseif s:find ( "\\item" ) then
     if in_enumerate == true then  -- do nothing unless in an 'enumerate' env.
        if in_item == false then   -- first instance of '\item' in the list
           s = s:gsub ( "\\item" , "\\begin{minipage}{\\linewidth} \\item" )
           in_item = true          -- switch 'in_item' to "true"
        else                       -- 2nd or later instance of '\item' 
           s = s:gsub ( "\\item" , "\\end{minipage} \\begin{minipage}{\\linewidth} \\item" )
        end
     end  
  end
  return ( s )
end
\end{luacode*}
%% Assign the Lua function to LuaTeX's 'process_input_buffer' callback:
\AtBeginDocument{\directlua{luatexbase.add_to_callback (
   "process_input_buffer" , add_minipage_wrappers , "addminipagewrappers" )}}

\begin{document}
\begin{enumerate}
\item
  Which bear is best?

  A. Honey bear\\
  B. Grizzly bear\\
  C. Black bear\\
  D. Teddy bear\\
  E. Polar bear
\item
  How much snow per year is the preferable amount?

  A. 90 inches\\
  B. 120+ inches\\
  C. 30 inches\\
  D. 60 inches
\end{enumerate}
\end{document}
1

You can capture the \items and process them using the approach provided by Macro to capture until end-of-line as argument:

enter image description here

\documentclass{article}

%\usepackage{xparse}% If LaTeX < 2020-10

\ExplSyntaxOn
\NewDocumentEnvironment{myenumerate}{ O{} +b }
 {
  % do the setup
  \keys_set:nn { haff/enumerate } { #1 }
  % split the contents at \item
  \seq_set_split:Nnn \l_haff_enumerate_input_seq { \item } { #2 }
  % remove the first (empty) item
  \seq_pop_left:NN \l_haff_enumerate_input_seq \l_tmpa_tl
  % issue the preamble
  \tl_use:N \l_haff_enumerate_pre_tl
  % adorn the items
  \seq_set_map:NNn
    \l_haff_enumerate_output_seq
    \l_haff_enumerate_input_seq
    { \exp_not:n { \__haff_enumerate_do:n { ##1 } } }
  % output the items, separated by the chosen separator
  \seq_use:NV \l_haff_enumerate_output_seq \l_haff_enumerate_sep_tl
  % issue the postamble
  \tl_use:N \l_haff_enumerate_post_tl
 }
 {}

\seq_new:N \l_haff_enumerate_input_seq
\seq_new:N \l_haff_enumerate_output_seq
\cs_generate_variant:Nn \seq_use:Nn { NV }

\keys_define:nn { haff/enumerate }
 {
  pre    .tl_set:N  = \l_haff_enumerate_pre_tl,
  post   .tl_set:N  = \l_haff_enumerate_post_tl,
  sep    .tl_set:N  = \l_haff_enumerate_sep_tl,
  action .code:n    = \cs_set_eq:NN \__haff_enumerate_do:n #1,
  action .initial:n = \use:n,
 }
\ExplSyntaxOff

\NewDocumentCommand{\minipageitem}{+m}{%
  \par\noindent
  \begin{minipage}{\linewidth}
    \setcounter{option}{0}%
    \item #1
  \end{minipage}
}
\NewDocumentEnvironment{minipageenumerate}{O{}}{%
  \begin{myenumerate}[
    action=\minipageitem,
    pre=\begin{enumerate},
    post=\end{enumerate}
  ]
}{%
  \end{myenumerate}
}

\newcounter{option}
\renewcommand{\theoption}{\Alph{option}}
\newcommand{\option}{%
  \par\noindent
  \refstepcounter{option}%
  \theoption.~\ignorespaces
}

\begin{document}

\begin{minipageenumerate}
  \item
    Which bear is best?

    \option Honey bear
    \option Grizzly bear
    \option Black bear
    \option Teddy bear
    \option Polar bear
    
  \item
    How much snow per year is the preferable amount?

    \option 90 inches
    \option 120+ inches
    \option 30 inches
    \option 60 inches
\end{minipageenumerate}

\end{document}
1

You can redefine the existing enumerate environment using the tasks package. The latter already puts its items in boxes so the result should suit your needs.

Possible major drawback: enumerate isn't nestable any more…

\documentclass{article}
\pagestyle{empty}
\usepackage[margin=5mm,paperheight=5cm]{geometry}

\usepackage{tasks}
\RenewTasksEnvironment[label=\arabic*.]{enumerate}[\item]

\begin{document}

\begin{enumerate}
\item
  Which bear is best?

  A. Honey bear\\
  B. Grizzly bear\\
  C. Black bear\\
  D. Teddy bear\\
  E. Polar bear
\item
  How much snow per year is the preferable amount?

  A. 90 inches\\
  B. 120+ inches\\
  C. 30 inches\\
  D. 60 inches
\end{enumerate}

\end{document}

enter image description here

0

You can do something like

\newcommand{\question}[1]{
  \begin{minipage}{\linewidth}
  \item
    {#1}
  \end{minipage}
}

So the full code would be

\documentclass{article}

\begin{document}

\newcommand{\question}[1]{
  \begin{minipage}{\linewidth}
  \item
    {#1}
  \end{minipage}
}

\begin{enumerate}
  \question{Which bear is best?\\A. Honey bear\\B. Teddy bear\\C. Black bear\\D. Grizzly bear\\E. Polar bear\\}
\end{enumerate}

\end{document}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .