当你想创建带有多个可选参数,或带有正式星号变体的命令时,\newcommand 那种只数参数个数的方法就不够用了。现代做法是 \NewDocumentCommand,它用一串字母声明每个参数的 *种类*。它最初来自 xparse 宏包,如今已是 LaTeX 内核的一部分。本页会介绍参数指定的迷你语言,以及用来检查用户实际给了什么参数的判定命令。
现在已是内核的一部分
过去需要 \usepackage{xparse},但 自 2020 年 10 月的 LaTeX 版本起,这套机制已进入 LaTeX 内核。\NewDocumentCommand 等命令开箱即用,几乎不再需要载入 xparse。内核中实现此功能的文件称为 ltcmd,而 xparse 宏包本身已被官方描述为“obsolete”。除了少数已弃用的参数类型,新代码可以直接使用这些命令,无需载入任何宏包。
这个现代接口的思想是 把含义(接口)与实现分开。你把用户看到的参数序列(必需、可选、星号)声明为“参数指定(argument specification, arg-spec)”,而命令本体总是以 #1、#2 等规范化形式接收参数。\newcommand 的基础在“定义宏”页面已经说明;如果还没读过,先看一遍会更容易看出差异。
定义命令与环境
基本形式是 \NewDocumentCommand{\cmd}{⟨arg-spec⟩}{⟨code⟩}:第一个参数是命令名,第二个是参数指定,第三个是本体。和 \newcommand 一样,它也有针对不同目的的同族命令;区别在于遇到已定义名称时如何处理。
| 命令 | 遇到已有名称时 |
|---|---|
\NewDocumentCommand | 若已定义则报错 |
\RenewDocumentCommand | 若尚未定义则报错(重新定义已有命令) |
\ProvideDocumentCommand | 仅在尚未给出时定义 |
\DeclareDocumentCommand | 总是覆盖(谨慎使用) |
环境也有同样机制,可用 \NewDocumentEnvironment{⟨env⟩}{⟨arg-spec⟩}{⟨start code⟩}{⟨end code⟩} 定义(也有 \Renew…、\Provide…、\Declare…)。参数在 \begin{⟨env⟩} 之后给出,开始代码和结束代码都能看到;详见“自定义环境”页面。还有一个好处:用这种方式创建的所有命令都会自动 robust(内部使用 ε-TeX 的 \protected 机制),因此在标题或图注中不用担心 \protect。
还有一个可展开版本 \NewExpandableDocumentCommand。它可以用于命令会被“展开”的上下文,例如 \edef 或 \write 中;代价是可用的参数类型受限(后述 s/t 这类布尔测试和 v 等不可用)。普通文档中通常使用标准版本就足够。
参数指定语言
参数指定是 一串字符,每个字符表示一个参数的“类型”:必需、可选、星号、由特定字符定界等等。各类型会作为 #1、#2 等传到本体中,但关键细节是:未给出的可选参数会得到一个特殊值。主要类型见下表。
| 类型 | 写法 | 含义 | 传入本体的形式 |
|---|---|---|---|
m | m | 必需的 {} 参数 | 普通的 #1 |
o | o | 可选的 [...] 参数 | 缺省时为 -NoValue- |
O | O{default} | 带默认值的可选参数 | 缺省时为 default |
s | s | 可选星号 * | \BooleanTrue / \BooleanFalse |
t | t⟨char⟩ | 可选的指定字符记号 | \BooleanTrue / \BooleanFalse |
r | r⟨d1⟩⟨d2⟩ | 由 d1…d2 定界的必需参数 | 缺失时报错后为 -NoValue- |
d | d⟨d1⟩⟨d2⟩ | 由 d1…d2 定界的可选参数 | 缺省时为 -NoValue- |
e | e{⟨tokens⟩} | 装饰项(如 ^、_) | 每项缺省时为 -NoValue- |
v | v | verbatim 参数 | 原样字符 |
m 是最基本的必需参数;它接受花括号组或单个记号,并在传给本体前去掉外层 {}。o 是标准可选参数 [...],O{default} 是带默认值的版本。像 O、D 这样的 大写类型可以指定默认值,而 o、d 等小写版本会返回特殊标记 -NoValue-。
s 用来检测前导星号 *:有星号时得到 \BooleanTrue,没有时得到 \BooleanFalse。推广到任意单个字符就是 t⟨char⟩;例如 t+ 会把是否有 + 作为布尔值传入。r⟨d1⟩⟨d2⟩ 是由你选择的定界符包住的“必需”定界参数(如 r() 表示圆括号);若缺少开定界符,会报错并插入 -NoValue-。d⟨d1⟩⟨d2⟩ 则是可选的定界版本。
稍特殊的是 e{⟨tokens⟩},它收集 ^、_ 这样的 装饰项(embellishments)。写 e{^_} 时,它会以任意顺序接收 ^{...} 和 _{...},未给出的项目为 -NoValue-(列出的记号必须彼此不同)。v 像 \verb 一样读取 verbatim 参数。注意,定界类型(r、d 等)的定界符不能使用 TeX 分组字符 { 和 };通常选择 []、() 或 "" 这样的自然成对字符。
检查参数:判定命令
要处理可选参数或星号,就需要在本体中判断“是否给出”。专用判定命令有两类。首先 \IfNoValueTF{#1}{⟨if no value⟩}{⟨if value⟩} 会检查参数是否为 -NoValue-(即未给出);没有值时执行第一个分支,有值时执行第二个分支。反向的 \IfValueTF 也存在,并且都有只取 T 或只取 F 的版本(\IfNoValueT / \IfNoValueF、\IfValueT / \IfValueF)。
对于 s 或 t 产生的布尔值,请使用专用的 \IfBooleanTF{#1}{⟨if starred⟩}{⟨if not⟩}:#1 为 \BooleanTrue 时执行第一个分支,为 \BooleanFalse 时执行第二个分支(也有 \IfBooleanT / \IfBooleanF)。经验规则是:\IfNoValueTF 系列用于 o/d 等可选参数,\IfBooleanTF 系列用于 s/t 的布尔参数。另外,-NoValue- 被设计成不会与字面文本 -NoValue- 相同,因此务必用 \IfNoValueTF 判断,而不是比较字符串。
示例:星号加带默认值的可选参数
用 s O{} m 这个参数指定,创建一个同时具有星号变体和带默认值可选参数的命令。这里以标题命令为例:#1 是星号(用 \IfBooleanTF 判断),#2 是可选短标题(默认空),#3 是主标题。
\NewDocumentCommand{\heading}{s O{} m}{%
\IfBooleanTF{#1}%
{\textbf{#3}}% 星つき: 番号なしの見出し / starred: unnumbered
{\section%
\IfNoValueTF{#2}%
{{#3}}% 短縮見出しなし / no short title
{[#2]{#3}}% 短縮見出しあり / with short title
}%
}
% 使用例 / usage
\heading{Introduction} % 通常の番号つき節
\heading[Intro]{A Long Introduction} % 目次用に短縮見出しを指定
\heading*{Preface} % 星つき: 太字の無番号見出し一个细节:不要混淆 o 和 O{}。使用 o 时,未给出的参数会变成 -NoValue-,因此可以像上面那样用 \IfNoValueTF 分支。使用 O{} 时则相反,参数 总是 有值(未给出时为空),此时应测试“是否为空”,例如用 \tl_if_blank:nTF(expl3)或 \ifblank(etoolbox),而不是 \IfNoValueTF。上例若把可选参数设为 o(无默认值),\IfNoValueTF 分支才符合意图。
为什么优于 \newcommand
\newcommand 只允许 一个 可选参数,而且必须是第一个;也没有内置的星号变体。传统做法是手写 \@ifstar、\@ifnextchar 等低层机制来弥补,但它们需要 \makeatletter,在空白和嵌套方面脆弱,也不易阅读。
- 多个独立可选参数(
o o m、O{a} O{b} m等)。 - 通过
s加\IfBooleanTF得到 真正的星号变体,不需要\@ifstar技巧。 - 声明式默认值:只要写
O{default},未给出时的值就确定了。 - 一开始就是 robust:标题和图注中不需要
\protect。 - 还能表达
\newcommand无法表达的输入语法,如定界参数(r、d)和装饰项(e)。
总之,简单缩写用 \newcommand 已足够;但 只要参数结构稍微复杂,\NewDocumentCommand 就应成为首选。当你还想把本体写成小程序时,与 expl3(LaTeX3 编程层)配合就是自然的发展方向。类和宏包的编写见“编写类与宏包”页面。