设\example
为一个xparse
已定义的命令,它接受一个可选参数和一个必选参数。 的定义\example
不能被修改(例如,因为它来自第三方包)。
的作者\example
忘记处理一个空的可选参数,所以我想修补它以供自己使用。TeX 方式会将的\let
原始定义改为\example
其他内容,并在重新定义中使用此备份。
这在 LaTeX 宏中是不可能的,因为它需要一个可选参数(但 Heiko 提出了letltxmacro
为此)。另外,这对于xparse
定义的宏来说是不可能的,因为xparse
它将实际的定义包装到了一些非常智能的参数解析器构造中。
我做了以下不是工作示例,我天真地使用了\cs_new_eq:NN
,这让 TeX 陷入无限循环(因为\example
现在在xparse
逻辑上递归调用自身)。
\documentclass{article}
\usepackage{xparse}
\begin{document}
\ExplSyntaxOn
\NewDocumentCommand \example { o m }
{
#1 ~ #2
}
\ExplSyntaxOff
\example[First]{Second}
\example{Second}
\ExplSyntaxOn
% This is obviously wrong
\cs_new_eq:NN \orig_example:wn \example
\RenewDocumentCommand \example { o m }
{
\IfValueTF { #1 }
{ \orig_example:wn [ #1 ] { #2 } }
{ \orig_example:wn { #2 } }
}
\ExplSyntaxOff
\example[First]{Second}
\example{Second}
\end{document}
答案1
一般来说答案是不要这样做。背后的想法xparse
是文档级命令(\example
)用于定义语法,但应通过使用文档化的代码级函数来实现(因此类似于\module_command:nn
此处)。因此,任何新的或更改的定义都应该不是传递“文档级”语法,但应该使用xparse
和原始使用的代码级函数进行定义
\NewDocumentCommand \example { o m }
{
\module_command:nn {#1} {#2} % Oops, doesn't cover \NoValue
}
...
\RenewDocumentCommand \example { o m }
{
\IfNoValueTF {#1}
{ \module_command:n {#2} } % Presumably different
{ \module_command:nn {#1} {#2} }
}
因此,该团队故意不提供\LetDocumentCommand
或类似的东西。
最佳实践expl3
和xparse
使用已经发展了一段时间,因此团队自己编写的一些包仍然需要修改才能遵循它们。从我自己的代码来看,notes2bib
可能是展示正确方法的“模型”包。另一方面,在撰写本文时,siunitx
需要修改才能跟上它们:代码大部分是与开发同时编写expl3
的xparse
。开发正在进行中创建第三个版本,siunitx
并将通过这种明确的分离来实现。(我希望在 2016 年准备好。)
如果您需要为xparse
已定义的命令创建一个新的接口,并且该接口基于未记录的代码函数构建,那么目前您必须使用那些
\RenewDocumentCommand \example { o m }
{
% To be revised once documented interfaces are available
\IfNoValueTF {#1}
{ \__module_command:n {#2} } % Presumably different
{ \__module_command:nn {#1} {#2} }
}
大多数利用的作者expl3
都知道这个目的,但您可能希望向他们核实此类发展是否正在进行。
答案2
用 定义的命令的行为是间接的;其情况与处理用 定义的命令时\NewDocumentCommand
的情况类似,但更容易描述。letltxmacro
\DeclareRobustCommand
当你这样做
\DeclareRobustCommand{\foo}[1]{...#1...}
LaTeX 实际上定义二宏,本质上
\edef\foo{\noexpand\protect\expandafter\noexpand\csname foo \endcsname}
\expandafter\newcommand\csname foo \endcsname[1]{...#1...}
请注意执行实际工作的宏名称中的尾随空格。因此,如果你天真地这样做
\let\origfoo\foo
\DeclareRobustCommand{\foo}[1]{\origfoo{#1}?}
\foo{x}
在普通文本中调用,\protect
其中\relax
,将连续给出(使用•
表示具有它的宏名称中的尾随空格)
\protect\foo•{x} % first level expansion
\foo•{x} % \relax disappears
\origfoo{x}? % replacement text of redefined \foo•
\protect\foo•{x}? % first level expansion of \origfoo
\foo•{x}? % \relax disappears
哎呀!无限循环!
如果你试试
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand \example { o m }
{
#1 ~ #2
}
\cs_show:N \example
终端输出将是(重新格式化以便于阅读)
> \example=\protected macro:->\int_zero:N \l__xparse_processor_int
\tl_set:Nn \l__xparse_args_tl {\example code }
\tl_set:Nn \l__xparse_fn_tl {\example }
\__xparse_grab_D:w []{-NoValue-}
\__xparse_grab_m_1:w \l__xparse_args_tl .
因此,当你这样做时,\let\origexample\example
你只需使它与这个宏相同,但后续
\RenewDocumentCommand{\example}{...}{...}
会改变的含义\example
和of \example•code
(名称中再次带有空格),这实际上是执行实际工作的宏。会出现相同类型的无限循环,因为新定义的\example•code
宏将包含一个\origexample
最终将调用的调用\example•code
。无限循环。
从概念上来说,构建\LetDocumentCommand
并不困难,也不需要将这些命令添加到由xpatch
或管理的命令中regexpatch
,因为它们遵循与 类似的通用模式\DeclareRobustCommand
。
然而情况是非常不同:主要原因是宏定义的方式\NewDocumentCommand
并不是一成不变的。如果团队决定这样做,它可能会突然改变:__
名称中使用“私有”函数意味着没有包开发人员应该依赖这个特定的实现。
的情况\DeclareRobustCommand
有所不同,因为 LaTeX2e 内核发布了接口(并且确实存在一些利用该接口的软件包,不仅仅是letltxmacro
和xpatch
)。
约瑟夫·赖特已经解释了为什么\LetDocumentCommand
不会提供xparse
。如果你敢的话,你现在有工具来管理它。我不会。;-)
答案3
虽然约瑟夫·赖特和埃格雷格的警告不应该今天这样做可能同样有效(也可能无效;我不知道),但似乎自前两个答案问世以来的近五年里情况发生了变化。在LaTeX 新闻,第 32 期,日期为 2020 年 10 月,在第 4 页,我注意到以下部分(重点是我的):
提供一种复制强大命令的方法...
在之前的 LaTeX2ε 版本中,几个用户级命令变得更加强大,因此需要一种方法来创建这些命令的副本(通常是为了重新定义它们),而 LaTeX2ε 内核却没有办法做到这一点。以前,此功能部分由 Heiko Oberdiek 的 letltxmacro 包提供,它允许 使用
\foo
复制强大的命令。\bar
\LetLtxMacro\bar\foo
从此版本开始,LaTeX2ε 内核提供
\NewCommandCopy
(和\Renew...
及其\Declare...
变体),其功能与 几乎相同\LetLtxMacro
。对于最终用户来说,两者的工作方式应该相同,并且不必担心命令的定义:\NewCommandCopy
应该完成艰苦的工作。
\NewCommandCopy
了解来自 LaTeX2ε 内核以及其他包中的不同类型的定义,例如xparse 的命令声明如下\NewDocumentCommand
以及 etoolbox 的\newrobustcmd
,并且它可以扩展以覆盖更多的软件包。
不幸的是,我手头没有足够先进的 LaTeX 安装来测试这个\NewCommandCopy
(或者\RenewCommandCopy
,我想)来提供它的使用示例。