我想定义一个新命令,如果参数为空,则不执行任何操作,如果参数不为空,则执行某些操作。我想这样定义它,因为它总是位于第二个命令中,但第二个命令并不总是为第一个命令提供参数。
我的想法是,当我写 \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}
我想要得到
- (文字$x$)
- ()
- ()
(我不知道如何在这里正确编译,所以在 1 中应该有一个数学 x,在 4 中应该没有字符,但我写了一个空格字符只是为了让它在这个网站上很好地对齐)
但我明白
- (文字$x$)
- ()
- ()
- ()
对于 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
?
\if
扩展标记,直到有两个不可扩展的标记。它以 开头,在 LaTeX 格式中\#
定义为(此处为第 610 行)。因此,是一个标记,因此是一个不可扩展的控制序列标记。对于,根据上面引用的规则,它的字符代码为 256(在传统 TeX 中),因为标记不等同于字符标记(它们只是不同种类的野兽)。\chardef\#=`\#
latex.ltx
\#
\chardef
\if
\chardef
\let
\if
需要另一个不可扩展标记来决定。接下来是什么?字符标记1
(字符代码 49,类别代码 12)。此字符标记处于非活动状态(其类别代码不同于 13),因此不可扩展。所以,我们现在有两个不可扩展标记\if
。其中第一个标记的
\if
字符代码为 256,第二个标记的字符代码为 49(这是的 TeX 内部代码1
,通常与 ASCII 一致)。256 与 49 不同,因此
\if
测试为假。如您所见,此测试的真假结果根本不取决于传递给宏的参数\reason
!(扩张构造的\if ... \fi
依赖于第一个参数,因为另一个参数#1
,但那是另一回事)。
这里有两种方法来实现你的宏,一种是使用\if
和\detokenize
,另一种是使用 的etoolbox
宏\ifstempty
(\detokenize
e-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
。