5

I don't know why, but I tried to follow https://tex.stackexchange.com/a/286089/116348 to redefine an environment like:

\let\answers\questions
\let\endanswers\endquestions

in order to save and restore an environment.

I tried to do:

\documentclass[]{article}

\begin{document}

\NewDocumentEnvironment{mydummyenv}{m}{I will print #1 before. ``}{'' And I will print #1 after.}

\begin{mydummyenv}{MyArg} Hey \end{mydummyenv}

%%% Overwrite the env and store the old version \let\mydummyenvOrig\mydummyenv \let\endmydummyenvOrig\endmydummyenv \DeclareDocumentEnvironment{mydummyenv}{D<>{}}{ I replace the dummy environment, see my value #1. }{}

%% Restore the env \let\mydummyenv\mydummyenvOrig \let\endmydummyenv\endmydummyenvOrig

\begin{mydummyenv}{MyArg} Hey \end{mydummyenv}

\end{document}

But sadly the old environment is used:

enter image description here

Do you have a solution to save and later restore an arbitrary environment? Ideally, this solution should only depend on the name of the environment (I don't want to make any assumption on how the original argument was made, as I only know the definition of the new environment).

PS: in practice my issue is more complicated as it is part of my robust-externalize library and involves rescan… so just wrapping the redefined environment in a group as a workaround would not work for my actual problem.

tobiasBora
  • 8,684
  • 3
    The code of environments defined with \NewDocumentEnvironment isn't stored in the macro of that environment directly (that holds the argument pattern). Use \NewEnvironmentCopy and \DeclareEnvironmentCopy instead of \let. – Skillmon Sep 07 '23 at 12:55
  • Alternatively you could use two different names with \NewDocumentEnvironment so that all the submacros remain intact. – John Kormylo Sep 07 '23 at 13:53

3 Answers3

8

You should use \NewEnvironmentCopy and \DeclareEnvironmentCopy instead of \let to save and restore an environments definition. That way you don't need to know how the environment was originally defined (with \NewDocumentEnvironment or \newenvironment), and things should just work™.

\documentclass[]{article}

\begin{document}

\NewDocumentEnvironment{mydummyenv}{m}{I will print #1 before. ``}{'' And I will print #1 after.}

\begin{mydummyenv}{MyArg} Hey \end{mydummyenv}

%%% Overwrite the env and store the old version \NewEnvironmentCopy{mydummyenvOrig}{mydummyenv} \DeclareDocumentEnvironment{mydummyenv}{D<>{}}{ I replace the dummy environment, see my value #1. }{}

%% Restore the env \DeclareEnvironmentCopy{mydummyenv}{mydummyenvOrig}

\begin{mydummyenv}{MyArg} Hey \end{mydummyenv}

\end{document}

Skillmon
  • 60,462
  • Thanks a lot! But it seems like this was defined only in very recent latex (mine does not have this for instance). For older distributions, do you have a solution? – tobiasBora Sep 07 '23 at 13:12
  • Uhm, seems like https://tex.stackexchange.com/a/680717/116348 can be used as a workaround. Let me try – tobiasBora Sep 07 '23 at 13:20
  • Yeah, it works, I just needed to define similarly a Declare version. – tobiasBora Sep 07 '23 at 13:26
  • By any chance do you know how it compares with the other answers in term of robustness? I heard that \DeclareEnvironmentCopy could have issues with star in arguments, but no idea if it is true. – tobiasBora Sep 07 '23 at 14:23
  • 1
    @tobiasBora \DeclareEnvironmentCopy and \RenewEnvironmentCopy are basically the same, just with different behaviour if the environment isn't yet defined, so from a robustness point of view mine and egreg's answer are equal. And I'd prefer my answer over JohnKormylo's because you shouldn't use \let on ltcmd-defined constructs. – Skillmon Sep 08 '23 at 07:01
5

Skillmon has explained how to fix, here's why your code can't work.

Generally speaking \NewDocumentEnvironment{foo} still defines \foo and \endfoo, but these are just the surface macros.

Indeed, you can try

\NewDocumentEnvironment{foo}{}{x}{y}
\show\foo
\show\endfoo
\stop

and you'll get

> \foo=\protected macro:
->\__cmd_start_env:nnnnn {}{foo}{}{}{}.
l.2 \show\foo

? > \endfoo=macro: ->\environment foo end aux . l.3 \show\endfoo

?

where \environment␣foo␣end␣aux␣ (with four spaces in the csname) is a single macro that contains the “end part”. The real code for the “begin part” is hidden.

With \let\fooOrig\foo you only save the surface macro. But when you do \RenewDocumentCommand{foo} (with appropriate arguments), the surface command doesn't change at all, only the internal real code. If you later do

\let\foo\fooOrig

you do nothing. The internal real code remains the one of the redefinition, which explains your problem.

For \newenvironment the problem would be the same, when the environment takes an optional argument. Before recent versions of LaTeX, you had to use \LetLtxMacro for safe saving, but this won't work for environments defined with \NewDocumentEnvironment.

I cannot recommend switching back and forth with redefinitions of environments (or commands), because you cannot know what foo would do at a given point. But you can do as shown or, better,

\NewDocumentEnvironment{foo}{...}{...}{...}
\NewEnvironmentCopy{fooOrig}{foo}

\RenewDocumentEnvironment{foo}{...}{...}{...}

\RenewEnvironmentCopy{foo}{fooOrig}

egreg
  • 1,121,712
3

This demonstrates the use of two differnnt names. I follow the rule "\def once, \let repeatedly."

\documentclass[]{article}

\NewDocumentEnvironment{mydummyenv}{m}{I will print #1 before. ``}{'' And I will print #1 after.}

\NewDocumentEnvironment{anothername}{D<>{}}{ I replace the dummy environment, see my value #1. }{}

\begin{document}

\begin{mydummyenv}{MyArg} Hey \end{mydummyenv}

%%% Overwrite the env and store the old version \let\mydummyenvOrig\mydummyenv \let\endmydummyenvOrig\endmydummyenv

\let\mydummyenv\anothername \let\endmydummyenv\endanothername

\begin{mydummyenv}{MyArg} Hey \end{mydummyenv}

%% Restore the env \let\mydummyenv\mydummyenvOrig \let\endmydummyenv\endmydummyenvOrig

\begin{mydummyenv}{MyArg} Hey \end{mydummyenv}

\end{document}

John Kormylo
  • 79,712
  • 3
  • 50
  • 120
  • Oh, I misunderstood what you were saying in comment, sorry. I wasn't aware of that trick, thanks. Is it rather more resilient than the accepted answer or less, or equivalent? (my concern being mostly that mydummyenv could be any kind of environment, and I read that NewEnvironmentCopy could have issues with stars). – tobiasBora Sep 07 '23 at 14:17
  • No idea. I didn't even know that \NewDocumentEnvironment passed arguments to the second half. – John Kormylo Sep 07 '23 at 14:20
  • Ok thanks, I’ll try with one solution, if it fails try the other – tobiasBora Sep 07 '23 at 14:22