以下 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}