包装命令(参数化) - csname 问题

包装命令(参数化) - csname 问题

我想用另一个命令“包装”现有命令。

例如,假设有一条命令

\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:NNnc变体将构建命令)。

在此处输入图片描述

答案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}

相关内容