如何使用带有可选参数的命令 \patchcmd?

如何使用带有可选参数的命令 \patchcmd?

这个问题导致了一个新的方案的出现:
xpatch

这可能是一个简单的问题,但我被一些小细节难住了。我有一个内部命令,由一个包定义为

\newcommand\foo@[3][]{...stuff...}

在其中的某个地方,我想修补命令以包含对的调用\label{#1}。我已经在使用etoolbox,所以我计划使用

\patchcmd{\foo@}{hook}{\label{#1}hook}{success}{failure}

不幸的是,这不起作用,因为\foo@定义为

> \foo@=macro:
->\@protected@testopt \foo@ \\foo@ {}.
l.359 \show\foo@

它没有参数,因此修补代码失败。所以我试图弄清楚如何获取\\foo@ (注意双斜杠和尾随空格)作为 csname,但我被搞糊涂了。

因此,我尝试使用旧式方法\let\my@foo@=\foo@\renewcommand\foo@[3][]{...\my@foo@[#1]{#2}{#3}——我知道原始习惯用法是使用\let\def,但由于\foo@定义为,\newcommand我尝试使用\renewcommand。但这不起作用,并因 TeX 容量超出错误而失败,并且日志文件似乎显示了宏扩展的无限循环。

所以\newcommand。当存在可选参数时,定义的内部宏的正确名称是什么,以及如何为其构建 csname 以便我可以对其进行修补\patchcmd

答案1

您可以\\foo@使用获得\csname\string\foo@\endcsname。 后面显示的空格\show不是宏名称的一部分(但可以使用\space前面的空格添加\endcsname)。 因此,您可以使用:

\expandafter\patchcmd\csname\string\foo@\endcsname{hook}{\label{#1}hook}{success}{failure}

完整示例:

\documentclass{article}

\usepackage{etoolbox}

\begin{document}

\makeatletter
\newcommand\foo@[3][]{stuff (#1) more stuff :#2: hook even more stuff = *#3*}

\expandafter\patchcmd\csname\string\foo@\endcsname{hook}{\label{#1}hook}{success}{failure}

\texttt{\string\foo@ = \meaning\foo@}

\texttt{\expandafter\string\csname\string\foo@\endcsname = \expandafter\meaning\csname\string\foo@\endcsname}

\makeatother


\section{Test}
\makeatletter
\foo@[test]{One}{Two}
\makeatother

\section{Test2}
See section \ref{test}!

\end{document}

结果

答案2

笔记:下面显示的代码存在一些小缺陷,因此最好使用相应的包。


以下宏定义命令\xpatchcmd\xpretocmd\xapptocmd,可识别强命令(由\DeclareRobustCommand或定义\newrobustcmd)以及它们是否具有可选参数。

\usepackage{etoolbox,xparse}

\ExplSyntaxOn
\makeatletter
% generate a variant of \tl_if_in:NnT
\cs_generate_variant:Nn \tl_if_in:NnT { Nx }
% a boolean for testing robust commands
\bool_new:N \l_xpatch_protect_bool
% a "bizarre" token list that's quite improbable to find in a replacement text
\tl_set:Nx \c_xpatch_bizarre_tl { \tl_to_str:n { **)-(**/**[-]** } }
% the main command; it takes as first argument one of \patchcmd, \pretocmd or
% \apptocmd; the second argument is the command we want to patch
\cs_new:Npn \xpatchcmd_main:NN #1 #2
  {
% first of all we gather the command-to-patch name
   \tl_set:Nx \l_xpatch_name_tl { \exp_after:wN \use_none:n \token_to_str:N #2  }
% now we get the replacement text of the command-to-patch, but adding the bizarre
% token list in front of it which consists of all category 12 characters
   \tl_set:Nx \l_xpatch_tmpa_tl { \c_xpatch_bizarre_tl \token_get_replacement_spec:N #2 }
% now we look whether the token list contains the bizarre list followed by \protect
% which happens if it's a control sequence defined by \DeclareRobustCommand, so
% we add a space to the command name
   \tl_if_in:NxT \l_xpatch_tmpa_tl { \c_xpatch_bizarre_tl \token_to_str:N \protect} 
     {
      \bool_set_true:N \l_xpatch_protect_bool 
      \xpatch_add_space:
     }
% now we look whether the token list contains the bizarre list followed by \x@protect
% which happens if it's a control symbol defined by \DeclareRobustCommand, so
% we add a space to the command name
   \tl_if_in:NxT \l_xpatch_tmpa_tl { \c_xpatch_bizarre_tl \token_to_str:N \x@protect} 
     {
      \bool_set_true:N \l_xpatch_protect_bool
      \xpatch_add_space:
     }
% now we look whether the token list contains the bizarre list followed by \@protected@testopt
% which happens if it's a command with an optional argument (from \newcommand)
   \tl_if_in:NxT \l_xpatch_tmpa_tl { \c_xpatch_bizarre_tl \token_to_str:N \@protected@testopt} 
     { \xpatch_add_backslash: }
% now we look whether the token list contains the bizarre list followed by \@testopt
% which happens if it's a command with an optional argument (from \newrobustcmd)
   \tl_if_in:NxT \l_xpatch_tmpa_tl { \c_xpatch_bizarre_tl \token_to_str:N \@testopt} 
     { \xpatch_add_backslash: }
% if the command-to-patch was defined by \DeclareRobustCommand we look whether the
% command name with backslash in front and space at the end exists and we assume that
% it has an optional argument
   \bool_if:NT \l_xpatch_protect_bool 
     {
      \cs_if_exist:cT { \c_backslash_tl \l_xpatch_name_tl }
        { \xpatch_add_backslash: }
     }
% now we pass the real command-to-patch name to the patching macro
   \exp_after:wN #1 \cs:w \l_xpatch_name_tl \cs_end:
  }
% how we add a space after the name
\cs_new:Npn \xpatch_add_space: 
  { \tl_set:Nx \l_xpatch_name_tl { \l_xpatch_name_tl \c_space_tl } }
% how we add a backslash in front of the name
\cs_new:Npn \xpatch_add_backslash:
  { \tl_set:Nx \l_xpatch_name_tl { \c_backslash_tl \l_xpatch_name_tl } }
% a constant containing a backslash (category 12)
\tl_set:Nx \c_backslash_tl { \exp_after:wN \use_none:n \token_to_str:N \\ }
% the user level commands
\NewDocumentCommand{\xpatchcmd}{}{ \xpatchcmd_main:NN \patchcmd }
\NewDocumentCommand{\xpretocmd}{}{ \xpatchcmd_main:NN \pretocmd }
\NewDocumentCommand{\xapptocmd}{}{ \xpatchcmd_main:NN \apptocmd }
\NewDocumentCommand{\xshowcmd}{}{ \xpatchcmd_main:NN \show }


\NewDocumentCommand{\xpatchbibmacro} { m }
  { \exp_after:wN \xpatchcmd_main:NN \exp_after:wN \patchcmd \cs:w abx@macro@ \tl_to_str:n {#1} \cs_end: }
\NewDocumentCommand{\xpretobibmacro} { m }
  { \exp_after:wN \xpatchcmd_main:NN \exp_after:wN \pretocmd \cs:w abx@macro@ \tl_to_str:n {#1} \cs_end: }
\NewDocumentCommand{\xapptobibmacro} { m }
  { \exp_after:wN \xpatchcmd_main:NN \exp_after:wN \apptocmd \cs:w abx@macro@ \tl_to_str:n {#1} \cs_end: }
\NewDocumentCommand{\xshowbibmacro} { m }
  { \exp_after:wN \xpatchcmd_main:NN \exp_after:wN \show \cs:w abx@macro@ \tl_to_str:n {#1} \cs_end: }

\makeatother
\ExplSyntaxOff

当然,以不正当的方式定义的命令可能会破坏这些宏,但几乎任何一组宏都是如此。

修补由定义或由\NewDocumentCommand提供的其他命令xparse不是支持的。

用法

\newcommand\foo[3][]{...hook...}
\xpatchcmd{\foo}{hook}{\label{#1}hook}{success}{failure}

\xshowcmd\foo

最后一个显示了 的“真正”含义\foo。命令

\xpatchbibmacro \xpretobibmacro \xapptobibmacro 

可用于修补通过 定义的命令\newbibmacrobiblatex可以\xshowbibmacro看到宏的“真正”含义。

答案3

为了修补通过\newcommand或定义的命令(带有或不带有可选参数) \DeclareRobustCommand,我使用了 的包装器。\xpatchcmd在 的第一个参数前面有一个额外的可选参数,它指示要修补的命令的性质:\patchcmd\xpatchcmd\patchcmd

  • n\newcommand
  • o\newcommand+ 可选参数
  • N\DeclareRobustCommand
  • O\DeclareRobustCommand+ 可选参数

定义和应用\xpatchcmd

\documentclass{article}

\usepackage{etoolbox}

\usepackage{xifthen}

\makeatletter

\newcommand*{\xpatchcmd}[1][]{%
  \bgroup%
  \let\@gobble@\@gobble\let\@space@\empty%
  \ifthenelse{\isempty{#1}\OR\equal{\unexpanded{#1}}{n}}{}{%
    \ifthenelse{\equal{\unexpanded{#1}}{o}}{\let\@gobble@\empty}{%
      \ifthenelse{\equal{\unexpanded{#1}}{N}}{\let\@space@\space}{%
        \ifthenelse{\equal{\unexpanded{#1}}{O}}{\let\@gobble@\empty\let\@space@\space}{%
          \typeout{%
            Command \protect\xpatchcmd\space Warning: Invalid command specifier "\unexpanded{#1}".%
          }%
        }%
      }%
    }%
  }%
  \catcode`\#=12%
  \@xpatchcmd%
}

\newcommand{\@xpatchcmd}[6][\undefined]{%
  \catcode`\#=6%
  \edef\undefined{\noexpand\undefined}%
  \def\@prefix{#1}%
  \letcs{\@command}{\expandafter\@gobble@\string#2\@space@}%
  \ifx\@prefix\undefined%
    \patchcmd{\@command}{#3}{#4}{#5}{#6}%
  \else%
    \patchcmd[\@prefix]{\@command}{#3}{#4}{#5}{#6}%
  \fi%
  \global\cslet{\expandafter\@gobble@\string#2\@space@}{\@command}%
  \egroup%
}

\makeatother

\begin{document}

\newcommand*{\test}[2]{#1me#2}
\show\test
\xpatchcmd[n][\protected]{\test}{me}{you}{}{}
\show\test

\renewcommand{\test}[2][]{#1me#2}
\expandafter\show\csname\string\test\endcsname
\xpatchcmd[o][]{\test}{me#2}{you#1}{}{}
\expandafter\show\csname\string\test\endcsname

\DeclareRobustCommand*{\test}[2]{#1me#2}
\expandafter\show\csname test \endcsname
\xpatchcmd[N][\long]{\test}{#1me}{#2you}{}{}
\expandafter\show\csname test \endcsname

\DeclareRobustCommand*{\test}[2][only]{#1me#2}
\expandafter\show\csname\string\test\space\endcsname
\xpatchcmd[O]{\test}{#2}{}{}{}
\expandafter\show\csname\string\test\space\endcsname

\end{document}

相关内容