
Estoy confundido acerca de cómo funciona la expansión en \addtocontents
, o más precisamente en \protected@write
. Según tengo entendido, \addtocontents
esencialmente debería expandir su segundo argumento \protected@edef
y escribir el resultado en el archivo auxiliar. Con este entendimiento, esperaría que el comportamiento fuera (hasta a \protected@
) el mismo que
\iow_shipout:Ne \@auxout
{
\exp_not:N \@writefile { #1 } { #2 }
}
Pero como muestra el siguiente MWE, se comportan de manera diferente cuando intentan evitar la expansión dentro de su argumento con \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}
El archivo auxiliar se parece
\relax
\@writefile {lof}{\abc }
\@writefile{lof}{some text}
\gdef \@abspage@last{1}
También lo \naiveaddtocontents
es prevenir la expansión con \exp_not:n
, pero \addtocontents
no lo es. Para mi caso de uso en el que no quiero que el argumento se expanda en absoluto, puedo usar esto \naiveaddtocontents
. Pero, ¿por qué no funciona \exp_not:n
como esperaba incorrectamente \addtocontents
?
Respuesta1
El comando \addtocontents
usa \protected@write
. A continuación se muestra la definición de \protected@write
de 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
}
La parte relevante es \edef\reserved@a{\write#1{#3}}%
. Si la definición anterior de \protected@write
se agregaría en el documento y en la línea anterior, \edef
se reemplaza por \def
luego \addtocontents{ lof }{ \exp_not:n { \abc } }
se proporciona \@writefile{lof}{\abc }
en el archivo .aux.
Sin embargo, con \edef
, se produce una expansión adicional que \addtocontents{ lof }{ \exp_not:n { \abc } }
da \@writefile{lof}{some text}
como resultado el archivo .aux.
Por lo tanto, con \edef
, el ejemplo funciona si \exp_not:n
se agrega un adicional: \addtocontents{ lof }{ \exp_not:n { \exp_not:n { \abc } } }
proporciona \@writefile{lof}{\abc }
el archivo .aux.
Respuesta2
Veamos la definición 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}}}
Con su ingenua definición, tendría grandes problemas si tuviera un \label
segundo argumento para \addtocontents
. Pero dejemos esto de lado, porque es un tecnicismo.
Supongamos que quieres \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}
La consola 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>
y el aux
archivo tendrá
\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}
Realmente no es lo que quieres ver, ¿verdad? Arreglemoslo.
\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}
Ahora el aux
archivo tendrá
\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}
Básicamente, \text_expand:n
hará más o menos lo mismo que \protected@edef
hace, pero dejará la lista de tokens resultante envuelta en \unexpanded
.
Si lo hace \exp_not:n
(es decir, \unexpanded
), TeX serviráNoexpansión, por lo que obtienes \abc
y no su expansión.
Sin \makeatletter
y \makeatother
:
\ExplSyntaxOn
\NewDocumentCommand { \naiveaddtocontents } { m m }
{
\iow_shipout:ce { @auxout }
{
\token_to_str:c { @writefile } { #1 } { \text_expand:n { #2 } }
}
}
\ExplSyntaxOff
En lugar de \token_to_str:c
puedes usar\exp_not:c