复制环境

复制环境

当我想全局修改文档中的命令时,我通常会在宏的帮助下使用模板中原始命令的复制版本\let。例如,如果我想在每次创建部分时执行某些操作,我可能会说:

\let\svsection\section
\renewcommand\section[1]{\mychanges\svsection{#1}}

我突然意识到我不知道如何或是否可以为环境做类似的事情。我希望的是这样的(为了简单起见,我省略了参数,不要关注这一点):

\let\svfigure\figure
\renewenvironment{figure}{\blahblah\begin{svfigure}}
                         {\end{svfigure}\moreblah}

当然,这种语法不起作用,因为\figure它不是一个命令。

但我希望环境的创建abc总是伴随着相关构造命令的创建,例如\start@abc\end@abc,以便这些相关命令可以\let复制原始环境。

我也意识到一种方法可能是“修补”原始环境,但我不认为这真正是我所要求的,因为我不确定修补是否可以轻松撤消,而\let以相反方向重做(例如\let\section\svsection)将完全撤消重新定义的效果。

知道如何复制环境是一个很方便的功能,这样就可以\renew按照我描述的方式编辑原始副本。

答案1

当你使用以下方式定义环境时

\newenvironment{foo}[1]{start code with #1}{end code}

内部发生的事情(在检查可选参数、测试环境是否存在以及处理可能的星号参数之后\newenvironment)基本上可以归结为

\newcommand\foo[1]{start code with #1}% actually \new@command
\def\endfoo{end code}

这意味着如果环境没有可选参数

\let\myfoo\foo
\let\endmyfoo\endfoo

将环境复制{foo}为 environment {myfoo}。(如果它有一个可选参数,\LetLtxMacro应该使用它来代替\let,请参阅何时使用 \LetLtxMacro?

article课堂上{figure},环境定义为

\newenvironment{figure}
               {\@float{figure}}
               {\end@float}

这么说吧

\let\myfigure\figure
\let\endmyfigure\endfigure

作品:

\documentclass{article}
\newenvironment{foo}[1]{start foo with (#1)}{end foo}

\let\myfoo\foo
\let\endmyfoo\endfoo

\let\myfigure\figure
\let\endmyfigure\endfigure

\begin{document}

\begin{myfoo}{arg}
  bar
\end{myfoo}

\begin{myfigure}
  copied figure
  \caption{my figure}
\end{myfigure}

\end{document}

在此处输入图片描述

某些环境(例如{verbatim}或 AMSmath 的){align}无法通过这种方式复制,因为它们需要分别精确地找到\end{verbatim}或。\end{align}

答案2

像你这样修补\section并不是一个特别好的方法,因为你失去了原始命令的所有灵活性;在修补命令之前,你总是必须查看命令是如何定义的。你会\section发现例如,论点。

对于环境也是如此;它们通过一对命令实现:

\newenvironment{foo}[1]
  {something with #1 at the start}
  {something at the end}

LaTeX 定义\foo\endfoo在某种程度上基本等同于

\newcommand{\foo}[1]{something with #1 at the start}
\def\endfoo{something at the end}

在标准 LaTeX 中,该\end...命令始终没有参数(并且\newcommand不允许定义它)。

了解以下几点也很重要什么时候命令被执行;这是的定义\begin

% latex.ltx, line 3944:
\def\begin#1{%
  \@ifundefined{#1}%
    {\def\reserved@a{\@latex@error{Environment #1 undefined}\@eha}}%
    {\def\reserved@a{\def\@currenvir{#1}%
     \edef\@currenvline{\on@line}%
     \csname #1\endcsname}}%
  \@ignorefalse
  \begingroup\@endpefalse\reserved@a}

除了一些细节之外,\begin{foo}LaTeX 本质上是

\begingroup<bookkeping stuff>\foo

进行一些簿记,以便以后检查该\end部分。同样,\end{foo}

\endfoo<checks>\endgroup

因此,与普通宏相同的方法将起作用;然而,

\let\svfigure\figure
\let\endsvfigure\endfigure
\renewenvironment{figure}
  {\blahblah\begin{svfigure}}
  {\end{svfigure}\moreblah}

这不是修补figure环境的特别好的方法。首先,\end{fiugre}输入文件中的输入错误会导致令人费解的消息

! LaTeX Error: \begin{svfigure} on input line 11 ended by \end{fiugre}.

See the LaTeX manual or LaTeX Companion for explanation.
Type  H <return>  for immediate help.
 ...

l.13 \end{fiugre}

并且用户不会理解,因为他们不知道svfigure他们所处的环境绝不开始。通常的解决方案是说

\let\svfigure\figure
\let\endsvfigure\endfigure
\renewenvironment{figure}
  {\blahblah\svfigure}
  {\endsvfigure\moreblah}

这样 ,<bookkeeping stuff>意志 得以 实现 ,figure而 不是 内心svfigure.

然而,并非所有环境都适合以这种方式重新定义。

带有可选参数的环境应使用\LetLtxMacro(with \usepackage{letltxmacro}) 而不是\let,但对于宏也是如此。对于\end...命令来说\let,这始终足够了。更重要的是,这种方式可能不适用于使用不同工具定义的环境,特别是\NewEnviron( environpackage) 或\NewDocumentEnvironment( xparsepackage)。

其他一些环境有自己的特点:不要尝试重新定义lrbox(这无论如何都没有意义),也不要verbatim。比如

\let\svverbatim\verbatim
\let\endsvverbatim\endverbatim
\renewenvironment{verbatim}
  {<something>\svverbatim}
  {\endsvverbatim<something>}

除非您加载该包,否则将无法正常工作,并使您处于永久逐字模式verbatim。但不要尝试使用此方法更改 中的字体大小verbatim,它不会起作用。

修补宏和环境有时是一项神秘的活动,最适合巫师而不是熟练的 LaTeX 程序员。

答案3

如果您使用的是 LaTeX 2023-06-01 或更高版本1,则命令\NewEnvironmentCopy\RenewEnvironmentCopy\DeclareEnvironmentCopy已内置于内核中,因此您可以使用:

\NewEnvironmentCopy{<new env>}{<existing env>}

进行复制。无论环境是使用\newenvironment还是定义的,此方法都会起作用\NewDocumentEnvironment。此方法应优于使用\let,因为即使环境采用可选参数或类似的东西,它也不会失败。

同一版本添加了\ShowEnvironment{<env>}在终端上查看环境的定义。

1同时,您可以使用-dev格式

答案4

\NewCommandCopy有效。但是,它的缺点是,当*需要包含环境名称等时,需要令人讨厌的语法。

\NewEnvironmentCopy您可以按如下方式实现自己的命令(在较新的 LaTeX 内核中\NewEnvironmentCopy是内置的,因此如果已经定义,则最好使用\ProvideDocumentCommand它来默默地回退到使用现有命令):

%! TEX program = lualatex
\documentclass{article}
\usepackage{amsmath}
\begin{document}

\NewDocumentEnvironment{test}{m}{hello #1}{end #1}

\begin{test}{aaa}
    environment body
\end{test}

\ExplSyntaxOn
\ProvideDocumentCommand \NewEnvironmentCopy {mm} {
    \expandafter \NewCommandCopy \csname#1\expandafter\endcsname \csname#2\endcsname
    \expandafter \NewCommandCopy \csname end#1\expandafter\endcsname \csname end#2\endcsname
}

\ProvideDocumentCommand \RenewEnvironmentCopy {mm} {
    \expandafter \RenewCommandCopy \csname#1\expandafter\endcsname \csname#2\endcsname
    \expandafter \RenewCommandCopy \csname end#1\expandafter\endcsname \csname end#2\endcsname
}
\ExplSyntaxOff

\NewEnvironmentCopy{testb}{test}
\RenewEnvironmentCopy{testb}{test}

\NewEnvironmentCopy{oldalign*}{align*}

\begin{testb}{aaa}
    environment body
\end{testb}

\RenewDocumentEnvironment{test}{m}{redefined #1}{stop #1}

\begin{testb}{aaa}
    environment body
\end{testb}

\begin{oldalign*}
    1 &= 1
\end{oldalign*}

\end{document}

请注意,如果使用\let而不是,\NewCommandCopy那么行为将会不同且出乎意料(请自己尝试)。


或者(根据具体用例可能适用也可能不适用),你可以使用 LaTeX 中的钩子系统:

%! TEX program = lualatex
\documentclass{article}
\usepackage{amsmath}
\begin{document}

\AddToHook{env/align*/begin}{x}
\AddToHook{env/align*/end}{y}

\begin{align*}
    1 &= 1 \\
    2 &= 2
\end{align*}


\end{document}

相关内容