newcommand 中的条件语句带有空参数

newcommand 中的条件语句带有空参数

我想定义一个新命令,如果参数为空,则不执行任何操作,如果参数不为空,则执行某些操作。我想这样定义它,因为它总是位于第二个命令中,但第二个命令并不总是为第一个命令提供参数。

我的想法是,当我写 \reason{input} 时,如果输入为空(实际上是空的:\reason{}),则 \reason 不执行任何操作,否则(这是:输入 = 任何你喜欢写的内容,包括文本和内联数学模式)\reason 所做的与 \textnormal{(input)} 完全相同。

我的尝试是

\documentclass{article}

\newcommand{\reason}[1][]{\if\#1\ \ \else \textnormal{(#1)}\fi}

\begin{document}
\begin{enumerate}
    \item \reason{text $x$}
    \item \reason{ }
    \item \reason{\ }
    \item \reason{}
\end{enumerate}
\begin{itemize}


\end{document}

我想要得到

  1. (文字$x$)
  2. ()
  3. ()

(我不知道如何在这里正确编译,所以在 1 中应该有一个数学 x,在 4 中应该没有字符,但我写了一个空格字符只是为了让它在这个网站上很好地对齐)

但我明白

  1. (文字$x$)
  2. ()
  3. ()
  4. ()

对于 4. 来说这是不正确的,因为没有参数,所以不应显示任何内容,而对于 1.、2. 和 3. 来说是正确的,因为有一个参数,因此括号应该始终存在。

我认为这里的主要问题是 \if...\else...\if 里面的内容,尽管我也不知道是否可以定义一个带有条件的新命令,但是当我编译它时,没有错误,我想我可以做到这一点。

我还想设置一个空的默认值。

你能帮我正确地定义我的命令吗?

谢谢

答案1

首先,你的函数没有使用可选参数,所以把它去掉。其次,使用\ifx而不是\if,因为你可能有一个非空参数,它会扩展为空,而且(我认为)你不希望它走空分支。最后,我使用\detokenize处理其他事情的参数,例如,如果参数本身是\relax

\else如果您想要空参数情况的默认行为,请在定义之前添加代码。

\documentclass{article}
\newcommand{\reason}[1]{\expandafter\ifx\expandafter\relax
  \detokenize{#1}\relax\else\textnormal{(#1)}\fi}
\begin{document}
\begin{enumerate}
    \item \reason{text $x$}
    \item \reason{ }
    \item \reason{\ }
    \item \reason{}
\end{enumerate}
\end{document}

在此处输入图片描述

这是实现的另一种方法默认答案对于空输入:

\documentclass{article}
\newcommand{\reason}[2][Default answer]{\expandafter\ifx\expandafter\relax
  \detokenize{#2}\relax#1\else\textnormal{(#2)}\fi}
\begin{document}
\begin{enumerate}
    \item \reason{text $x$}
    \item \reason{ }
    \item \reason{\ }
    \item \reason{}
\end{enumerate}
\end{document}

答案2

根据定义

\newcommand{\reason}[1][]{\if\#1\ \ \else \textnormal{(#1)}\fi}

由于之后,您正在定义\reason采用可选参数。您似乎想要一个括号中的参数,因此首先您必须删除。[][1][]

但是,这还不够。您的代码与\if\#1进行比较。由于前者是控制序列(技术上称为标记),而后者是字符,因此判定测试返回 false,因此无论如何您都会得到结果。\#1\chardef\if\textnormal{(#1)}

如何测试一个参数是否为空?最简单的方法就是使用\detokenize

\newcommand{\reason}[1]{\if\relax\detokenize{#1}\relax\else\textnormal{(#1)}\fi}

如果参数不为空(并且空格也算作不为空),\if\relax与字符串化的第一个标记进行比较#1,返回 false;如果参数为空,则与\if进行比较。\relax\relax

您还可以使用expl3

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\reason}{m}
 {
  \tl_if_empty:nF { #1 } { \textnormal{(#1)} }
 }
\ExplSyntaxOff

\begin{document}
\begin{enumerate}
\item \reason{text $x$}

\item \reason{ }

\item \reason{\ }

\item \reason{}
\end{enumerate}

\end{document}

在此处输入图片描述

如果更改\tl_if_empty:nF\tl_if_blank:nF,则第 2 项将不会产生任何结果。

答案3

你的测试没有按预期工作,因为它\if没有按照你想象的方式工作。\if扩展它前面的标记,直到它有两个不可扩展的标记,然后比较它们的字符代码更准确地说,让我们引用 TeXbook(第 209 页):

TeX 会扩展后面的宏,\if直到找到两个不可扩展的标记。如果其中一个标记是控制序列,TeX 会认为它具有字符代码 256 和类别代码 16,除非该控制序列的当前等价物等于\let非活动字符标记。这样,每个标记都指定一个 (字符代码,类别代码) 对。如果字符代码相等,则条件为真,与类别代码无关。例如,在\def\a{*}和 和\let\b=*之后\def\c{/},测试\if*\a\if\a\b将为真,但\if\a\c将为假。Also\if\a\par将为假,但\if\par\let将为真。

现在,让我们分析一下它在你的宏中是如何工作的(Steven B. Segletes 和 egreg 已经指出你没有对可选参数使用正确的语法[即使用宏时的方括号],我不会对此进一步评论):

\newcommand{\reason}[1][]{\if\#1\ \ \else \textnormal{(#1)}\fi}

当 TeX 读取此定义时,它将存储为替换文本:

  • 控制序列标记\if\#

  • 字符代码为 49(的 TeX 内部代码1)且类别代码为 12(其他)的字符标记;

  • 两个控制序列标记\<space>(反斜杠后跟一个空格),即控制空间

  • 控制序列标记\else

  • 控制序列标记\textnormal

  • ETC。

这非常重要,因为你可能认为在展开#1时第一个参数会被替换为第一个参数\reason,但事实并非如此。#正如我们刚刚看到的,第一个参数被以不同的方式标记化(它是控制序列标记的名称\#,因此可以说是“嵌入”在此控制序列标记中)。

现在,根据上面引用的 TeXbook 摘录中给出的规则,它的表现如何\if

  1. \if扩展标记,直到有两个不可扩展的标记。它以 开头,在 LaTeX 格式中\#定义为(此处为第 610 行)。因此,是一个标记,因此是一个不可扩展的控制序列标记。对于,根据上面引用的规则,它的字符代码为 256(在传统 TeX 中),因为标记不等同于字符标记(它们只是不同种类的野兽)。\chardef\#=`\#latex.ltx\#\chardef\if\chardef\let

  2. \if需要另一个不可扩展标记来决定。接下来是什么?字符标记1(字符代码 49,类别代码 12)。此字符标记处于非活动状态(其类别代码不同于 13),因此不可扩展。所以,我们现在有两个不可扩展标记\if

  3. 其中第一个标记的\if字符代码为 256,第二个标记的字符代码为 49(这是的 TeX 内部代码1,通常与 ASCII 一致)。

  4. 256 与 49 不同,因此\if测试为假。如您所见,此测试的真假结果根本不取决于传递给宏的参数\reason!(扩张构造的\if ... \fi依赖于第一个参数,因为另一个参数#1,但那是另一回事)。

这里有两种方法来实现你的宏,一种是使用\if\detokenize,另一种是使用 的etoolbox\ifstempty\detokenizee-TeX 原语扩展为类别代码 12 的字符标记,但类别代码 10 出现的空格除外;\detokenize{...}如果...“参数”为空,则 的扩展也为空)。

\documentclass{article}

\newcommand{\reason}[1]{%
  \if\relax\detokenize{#1}\relax
    % nothing
  \else
    \textnormal{(#1)}%
  \fi
}

\begin{document}

\begin{enumerate}
    \item \reason{text $x$}
    \item \reason{ }
    \item \reason{\ }
    \item \reason{}
\end{enumerate}

\end{document}

在此处输入图片描述

\documentclass{article}
\usepackage{etoolbox}

\newcommand{\reason}[1]{%
  \ifstrempty{#1}{}{\textnormal{(#1)}}%
}

\begin{document}

\begin{enumerate}
    \item \reason{text $x$}
    \item \reason{ }
    \item \reason{\ }
    \item \reason{}
\end{enumerate}

\end{document}

相同的输出。

请注意,如果您想以与“空”相同的方式考虑“仅空格”,则etoolbox可以\ifblank使用宏。

\reason如果您想在宏的参数包含空行(或等效地,\par标记)时获取错误,请使用\newcommand*而不是\newcommand

相关内容