这个问题导致了一个新的方案的出现:
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
可用于修补通过 定义的命令\newbibmacro
;biblatex
可以\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}