这是我在过去几周遇到过几次的问题。我尝试将宏用作其他宏的参数,但不起作用。
最近的例子:我尝试\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}
并执行此原始命令,即\foo
si 已定义。另一方面,如果您尝试键入,\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}}