
Я не понимаю, как работает расширение в \addtocontents
, или, точнее, в \protected@write
. Насколько я понимаю, \addtocontents
по сути, должен расширить свой второй аргумент с помощью \protected@edef
и записать результат в файл aux. Понимая это, я бы ожидал, что поведение будет (до a \protected@
) таким же, как
\iow_shipout:Ne \@auxout
{
\exp_not:N \@writefile { #1 } { #2 }
}
Но как показывает следующий пример MWE, они ведут себя по-разному, пытаясь предотвратить расширение внутри своего аргумента с помощью \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}
Файл aux выглядит так
\relax
\@writefile {lof}{\abc }
\@writefile{lof}{some text}
\gdef \@abspage@last{1}
Так же как \naiveaddtocontents
и предотвращение расширения с помощью \exp_not:n
, но \addtocontents
не является. Для моего варианта использования, где я вообще не хочу, чтобы аргумент был расширен, я могу просто использовать это \naiveaddtocontents
. Но почему не работает \exp_not:n
так, как я неправильно ожидал в \addtocontents
?
решение1
Команда \addtocontents
использует \protected@write
. Ниже приведено определение \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
}
Соответствующая часть — \edef\reserved@a{\write#1{#3}}%
. Если указанное выше определение \protected@write
будет добавлено в документ и в предыдущей строке \edef
будет заменено на , \def
то \addtocontents{ lof }{ \exp_not:n { \abc } }
дает \@writefile{lof}{\abc }
в файле .aux.
Однако при использовании \edef
происходит дополнительное расширение, которое \addtocontents{ lof }{ \exp_not:n { \abc } }
приводит \@writefile{lof}{some text}
к появлению файла .aux.
Таким образом, с \edef
, пример работает, если \exp_not:n
добавлено дополнительное: \addtocontents{ lof }{ \exp_not:n { \exp_not:n { \abc } } }
дает \@writefile{lof}{\abc }
в файле .aux.
решение2
Давайте рассмотрим определение \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}}}
С вашим наивным определением у вас были бы большие проблемы, если бы у вас был a \label
во втором аргументе \addtocontents
. Но давайте опустим это, потому что это техническая деталь.
Предположим, вы хотите \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}
Консоль выведет
(\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>
и aux
файл будет иметь
\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}
Не совсем то, что вы хотели бы видеть, не так ли? Давайте исправим это.
\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}
Теперь aux
файл будет иметь
\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}
По сути, \text_expand:n
будет делать примерно то же самое, что \protected@edef
и делает , но оставит полученный список токенов завернутым в \unexpanded
.
Если вы это сделаете \exp_not:n
(то есть,\unexpanded
), TeX сделает этонетрасширение, поэтому вы получаете \abc
и не его расширение.
Без \makeatletter
и \makeatother
:
\ExplSyntaxOn
\NewDocumentCommand { \naiveaddtocontents } { m m }
{
\iow_shipout:ce { @auxout }
{
\token_to_str:c { @writefile } { #1 } { \text_expand:n { #2 } }
}
}
\ExplSyntaxOff
Вместо \token_to_str:c
вы можете использовать\exp_not:c