9

I am trying to save the contents (i.e., the body) of an environment into a macro for later use (in order to typeset it later in the document). I thought of using the environ package (see the minimal example below), but I get an error if there are other macros inside the body of the environment (like a \textbf{bold text}).

\documentclass{article}
\usepackage{environ}
\newcounter{problemnumber}\setcounter{problemnumber}{0}
\NewEnviron{problem}{%
    \stepcounter{problemnumber}
    \expandafter\long\expandafter\xdef\csname myproblem-\theproblemnumber\endcsname{\BODY}
    }

\begin{document}

\begin{problem} Some equation: $E=mc^2$ \end{problem}

\begin{problem} Some text.

A second paragraph with a \textbf{bold text}. And an inline equation: $E=mc^2$. \end{problem}

... Typsetting the problems in a different order, later in the document.

\textbf{Problem 2:}\quad\csname myproblem-2\endcsname\par\hrulefill

\textbf{Problem 1:}\quad\csname myproblem-1\endcsname\par\hrulefill

\end{document}

digital-Ink
  • 3,083
  • 1
    you have what you want in \BODY the error is unrelated, you can not have \textbf (or most latex) in \xdef. – David Carlisle Jan 14 '21 at 20:14
  • Apart from this approach (environ package) there's also VerbatimOut from fancyvrb, or scontents (see https://tex.stackexchange.com/a/513810/250119 ) – user202729 Nov 01 '21 at 14:25

2 Answers2

9

It is never safe to place arbitrary LaTeX content in edef or xdef.

The macro you want is already defined as \BODY so you need \let not \xdef to give it a global name,

\documentclass{article}
\usepackage{environ}
\newcounter{problemnumber}\setcounter{problemnumber}{0}
\NewEnviron{problem}{%
    \stepcounter{problemnumber}%%%%%%
    \global\expandafter\let\csname myproblem-\theproblemnumber\endcsname\BODY
    }

\begin{document}

\begin{problem} Some equation: $E=mc^2$ \end{problem}

\begin{problem} Some text.

A second paragraph with a \textbf{bold text}. And an inline equation: $E=mc^2$. \end{problem}

... Typsetting the problems in a different order, later in the document.

\textbf{Problem 2:}\quad\csname myproblem-2\endcsname\par\hrulefill

\textbf{Problem 1:}\quad\csname myproblem-1\endcsname\par\hrulefill

\end{document}

David Carlisle
  • 757,742
9

You should use \global\let as pointed out by David.

Here's a shorter implementation with also an interface to print the problems.

\documentclass{article}

\ExplSyntaxOn

\seq_new:N \g_digitalink_problem_seq

\NewDocumentEnvironment{problem}{+b} { \seq_gput_right:Nn \g_digitalink_problem_seq { #1 } } {} \NewExpandableDocumentCommand{\getproblem}{m} { \seq_item:Nn \g_digitalink_problem_seq { #1 } }

\ExplSyntaxOff

\begin{document}

\begin{problem} Some equation: $E=mc^2$ \end{problem}

\begin{problem} Some text.

A second paragraph with a \textbf{bold text}. And an inline equation: $E=mc^2$. \end{problem}

Typesetting the problems in a different order, later in the document.

\bigskip

\textbf{Problem 2:}\quad\getproblem{2}\par\hrulefill

\textbf{Problem 1:}\quad\getproblem{1}\par\hrulefill

\end{document}

enter image description here

egreg
  • 1,121,712