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}
\write
of\addtocontents
is delayed until shipout, and at that time\protect
has a different meaning. To remedy that you need\protect\protect\protect\testA
. However, the\@writefile
code uses some toks registers to handle end of lines, and then the unmatched conditionals are seen and chaos ensues. I'm not sure if this is doable without a heavy surgery in some LaTeX internals...:-)
Obviously also @PhelypeOleinik gets an error, so the problem (well, the absence thereof...) in on my side. My only clue is that on my work computer I have only TeXLive2017.2018/12/01
. More specifically, when fixing this bug.