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.)
- 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.}
- 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}