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?

  • 1
    @currentlabel should certainly not be \BODY. What are you trying to achieve here? – Ulrike Fischer Apr 11 '20 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. – David Carlisle Apr 11 '20 at 20:11
  • @UlrikeFischer Later on in the tex file i reference a specific solution. So what I was doing was labeling it when its defined and then referencing that label later. – Justin Dalrymple Apr 11 '20 at 21:04
  • @DavidCarlisle you mentioned using another way to achieve this workflow? Briefly looking into endnotes doesn't seem like the way to go, but ill read into it some more! – Justin Dalrymple Apr 11 '20 at 21:04
  • 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? – David Carlisle Apr 11 '20 at 21:34
  • Ah sorry. I use the information to generate a "solutions only" section later on in the file, where i list all the solutions that where defined previously – Justin Dalrymple Apr 11 '20 at 21:46
  • 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 Apr 11 '20 at 23:17
  • I updated the question description to hopefully be clearer. Its not the shortest, but i was trying to give a proper overview – Justin Dalrymple Apr 12 '20 at 08:52
  • Any suggestions based on the updated description? – Justin Dalrymple Apr 14 '20 at 19:49
  • 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 Apr 14 '20 at 23:12

1 Answers1

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

frougon
  • 24,283
  • 1
  • 32
  • 55