命令行为取决于当前环境

命令行为取决于当前环境

我想编写一个 LaTeX 宏,其代码依赖于父环境,例如:

\newcommand\test{
%if current_environment=env1
test1
%elseif current_environment=env2
test2
%else
}

\begin{env1}
\test %output test1
\end{env1}


\begin{env2}
\test %output test2
\end{env2}

\test %output nothing

任何想法?

答案1

环境的当前名称保存在宏中\@currenvir

下面的例子中定义了一个名为 的新测试\ifenv。该命令的语法是:

\ifenv{environment name}{TRUE}{FALSE}

\ifenv测试当前环境的名称是否等于输入。根据结果,将使用真或假部分。

\documentclass{article}

\makeatletter
\def\ifenv#1{
   \def\@tempa{#1}%
   \ifx\@tempa\@currenvir
      \expandafter\@firstoftwo
    \else
      \expandafter\@secondoftwo
   \fi
}
\makeatother
\newenvironment{enva}{}{}
\newenvironment{envb}{}{}
\begin{document}
\begin{enva}
\ifenv{enva}{TRUE}{FALSE}

\ifenv{envb}{TRUE}{FALSE}
\end{enva}

\begin{envb}
\ifenv{enva}{TRUE}{FALSE}

\ifenv{envb}{TRUE}{FALSE}
\end{envb}
\end{document}

使用\begin---\end来定义很重要\@currenvir

正如 Marc van Dongen 提到的,如果合并环境,测试就会失败。

在这种情况下,我会使用另一种解决方案。

\documentclass{article}

\makeatletter
\def\Mycurrentvir{document}
\def\ifenv#1{
   \def\@tempa{#1}%
   \ifx\@tempa\Mycurrentvir
      \expandafter\@firstoftwo
    \else
      \expandafter\@secondoftwo
   \fi
}
\makeatother
\newenvironment{enva}{\def\Mycurrentvir{enva}}{}
\newenvironment{envb}{\def\Mycurrentvir{envb}}{}
\begin{document}
\begin{enva}
\ifenv{enva}{TRUE}{FALSE}

\ifenv{envb}{TRUE}{FALSE}
\end{enva}

\begin{envb}
\ifenv{enva}{TRUE}{FALSE}

\ifenv{envb}{TRUE}{FALSE}
\end{envb}
\end{document}

答案2

根本不需要使用任何条件:让 TeX 为您进行切换!它的工作原理如下:

  • 对于每个需要特殊效果的环境,请调用\NewEnvCode{<environment>}{<code>}。您至少应该写\NewEnvCode{document}{<code>};这是作为未指定环境(包括顶级;即在document“环境”内)的默认操作安装的。

  • 可以在任何地方调用\RunEnvCode。如果是在您的某个特殊环境中,则将运行您的代码;否则,将运行默认代码。您可以通过调用将当前环境设为所有子环境的默认环境\MakeDefault(实际上,这就是安装的方式document)。离开调用环境后,该环境将被重置。

以下是我的代码和示例文档:

\documentclass{article}
\usepackage{filecontents}

% Simulated package file
\begin{filecontents}{envcode.sty}
\newcommand\NewEnvCode[2]{%
 \expandafter\def\csname code@#1\endcsname{#2}%
 \expandafter\def\csname change@code@#1\endcsname{%
  \expandafter\let\expandafter\Code\csname code@#1\endcsname
 }%
}

\newcommand\MakeDefault{%
 \expandafter\let\expandafter\code@@default\csname code@\@currenvir\endcsname
}

\newcommand\RunEnvCode{%
 \let\Code=\code@@default
 \csname change@code@\@currenvir\endcsname
 \Code
}

\AtBeginDocument{\MakeDefault}
\end{filecontents}

\usepackage{envcode}

\NewEnvCode{document}{default code}
\NewEnvCode{equation}{\int_{-\infty}^\infty e^{-x^2/2} dx = \sqrt{2\pi}}
\NewEnvCode{itemize}{itemize!}

\begin{document}
 \RunEnvCode

 \begin{equation}
  \RunEnvCode
 \end{equation}

 \begin{enumerate}
  \item \RunEnvCode
 \end{enumerate}

 \begin{itemize}
  \item \RunEnvCode

  \item
  \begin{enumerate}
   \item \RunEnvCode
  \end{enumerate}

  \item \MakeDefault
  \begin{enumerate}
   \item \RunEnvCode
  \end{enumerate}
 \end{itemize}

\end{document}

好吧,它的实际工作原理如下:

  • \NewEnvCode创建一个宏\change@code@#1,其中#1是环境的名称,当调用它时,它会重新定义一些调用\Code来生成代码#2。实际上,它会创建一个宏,然后\code@#1生成并执行此宏。#2\change@code@#1 \let \Code

  • \RunEnvCode首先给出\Code默认效果\code@@default,然后尝试使用其他答案中所述的(当前环境的名称)调用其中一个\change@code@<env>宏。它使用构造此宏名称。<env> = \@currenvir\csname...\endcsname

  • 由于这种方式的\csname工作方式,如果它产生的控制序列尚未定义,它的行为就像\relax;即什么也不做。因此,如果当前环境不是您指定的环境之一,则\Code保持定义为默认操作,否则,进行切换。

  • \MakeDefault只是\let默认操作\code@@default\code@<env>因此如果\RunEnvCode在未知环境中调用,它它正在调用默认值,但该默认值将是最后设置的默认值\MakeDefault

如果要添加更多案例,只需调用\NewEnvCode相关环境即可。

答案3

可以通过以下方式获得更容易定制的版本expl3

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\makeatletter
\NewDocumentCommand{\test}{}
  {
   \prg_case_str:onn { \@currenvir }
      {
       {envA}{\testenvA}
       {envB}{\testenvB}
      }
      {\testDefault}
  }
\makeatother
\ExplSyntaxOff

\newenvironment{envA}{}{}
\newenvironment{envB}{}{}
\newcommand{\testenvA}{We're~in~\texttt{envA}}
\newcommand{\testenvB}{We're~in~\texttt{envB}}
\newcommand{\testDefault}{We're~neither~in~\texttt{envA}~nor~in~\texttt{envB}}

\begin{document}

\test

\begin{envA}\test\end{envA}

\begin{envB}\test\end{envB}

\end{document}

当然,这些定义没有考虑嵌套环境:所以

\begin{envA}\begin{envC}\test\end{envC}\end{envA}

将生产\testDefault不是 \testenvA

另一种方法是向环境中添加代码:

\usepackage{etoolbox}
\newcommand{\testenvA}{We're~in~\texttt{envA}}
\newcommand{\testenvB}{We're~in~\texttt{envB}}
\newcommand{\testDefault}{We're~neither~in~\texttt{envA}~nor~in~\texttt{envB}}
\AtBeginEnvironment{envA}{\renewcommand{\test}{\testenvA}}
\AtBeginEnvironment{envB}{\renewcommand{\test}{\testenvB}}
\newcommand{\test}{\testDefault} % default

在这种情况下

\begin{envA}\begin{envC}\test\end{envC}\end{envA}

将会产生\testenvA

答案4

因为我相信你应该用于pgfkeys一切(与编程相关)让我提交第二个答案,其一般行为与第一个答案相同。接口中唯一的区别是\NewEnvCode只接受一个参数,即以逗号分隔的键值对列表<env> = <code><code>如果有逗号或等号,则放在括号中)。编码的差异显而易见:

\documentclass{article}
\usepackage{filecontents}

% Simulated package file
\begin{filecontents}{pgfkeys-envcode.sty}
\RequirePackage{pgfkeys}

\pgfkeys{
 /envcode/.is family, /envcode,
 define/.is family,
 code/.is family,
}

\pgfkeys{
 /envcode/define,
 .unknown/.style = {
  /envcode/code/\pgfkeyscurrentname/.code={#1},
 },
}

\AtBeginDocument{\MakeDefault}

\newcommand\NewEnvCode[1]{%
 \pgfkeys{/envcode/define,#1}%
}

\newcommand\MakeDefault{%
 \pgfkeys{/envcode/code/.unknown/.style/.expanded=\@currenvir}%
}

\newcommand\RunEnvCode{%
 \pgfkeys{/envcode/code,\@currenvir}%
}
\end{filecontents}

\usepackage{pgfkeys-envcode}

\NewEnvCode{
 document = default code,
 equation = {\int_{-\infty}^\infty e^{-x^2/2} dx = \sqrt{2\pi}},
 itemize  = itemize!,
}

\begin{document}
 \RunEnvCode

 \begin{equation}
  \RunEnvCode
 \end{equation}

 \begin{enumerate}
  \item \RunEnvCode
 \end{enumerate}

 \begin{itemize}
  \item \RunEnvCode

  \item
  \begin{enumerate}
   \item \RunEnvCode
  \end{enumerate}

  \item \MakeDefault
  \begin{enumerate}
   \item \RunEnvCode
  \end{enumerate}
 \end{itemize}

\end{document}

工作原理如下:

  • 回想一下,它pgfkeys有一个键的“目录树”的概念,并且它的键除了存储值之外还可以执行相当通用的功能:例如,它们可以调用其他键,并且有一种通过默认搜索路径处理未知键的机制。这是我利用来获得“默认”行为的机制。

  • <env> = <code>您传递给的任何键都会\NewEnvCode在目录中定义一个/envcode/code名为的键<env>,并将其设置为<code>在运行时执行;当您调用时\RunEnvCode,它会尝试使用来执行此键<env> = \@currenvir

  • /envcode/code/<env>不存在时,/envcode/code/.unknown将改为调用默认的“处理程序”。这是由设置的\MakeDefault,它表示它应该具有“样式” \@currenvir(在“编译”时完全展开),这意味着它只调用该名称的键,例如“document”。我将其设置为\MakeDefault在之后立即调用\begin{document},这样在没有其他更改的情况下,默认代码是“document”,您应该设置它。

  • 同样的机制也负责\NewEnvCode其自身的运作:你给它的每个键都在目录下执行/envcode/define绝不在其中定义了任何键。那里的未知处理程序始终运行,我们要求它在 中设置一个/envcode/code同名的键,其“代码”为<code>。这为用户提供了便利,因此您不必/.code在所有内容之后写入。

正如您所看到的,您正在寻找的行为基本上等同于的查找行为pgfkeys,因此这是一个非常合适的工具,而且在编程方面比 TeX 中发生的有趣事情更出色。显然,这是一个很多幕后更加复杂;这些宏中的每一个可能都会扩展到数十个内部宏,然后才能最终有效地完成我在另一个答案中写到的工作。但是,此代码是自文档化的,并且由于pgfkeys非常灵活,因此更容易扩展。

相关内容