
Estou confuso sobre como a expansão funciona \addtocontents
, ou mais precisamente, em \protected@write
. Pelo que entendi, \addtocontents
deveria essencialmente expandir seu segundo argumento \protected@edef
e gravar o resultado no arquivo aux. Com esse entendimento, eu esperaria que o comportamento fosse (até a \protected@
) o mesmo que
\iow_shipout:Ne \@auxout
{
\exp_not:N \@writefile { #1 } { #2 }
}
Mas, como mostra o MWE a seguir, eles se comportam de maneira diferente ao tentar impedir a expansão dentro de seu argumento com \exp_not:n
.
\documentclass{article}
\makeatletter
\ExplSyntaxOn
\NewDocumentCommand { \naiveaddtocontents } { m m }
{
\iow_shipout:Ne \@auxout
{
\exp_not:N \@writefile { #1 } { #2 }
}
}
\ExplSyntaxOff
\def\abc{some text}
\begin{document}
bla
\ExplSyntaxOn
% this does what I expect
\protected@edef \l_tmpa_tl { \exp_not:n { \abc } }
\tl_show:N \l_tmpa_tl
% so does this
\naiveaddtocontents{ lof }{ \exp_not:n { \abc } }
% this does not
\addtocontents{ lof }{ \exp_not:n { \abc } }
\ExplSyntaxOff
\end{document}
O arquivo aux se parece com
\relax
\@writefile {lof}{\abc }
\@writefile{lof}{some text}
\gdef \@abspage@last{1}
O mesmo \naiveaddtocontents
acontece com a prevenção da expansão \exp_not:n
, mas \addtocontents
não é. Para o meu caso de uso em que não quero que o argumento seja expandido, posso simplesmente usar this \naiveaddtocontents
. Mas por que não \exp_not:n
funciona como eu esperava incorretamente \addtocontents
?
Responder1
O comando \addtocontents
usa \protected@write
. Abaixo está a definição de \protected@write
from source2e
:
\long\def \protected@write#1#2#3{%
\begingroup
\let\thepage\relax
#2%
\let\protect\@unexpandable@protect
\edef\reserved@a{\write#1{#3}}%
\reserved@a
\endgroup
\if@nobreak\ifvmode\nobreak\fi\fi
}
A parte relevante é \edef\reserved@a{\write#1{#3}}%
. Se a definição acima \protected@write
for adicionada no documento e na linha anterior, \edef
é substituída por \def
então \addtocontents{ lof }{ \exp_not:n { \abc } }
dá \@writefile{lof}{\abc }
no arquivo .aux.
Porém com \edef
, ocorre uma expansão adicional que \addtocontents{ lof }{ \exp_not:n { \abc } }
dá \@writefile{lof}{some text}
no arquivo .aux.
Portanto com \edef
, o exemplo funciona se \exp_not:n
for adicionado um adicional: \addtocontents{ lof }{ \exp_not:n { \exp_not:n { \abc } } }
dá \@writefile{lof}{\abc }
no arquivo .aux.
Responder2
Vejamos a definição de \addtocontents
:
% latex.ltx, line 14289:
\long\def\addtocontents#1#2{%
\protected@write\@auxout
{\let\label\@gobble \let\index\@gobble \let\glossary\@gobble}%
{\string\@writefile{#1}{#2}}}
Com sua definição ingênua, você teria grandes problemas se tivesse um \label
no segundo argumento para \addtocontents
. Mas vamos deixar isso de lado, porque é um detalhe técnico.
Suponha que você queira \addtocontents{toc}{Hey, this is \textbf{boldface}}
.
\documentclass{article}
\makeatletter
\ExplSyntaxOn
\NewDocumentCommand { \naiveaddtocontents } { m m }
{
\iow_shipout:Ne \@auxout
{
\exp_not:N \@writefile { #1 } { #2 }
}
}
\ExplSyntaxOff
\makeatother
\begin{document}
Some text
\addtocontents{toc}{Hey, this is \textbf{boldface}}
\naiveaddtocontents{toc}{Hey, this is \textbf{boldface}}
\end{document}
O console irá imprimir
(\end occurred when \ifx on line 21 was incomplete)
(\end occurred when \ifx on line 21 was incomplete)
(\end occurred when \ifx on line 21 was incomplete)
(\end occurred when \ifmmode on line 21 was incomplete)</usr/local/texlive/2023
/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb>
e o aux
arquivo terá
\relax
\@writefile{toc}{Hey, this is \textbf {boldface}}
\@writefile {toc}{Hey, this is \protect \unhbox \voidb@x \bgroup \edef l3backend-pdftex.def{boldface}\let \futurelet \@let@token \let \protect \relax \edef cmr{cmr}\edef cmss{cmss}\edef cmtt{cmtt}\def ##1,b,{}\series@check@toks {,ulm,elm,lm,slm,mm,sbm,bm,ebm,ubm,muc,mec,mc,msc,msx,mx,mex,mux,{}{},b,}\edef {}\edef b{b}\def ##1,m,{}\series@check@toks {,ulm,elm,lm,slm,mm,sbm,bm,ebm,ubm,muc,mec,mc,msc,msx,mx,mex,mux,{}{},m,}\edef {}\edef m{m}\protect \let }
\gdef \@abspage@last{1}
Não é realmente o que você quer ver, não é? Vamos consertar isso.
\documentclass{article}
\makeatletter
\ExplSyntaxOn
\NewDocumentCommand { \naiveaddtocontents } { m m }
{
\iow_shipout:Ne \@auxout
{
\exp_not:N \@writefile { #1 } { \text_expand:n { #2 } }
}
}
\ExplSyntaxOff
\makeatother
\newcommand{\abc}{some text}
\begin{document}
Some text
\addtocontents{toc}{Hey, this is \textbf{boldface} and \abc}
\naiveaddtocontents{toc}{Hey, this is \textbf{boldface} and \abc}
\end{document}
Agora o aux
arquivo terá
\relax
\@writefile{toc}{Hey, this is \textbf {boldface} and some text}
\@writefile {toc}{Hey, this is \textbf {boldface} and some text}
\gdef \@abspage@last{1}
Basicamente, \text_expand:n
fará aproximadamente o mesmo \protected@edef
, mas deixando a lista de tokens resultante envolvida em \unexpanded
.
Se você fizer isso \exp_not:n
(ou seja, \unexpanded
), o TeX faránãoexpansão, então você obtém \abc
e não sua expansão.
Sem \makeatletter
e \makeatother
:
\ExplSyntaxOn
\NewDocumentCommand { \naiveaddtocontents } { m m }
{
\iow_shipout:ce { @auxout }
{
\token_to_str:c { @writefile } { #1 } { \text_expand:n { #2 } }
}
}
\ExplSyntaxOff
Em vez de \token_to_str:c
você pode usar\exp_not:c