如何 \let 一个 xparse 定义命令?

如何 \let 一个 xparse 定义命令?

\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或类似的东西。


最佳实践expl3xparse使用已经发展了一段时间,因此团队自己编写的一些包仍然需要修改才能遵循它们。从我自己的代码来看,notes2bib可能是展示正确方法的“模型”包。另一方面,在撰写本文时,siunitx需要修改才能跟上它们:代码大部分是与开发同时编写expl3xparse开发正在进行中创建第三个版本,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 内核发布了接口(并且确实存在一些利用该接口的软件包,不仅仅是letltxmacroxpatch)。

约瑟夫·赖特已经解释了为什么\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,并且它可以扩展以覆盖更多的软件包。

(github 问题 239)

不幸的是,我手头没有足够先进的 LaTeX 安装来测试这个\NewCommandCopy(或者\RenewCommandCopy,我想)来提供它的使用示例。

相关内容