我想以\def
最简单的方式定义一个命令(不是用)
- 采用逗号分隔的参数,
- 支持可选参数(第一个)。
也许将来另一种带有额外可选参数的解决方案(不一定是第一个)会受到欢迎。
我想要这样的东西:\mycmd[opt arg1, opt arg2]{arg3, arg4}
。
我已经看到了https://tex.stackexchange.com/a/29975/92620和https://tex.stackexchange.com/a/2860/92620。
\documentclass{report}
\newcommand\mycmd[4][]{optional: #1\\ mandatory: #2 - #3 - #4}
\begin{document}
\mycmd[optional arg]{second arg,third arg,fourth arg}
\end{document}
答案1
下面的示例使用\comma@parse
包kvsetkeys
来解析逗号分隔的参数列表。
\documentclass{report}
\usepackage{kvsetkeys}% provides \comma@parse
\usepackage{etexcmds}% provides \etex@unexpanded
\makeatletter
\newcount\arg@count
\newcommand{\arg@parser}[1]{%
\advance\arg@count\@ne
\expandafter\let\csname arg\romannumeral\arg@count\endcsname\comma@entry
}
% \mycmd[opt arg1, opt arg2]{arg 3, arg4, arg5}
\newcommand\mycmd[2][]{% Default is empty and will be configured later
% Set default values
\arg@count=\z@
\comma@parse{default1, default2}\arg@parser % Default values
% Parse optional argument
\arg@count=\z@
\comma@parse{#1}\arg@parser
\ifnum\arg@count>2 %
\@latex@error{Too many optional arguments}{%
The macro \string\mycmd\space got \the\arg@count\space
optional arguments,\MessageBreak
but expected are 2 optional arguments.\MessageBreak
\@ehd
}%
\fi
% Mandatory arguments
\arg@count=2
\comma@parse{#2}\arg@parser
\ifnum\arg@count=5 %
\else
\@latex@error{Wrong number of mandatory arguments}{%
The macro \string\mycmd\space got \the\numexpr\arg@count-2\relax\space
mandatory arguments,\MessageBreak
but expected are 3 mandatory arguments.\MessageBreak
\@ehd
}%
\fi
% Either using \argi, \argii, \argiii, \argiv, \argv
% or
% \@mycmd\argi\argii\argiii\argiv\argv
% or
\edef\process@me{%
\noexpand\@mycmd
{\etex@unexpanded\expandafter{\argi}}%
{\etex@unexpanded\expandafter{\argii}}%
{\etex@unexpanded\expandafter{\argiii}}%
{\etex@unexpanded\expandafter{\argiv}}%
{\etex@unexpanded\expandafter{\argv}}%
}%
\process@me
}
\newcommand{\@mycmd}[5]{%
\noindent
optional: #1 -- #2\\
mandatory: #3 -- #4 -- #5%
}
\makeatother
\begin{document}
\mycmd[optional arg]{third arg,fourth arg, fifth arg }
\end{document}
评论:
可选参数列表可以更短,省略的参数将获取默认值。
\comma@parse
通过删除前导和尾随空格来修剪参数。\comma@parse
删除空条目。空参数的解决方法是使用\relax
或\empty
。
答案2
jon 答案的变体,其中使用了原始设置。它主要用于展示如何expl3
让事情变得简单。
我假设您想用“Optional:”将可选参数前缀分隔开,然后将它们排版在一行上,用“空格、短破折号、空格”分隔;强制参数以“Mandatory”为前缀,用“空格、长破折号、空格”分隔。
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\mycmd}{om}
{
\IfValueT{#1}
{
Optional:~\guest_print_list:nn { #1 } { ~--~ } \\*
}
Mandatory:~\guest_print_list:nn { #2 } { ~---~ }
}
\seq_new:N \l_guest_list_seq
\cs_new_protected:Nn \guest_print_list:nn
{
\seq_set_from_clist:Nn \l_guest_list_seq { #1 }
\seq_use:Nn \l_guest_list_seq { #2 }
}
\ExplSyntaxOff
\setlength{\parindent}{0pt} % just for the example
\begin{document}
\mycmd{a, b , c}
\mycmd{a}
\mycmd[A]{a,b,c}
\mycmd[A, B]{a,b,c}
\end{document}
非常简单,无需重复代码,只需重用该\guest_print_list:nn
函数即可,该函数将要打印的参数列表作为第一个参数,将分隔符作为第二个参数。请注意,输入中逗号周围的空格将被忽略。
答案3
此解决方案与解决方案本身中定义的两个轻量级逗号解析器(\p@rse@csl@opt
和\p@rse@csl@mnd
)配合使用。对解析后的参数执行的操作分别布局到\domycmdopt
和\domycmdmnd
:
\documentclass{article}
\makeatletter
\newcommand\mycmd[2][]{%
\if\relax\detokenize{#1}\relax\else\p@rse@csl@opt#1,\@nil\fi
\p@rse@csl@mnd#2,\@nil
}
\def\p@rse@csl@opt#1,#2{%
\domycmdopt{#1}%
\ifx#2\@nil\else\expandafter\p@rse@csl@opt#2\fi
}
\def\p@rse@csl@mnd#1,#2{%
\domycmdmnd{#1}%
\ifx#2\@nil\else\expandafter\p@rse@csl@mnd#2\fi
}
\def\domycmdopt#1{opt-arg:#1\par}
\def\domycmdmnd#1{mnd-arg:#1\par}
\makeatletter
\begin{document}
\def\abc{def}
\mycmd[f00,bar]{hell0,w0rld}
\mycmd{1,2,3,$\alpha$,\abc}
\end{document}
答案4
如果不知道参数的数量,可以使用pgfkeys
如下方法:
\documentclass{article}
\usepackage{pgfkeys,pgffor}
\newcounter{mainargs}
\pgfkeys{mainargs/.is family, mainargs,
step counter/.code=\stepcounter{mainargs},
add argument/.style={step counter, arg\themainargs/.initial={#1}},
}
\newcounter{optargs}
\newif\ifoptargs
\pgfkeys{optargs/.is family, optargs,
opt args present/.is if=optargs,
step counter/.code=\stepcounter{optargs},
add argument/.style={opt args present=true, step counter, arg\theoptargs/.initial={#1}},
}
\newcommand{\mycommand}[2][]{%
\setcounter{mainargs}{0}%
\pgfkeys{mainargs, add argument/.list={#2}}%
\setcounter{optargs}{0}%
\pgfkeys{optargs, add argument/.list={#1}}%
%
main arguments:\\%
\foreach \n in {1,...,\themainargs}{%
\pgfkeysvalueof{/mainargs/arg\n}\\%
}%
%
\ifoptargs%
optional arguments:\\%
\foreach \n in {1,...,\theoptargs}{%
\pgfkeysvalueof{/optargs/arg\n}\\%
}%
\fi%
}
\parindent=0pt
\begin{document}
\mycommand{foo, bar}\\
\mycommand[a,b]{foo, bar, baz}
\end{document}
当然,如果知道参数的数量,则可以使用\pgfkeysvalueof{/mainargs/arg1}
、\pgfkeysvalueof{/mainargs/arg2}
等,而无需foreach
循环。
由于参数值即使在命令执行完成后仍然可用,因此使用组来保持本地定义或定义重置键可能更安全,例如:
\pgfkeys{mainargs,
reset one arg/.style={arg#1/.initial={}},
reset args/.style={reset one arg/.list={1,...,\themainargs}}
}
\pgfkeys{optargs,
reset one arg/.style={arg#1/.initial={}},
reset args/.style={reset one arg/.list={1,...,\theoptargs}, opt args present=false}
}
然后应该在命令定义的末尾调用它们:
\pgfkeys{mainargs, reset args}
\pgfkeys{optargs, reset args}
问题中使用的格式的完整命令是:
\newcommand{\mycommand}[2][]{%
\setcounter{mainargs}{0}%
\pgfkeys{mainargs, add argument/.list={#2}}%
\setcounter{optargs}{0}%
\pgfkeys{optargs, add argument/.list={#1}}%
%
\ifoptargs%
optional: %
\pgfkeysvalueof{/optargs/arg1}
\ifnum\theoptargs>1%
\foreach \n in {2,...,\theoptargs}{%
--- \pgfkeysvalueof{/optargs/arg\n}%
}%
\fi\\%
\pgfkeys{optargs, reset args}%
\fi%
%
mandatory: %
\pgfkeysvalueof{/mainargs/arg1}
\ifnum\themainargs>1%
\foreach \n in {2,...,\themainargs}{%
--- \pgfkeysvalueof{/mainargs/arg\n}%
}%
\fi%
%
\pgfkeys{mainargs, reset args}%
}