添加到命令时保留默认可选参数

添加到命令时保留默认可选参数

TeX 常见问题解答修补现有命令提供一种方法来添加到具有可选参数的现有命令:

\documentclass{article}
\usepackage{letltxmacro}

\newcommand{\rough}[2][\default]{...}

\LetLtxMacro{\OldRough}{\rough}

\renewcommand{\rough}[2][\newdef]{\mumble\OldRough[{#1}]{#2}}

但是,它假设您需要一个新的默认参数“ \newdef”。如果要保留默认参数,您可以将“ \newdef”更改为“ \default”,但这只有在您对先前定义的命令有内部了解的情况下才有可能。此外,如果修改了该值,您也必须在修补程序中更改它。

是否可以向现有命令添加可选参数,同时保留默认参数,即使您不知道该参数是什么?

注意:该参数在新的定义中仍然可用。


编辑:尝试了建议后\xapptocmd,我仍然无法在我\xapptocmd正在使用的命令中重现所需的结果clevelref\label

我希望下面的代码写入“aaa”,然后应用标签,但事实并非如此,即使用数学之类的东西替换,内容仍然不会出现在最终文档中:

\documentclass[]{article}

\usepackage{cleveref,xpatch}

\xpretocmd\label{aaa}{}{}

\begin{document}
\label{example}
\end{document}

这给我的印象是它没有被正确附加。

此外,当尝试访问的第二个参数时\label,TeX 会抛出错误Illegal parameter number in definition of \etb@resrvda. \xpretocmd\label{#1#2}

\documentclass[]{article}

\usepackage{cleveref,xpatch}

\xpretocmd\label{#1#2}{}{}

\begin{document}
\end{document}

我推测这是因为它正在检测 的标准定义\label,它只有一个参数,而不是cleverf的参数。

如何纠正这些问题?

答案1

在处理带有可选参数的命令定义时,LaTeX 会做什么?假设我们有

\newcommand{\test}[2][default]{Optional: #1; mandatory: #2.}

由于步骤很长,与讨论无关,基本工作如下:

\def\test{\@protected@testopt\test\\test{default}}
\expandafter\def\csname\string\test\endcsname[#1]#2{Optional: #1; mandatory: #2.}

在第一行中,\\test代表单身的名称中带有反斜杠的令牌,可通过 获得\csname\string\test\endcsname

\@protected@testopt根据是否\protect\relax进行分支,然后调用\@testopt(删除\else...\fi部分)或\@x@protect\test(删除直到的所有内容\fi并仅留下\protect\test)。宏\@testopt检查后续内容[并执行正确的操作。

因此,如果你想修补此类命令的真实替换文本,你需要执行以下操作

\expandafter\patchcmd\csname\string\test\endcsname
  {<search>}
  {<replace>}
  {<success>}{<fail>}

如果你有

\DeclareRobustCommand{\test}[2][default]{Optional: #1; mandatory: #2.}

因为在这种情况下你需要做

\expandafter\patchcmd\csname\string\test\space\endcsname
  {<search>}
  {<replace>}
  {<success>}{<fail>}

对于没有可选参数的情况,在调用时\DeclareRobustCommand{\test}[<n>]{...}应该

\expandafter\patchcmd\csname test \endcsname
  {<search>}
  {<replace>}
  {<success>}{<fail>}

这就是我编写该软件包的原因xpatch,它能够测试命令是如何定义的,并\patchcmd使用正确的咒语进行调用。它还支持\newrobustcmdfrometoolbox并具有用于修补 内部的内置工具biblatex

所以你可以简单地做

\usepackage{xpatch}

\xpatchcmd{\test}
  {<search>}
  {<replace>}
  {<success>}{<fail>}

并且它在所有情况下都有效。

如果你想修改可选参数的默认值怎么办?即使不知道默认值是什么?

\documentclass{article}
\usepackage{etoolbox}

\makeatletter
\def\replaceoptionalargument#1#2#3#4{%
  \ifcsname\string#1\endcsname
    \@replaceoptionalargument#1{#2}{#3}%
  \else
    #4%
  \fi
}
\def\@replaceoptionalargument#1#2#3{%
  \edef#1{%
    \unexpanded\expandafter\expandafter\expandafter{%
      \expandafter\@@replaceoptionalargument#1{#2}%
    }%
  }%
  #3%
}
\def\@@replaceoptionalargument#1#2#3#4#5{#1#2#3{#5}}
\makeatother

\newcommand{\test}[2][default]{}

\replaceoptionalargument\test{new}{\message{YES}}{\message{NO}}

关于修补的补充\label

当你尝试修补命令时,你必须详细了解它的定义方式。例如,如果你\show\label在测试文档的序言中这样做,你将看到以下信息

> \label=macro:
#1->\@bsphack \protected@write \@auxout {}{\string \newlabel {#1}{{\@currentlabel }{\thepage }}}\@esphack .

这是标准的 LaTeX 定义。事实上,cleveref延迟了在文档开始时的重新定义。好的,让我们\show\label在之后发出\begin{document},得到

> \label=macro:
->\@ifnextchar [\label@optarg \label@noarg .

这表明\label没有像

\renewcommand{\label}[2][<default>]{...}

因此你的尝试将失败,因为\label没有任何参数。开发人员为什么要这样做?因为可选参数的默认值,并且无论是否使用可选参数都必须执行不同的代码。

答案2

xpatch软件包可以修补包含可选参数的命令。以下更改定义而不更改可选参数的值(并且可以使用#1或访问参数#2):

\documentclass[]{article}

\usepackage{xpatch}

\newcommand\foo[2][default]
  {%
    Normal definition. Optional argument: #1. Mandatory argument: #2.%
  }

\begin{document}
\foo{abc}

\xpatchcmd\foo{Normal}{Patched}{}{}
\foo{abc}

\xpretocmd\foo{Optional=#1. }{}{}
\foo{abc}

\xapptocmd\foo{ Mandatory=#2.}{}{}
\foo{abc}
\end{document}

答案3

\show\rough揭示了:

> \rough=macro:
->\@protected@testopt \rough \\rough {\newdef }.
l.12 \show\rough

\rough只是一个包装器,用于启动 LaTeX 2e-kernel 的机制,用于在考虑\protect离子机制的同时检测可选参数的存在。

内部调用宏\\rough来处理参数,因此需要修补该宏:

\documentclass{article}

\newcommand{\rough}[2][\default]{... s.th. with #1 and #2 ...}

\expandafter\show\csname\string\rough\endcsname

% > \\rough=\long macro:
% [#1]#2->... s.th. with #1 and #2 ....
% <recently read> \\rough 

%------------------------------------------------------------

\newcommand\PassFirstToSecond[2]{#2{#1}}%
\newcommand\Exchange[2]{#2#1}%

\expandafter\PassFirstToSecond\expandafter{%
  \romannumeral
  \expandafter\expandafter\expandafter\Exchange
  \expandafter\expandafter\expandafter{%
    \csname\string\rough\endcsname[{#1}]{#2}%
  }{0 \mumbleatbegin}%
  \mumbleatend%
}{%
  \long\expandafter\def\csname\string\rough\endcsname[#1]#2%
}%

\expandafter\show\csname\string\rough\endcsname

% > \\rough=\long macro:
% [#1]#2->\mumbleatbegin ... s.th. with #1 and #2 ...\mumbleatend .
% <recently read> \\rough 

\stop

相关内容