0

Question

What would be the best way to reference previously defined environment content later on in a tex file? Specifically content that can include other latex commands and not just text?

Related: Labeling a text and referencing it later (Text only)

The Goal

Create a separate page for just the exam solutions.

Implementation

To accomplish this I created a new environment sol that wrapped the solution environment native to the exam docclass. This wrapper then created a label for the solutions body content which I would use later on to reference.

The sol environment:

\makeatletter
\NewEnviron{sol}{%
\def\@currentlabel{\BODY}\label{solt:\thequestion}%
\begin{solution}%
\protect\BODY
\end{solution}%
}
\makeatother

I would then define my questions and their solutions:

\begin{questions}

\begin{question}
Test Question

\begin{sol}
Test Solution
\includegraphics[width=3cm,height=3cm,keepaspectratio]{image.png}
\end{sol}
\end{question}

\end{questions}

And finally print the solutions using a custom command that would look through each question, and find its labelled solution:

\newcommand\printsolutions{%
\begin{multicols*}{2}
\xintFor* ##1 in {\xintSeq{1}{\thequestion}} \do {%
\def\x{##1}
\noindent
\x) ~\ref{solt:\x}\\
\\
}
\end{multicols*}
}

All together, there are four files:

  • index.tex which holds the configuration and the includes
  • questions.tex which holds the questions
  • solutions.tex which holds the solutions
  • index-solutions.tex which is used to compile the solutions only into a pdf

index.tex

\documentclass[addpoints,12pt]{exam}
\usepackage{amsmath}
\usepackage{graphicx}
\usepackage{array}
\usepackage{blindtext}
\usepackage{xintexpr}
\usepackage{pgffor}
\usepackage{environ}
\usepackage{multicol}

\makeatletter
\NewEnviron{sol}{%
\def\@currentlabel{\BODY}\label{solt:\thequestion}%
\begin{solution}%
\protect\BODY
\end{solution}%
}
\makeatother

\newcommand\printsolutions{%
\begin{multicols*}{2}
\xintFor* ##1 in {\xintSeq{1}{\thequestion}} \do {%
\def\x{##1}
\noindent
\x) ~\ref{solt:\x}\\
\\
}
\end{multicols*}
}

\begin{document}

\include{questions}
\include{solutions}

\end{document}

questions.tex

\begin{questions}

\begin{question}
Test Question

\begin{sol}
Test Solution
\protect\includegraphics[width=3cm,height=3cm,keepaspectratio]{image.png}
\end{sol}
\end{question}
\begin{questions}

solutions.tex

\section*{\centering Solutions}

\printsolutions

index-solutions.tex

\includeonly{solutions}
\input{index}

The Problem

This approach worked for 90% of the cases but would fail when the \BODY argument contains elements such as the \includegraphics command. If I protect the \includegraphics command, it works perfectly. I'm guessing because it doesn't expand the includegraphics command immediately.

Since I can't always know what will be the contents of the \BODY, I cant manually go through and set the \protect on troublesome commands such as the \includegraphics. Is there a better way to get around this?

10
  • 1
    \@currentlabel should certainly not be \BODY. What are you trying to achieve here? Commented Apr 11, 2020 at 20:04
  • you can save the body of an environment on a file to use later (see endnotes and similar packages) but you shouldn't use \label to do that. Commented Apr 11, 2020 at 20:11
  • 1
    you haven't really given any indication of the workflow, or why you need to write it to a file, do you need forward references? Commented Apr 11, 2020 at 21:34
  • 1
    Well, so it seems that no forward references are needed, which implies that you can probably store what you need to save in macros (possibly with dynamic names constructed using \csname ... \endcsname) and simply use the macros in the end to access the saved contents. Now, I believe you mostly need to give a short and clear description of the expected input syntax and output.
    – frougon
    Commented Apr 11, 2020 at 23:17
  • 1
    I wrote an answer because I remembered and came back to this question, but please note that I didn't receive any notification, since you didn't address me with @frougon. Hope this helps.
    – frougon
    Commented Apr 14, 2020 at 23:12

1 Answer 1

1

The reason your \protect helped is that \label uses \protected@write to write the “protected expansion“ of \@currentlabel to the .aux file. However, as you figured out, many things would need to be protected with this approach; thus, it is rather impractical.

Storing the solutions inside macros can be a successful approach, but it entails one tricky aspect: in your input, the sol environment is embedded in question, itself embedded in questions, and in the “only printing solutions” use case, you don't want to print the question text. So, in that case (which is when \ifmyPrintEverything is false in my example), I define the questions environment so that it defines the question environment to ignore everything until it finds \begin{sol}. This has to be done using recursion, because one can't use braces with catcodes 1 or 2 in tokens following delimited arguments when defining macros, AFAIK (IOW: I can't simply use \begin{sol} as a macro argument delimiter, but using \begin is fine; one “just” needs to check whether {sol} follows and, if not, recurse).

The following code works with a simple structure as in your example: only one sol environment as the last thing in the body of a question, and certainly no sol environment in subquestions, etc. It's a bit stupid and won't work if you have a question environment that doesn't end with its sol environment. I hope this helps anyway.

common.tex:

\documentclass[addpoints,12pt]{exam}
\usepackage{graphicx}
\usepackage{expl3}
\usepackage{environ}
\usepackage{multicol}
\usepackage{etoolbox}

\ExplSyntaxOn
% Borrow \int_step_inline:nnn from expl3
\cs_new_eq:NN \intstepinline \int_step_inline:nnn
\ExplSyntaxOff

\newif\ifprintSolutionsmode     % initially false
\newtoks\mytoks

\makeatletter
\NewEnviron{sol}{%
  \mytoks=\expandafter{\BODY}%
  \csxdef{my@sol@\number\value{question}}{\the\mytoks}%
  \unless\ifprintSolutionsmode
    \begin{solution}
      \BODY
    \end{solution}%
  \fi
}
\makeatother

\newcommand\printsolutions{%
  \begin{multicols*}{2}
    \intstepinline{1}{\number\value{question}}{%
      \noindent ##1) \csuse{my@sol@##1}\par\vspace{\baselineskip}
    }%
  \end{multicols*}
}

\begin{document}

\ifmyPrintEverything
  \printSolutionsmodefalse
\else
  \renewenvironment{questions}
    {\newenvironment{question}{\refstepcounter{question}\@my@skip@question}{}}
    {}
  \long\def\@my@skip@question#1\begin#2{%
    \ifstrequal{#2}{sol}{\begin{sol}}{\@my@skip@question}%
  }%
  \printSolutionsmodetrue
\fi

\input{questions}\newpage
\printsolutions

\end{document}

questions.tex:

\begin{questions}
  \begin{question}
    Test Question.

    \begin{sol}
      Test Solution.
      \includegraphics[width=3cm,keepaspectratio]{example-image}
    \end{sol}
  \end{question}
  \begin{question}
    This is another question.
    \begin{sol}
      Solution of the second question. \textbf{Non-expandable stuff}
      \def\zzz{is fine}\zzz.
    \end{sol}
  \end{question}
\end{questions}

start-solutions.tex:

\newif\ifmyPrintEverything
\myPrintEverythingfalse

\input{common}

start-all.tex:

\newif\ifmyPrintEverything
\myPrintEverythingtrue

\input{common}

Compiling start-all.tex yields:

enter image description here

Compiling start-solutions.tex yields:

enter image description here

You must log in to answer this question.

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