我试图在\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}