Write if statement to the toc

Edit:

This fix was added to the LaTeX 2ε and is available in the kernel since the 2020-02-02 release, so since TeXLive 2019.


The issue was introduced with the 2018/12/01 release of LaTeX. Before that, the macro \@writefile (which does the writing from the .aux to the .toc essentially did \write\tocfile{\unexpanded{<stuff>}}, so your unbalanced conditional was written without problems.

After the aforementioned release, a fix for this problem was introduced and the problem boils down to:

\let\testB\fi
\iftrue\else
  \toks0{\testB}
\fi

TeX doesn't care about balancing braces when looking for the matching \else or \fi of a conditional. In this case, \testB is seen as a \fi and you're left with just }\fi.

I think the definition of \add@percent@to@temptokena could be changed a bit to allow avoid this sort of problem:

\documentclass[a4paper]{book}
\newcommand\test{\noindent test\par}
\let\testA\iffalse
\let\testB\fi

\makeatletter
\long\def\add@percent@to@temptokena
    #1\protected@file@percent#2\add@percent@to@temptokena
    {\ifx!#2!\expandafter\dont@add@percent@to@temptokena\else
             \expandafter\do@add@percent@to@temptokena\fi{#1}}
\long\def\dont@add@percent@to@temptokena#1{%
  \@temptokena\expandafter{#1}}
\begingroup
\catcode`\%=12
\catcode`\^^A=9
\long\gdef\do@add@percent@to@temptokena#1{%
  \@temptokena\expandafter{#1%^^A
  }}
\endgroup
\makeatother

\begin{document}

\addtocontents{toc}{\protect\testA}
\addtocontents{toc}{\protect\test}
\addtocontents{toc}{\protect\testB}

\tableofcontents

\chapter{test}

\end{document}

\documentclass[a4paper]{book}
\newcommand\test{\noindent test\par}
\DeclareRobustCommand\activateif{%
\let\testA\iffalse
\let\testB\fi}
\DeclareRobustCommand\deactivateif{%
\let\testA\relax
\let\testB\relax}



\begin{document}

\addtocontents{toc}{
 \activateif
 \protect\testA test\par 
 \protect\testB
 \deactivateif}


\tableofcontents

\chapter{test}

\end{document}

This lets the chapter entry in the toc disappear

\documentclass[a4paper]{book}
\newcommand\test{\noindent test\par}
\DeclareRobustCommand\activateif{%
\let\testA\iffalse
\let\testB\fi}
\DeclareRobustCommand\deactivateif{%
\let\testA\relax
\let\testB\relax} 

\begin{document}

\addtocontents{toc}{\activateif}

\tableofcontents

\addtocontents{toc}{\protect\testA}
\chapter{test}
\addtocontents{toc}{\protect\testB}

\addtocontents{toc}{\deactivateif}

\end{document}

It seems that in more recent LaTeX2e-releases some \add@percent@to@temptokena-mechanism was added to \@writefile:

> \@writefile=\long macro:
#1#2->\@ifundefined {tf@#1}\relax {\add@percent@to@temptokena \@empty #2\protec
ted@file@percent \add@percent@to@temptokena \immediate \write \csname tf@#1\end
csname {\the \@temptokena }}.
l.19 \show\@writefile


> \add@percent@to@temptokena=\long macro:
#1\protected@file@percent #2\add@percent@to@temptokena ->\ifx !#2!\@temptokena 
\expandafter {#1}\else \@temptokena \expandafter {#1% }\fi .
l.20 \show\add@percent@to@temptokena

I assume this \ifx !#2!... is intended to be an empty-check for detecting whether \@writefile's second argument contains the token \protected@file@percent. (If so, the first such token not nested in curly braces shall be replaced by % and everything behind it shall be dropped.)

  1. I don't reccomend trying to write a percent-char trailed by an exclamation-mark as in:
    \@writefile{toc}{something\protected@file@percent! This is a very important comment.}
  2. In case \add@percent@to@temptokena's first argument contains unbalanced \else/\fi (as is the case with your scenario), these will erroneously match up the \ifx of
    \ifx !#2!\@temptokena\expandafter{#1}.

I foresee that this \add@percent@to@temptokena-thingie breaks a lot of home-brewed code written by users who rely on having the possibility of writing unmatched \if.., \else or \fi by means of \@writefile.

I might suggest something like:

\documentclass[a4paper]{book}

\begingroup
\makeatletter
\catcode`\&=14 %
\catcode`\%=12 &
\@firstofone{&
  \endgroup
  &&-----------------------------------------------------------------------------
  && Change \add@percent@to@temptokena:
  &&.............................................................................
  \long\def\add@percent@to@temptokena#1\protected@file@percent#2\add@percent@to@temptokena{&
    \ifcat A\detokenize{#2}A\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
    {\@temptokena\expandafter{#1}}{\@temptokena\expandafter{#1% }}&
  }&
}%

\newcommand\test{\noindent test\par}
\let\testA\iffalse
\let\testB\fi

\begin{document}

\addtocontents{toc}{\protect\testA}
\addtocontents{toc}{\protect\test}
\addtocontents{toc}{\protect\testB}

\tableofcontents

\chapter{test}

\end{document}

If you don't like the e-TeX-\detokenize-thingie:

\documentclass[a4paper]{book}

\begingroup    
\makeatletter
\catcode`\&=14 %
\catcode`\%=12 &
\@firstofone{&
  \endgroup
  &&-----------------------------------------------------------------------------
  && Check whether argument is empty:
  &&.............................................................................
  && \UD@CheckWhetherNull{<Argument which is to be checked>}
  &&                     {<Tokens to be delivered in case that argument
  &&                       which is to be checked is empty>}
  &&                     {<Tokens to be delivered in case that argument
  &&                       which is to be checked is not empty>}
  &&
  && The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
  && <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
  \newcommand\UD@CheckWhetherNull[1]{&
    \romannumeral0\expandafter\@secondoftwo\string{\expandafter
    \@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
    \@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
    \@secondoftwo\string}\@firstoftwo\expandafter{} \@secondoftwo}&
    {\@firstoftwo\expandafter{} \@firstoftwo}&
  }&
  &&-----------------------------------------------------------------------------
  && Change \add@percent@to@temptokena:
  &&.............................................................................
  \long\def\add@percent@to@temptokena#1\protected@file@percent#2\add@percent@to@temptokena{&
    \UD@CheckWhetherNull{#2}&
                        {\@temptokena\expandafter{#1}}&
                        {\@temptokena\expandafter{#1% }}&
  }&
}%

\newcommand\test{\noindent test\par}
\let\testA\iffalse
\let\testB\fi

\begin{document}

\addtocontents{toc}{\protect\testA}
\addtocontents{toc}{\protect\test}
\addtocontents{toc}{\protect\testB}

\tableofcontents

\chapter{test}

\end{document}

By the way:

One could as well redefine \@writefile to only replace the first \protected@file@percent by % while leaving everything else in place:

\documentclass[a4paper]{book}

\makeatletter
%%-----------------------------------------------------------------------------
%% Change \@writefile to replace the first non-brace-nested
%% \@protected@file@percent in #2 by %
%%.............................................................................
\@ifdefinable\RemoveTo@protected@file@percent{%
  \long\def\RemoveTo@protected@file@percent#1\protected@file@percent{}%
}%
\begingroup
\catcode`\&=14 %
\catcode`\%=12 &
\@firstofone{&
  \endgroup
  \@ifdefinable\Replace@protected@file@percent{&
    \long\def\Replace@protected@file@percent#1\protected@file@percent{&
      \@firstoftwo{ }#1%&
    }&
  }&
}%
\long\def\@writefile#1#2{%
  \@ifundefined{tf@#1}\relax{%
    \immediate\write\csname tf@#1\endcsname{%
      \unexpanded\expandafter{\romannumeral0%
        \ifcat A\detokenize\expandafter{%
          \RemoveTo@protected@file@percent#2\protected@file@percent
        }A\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
        { }%
        {\Replace@protected@file@percent.}%
        #2%
      }%
    }%
  }%
}%
\makeatother

\newcommand\test{\noindent test\par}
\let\testA\iffalse
\let\testB\fi

\begin{document}

\makeatletter
\addtocontents{toc}{\protect\testA\protected@file@percent{This is my nice comment.}}
\makeatother
\addtocontents{toc}{\protect\test}
\addtocontents{toc}{\protect\testB}

\tableofcontents

\chapter{test}

\end{document}

Without \detokenize and without \unexpanded:

\documentclass[a4paper]{book}

\makeatletter
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is empty>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral0\expandafter\@secondoftwo\string{\expandafter
  \@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
  \@secondoftwo\string}\@firstoftwo\expandafter{} \@secondoftwo}%
  {\@firstoftwo\expandafter{} \@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Change \@writefile to replace the first non-brace-nested
%% \@protected@file@percent in #2 by %
%%.............................................................................
\@ifdefinable\RemoveTo@protected@file@percent{%
  \long\def\RemoveTo@protected@file@percent#1\protected@file@percent{}%
}%
\begingroup
\catcode`\&=14 %
\catcode`\%=12 &
\@firstofone{&
  \endgroup
  \@ifdefinable\Replace@protected@file@percent{&
    \long\def\Replace@protected@file@percent#1\protected@file@percent{&
      \@firstoftwo{}#1%&
    }&
  }&
}%
\long\def\@writefile#1#2{%
  \@ifundefined{tf@#1}\relax{%
    \expandafter\UD@CheckWhetherNull\expandafter{\RemoveTo@protected@file@percent#2\protected@file@percent}%
    {%
      \@temptokena{#2}%
    }{%
      \@temptokena\expandafter\expandafter\expandafter{\Replace@protected@file@percent.#2}%
    }%
    \immediate\write\csname tf@#1\endcsname{\the\@temptokena}%
  }%
}%
\makeatother

\newcommand\test{\noindent test\par}
\let\testA\iffalse
\let\testB\fi

\begin{document}

\makeatletter
\addtocontents{toc}{\protect\testA\protected@file@percent{This is my nice comment.}}
\makeatother
\addtocontents{toc}{\protect\test}
\addtocontents{toc}{\protect\testB}

\tableofcontents

\chapter{test}

\end{document}

Tags:

Conditionals