当使用宏作为其他宏的参数时会出现什么问题?

当使用宏作为其他宏的参数时会出现什么问题?

这是我在过去几周遇到过几次的问题。我尝试将宏用作其他宏的参数,但不起作用。

最近的例子:我尝试\addcontentsline将数据库中的名称放入datatool目录中。

最大能量损失 1:

\documentclass{article}

\usepackage{datatool}

\DTLnewdb{database}

\DTLnewrow{database}
\DTLnewdbentry{database}{shortcut}{JD}
\DTLnewdbentry{database}{name}{John Doe}

\begin{document}

\tableofcontents

%this works and produces the output "John Doe"
\DTLfetch{database}{shortcut}{JD}{name}

%if I use the same command inside \addcontentsline,
%it doesn't work and returns errors
\addcontentsline{toc}{section}{\DTLfetch{database}{shortcut}{JD}{name}}

\end{document}

我并不是(仅仅)寻求这个特定问题的解决方案,而是寻求更一般的解释,当使用宏作为其他宏的参数时我应该考虑什么,以及这样做会导致什么错误。

如果这是一个愚蠢的问题,我很抱歉,但我找不到有关该主题的任何信息,无论是在LaTeX 伴侣也没有互联网,尽管我花了很多时间去搜索。

编辑:如果可能的话,请提供一个通用答案,而不是只解决 MWE 中的问题。我想了解导致这些错误的原因以及如何避免这些错误,我想有很多没有经验的用户也可能会发现这很有用。

为了说明我谈论的是哪种问题,下面是另一个 MWE,其问题类似于微波能量吸收 1\protect。我尝试通过在几个地方随机添加来解决这个问题(如埃格尔在他的回答中微波能量吸收 1,除非我猜他知道自己在做什么;-)),但我无法让它以这种方式发挥作用。

最大能量损失 2:

\documentclass{article}

\usepackage{datatool}

\DTLnewdb{database}

\DTLnewrow{database}
\DTLnewdbentry{database}{shortcut}{JD}
\DTLnewdbentry{database}{name}{John Doe}

%command which reads and prints the name belonging to the shortcut
\newcommand{\readname}[1]{\DTLfetch{database}{shortcut}{#1}{name}}

%environment for some text written by the author whose shortcut is in the argument
\newenvironment{authorstext}[1]{%
    \addcontentsline{toc}{subsection}{#1}%when this line is removed, everything works
    {\textbf{#1} has written this text:}%
}{}

\begin{document}

\tableofcontents

\section{Authors}

%this doesn't work
\begin{authorstext}{\readname{JD}}
Some text by John Doe.
\end{authorstext}

%this works
\readname{JD}

\end{document}

答案1

问题是,宏的扩展(在正常处理期间)与原始命令混合在一起,这会产生所需的输出。但是,当将参数写入文件时,它只会被扩展,而不会处理原始(和不可扩展)命令。它们只会被重写到文件中。假设您有宏:

\newcount\num  \num=0
\def\macro{\num=113 \ifnum\num=113 YES\else NO\fi}

\macro在正常处理过程中处理时,原始命令 (assigment) 设置\num为 113,之后扩展命令\ifnum将条件评估为真并YES打印。但现在,假设宏是一个输出到文件的参数,例如:

\num=0
\typeout{\macro}

然后\macro被展开,但是\num是不可展开的命令(分配),现在不处理(仅重新打印),并且\ifnum是可扩展的并且被处理。结果是

\num =133 NO

如果您稍后处理此输出,则会处理分配,但结果是NO

另一个例子。假设\macro按以下方式定义:

\def\macro{...\def\foo{something}...}

当正常处理正在进行时,\macro会扩展为\def\foo{something}并执行此原始命令,即\foosi 已定义。另一方面,如果您尝试键入,\typeout{\macro}\def不会处理命令(它只会被重写)并\foo进行扩展。但它通常是未定义的,因此您会收到未定义的控制序列错误。

最后假设\macro定义为\nobreak\space并且\nobreak定义为\penalty\@M。正常处理会产生不可中断的空间,因为\@M定义为 10000 常量。当您键入 时\typeout{\macro},您将获得原始命令和声明的常量:\penalty\@M后跟扩展空间,即不可见空间。当您需要再次读取此输出时(这通常在 TOC 处理期间完成),您将得到错误\@未定义或它不是常量(因为 的 catcode@此时不同)。如果 的 catcode@是合适的设置,则空间会消失。所以有一个问题。

这就是您需要将\macro未展开的 写入文件的原因。这可以通过两种不同的方式完成:在将 写入文件时,\macro可以加上前缀,或者它具有临时的不同含义(例如)。LaTeX 使用第一种方法,并提供命令,该命令将 展开(依赖于上下文)为或类似内容。本身(而不是 的展开)存储到文件中。当再次读取文件内容时,通常会进行处理。\noexpand\relax\protect\noexpand\macro\macro\macro

LaTeX 提供了\DeclareRobustCommand\macro,其定义\macro\protect\internal-version-of-macro。内部版本具有相同的名称,但在命令名称中添加了空格。我不会评论 LaTeX 作者的这个决定。

答案2

\DTLfetch是一个脆弱的命令,第三个参数\addcontentsline是一个移动参数。所以你想要

\addcontentsline{toc}{section}{\protect\DTLfetch{database}{shortcut}{JD}{name}}

问题在于\addcontentsline需要完全扩展其第三个参数,该参数可能包含应与其当前值一起存储的数据,但在\write执行适当的指令时(即,当 TeX 发送出页面时)可能会发生变化,通常是一个章节编号。

有了\protect,脆弱的命令就会“永远”受到保护,不被扩展,并且按原样写入.toc文件中。

另一方面,\protect在涉及排版时不执行任何操作,因此您可以通过定义来解决更普遍的第二个问题

\newcommand{\readname}[1]{\protect\DTLfetch{database}{shortcut}{#1}{name}}

相关内容