\def 内的 \def 在标题或说明中不可见

\def 内的 \def 在标题或说明中不可见

以下 MWE 产生编译错误:

\documentclass{article}

\usepackage[english]{babel}

\begin{document}
\def\AAA#1{\bgroup\def\contents{AAA}#1\egroup}
\def\BBB#1{\bgroup\def\contents{BBB}#1\egroup}

% Without this line, compilation fails
\def\contents{\relax}

\section{\AAA{\contents}}
\section{\BBB{\contents}}
\section{\AAA{\contents}}
\section{\BBB{\contents}}
\end{document}

错误:未定义控制序列。

--- TeX said ---
\AAA #1->\bgroup \def \contents 
                                {AAA}#1\egroup 
l.12 \section{\AAA{\contents}}

但是,当我取消注释顶层\def\contents行时,该示例似乎有效。

为什么需要这个顶层\def?添加它有什么缺点?有没有更简洁的方法来做宏局部定义(仅在宏主体中有效的定义)?

以下情况也会出现同样的问题:

\begin{figure}
   \caption{\AAA{\contents}}
\end{figure}

答案1

章节和标题是移动参数。它们进入.aux文件,然后从文件进入.toc.lof/.lot文件。章节标题也可以出现在标题行中。

当宏被写入文件时,它们会被展开,并且 Werner 的回答非常全面地说明了这一点。宏中的赋值或定义通常会导致脆弱的宏,它们在展开时会中断,来自 Werner 的回答:

\@writefile{toc}{\contentsline {section}{\numberline {1}\bgroup \def {AAA}\egroup }{1}}    

这是一个无效的定义,因为它试图定义{

或者\def\contents{\relax}

\bgroup \def \relax {AAA}\relax \egroup

然后\relax重新定义,这是永远不应该做的。

更好的顶层定义\contents是:

\let\contents\relax

calc使用不可扩展\ignorespaces来实现此目的:

\let\widthof\ignorespaces
\let\heightof\ignorespaces
\let\depthof\ignorespaces
\let\totalheightof\ignorespaces

然后\contents不是写入文件时展开:

\bgroup \def \contents {AAA}\contents \egroup

还可以保护定义\AAA以避免写入完整的定义文本:

\let\contents\relax
\section{\protect\AAA{\contents}}

然后.aux文件包含:

\@writefile{toc}{\contentsline {section}{\numberline {1}\AAA {\contents }}{1}}

.toc文件:

\contentsline {section}{\numberline {1}\AAA {\contents }}{1}

或者,\AAA可以使宏变得更强大:

\DeclareRobustCommand*{\AAA}{...}% The LaTeX2e way
\let\contents\relax
...
\section{\AAA{\contents}}

.aux文件条目:

\@writefile{toc}{\contentsline {section}{\numberline {1}\AAA  {\contents }}{1}}

\protected或者也可以使用eTeX :

\protected\def\AAA#1{...}
\let\contents\relax
...
\section{\AAA{\contents}}

既不\def检查\DeclareRobustCommand要定义的宏是否已定义。\newcommand如果是这种情况,虚拟宏有助于获取错误消息:

\newcommand{\AAA}{}
\DeclareRobustCommand*{\AAA}[1]{...}

或 \newcommand{\AAA}{} \protected\def\AAA#1{...}

LaTeX 对未定义宏的解释包括具有含义的命令\relax。因此,\contents将被覆盖而不会出现错误:

\let\contents\relax
\newcommand*{\contents}{foobar}

而宏\AAA\BBB如果参数中包含\contents移动参数,又会被破坏。包使用calc含义来避免这种情况\ignorespaces

概括:

\newcommand{\contents}{}% Check that \contents is undefined
\let\contents\ignorespaces

\newcommand{\AAA}{}%
% \DeclareRobustCommand*{\AAA}[1]{...}
\protected\def\AAA#1{\bgroup\def\contents{AAA}#1\group}

% ...
\section{\AAA{\contents}}
% ...
\caption{\AAA{\contents}}

答案2

无论你是否愿意,部分单元都会将其参数写入.aux你可能想要使用的\tableofcontents(比如说)中。同样适用于,当你使用或时\caption,它会将内容写入 for 。因此,你的局部定义在实际设置中仍然存在,但不会写入。要解决这个问题,你必须禁用该功能,或者提供一个替代的 ToC 相关条目:.aux\listoftables\listoffigures\contents.aux

\documentclass{article}

\begin{document}

\def\AAA#1{\bgroup\def\contents{AAA}#1\egroup}
\def\BBB#1{\bgroup\def\contents{BBB}#1\egroup}

\section[AAA]{\AAA{\contents}}
\section[BBB]{\BBB{\contents}}
\section[CCC]{\AAA{\contents}}
\section[DDD]{\BBB{\contents}}

\end{document}

.aux如果你不执行上述操作,则会收到以下信息:

\relax 
\@writefile{toc}{\contentsline {section}{\numberline {1}\bgroup \def {AAA}\egroup }{1}}
\@writefile{toc}{\contentsline {section}{\numberline {2}\bgroup \def {BBB}\egroup }{1}}
\@writefile{toc}{\contentsline {section}{\numberline {3}\bgroup \def {AAA}\egroup }{1}}
\@writefile{toc}{\contentsline {section}{\numberline {4}\bgroup \def {BBB}\egroup }{1}}

请注意,在您的情况下\def,缺少其控制序列参数\contents,该参数在写入时并不存在.aux

即使你使用你的“修复”

\def\contents{\relax}

写入目录后的扩展仍然会导致一些奇怪的事情:

\@writefile{toc}{\contentsline {section}{\numberline {1}\bgroup \def \relax {AAA}\relax \egroup }{1}}
\@writefile{toc}{\contentsline {section}{\numberline {2}\bgroup \def \relax {BBB}\relax \egroup }{1}}
\@writefile{toc}{\contentsline {section}{\numberline {3}\bgroup \def \relax {AAA}\relax \egroup }{1}}
\@writefile{toc}{\contentsline {section}{\numberline {4}\bgroup \def \relax {BBB}\relax \egroup }{1}}

答案3

其他人已经解释了错误的原因。

如果您已在使用xparse,那么有一个简单的解决方法。

\NewDocumentCommand\contents{}{} % placeholder

\NewDocumentCommand\AAA{m}{%
  \begingroup
  \RenewDocumentCommand\contents{}{AAA}%
  #1%
  \endgroup
}

这是基于这样的事实:用 定义的命令\NewDocumentCommand自动地是强大的。

您可以将的默认定义设置为\contents您想要的任何内容。

您还可以抽象重复的部分:

\NewDocumentCommand{\defcontentscommand}{mm}{%
  % #1 = macro name, #2 = tokens
  \NewDocumentCommand{#1}{m}{%
    \begingroup
    \RenewDocumentCommand\contents{}{#2}##1%
    \endgroup
  }%
}
\NewDocumentCommand{\contents}{}{} % placeholder
\defcontentscommand{\AAA}{AAA}
\defcontentscommand{\BBB}{BBB}

相关内容