我有一个宏,它定义了一些内部宏,并在本地进行定义,以免弄乱全局命名空间。但是,\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
),则宏可能已写入文件。