6

I'm trying to create an environment where the text only appears if an optional argument is not changed (and disappears when it is). The gist of what I'm trying to do is:

\newenvironment{entry}[3][0]
    {\ifcase #1
    \textbf{#2} \hfill {\textit{#3}}\\
    \noindent
}
{\fi}

So that changing #1 toggles both #2/#3 and any text that I actually type in the environment. If I put \fi inside the first set of braces (after \noindent), it makes #2 and #3 toggle, but not the actual text. However, moving \fi to the second set of braces gives "incomplete ifcase" error. Is there a way to slightly modify the code to produce the effect that I'm looking for?

I'm trying to create a somewhat dynamic document where, by changing a couple quick values, I can make entire sections display or not display. If you have any recommendations on how to do this better, feel free to tell me.

P.S. If anyone can direct me towards a source with thorough coverage of conditionals, I would massively appreciate that.

  • Also, sorry if I'm brutally misunderstanding something. My LaTeX education has been rough. I'd appreciate it if you took it easy on me. – BillyTheSquid Nov 9 at 8:42
  • May be this post will be found usefull for your purposes tex.stackexchange.com/a/515484/120578 – koleygr Nov 9 at 9:25
2

How about

\documentclass{article}
\usepackage{environ}
\NewEnviron{entry}[3][0]{\ifnum#1=0
\textbf{#2} \hfill {\textit{#3}}\\
    \noindent\BODY\fi}
\begin{document}
\begin{entry}{a}{b}
Blub
\end{entry}

\begin{entry}[1]{c}{d}
Pft
\end{entry}
\end{document}  
4

Analysis

Let me explain what's the problem with your code.

When the next token in the input stream is a conditional, TeX looks for the appropriate test following it and decides whether to follow the true or false branch. The other branch is skipped over with no interpretation of the tokens, but taking note of possible conditionals appearing at the outer level, in order to match with the correct \else or \fi.

The conditional \ifcase is slightly different, see A question about \ifcase syntax, but the idea is exactly the same.

What happens when you call \begin{entry}{Hello}{World}? There's no optional argument, so (skipping some details about the process), TeX ends up seeing

\ifcase0 \textbf{Hello} \hfill {\textit{World}}\\\noindent<other text>\end{entry}

OK, the test tells TeX to skip text only after the first \or (\else, if no \or comes along), so it starts processing tokens. Upon arriving at \end{entry}, TeX does its usual business, including \endentry that, in this case, produces \fi. Good! No \or nor \else have appeared, so nothing to be skipped; the token \fi vanishes according to the rules.

What with \begin{entry}[1]{Hello World}? After macro replacement, TeX gets to

\ifcase1 \textbf{Hello} \hfill {\textit{World}}\\\noindent<other text>\end{entry}<rest of the document>

Here the problem is visible: TeX has to skip text until coming to the first \or or \else or \fi at the outer level, because it won't do macro expansion when skipping text. And there is no token of the required form. TeX will skip up to the end of the document and will complain about an incomplete conditional.

Solution

You have to absorb the text before the conditional is evaluated. The standard ways are with environ or xparse.

The latter is more robust and friendlier.

\usepackage{xparse}

\NewDocumentEnvironment{entry}{O{0} m m +b}
 {\ifnum#1=0 \textbf{#2}\hfill\textit{#3}\\*#4\fi}
 {}

The arguments are specified as

  • O{0}: an optional argument with default value 0
  • m: a standard mandatory argument
  • +b: the environment's body, allowing for blank lines

The “end part” is empty, because there's no special processing to do there.

This way, the \fi is seen at the outer level.

With environ is almost the same:

\usepackage{environ}

\NewEnviron{entry}[3][0]{%
 \ifnum#1=0 \textbf{#2}\hfill\textit{#3}\\*\BODY\fi
}

The main differences are that the syntax for arguments is the same as for \newenvironment and that the environment's body is represented by \BODY.

I recommend the former. The solution with environ has been given in another answer; please, check the differences:

  1. \ifcase is not the right conditional to use; \ifnum is easier;
  2. \noindent is redundant;
  3. \\* will disallow a page break after this particular line break.
  • The environ solution is already present in an earlier answer.... – Schrödinger's cat Nov 9 at 10:08
  • @Schrödinger'scat OK, let me fix it. I prefer to also fix the OP's mistakes. – egreg Nov 9 at 10:09
  • The xparse solution is giving me a weird error, saying xparse/unknown-argument-type and Unknown argument type 'b' replaced by 'm'. Any clue why it's not recognizing that? – BillyTheSquid Nov 9 at 17:49
  • Thanks for the explanation though. It cleared things up. Also, \ifnum is a lot nicer than what I'd been using. – BillyTheSquid Nov 9 at 18:02
  • @BillyTheSquid Update your TeX system for the xparse solution. – egreg Nov 9 at 18:31
2

For example, following my comment you could do this:

\documentclass{article}

\newsavebox{\mybox}
\newenvironment{entry}[3][0]
    {%
    \def\mycase{0}%
    \def\myArg{#1}%
    \ifx\myArg\mycase
    \textbf{#2} \hfill {\textit{#3}}\\
    \else\relax\fi
    \savebox\mybox\bgroup\vbox\bgroup}
{\egroup\egroup\ifx\mycase\myArg\usebox{\mybox}\fi}


\begin{document}
Test

\begin{entry}{Some}{text}
My content will be shown
\end{entry}

\begin{entry}[2]{Some}{text}
My content will not be shown
\end{entry}
\end{document}
  • 1
    \setbox\mybox=\vbox{} will also work. \savebox translates as \setbox\mybox=\hbox{}. – John Kormylo Nov 9 at 14:06
  • 1
    Nice. Perhaps it is worth mentioning that due to \savebox being carried out in any case, global assignments—like \stepcounter—wthin the body of an entry-environment will be carried out in any case. I cannot fix my opinion on whether this is a benefit or not: I can think of scenarios where this is desired. I can also think of scenarios where this is not desired. – Ulrich Diez Nov 9 at 14:15
2

Environments baiscally (I don't tell all the details and subtleties) work as follows:

With

\newenvironment{foo}[⟨args⟩][⟨optional⟩]%
               {⟨tokens before environment body⟩}%
               {⟨tokens after environment body⟩}
  • a macro \foo is defined whose definition's parameter-text is according to [⟨args⟩][⟨optional⟩] and whose definition's ⟨balanced text⟩ is formed from ⟨tokens before environment body⟩.
  • a macro \endfoo is defined which does not process arguments and whose definition's ⟨balanced text⟩ is formed from ⟨tokens after environment body⟩.

With \begin{foo) the \begin-macro processes {foo} as its argument and

  • begins a new group/a new local scope,
  • redefines \@currenvir to expand to the name of the current environment, i.e., to expand to the phrase foo,
  • does \csname foo\endcsname, i.e., calls the macro \foo, which in turn processes arguments and hereby delivers ⟨tokens before environment body⟩.

With \end{foo} the \end-macro processes {foo} as its argument and

  • does \csname endfoo\endcsname, i.e., calls the macro \endfoo which in turn delivers ⟨tokens after environment body⟩,
  • checks whether the argument of \end equals the expansion of \@currenvir and if not, then delivers an error-message,
  • ends the local scope/the local group,
  • under some circumstances does something about the spacing of things that appear after the environment.

With your code the problem is:

The \fi which should match \ifcase is hidden inside the definition-text of the macro \endentry while the control-word-token \endentry is to be constructed when carrying out \end{entry}.
Therefore when—after processing \begin{entry} and hereby expanding the macro \entry—LaTeX is searching the token-stream for \ifcase's matching \fi , it will definitely not "see" it in case of the condition in the \ifcase-branch being false (= in case the optional argument denoting a number larger than 0) and therefore things in the \ifcase-branch not being expanded/executed.

I suggest loading the verbatim-package and in case the condition is false using the internals of its comment-environment.
A pitfall with this approach is that you cannot nest entry-environments.
Another pitfall with this approach is that the entry-ennvironment cannot be used within macro-arguments or within balanced-texts of definitions of macros and the like where things get tokenized under a category-code-régime inappropriate for gobbling the entry-ennvironment by means of verbatim-package-code.
Yet another pitfall with this approach is that within lines that contain \end{entry}, the phrase \end{entry} must not be trailed by something else.
Also worth mentioning is that with this approach global assignments—e.g., things like \stepcounter—within the body of the environment are carried out only in case the body of the environment is printed/is not treated as a comment.

\documentclass{article}
\usepackage{verbatim}

\newenvironment{entry}[3][0]{%
    %\csname @\ifcase #1first\else second\fi oftwo\endcsname
    \csname @\ifnum#1=0 first\else second\fi oftwo\endcsname
    {%
      \textbf{#2} \hfill {\textit{#3}}%
      % \noindent right behind \\ has no effect as \\ 
      % does _not_ cause the starting of a new paragraph. (Only the first
      % line of a (new) paragraph would - without \noindent right _after_
      % starting the paragraph - horizontally be indented by
      %`\parindent`-glue.)
      \\*%
      \ignorespaces % This will make sure that a space(-token) right behind
                    % the closing brace of the environment's second 
                    % non-optional argument will not yield horizontal glue.
    }%
    {%
      \expandafter\let\csname end\csname @currenvir\endcsname\endcsname=\endcomment
      \comment
    }%
}%
{%
  % Due to (La)TeX's \endlinechar-mechanism line-endings in the source usually
  % are treated as if there were just spaces in the source. This in many 
  % situations leads to the coming into being of space-tokens. Space-tokens
  % in turn in might yield undesired horizontal glue in the .pdf-output.
  %
  %\ifhmode\unskip\fi % In case the last thing produced by (La)TeX was glue,
           % \unskip will remove that glue.
           % The above might remove horizontal glue which comes into 
           % being at the end of the last line of the environment's body due to
           % the \endlinechar-mechanism inserting a space at the end of lines.
           %
           % Instead you can end the last line of the environment-body with a
           % comment-char = a percent-char = %.
  \ignorespacesafterend % \ignorespacesafterend ensures that space-tokens
                        % behind \end{entry}, which might come into being due to the
                        % \endlinechar-mecnahnism, will not yield horizontal glue.
}

\newcounter{MyNiceDemoCounter}
\newcommand*\PrintMyNiceDemoCountersValue[1]{%
  \noindent
  \rlap{\lower-\ht\strutbox\hbox to\hsize{\hrulefill\null}}%
  #1 the environment the value of 
  {\csname verbatim@font\endcsname MyNiceDemoCounter}
  is \arabic{MyNiceDemoCounter}.\hfill\null
  \llap{\lower\dp\strutbox\hbox to\hsize{\hrulefill\null}}%
}%


\begin{document}

\PrintMyNiceDemoCountersValue{Before}\bigskip

X\begin{entry}{bold}{italic}
This environment is printed and 
\stepcounter{MyNiceDemoCounter}%
\verb|MyNiceDemoCounter| is stepped.
\end{entry}
X

\bigskip\PrintMyNiceDemoCountersValue{After}

\vfill

\PrintMyNiceDemoCountersValue{Before}\bigskip

X\begin{entry}[1]{bold}{italic}
This environment is not printed and 
\stepcounter{MyNiceDemoCounter}%
\verb|MyNiceDemoCounter| is not stepped.
\end{entry}
X

\bigskip\PrintMyNiceDemoCountersValue{After}

\vfill

\PrintMyNiceDemoCountersValue{Before}\bigskip

X\begin{entry}{bold}{italic}
This environment is printed and 
\stepcounter{MyNiceDemoCounter}%
\verb|MyNiceDemoCounter| is stepped.%
\end{entry}
X

\bigskip\PrintMyNiceDemoCountersValue{After}

\vfill

\PrintMyNiceDemoCountersValue{Before}\bigskip

X\begin{entry}[1]{bold}{italic}
This environment is not printed and 
\stepcounter{MyNiceDemoCounter}%
\verb|MyNiceDemoCounter| is not stepped.%
\end{entry}
X

\bigskip\PrintMyNiceDemoCountersValue{After}

\end{document}

enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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