如何在 \newcommand 中区分未使用的可选参数和空参数?

如何在 \newcommand 中区分未使用的可选参数和空参数?

我想根据可选参数是否根本没有给出或者是否明确说明(使用[])但为空来改变宏的行为。

MWE 可能看起来像这样(摘自这个答案):

\documentclass{article}
\usepackage{ifthen}

\newcommand{\mymacro}[1][]{
\ifthenelse{\equal{#1}{}}{not stated}{explicitly stated but empty}
}

\begin{document}

The optional argument is \mymacro.

The optional argument is \mymacro[].

\end{document}

我想要的结果是:

The optional argument is not stated.
The optional argument is explicitly stated but empty.

我该如何改变\ifthenelse条件? 在 内这可能吗\newcommand

答案1

\documentclass{article}
\usepackage{ifthen}

\newcommand{\mymacro}[1][\noexpand\empty]{%
\ifthenelse{\equal{#1}{}}{explicitly stated but empty}{%
  \ifthenelse{\equal{#1}{\noexpand\empty}}{not stated}{something else}}%
}

\begin{document}

The optional argument is \mymacro.

The optional argument is \mymacro[].

The optional argument is \mymacro[X].
\end{document}

在此处输入图片描述

甚至不需要ifthen

\documentclass{article}

\newcommand{\mymacro}[1][\empty]{%
  \ifx\empty#1not stated\else%
  \ifx\relax#1\relax explicitly stated but empty%
  \else something else\fi\fi
}

\begin{document}

The optional argument is \mymacro.

The optional argument is \mymacro[].

The optional argument is \mymacro[X].
\end{document}

答案2

工作内容ltcmd

\documentclass{article}
% For older kernels
\ifdefined\NewDocumentCommand\else
  \usepackage{xparse}
\fi
%
\ExplSyntaxOn
\cs_new_eq:NN \IfBlankTF \tl_if_blank:nTF
\ExplSyntaxOff

\NewDocumentCommand\mymacro{o}{%
  \IfNoValueTF{#1}{not stated}{\IfBlankTF{#1}{explicitly stated but empty}{WHATNOT}}%
}

\begin{document}

The optional argument is \mymacro.

The optional argument is \mymacro[].

\end{document}

答案3

当通过 定义带有可选参数的命令时\newcommand,在该命令的定义文本中,您无法区分明确提供的可选参数的默认值的情况和由于根本没有提供可选参数而插入的默认值的情况。

当默认值为“空”时这也适用,就像您的情况一样。

\mymacro这是因为当通过 定义带有可选参数的命令时\newcommand,实际上\mymacro定义为考虑 -concept 提供的扩展控制\protect,并且在无法阻止扩展的情况下,只需启动 LaTeX 2ε 内核“机制”(由内核宏\@protected@testopt\@testopt和组成)用于检测是否存在明确提供的可选参数的开头并适当地调用宏,\@x@protect即,在没有提供可选参数的情况下,明确调用宏并附加构成可选参数默认值的标记,这些标记嵌套在花括号和方括号中。反过来是一个由 和 分隔的宏,其第一个参数由 和 分隔,它收集参数并提供替换文本。\kernel@ifnextchar[\\mymacro\\mymacro\\mymacro[]

用于检测可选参数的存在并进行适当调用的 LaTeX 2ε 内核机制\\mymacro不会传递\\mymacro有关可选参数产生的信息,即不会传递\\mymacro有关

  • 要么没有检测到可选参数的存在,因此需要附加默认值,嵌套在花括号和方括号中,
  • 或者检测到可选参数的存在,因此不需要附加嵌套在方括号中的默认值,因为可以收集明确提供的内容。

解决此问题的一种方法可能适合您在较旧的平台上工作,\NewDocumentCommand并且 xparse 和类似的基础设施不可用,可以是:

定义您自己的变体\@protected@testopt并将\@testopt此信息传递给\\mymacro 通过单独的论证并使用这些变体“手动”执行定义\mymacro\\mymacro类似于通过以下方式为您完成的操作\newcommand

\documentclass{article}

\makeatletter
\newcommand*\MY@protected@testopt[1]{%
  \ifx\protect\@typeset@protect\expandafter\MY@testopt\else\@x@protect#1\fi
}%
\newcommand\MY@testopt[2]{%
  \kernel@ifnextchar[{#1{1}}{#1{0}[{#2}]}%
}%
\expandafter\newcommand\expandafter\mymacro\expandafter{%
  \expandafter\MY@protected@testopt\expandafter\mymacro
  \csname\string\mymacro\endcsname {DEFAULT}%
}%
\expandafter\@ifdefinable\expandafter{\csname\string\mymacro\endcsname}{%
  \long\expandafter\def\csname\string\mymacro\endcsname#1[#2]#3#4{%
    % #1 - flag denoting whether optional argument is 
    %      due to TeX inserting the default-value=0 /
    %      due to TeX gathering something that was provided explicitly=1
    % #2 - what is used as optional argument
    % #3 - first non-optional argument
    % #4 - second non-optional argument
    The optional argument is \ifnum#1=0 not provided\else provided explicitly\fi.\\
    As optional argument used is ``#2''.\\
    The optional argument
    \begingroup
    \toks@{DEFAULT}\edef\tempa{\the\toks@}%
    \toks@{#2}\edef\tempb{\the\toks@}%
    \expandafter\endgroup
    \ifx\tempa\tempb\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
    {equals}{does not equal} the default.\\
    As second argument you provided ``#3''.\\
    As third argument you provided ``#4''.%
  }%
}%
\makeatother

\begin{document}

\noindent\mymacro{A}{B}

\bigskip

\noindent\mymacro[something]{A}{B}

\bigskip

\noindent\mymacro[DEFAULT]{A}{B}

\end{document}

在此处输入图片描述

\newcommand这种方法的两个可选参数的缺点是,\kernel@ifnextchar用于检测是否存在显式[的 catcode 12(other)。
\kernel@ifnextchar反过来只查看下一个标记的含义,因此可以通过使用隐式[的 catcode 12(other) 来欺骗。

例如,\let\lbracket=[ 你可能会期望\mymacro\lbracket{A}得到

在此处输入图片描述

,但这不会起作用。相反,您会收到一堆奇怪的错误消息。

您可以通过添加显式检查来轻松解决此问题[

\documentclass{article}

\makeatletter
\newcommand\ExplicitImplicitFork[3]{%
  \ForkExplicitImplicit!#3!{#1}![!{#2}!!!!#3%
}%
\@ifdefinable\ForkExplicitImplicit{%
  \long\def\ForkExplicitImplicit#1![!#2#3!!!!{#2}%
}%
\newcommand*\MY@protected@testopt[1]{%
  \ifx\protect\@typeset@protect\expandafter\MY@testopt\else\@x@protect#1\fi
}%
\newcommand\MY@testopt[2]{%
  \kernel@ifnextchar[{\ExplicitImplicitFork{#1{1}}{#1{0}[{#2}]}}{#1{0}[{#2}]}%
}%
\expandafter\newcommand\expandafter\mymacro\expandafter{%
  \expandafter\MY@protected@testopt\expandafter\mymacro
  \csname\string\mymacro\endcsname {DEFAULT}%
}%
\expandafter\@ifdefinable\expandafter{\csname\string\mymacro\endcsname}{%
  \long\expandafter\def\csname\string\mymacro\endcsname#1[#2]#3#4{%
    % #1 - flag denoting whether optional argument is 
    %      due to TeX inserting the default-value=0 /
    %      due to TeX gathering something that was provided explicitly=1
    % #2 - what is used as optional argument
    % #3 - first non-optional argument
    % #4 - second non-optional argument
    The optional argument is \ifnum#1=0 not provided\else provided explicitly\fi.\\
    As optional argument used is ``#2''.\\
    The optional argument
    \begingroup
    \toks@{DEFAULT}\edef\tempa{\the\toks@}%
    \toks@{#2}\edef\tempb{\the\toks@}%
    \expandafter\endgroup
    \ifx\tempa\tempb\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
    {equals}{does not equal} the default.\\
    As second argument you provided ``#3''.\\
    As third argument you provided ``#4''.%
  }%
}%
\makeatother

\begin{document}

\noindent\mymacro{A}{B}

\bigskip

\noindent\mymacro[something]{A}{B}

\bigskip

\noindent\mymacro[DEFAULT]{A}{B}

\bigskip

\let\lbracket=[
\noindent\mymacro\lbracket{A}

\end{document}

在此处输入图片描述

与下一个讨论的方法不同,这种方法的机制无法通过明确提供特殊默认值来欺骗,因为无法检测可选参数是由于 TeX 收集了明确提供的内容还是由于 TeX 插入了默认值。
但这是以宏参数为代价的,宏参数保存了有关可选参数如何产生的信息。


另一种方法是使用一个特殊的标记序列作为所讨论的可选参数的默认值,这表示可选参数不是由于 TeX 收集了明确提供的内容而是由于 TeX 插入了该默认值。在
宏处理所讨论指令中的可选参数的定义文本中,可以注意到用于检查当前构成所讨论的可选参数的标记序列是否等于该特殊的标记序列。

如果你采用这种方法,那么机制用于检测可选参数是否由于 TeX 收集明确提供的内容或由于 TeX 插入默认值可以通过明确提供默认值/特殊的标记序列来欺骗

 

这种方法可以实现,例如,使用\NewDocumentCommand-type o-argument 并\IfNoValueTF查询“可选参数的存在”。

\NewDocumentCommando-type-arguments 并由\IfNoValueTF包提供解析并且还被纳入了 LaTeX 2ε 内核的较新版本中。

使用-type-arguments 时,o默认值/特殊的标记序列表示所讨论的可选参数不是由于 TeX 收集了明确提供的内容,而是由于 TeX 插入该默认值是标记序列 ,,,,,,,,,—第一个catcode偏离了通常的 catocode 设置。-11N11o11V11a11l11u11e11-12-(这个标记序列来自扩展 expl3 的宏\c_novalue_tl,它表示没有明确提供值的标记。)通过 检查当前形成相关参数的标记序列是否等于特殊的标记序列\IfNoValueTF

o-type-argument与带有=的 -type-argument相同。O{⟨default⟩}⟨default⟩⟨expansion of \c_novalue_tl⟩

\IfNoValueTF通过明确提供这个标记序列,您可以轻松欺骗机制。

已经提供了\NewDocumentCommand使用o-type-argument 并通过查询的示例\IfNoValueTF在约瑟夫·赖特的回答中

根据定义\mymacro 就像 Joseph Wright 的例子一样尝试,例如,

the optional argument is
\expandafter\expandafter\expandafter\mymacro
\expandafter\expandafter\expandafter[%
\csname c_novalue_tl\endcsname]

或者

\begingroup
\lccode`\x=`\-
the optional argument is \lowercase{\mymacro[x}NoValue-]
\endgroup

the optional argument is not stated尽管明确提供了可选参数,但在两种情况下,您都会得到该短语。

 

随着变体史蒂文·B·塞格莱特斯特殊的标记序列表示可选参数不是由于 TeX 收集了明确提供的内容,而是由于 TeX 插入了默认值,它由单个标记形成\empty,在第一个示例中需要防止其扩展。

在这里,检查可选参数是由于 TeX 收集了明确提供的内容还是由于 TeX 插入默认值的机制也可以通过明确提供特殊的标记序列来欺骗:

\mymacro根据第一个例子中提供的定义,尝试,例如,—尽管明确提供了可选参数,但the optional argument is \mymacro[\noexpand\empty]您还是得到了短语。the optional argument is not stated

\mymacro根据第二个示例中所提供的定义,尝试例如 —尽管明确提供了可选参数,但the optional argument is \mymacro[\empty]您仍得到短语。the optional argument is not stated

请注意,随意使用宏参数来完成\if..表达式可能会导致意外的结果:

使用 的定义\mymacro,如第二个示例所示

  • the optional argument is \mymacro[\empty some badness ]产量the optional argument is some badness not stated

  • the optional argument is \mymacro[\empty\fi\iftrue]the optional argument is not stated尽管明确提供了可选参数,但仍会产生结果。

  • the optional argument is \mymacro[\relax]the optional argument is explicitly stated but empty尽管可选参数不为空,但是仍然会产生\relax

相关内容