Понимание расширения в \addtocontents

Понимание расширения в \addtocontents

Я не понимаю, как работает расширение в \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@writefrom 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

Связанный контент