\addtocontents の展開を理解する

\addtocontents の展開を理解する

\addtocontents、より正確には での展開がどのように機能するかについて混乱しています\protected@write。私の理解では、 は\addtocontents基本的に 2 番目の引数を で展開し、その結果を aux ファイルに書き込む必要があります\protected@edef。この理解では、動作は ( まで\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@writesource2e

\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と、 .aux ファイルでは になり\addtocontents{ lof }{ \exp_not:n { \abc } }ます。\@writefile{lof}{\abc }

ただし、 では\edef、追加の展開が発生し、 が.aux ファイルで\addtocontents{ lof }{ \exp_not:n { \abc } }提供されます。\@writefile{lof}{some text}

したがって\edef、 では、 を追加すれば例は機能します\exp_not:n。 は、.aux ファイルに\addtocontents{ lof }{ \exp_not:n { \exp_not:n { \abc } } }示されます。\@writefile{lof}{\abc }

答え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}}}

\labelあなたの素朴な定義では、 の 2 番目の引数にがある場合に大きな問題が生じます\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

関連情報