在一个\foreach
循环中定义一组宏,其中可选参数(示例代码中未显示)。为此,我使用\NewDocumentCommand
,但\newcommand
也可以工作。
\edef
但是我如何在这些定义中获得优势呢?如果没有\edef
能力,我看不出有什么办法可以使用循环变量来改变这些定义!
\documentclass{article}
\usepackage{tikz}
\usepackage{xparse}
\begin{document}
\foreach \x in {one, two}{
\globaldefs=1
% I would want to use \eNewDocumentCommand here
\expandafter\NewDocumentCommand\csname\x\endcsname{}{
\x
}
}
\expandafter\show\csname one code\endcsname % seems to show the "real" macro
\one % fails, because \x was not expanded at definition time
\end{document}
顺便问一下,您如何调试这些新命令?\show\macro
是没用的...我注意到宏\csname macro code\endcsname
提供了在解析/扩展后查看宏的机会(如果)...
答案1
不确定这有什么用处。
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\bunchdefine}{m}
{
\clist_map_inline:nn { #1 }
{
\exp_args:Nc \NewDocumentCommand { ##1 } { } { ##1 }
}
}
\ExplSyntaxOff
\bunchdefine{one,two}
\begin{document}
\one
\two
\end{document}
如您所见,这里没有\x
,而只有(由于它在定义主体中所以#1
加倍)代表当前项目。#
神秘的\exp_args:Nc
意思是:跳过下一个标记,并使用后面的大括号参数形成一个控制序列名称,因此它本质上与
\expandafter\NewDocumentCommand\csname##1\endcsname
但更容易。
答案2
您不仅需要在宏名称中展开\x
,还需要在定义中展开。而且您不仅没有需要表格\edef
,但在这种情况下,你可能实际上并不需要想表单\edef
。也就是说,最好\x
只展开一次。
\documentclass{article}
\usepackage{tikz}
\usepackage{xparse}
\begin{document}
\foreach \x in {one, two, \today}{
\globaldefs=1
\expandafter\NewDocumentCommand\csname\x\expandafter\endcsname\expandafter
{\expandafter}\expandafter{\x*!@}
}
\one
\two
\csname\today\endcsname\\
= \csname September 5, 2019\endcsname
\end{document}
如果有人真的想争论充分使用新的 TeX 引擎进行扩展,可以\expanded
引入
\expandafter\NewDocumentCommand\csname\x\expandafter\endcsname\expandafter
{\expandafter}\expandafter{\expanded{\x*!@}}
答案3
这就是你要去的地方吗?也就是说,你有来自\newcommand
或 的论据,甚至还有\xparse
来自 的结果\xdef
。
\documentclass{article}
\usepackage{tikz}
\newcommand{\createname}[2]{% #1 = name, #2=contents
\expandafter\xdef\csname #1\endcsname{#2}}
\begin{document}
\foreach \x in {one, two}{\createname{\x}{\x}}
\one
\two
\end{document}
答案4
应用\romannumeral0
-expansion,使用\expandafter
和交换参数,您可能无需改变参数的值,也根本\globaldefs
不需要使用就可以实现您需要的功能:\edef
\documentclass{article}
\usepackage{tikz}
\usepackage{xparse}
\newcommand\passfirsttosecond[2]{#2{#1}}%
\newcommand\exchange[2]{#2#1}%
%%---------------------------------------------------------------
%% \name <token sequence _without_ curly braces>{<tokens that yield the sequence "macroname">}
%% -> <token sequence _without_ curly braces>\macroname
%%
%% Examples: \name{bar} -> \bar
%% \name\newcommand*{bar}... -> \newcommand*\bar...
%% \name\outer\global\long\def{bar}... -> \outer\global\long\def\bar...
%% \name\string{bar} -> \string\bar
%% \name\name\let{foo}={bar}
%% -> \name\let\foo={bar}
%% -> \let\foo=\bar
%%
%% The gist of the trick is: Due to #1#-notation \name processes one
%% argument which is delimited by a left-curly-brace.
%% Unlike other argument-delimiters the delimiting left-curly-brace will
%% not be removed but will be left in place when (La)TeX reads the
%% argument.
%% The tokens at the left of the delimiting left-curly-brace are to be
%% prepended after applying \csname...\endcsname to the tokens nested in
%% curly braces.
%%
\csname @ifdefinable\endcsname\name{%
\long\def\name#1#{\romannumeral0\nameinternal{#1}}%
}%
\newcommand\nameinternal[2]{%
\expandafter\exchange\expandafter{\csname#2\endcsname}{ #1}%
}%
%%---------------------------------------------------------------
%% Scratch macro for accumulating calls to \NewDocumentCommand:
%% (Due to tikz's `\foreach` doing each iteration within a
%% local scope accumulating needs to be done via global
%% assignments, e.g.,in terms of \g@addto@macro.)
\newcommand\scratchy{}%
%%---------------------------------------------------------------
\def\scratchy{}%
\foreach \x in {one, two}{%
\name\expandafter{g@addto@macro}\expandafter\scratchy\expandafter{%
\romannumeral0% <-\romannumeral keeps searching digits, hereby expanding expandable things until
% finding something, e.g., a space, that terminates the digit-sequence.
% (\romannumeral will remove spaces that terminate digit sequences.)
\expandafter\passfirsttosecond\expandafter{\x}{%
\name\passfirsttosecond{\x}{ \NewDocumentCommand}{}%<- the space before \NewDocumentCommand
}% % must be as it terminates \romannumeral0's
}% % searching for more digits. Thus \romannumeral
}% % only finds the non-positive number 0, swallows
% that number not returning any token for it.
\scratchy
\message{%
Show the toplevel-definitions done by \string\NewDocumentCommand:^^J^^J%
}
\show\one
\show\two
\message{%
You can also have a nice name-show also exhibiting the^^J%
internal commands defined by \string\NewDocumentCommand:^^J^^J%
}
\name\show{one}
\name\show{one }
\name\show{one code}
\name\show{two}
\name\show{two }
\name\show{two code}
\begin{document}
\one
\two
\end{document}
另一种变体是,通过以下方式对用户级宏和内部宏的分配进行\NewDocumentCommand
“全球化” :\globel\let⟨macro⟩=⟨macro⟩
\documentclass{article}
\usepackage{tikz}
\usepackage{xparse}
\newcommand\passfirsttosecond[2]{#2{#1}}%
\newcommand\exchange[2]{#2#1}%
%%---------------------------------------------------------------
%% \name <token sequence _without_ curly braces>{<tokens that yield the sequence "macroname">}
%% -> <token sequence _without_ curly braces>\macroname
%%
%% Examples: \name{bar} -> \bar
%% \name\newcommand*{bar}... -> \newcommand*\bar...
%% \name\outer\global\long\def{bar}... -> \outer\global\long\def\bar...
%% \name\string{bar} -> \string\bar
%% \name\name\let{foo}={bar}
%% -> \name\let\foo={bar}
%% -> \let\foo=\bar
%%
%% The gist of the trick is: Due to #1#-notation \name processes one
%% argument which is delimited by a left-curly-brace.
%% Unlike other argument-delimiters the delimiting left-curly-brace will
%% not be removed but will be left in place when (La)TeX reads the
%% argument.
%% The tokens at the left of the delimiting left-curly-brace are to be
%% prepended after applying \csname...\endcsname to the tokens nested in
%% curly braces.
%%
\csname @ifdefinable\endcsname\name{%
\long\def\name#1#{\romannumeral0\nameinternal{#1}}%
}%
\newcommand\nameinternal[2]{%
\expandafter\exchange\expandafter{\csname#2\endcsname}{ #1}%
}%
\foreach \x in {one, two}{%
\expandafter\passfirsttosecond\expandafter{\x}{%
\name\NewDocumentCommand{\x}{}%
}%
\name\name\global\let{\x}={\x}%
\name\name\global\let{\x\space}={\x\space}%
\name\name\global\let{\x\space code}={\x\space code}%
}%
\message{%
Show the toplevel-definitions done by \string\NewDocumentCommand:^^J^^J%
}
\show\one
\show\two
\message{%
You can also have a nice name-show also exhibiting the^^J%
internal commands defined by \string\NewDocumentCommand:^^J^^J%
}
\name\show{one}
\name\show{one }
\name\show{one code}
\name\show{two}
\name\show{two }
\name\show{two code}
\begin{document}
\one
\two
\end{document}
输出(除了行号\show
)与第一个例子相同。
细微的差别在于:
对于第一个示例,只有宏\scratchy
会被全局更改。
\one
并且\two
所属的内部宏将仅在当前范围内定义。
在第二个示例中\one
,及其\two
所属的内部宏将被全局(重新)定义。
我写“(重新)定义”是因为像\newcommand
LaTeX 2e 内核那样\NewDocumentCommand
,如果宏在当前范围内未定义但在某个上级范围内定义,则不会抛出错误消息。\global\let...
如果要定义的控制序列已经在当前范围内或某个上级范围内定义,则也不会抛出错误消息。
\documentclass{article}
\usepackage{xparse}
\NewDocumentCommand{\foobar}{}{This is foobar}
\show\foobar
\expandafter\show\csname foobar \endcsname
\expandafter\show\csname foobar code\endcsname
% This will throw an error-message:
\NewDocumentCommand{\foobar}{}{This is another foobar}
\show\foobar
\expandafter\show\csname foobar \endcsname
\expandafter\show\csname foobar code\endcsname
\begingroup
\let\foobar=\UnDEFInED
% This will not throw an error-message... :
\NewDocumentCommand{\foobar}{}{This is yet another foobar}
\global\let\foobar=\foobar
\expandafter\global\expandafter\let\csname foobar \expandafter\endcsname\expandafter=\csname foobar \endcsname
\expandafter\global\expandafter\let\csname foobar code\expandafter\endcsname\expandafter=\csname foobar code\endcsname
\endgroup
% ... but \foobar is redefined outside the local scope also/is redefined in
% all scopes:
\show\foobar
\expandafter\show\csname foobar \endcsname
\expandafter\show\csname foobar code\endcsname
\begin{document}
\end{document}