我想用另一个命令“包装”现有命令。
例如,假设有一条命令
\cmda
我希望每次调用这个函数时我们最终都会执行:
\wrapper{some text}{\cmda}
如果这只是一个命令,我知道如何执行此操作:
\let\oldcmda{\cmda}
\renewcommand{\cmda}{\wrapper{some text}{\oldcmda}}
然而,我希望这个过程是自动化的。
例如:
\wrapcmd{cmda}{Some text for cmda}
\wrapcmd{cmdb}{Some other text for cmdb}
\wrapcmd{cmdc}{No text for cmdc}
看了这个 SO 好几个小时,我终于意识到各种“补丁”命令要么在开头要么在结尾附加一些内容,但对于这种情况不起作用。
这是我的“几乎有效的解决方案”:
\def\oldcmd{}
\newcommand{\wrapcmd}[2]{%
\expandafter\let\expandafter\oldcmd\csname #1\endcsname%
\expandafter\renewcommand\csname #1\endcsname{\wrapper{#2}{\oldcmd}}%
}
这对于一个命令有效。但是,对于多个命令的情况,它不起作用,因为\oldcmd
它被重复使用。
我正在尝试拼凑一个解决方案,而不是使用\oldcmd
会扩展为\old#1
,而是另一个\csname
。但是我陷入困境。
这是一个简单的例子:
\documentclass{article}
\newcommand{\cmda}{CommandA}
\newcommand{\cmdb}{CommandB}
\newcommand{\wrapper}[2]{about (#1): #2}
\def\oldcmd{}
\newcommand{\wrapcmd}[2]{%
\expandafter\let\expandafter\oldcmd\csname #1\endcsname%
\expandafter\renewcommand\csname #1\endcsname{\wrapper{#2}{\oldcmd}}%
}
\begin{document}
Before:
\begin{itemize}
\item \cmda
\item \cmdb
\end{itemize}
\wrapcmd{cmda}{This is comment for cmda}
\wrapcmd{cmdb}{This is comment for cmdb}
After:
\begin{itemize}
\item \cmda
\item \cmdb
\end{itemize}
\end{document}
可以看到,现在\cmda
有了 的原始内容cmdb
。
答案1
好吧,每次调用\wrapcmd
总是使用与备份相同的宏\oldcmd
,所以你会不断覆盖它。
\documentclass{article}
\newcommand{\cmda}{CommandA}
\newcommand{\cmdb}{CommandB}
\newcommand{\wrapper}[2]{about (#1): #2}
\newcommand{\wrapcmd}[2]{%
\expandafter\let\csname old@\string#1\endcsname=#1%
\renewcommand{#1}{\wrapper{#2}{\csname old@\string#1\endcsname}}%
}
\begin{document}
Before:
\begin{itemize}
\item \cmda
\item \cmdb
\end{itemize}
\wrapcmd{\cmda}{This is comment for cmda}
\wrapcmd{\cmdb}{This is comment for cmdb}
After:
\begin{itemize}
\item \cmda
\item \cmdb
\end{itemize}
\end{document}
我实现了稍微不同(但更安全)的语法,这样您应该使用\wrapcmd{\cmda}{...}
而不是您的\wrapcmd{cmda}{...}
。
答案2
您希望它\cmda
变成\wrapper{some text}{\cmda}
,但当然您需要一个别名,\cmda
否则您会得到无限递归。
你的尝试几乎是好的,但却\let\oldcmda{\cmda}
是错误的:不应该使用括号。而且这很危险,因为这\cmda
可能是一个强大的命令。更安全的方法是
\NewCommandCopy\oldcmda\cmda
\renewcommand{\cmda}{\wrapper{some text}{\oldcmda}}
现在我们要抽象这一点。您可能决定使用名称而不是宏,这确实更容易:
\newcommand{\wrapcmd}[2]{%
% alias the command
\expandafter\NewCommandCopy\csname old#1\expandafter\endcsname\csname #1\endcsname
\expandafter\renewcommand\csname #1\endcsname{%
\wrapper{#2}{\csname old#1\endcsname}%
}%
}
现在调用如下命令
\wrapcmd{cmda}{Some text}
将与初次尝试的效果相同。但这样做的缺点是 的新定义\cmda
将包含\csname oldcmda\endcsname
,而不是\oldcmda
。
我们能否做得更好并实现这一\cmda
目标\wrapper{Some text}{\oldcmda}
?当然可以。
\newcommand{\wrapcmd}[2]{%
% alias the command
\expandafter\NewCommandCopy\csname old#1\expandafter\endcsname\csname #1\endcsname
\expandafter\edef\csname #1\endcsname{%
\unexpanded{\wrapper{#2}}{\expandafter\noexpand\csname old#1\endcsname}%
}%
}
让我们尝试一下你的例子。
\documentclass{article}
\newcommand{\cmda}{CommandA}
\newcommand{\cmdb}{CommandB}
\newcommand{\wrapper}[2]{about (#1): #2}
\newcommand{\wrapcmd}[2]{%
% alias the command
\expandafter\NewCommandCopy\csname old#1\expandafter\endcsname\csname #1\endcsname
\expandafter\edef\csname #1\endcsname{%
\unexpanded{\wrapper{#2}}{\expandafter\noexpand\csname old#1\endcsname}%
}%
}
\begin{document}
Before:
\begin{itemize}
\item \cmda
\item \cmdb
\end{itemize}
\wrapcmd{cmda}{This is comment for cmda}
\wrapcmd{cmdb}{This is comment for cmdb}
After:
\begin{itemize}
\item \cmda \\ \texttt{\meaning\cmda}
\item \cmdb \\ \texttt{\meaning\cmdb}
\end{itemize}
\end{document}
当然,您想使用不同于old
作为前缀的东西。
不过,您可以避免使用所有这些\expandafter
标记,而使用命令而不是名称。
\documentclass{article}
\usepackage{geometry} % wider textwidth
\newcommand{\cmda}{CommandA}
\newcommand{\cmdb}{CommandB}
\newcommand{\wrapper}[2]{about (#1): #2}
\ExplSyntaxOn
\NewDocumentCommand{\wrapcmd}{mm}
{
\andreacensi_wrapcmd:Ncn #1 {andreacensi@\cs_to_str:N #1} { #2 }
}
\cs_new_protected:Nn \andreacensi_wrapcmd:NNn
{
\NewCommandCopy #2 #1
\renewcommand #1 { \wrapper{#3}{#2} }
}
\cs_generate_variant:Nn \andreacensi_wrapcmd:NNn { Nc }
\ExplSyntaxOff
\begin{document}
Before:
\begin{itemize}
\item \cmda
\item \cmdb
\end{itemize}
\wrapcmd{\cmda}{This is comment for cmda}
\wrapcmd{\cmdb}{This is comment for cmdb}
After:
\begin{itemize}
\item \cmda \\ \texttt{\meaning\cmda}
\item \cmdb \\ \texttt{\meaning\cmdb}
\end{itemize}
\end{document}
诀窍是使用另一个参数,它将是旧宏的别名,\andreacensi_wrapcmd:NNn
(c
变体将构建命令)。
答案3
一些事实:
- 由单个标记组成的非分隔宏参数不需要嵌套在花括号中。
\csname..\endcsname
用于创建控制序列标记。- -trick
\expandafter\endcsname\csname
可用于连续创建多个控制序列标记: 通过在第一个上触发单个扩展步骤来
\csname foo\expandafter\endcsname\csname bar\expandafter\endcsname\csname baz\endcsname
产生收益。
\foo\bar\baz
\csname
一个常见的技巧是让辅助宏完成实际工作,将所有需要的控制序列标记作为参数,而在主宏/用户级宏的定义中使用技巧\expandafter\endcsname\csname
来创建并将所有需要的控制序列标记作为参数传递给辅助宏:
%Format: LaTeX2e
%
\newcommand\wrapcmd[2]{%
% \wrapcmd{foo}{argument} yields: \wrapcmdhelper\foo\oldfoo{argument}
\expandafter\wrapcmdhelper\csname#1\expandafter\endcsname\csname old#1\endcsname{#2}%
}%
\newcommand\wrapcmdhelper[3]{%
% Probably \protected\def instead of \def?
\csname @ifdefinable\endcsname#2{\let#2=#1\def#1{\wrapper{#3}{#2}}}%
}%
%%===============================================================================
%% Testing:
%%===============================================================================
\wrapcmd{cmda}{Some text for cmda}
\message{^^J\string\cmda=\meaning\cmda^^J}
%%-------------------------------------------------------------------------------
\wrapcmd{cmdb}{Some other text for cmdb}
\message{^^J\string\cmdb=\meaning\cmdb^^J}
%%-------------------------------------------------------------------------------
\wrapcmd{cmdc}{No text for cmdc}
\message{^^J\string\cmdc=\meaning\cmdc^^J}
%%-------------------------------------------------------------------------------
\stop
控制台输出:
This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020) (preloaded format=pdflatex-dev)
restricted \write18 enabled.
entering extended mode
(./test.tex
LaTeX2e <2021-05-01> pre-release-1 (develop 2021-2-27 branch)
L3 programming layer <2021-02-18>
\cmda=macro:->\wrapper {Some text for cmda}{\oldcmda }
\cmdb=macro:->\wrapper {Some other text for cmdb}{\oldcmdb }
\cmdc=macro:->\wrapper {No text for cmdc}{\oldcmdc }
)
No pages of output.
Transcript written on test.log.
可能是一种例行公事→\CsNameToCsToken⟨stuff not in braces⟩{NameOfCs}
⟨stuff not in braces⟩\NameOfCs
⟨括号内的内容⟩可能为空也可以达到同样的效果:
%Format: LaTeX2e
%
\makeatletter
%%===============================================================================
%% End \romannumeral-driven expansion safely:
%%===============================================================================
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
%%===============================================================================
%% Obtain control sequence token from name of control sequence token:
%%===============================================================================
%% \CsNameToCsToken<stuff not in braces>{NameOfCs}
%% -> <stuff not in braces>\NameOfCs
%% (<stuff not in braces> may be empty.)
\@ifdefinable\CsNameToCsToken{%
\long\def\CsNameToCsToken#1#{\romannumeral\InnerCsNameToCsToken{#1}}%
}%
\newcommand\InnerCsNameToCsToken[2]{%
\expandafter\UD@Exchange\expandafter{\csname#2\endcsname}{\UD@stopromannumeral#1}%
}%
\newcommand\UD@Exchange[2]{#2#1}%
%%===============================================================================
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
%%===============================================================================
%% \wrapcmd{<csname>}{<arg>}
%%===============================================================================
\newcommand\wrapcmd[2]{%
\CsNameToCsToken\@ifdefinable{old#1}{%
\CsNameToCsToken\CsNameToCsToken\let{old#1}={#1}%
%\CsNameToCsToken\protected\expandafter\def\expandafter{#1}\expandafter{%
\CsNameToCsToken\expandafter\def\expandafter{#1}\expandafter{%
\romannumeral
\CsNameToCsToken\UD@PassFirstToSecond{old#1}{\UD@stopromannumeral\wrapper{#2}}%
}%
}%
}%
\makeatother
%%===============================================================================
%% Testing:
%%===============================================================================
\wrapcmd{cmda}{Some text for cmda}
\message{^^J\string\cmda=\meaning\cmda^^J}
%%-------------------------------------------------------------------------------
\wrapcmd{cmdb}{Some other text for cmdb}
\message{^^J\string\cmdb=\meaning\cmdb^^J}
%%-------------------------------------------------------------------------------
\wrapcmd{cmdc}{No text for cmdc}
\message{^^J\string\cmdc=\meaning\cmdc^^J}
%%-------------------------------------------------------------------------------
\stop
控制台输出:
This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020) (preloaded format=pdflatex-dev)
restricted \write18 enabled.
entering extended mode
(./test.tex
LaTeX2e <2021-05-01> pre-release-1 (develop 2021-2-27 branch)
L3 programming layer <2021-02-18>
\cmda=macro:->\wrapper {Some text for cmda}{\oldcmda }
\cmdb=macro:->\wrapper {Some other text for cmdb}{\oldcmdb }
\cmdc=macro:->\wrapper {No text for cmdc}{\oldcmdc }
)
No pages of output.
Transcript written on test.log.
如果修补的命令变体仅在您编写的代码中使用,以便命令的修补不会影响其他人编写的代码/在其他人提供的包代码和类似内容中使用时不会影响宏,那么您可能考虑根本不修补命令,而只是定义一个宏,\wrapcmd{NameOfCs}{arg}
并\wrapper{arg}{\NameOfCs}
在您的代码中使用它而不是\NameOfCs
:
%Format: LaTeX2e
%
\newcommand\PassFirstToSecond[2]{#2{#1}}%
\newcommand\wrapcmd[2]{%
\expandafter\PassFirstToSecond\expandafter{\csname#1\endcsname}{\wrapper{#2}}%
}%
% Dummy-definition for \wrapper
\newcommand\wrapper[2]{\message{^^J\detokenize{\wrapper{#1}{#2}}^^J}}%
\wrapcmd{cmda}{Some text for cmda}
\wrapcmd{cmdb}{Some other text for cmdb}
\wrapcmd{cmdc}{No text for cmdc}
\stop
控制台输出:
This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020) (preloaded format=pdflatex-dev)
restricted \write18 enabled.
entering extended mode
(./test.tex
LaTeX2e <2021-05-01> pre-release-1 (develop 2021-2-27 branch)
L3 programming layer <2021-02-18>
\wrapper {Some text for cmda}{\cmda }
\wrapper {Some other text for cmdb}{\cmdb }
\wrapper {No text for cmdc}{\cmdc }
)
No pages of output.
Transcript written on test.log.
为了确保通过触发两个扩展步骤来传递结果,您可以将其包装到\romannumeral
扩展中:
%Format: LaTeX2e
%
\makeatletter
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\wrapcmd[2]{%
\romannumeral
\expandafter\UD@PassFirstToSecond
\expandafter{\csname#1\endcsname}%
{\UD@stopromannumeral\wrapper{#2}}%
}%
\makeatother
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\temp
\expandafter\expandafter\expandafter{%
\wrapcmd{cmda}{Some text for cmda}%
}%
\message{^^J\string\temp=\meaning\temp^^J}
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\temp
\expandafter\expandafter\expandafter{%
\wrapcmd{cmdb}{Some other text for cmdb}%
}%
\message{^^J\string\temp=\meaning\temp^^J}
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\temp
\expandafter\expandafter\expandafter{%
\wrapcmd{cmdc}{No text for cmdc}%
}%
\message{^^J\string\temp=\meaning\temp^^J}
\stop
控制台输出:
This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020) (preloaded format=pdflatex-dev)
restricted \write18 enabled.
entering extended mode
(./test.tex
LaTeX2e <2021-05-01> pre-release-1 (develop 2021-2-27 branch)
L3 programming layer <2021-02-18>
\temp=macro:->\wrapper {Some text for cmda}{\cmda }
\temp=macro:->\wrapper {Some other text for cmdb}{\cmdb }
\temp=macro:->\wrapper {No text for cmdc}{\cmdc }
)
No pages of output.
Transcript written on test.log.
答案4
如果您能够立即扩展命令,则可以将命令定义\oldcmd
为组内的局部命令。如果您必须等待,那么您将需要一个全局宏,每次使用时使用不同的名称。
\documentclass{article}
\newcommand{\cmda}{CommandA}
\newcommand{\cmdb}{CommandB}
\newcommand{\wrapper}[2]{about (#1): #2}
\newcommand{\wrapcmd}[2]{\bgroup
\expandafter\let\expandafter\oldcmd\csname #1\endcsname%
\expandafter\xdef\csname #1\endcsname{\wrapper{#2}{\oldcmd}}%
\egroup}
\begin{document}
Before:
\begin{itemize}
\item \cmda
\item \cmdb
\end{itemize}
\wrapcmd{cmda}{This is comment for cmda}
\wrapcmd{cmdb}{This is comment for cmdb}
After:
\begin{itemize}
\item \cmda
\item \cmdb
\end{itemize}
\end{document}