如何避免使用可选参数的宏创建任何标记?

如何避免使用可选参数的宏创建任何标记?

我试图在\cite可选参数中使用宏。我预计如果宏扩展为空(即得到类似[1])则可选参数会被忽略,但有时本应为空的参数仍会给出类似 的结果[1,]

%\usepackage{natbib,xparse}

\NewDocumentCommand\foo{m}{#1}
%\newcommand\foo[1]{#1}         % similar results
foo
\cite[\foo{}]{ref}              % [1]

\NewDocumentCommand\fooOpt{o m}{#2}
%\newcommand\fooOpt[2][dummy]{#2}  % similar results
fooOpt
\cite[\fooOpt{}]{ref}           % [1,]
\cite[\fooOpt[useless]{}]{ref}  % this one is broken, I suppose because of the nested []

\NewDocumentCommand\fooStar{s m}{\IfBooleanF{#1}{#2}}
fooStar
\cite[\fooStar{}]{ref}          % [1,]
\cite[\fooStar*{ignored}]{ref}  % [1,]

我发现natbib使用\if命令来区分空参数和非空参数。如果你查看 的源代码natbib,你会发现\if*#2*\else\NAT@cmt#2\fi(这意味着如果可选参数以 开头,命令将出现意外行为*)。问题似乎是我定义的一些宏仍然创建了一个标记,尽管结果为空。

基于此,我创建了这个测试用例:

\def\YN[#1]{\if*#1*Y\else N\fi}
\YN[]           % Y
\YN[{}]         % Y
\YN[{{}}]       % N (weirdly, \cite[{{}}]{ref} produces [1], not [1,]
\YN[{{{}}}]     % N
\YN[\foo{}]     % Y
\YN[\fooOpt{}]  % N
\YN[\fooStar{}] % N

我如何更改\fooOpt\fooStar(以及可能包含至少一个可选参数的复杂签名的任何宏)以使这个简单的测试返回Y?我对可选的星号参数(\NewDocumentCommand{…}{s …}{…})特别感兴趣。的 def\YN无法更改(因为我无法编辑包natbib)。

我不太了解令牌的工作原理,也不理解这种奇怪行为背后的机制。

答案1

问题是,如果您没有明确要求,则对可选星号或参数的解析不会以可扩展的方式实现。在第一个宏的情况下,\foo-argumentm实际上是以可扩展的方式抓取的(即使宏本身是 defined \protected,但\if并不关心\protected并且无论如何都会扩展它们 - 但是使用\futurelet或任何其他赋值的东西将不会扩展并导致一些不等于*for 的标记\if)。

您可以使用 将用于解析可选参数的实现更改为完全可扩展的实现\NewExpandableDocumentCommand,但请注意,使用此宏时,宏必须在所有可选内容之后采用一个强制参数。此外,解析的稳定性稍差一些(无法区分[{[}扩展解析,因此\fooOpt{[}会中断)。

至少你的测试用例是这样的:

\documentclass{article}

\NewExpandableDocumentCommand\foo{m}{#1}
\NewExpandableDocumentCommand\fooOpt{o m}{#2}
\NewExpandableDocumentCommand\fooStar{s m}{\IfBooleanF{#1}{#2}}

% for things outside of this MWE I wouldn't suggest using `r` arguments,
% instead stick to `m` for required arguments
\NewDocumentCommand\YN{r[]}{\if*#1*Y\else N\fi\par}

\begin{document}
\YN[]           % Y
\YN[{}]         % Y
\YN[{{}}]       % N (weirdly, \cite[{{}}]{ref} produces [1], not [1,]
\YN[{{{}}}]     % N
\YN[\foo{}]     % Y
\YN[\fooOpt{}]  % Y and no longer N
\YN[\fooStar{}] % Y and no longer N
\end{document}

相关内容