Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

I would like to write a LaTeX macro whose code depends on the parent environment such as:

\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

Any idea?

share|improve this question

migrated from stackoverflow.com Dec 31 '11 at 16:42

This question came from our site for professional and enthusiast programmers.

add comment

5 Answers

The current name of the environment is saved in the macro \@currenvir.

In the example below is defined a new test named \ifenv. The syntax of the command is:

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

\ifenv test whether the name of the current environment is equal to the input or not. Depending of the result the true or false part will be used.

\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}

It is important to use \begin --- \end to define \@currenvir.

As Marc van Dongen mentioned the test fails if you combine environments.

In this case I would use another solution.

\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}
share|improve this answer
    
Examples of usage? –  egreg Dec 31 '11 at 16:54
    
@egreg: Example ;-) –  Marco Daniel Dec 31 '11 at 16:58
    
This is a partial solution. For example, if there's another environment ``in between'' it gives the wrong result. For example, \begin{enva} \begin{tt} \ifenv{enva}{THIS IS RIGHT}{I AM WRONG}\end{tt}\end{enva}. –  Marc van Dongen Dec 31 '11 at 18:56
    
@MarcvanDongen: Of course. begin{..} defines the \@currenvir. In other cases you must work with flags ala \newif\ifenva and set the flags at the beginning of the environment. –  Marco Daniel Dec 31 '11 at 18:59
    
@Marco It would be nice if it was possible to modify the solution and cater for cases like this. For example, the center environment is something that users may typically want to use as an ``inbetween'' environment. –  Marc van Dongen Dec 31 '11 at 19:24
show 2 more comments

No need to use any conditionals at all: let TeX do the switching for you! Here's how it works:

  • For every environment for which you want a special effect, call \NewEnvCode{<environment>}{<code>}. You should at least write \NewEnvCode{document}{<code>}; this is installed as the default action for unspecified environments (including top-level; i.e. inside the document "environment").

  • Call \RunEnvCode anywhere. If it's in one of your special environments, then your code will be run; otherwise, the default code is run. You can make the current environment the default for all sub-environments by calling \MakeDefault (this is how document is installed, actually). This is reset after you leave the calling environment.

Here's my code with a sample 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}

Okay, here's how it really works:

  • \NewEnvCode creates a macro \change@code@#1, where #1 is the name of an environment, and which, when called, redefines something called \Code to produce the code #2. Actually, it creates a macro \code@#1 that produces #2 and then has \change@code@#1 \let \Code to this macro.

  • \RunEnvCode begins by giving \Code the default effect \code@@default, and then tries to call one of the \change@code@<env> macros, with <env> = \@currenvir as described in the other answers (the name of the current environment). It constructs this macro name using \csname...\endcsname.

  • Because of the way \csname works, if the control sequence it produces is not already defined, it acts like \relax; i.e. does nothing. So if the current environment is not one of the ones you specified, \Code remains defined as the default action, and otherwise, gets switched.

  • \MakeDefault just \lets the default action \code@@default to \code@<env>, so that if \RunEnvCode is called in an unknown environment, it thinks it's calling the default, but that default will be whatever was last set by \MakeDefault.

If you want to add more cases, just call \NewEnvCode for the relevant environments.

share|improve this answer
add comment

A more easily customizable version can be obtained with 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}

Of course, these definition don't take into account nested environments: so

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

will produce \testDefault and not \testenvA.

A different approach is to add code to the environments:

\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

In this case

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

will produce \testenvA.

share|improve this answer
    
The same problem which Marc mentioned. Is there a LaTeX 3 solution? –  Marco Daniel Dec 31 '11 at 19:48
    
@MarcoDaniel The definition of \test must go in the environment's definition, then. –  egreg Dec 31 '11 at 20:48
    
I thought there is a command like previousenv or so ;-) –  Marco Daniel Dec 31 '11 at 20:56
    
@MarcoDaniel How many steps above? –  egreg Dec 31 '11 at 21:15
    
I think 3 is enough. –  Marco Daniel Dec 31 '11 at 21:24
show 1 more comment

I've created two "dummy" environments envA and envB to illustrate the use of \@currenvir:

enter image description here

\documentclass{article}
\newenvironment{envA}{}{\par}% Dummy environment
\newenvironment{envB}{}{\par}% Dummy environment
\makeatletter
\newcommand\test{%
  \def\@tempa{envA}% if current_environment=envA
  \ifx\@tempa\@currenvir test1\else%
  \def\@tempa{envB}% if current_environment=envB
  \ifx\@tempa\@currenvir test2\else\fi\fi%
}
\begin{document}

Entering \texttt{envA} \par
\begin{envA}
\test %output test1
\end{envA}
Exiting \texttt{envA}

Entering \texttt{envB} \par
\begin{envB}
\test %output test2
\end{envB}
Exiting \texttt{envB}

No environment \par
\test %output nothing
\end{document}
share|improve this answer
add comment

Since I believe you should use pgfkeys for everything (programming-related) let me submit a second answer with the same general behavior as the first. The only difference in the interface is that \NewEnvCode takes just one argument, which is a comma-separated list of key-value pairs <env> = <code> (put <code> in braces if it has commas or equals signs). The difference in coding is readily apparent:

\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}

Here's how it works:

  • Recall that pgfkeys has the notion of a "directory tree" of keys, and that its keys can perform quite general functions other than storing values: for example, they can invoke other keys, and there is a mechanism for dealing with unknown keys via a default search path. This is the mechanism I exploit to get the "default" behavior.

  • Any key <env> = <code> you pass to \NewEnvCode defines a key in the directory /envcode/code with the name <env>, and sets it to execute <code> when run; when you call \RunEnvCode, it tries to execute this key with <env> = \@currenvir.

  • When /envcode/code/<env> does not exist, the default "handler" /envcode/code/.unknown is called instead. This is set up by \MakeDefault, which says that it should have the "style" \@currenvir (completely expanded at "compile" time), meaning that it just calls the key of that name, e.g. "document". I made it so that \MakeDefault is called right after \begin{document}, so that with no other changes, the default code is for "document", which you should set up.

  • This same mechanism is responsible for the workings of \NewEnvCode itself: each key you give it is executed under the directory /envcode/define, which never has any keys defined in it. The unknown handler there always runs, and we ask it to set up a key in /envcode/code of the same name, whose "code" is <code>. This is a convenience for the user, so you don't have to write /.code after everything.

As you can see, the behavior you are looking for is basically equivalent to the lookup behavior of pgfkeys, so this is a pretty appropriate tool in addition to being superior programming-wise to the funny things that happen in TeX. Obviously it's a lot more complicated behind the scenes; every one of these macros probably expands to dozens of internal ones before eventually doing effectively what I wrote in my other answer. However, this code is self-documenting and, because pgfkeys is so flexible, much more easily extendable.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.