防止 \caption 过早扩展参数

防止 \caption 过早扩展参数

我有一个宏,它定义了一些内部宏,并在本地进行定义,以免弄乱全局命名空间。但是,\caption似乎过早扩展了宏的参数。这是一个最小示例。

\documentclass{article}
\begin{document}
\DeclareRobustCommand\aaaa[1]{%
  \bgroup%
  \def\bbbb{bbbb}% exists only inside \aaaa (as not to clutter global namespace)
  #1% 
  \egroup%
}
\aaaa{\bbbb}% ok
\begin{figure}
  \caption{long \aaaa{\bbbb}}% not ok
\end{figure}

这是什么原因造成的?有办法解决这个问题吗?(我的宏的 LaTeX3 解决方案也可以。)

答案1

LaTeX 的保护机制仅保护宏,而不保护其参数。在 LaTeX 中,宏可以在不同的上下文中使用/执行:

  • 正常排版上下文(\set@typeset@protect)。
    \protect含义为\relax。 它不影响正常执行的后续宏。

  • 显示上下文(\set@display@protect)。
    宏显示在消息、错误、警告或\typeout.中\protect,将\string以下宏转换为无法再执行的简单字符标记。

  • 扩展上下文(\@unexpandable@protect)。
    \protect\noexpand这里阻止下面的宏进行下一次扩展。

\string\noexpand仅影响下一个标记。它们不保护带有许多标记的整个参数。如今,大多数 TeX 引擎都支持 e-TeX,它附带\detokenize\unexpanded。两者都适用于标记列表,可用于保护参数。

在某种程度上, 的定义\aaaa可以扩展以支持其论点的保护,例如:

\documentclass{article}

\makeatletter
\newcommand*{\aaaa}{%
  \ifx\protect\@typeset@protect
    \csname aaaa \expandafter\endcsname
  \else
    \ifx\protect\@unexpandable@protect
      \protect@unexpand@cmd@arg\aaaa
    \else
      \ifx\protect\string
        \protect@string@cmd@arg\aaaa
      \else
        \expandafter\protect@unknown@cmd@arg
        \csname aaaa \endcsname
      \fi
    \fi
  \fi
}
\expandafter\newcommand\csname aaaa \endcsname[1]{%
  \begingroup
    \def\bbbb{bbbb}%
    #1%
  \endgroup
}
% unexpanded protect
\def\protect@unexpand@cmd@arg#1\else#2\fi\fi\fi#3{%
  \fi\fi
  \ifx\thepage\relax 
    \detokenize
  \else
    \unexpanded
  \fi
  {#1{#3}}%
}
% display protect
\def\protect@string@cmd@arg#1\else#2\fi\fi\fi#3{%
  \fi\fi\fi
  \detokenize{#1{#3}}%
}
% unknown protect
\def\protect@unknown@cmd@arg#1\fi\fi\fi{%
  \fi\fi\fi
  \protect#1%
}
\makeatother

\begin{document}
\listoffigures

% test typeset context
\aaaa{\bbbb}

%
\makeatletter
\protected@edef\x{\aaaa{\bbbb}}
\typeout{\meaning\x}
\makeatother

\typeout{\aaaa{\bbbb}}

\begin{figure}
  \caption{long \aaaa{\bbbb}}
\end{figure}
\end{document}

但是,延迟写入仍然存在严重问题,例如用于索引。如果写入索引条目,\thepage则无法扩展;因此写入节点将包含宏\thepage而不是当前页码,因此可能是错误的。在发货时,写入节点会扩展其内容并将其刷新到文件中。此时页码已知并\thepage扩展为正确的页码。

不幸的是,这会导致歧义\aaaa

  • 如果将其写入文件,则它应该用于\detokenize自身及其参数,以防止任何早期扩展。

  • \unexpanded如果它在宏定义内部使用\protected@edef并且宏未写入文件而是稍后执行,则应该使用它。

不幸的是,没有安全可靠的方法来区分宏内部的这两种情况\aaaa。上面的示例使用启发式方法,并假设如果\thepage禁用扩展(通过\let\thepage\relax),则宏可能已写入文件。

相关内容