测试宏参数的最佳/正确方法是什么?

测试宏参数的最佳/正确方法是什么?

这实际上是在问如何case在 LaTeX 中进行切换。我想定义一个宏,这样如果传递的参数是一件事,则会发生某件事,如果不是,则会发生其他事情。我知道一些破解方法,但它们确实感觉很黑客。我目前使用的是类似这样的方法:

\newcommand{\my@testcase}{world}
\newcommand{\hello}[1]{%
 \edef\my@temp{#1}%
 \ifx\my@temp\my@testcase
  Greetings%
 \else
  Hello%
 \fi
}

嵌套很麻烦(虽然我认为使用循环会很巧妙),而且为了测试它们是否扩展为相同的东西而必须定义两个宏似乎有点过分。那么:最简洁的方法是哪种(最好在 LaTeX2e 中,尽管我开始使用一点,但expl3我也很乐意了解这一点,当然,解释在其他变体中这有多么容易的答案也同样受欢迎)?

(编辑以回应 Joseph 的评论,问题的一部分是我不知道所有这些词的意思!):这是我的“真实世界”示例。使用该unicode-math包,我对许多字母进行了颜色编码,以便颜色(和样式)与其含义相关联。这很好,只是有时字母或数字会扮演多个角色。所以我有一个可以执行一次性切换的命令。问题是这些一次性切换似乎非常昂贵(我的文档编译时间在新机器上增加了约 20 分钟)。因此,对于最常见的,我找到了一种绕过一次性切换的方法 - 最好的例子是它0有时是一个实数,有时是一个向量,有时是一个函数。但就文档就此而言仍然一次性切换:也就是说,\vect{0}\vect{x}都说“进行一次性切换以将0(或x)排版为向量”,但\vect{0}实现“绕过方式”,同时\vect{x}执行昂贵的切换。

所以我传递给宏的都是字符,但我希望它足够强大,这样如果传递了更长的内容,它就会做正确的事情而不会抱怨。

(这样清楚些了吗?)

哦,我正在使用 TeXLive 2010。

答案1

由于这些问题似乎希望在不使用的情况下找到解决方案expl3,我将采用我们现有的代码并以传统风格重新编写它来提供一个解决方案。这当然不是唯一的方法,但似乎为一个答案提供了足够的材料!(基于该解决方案的原因expl3是,无论您是否喜欢特定方法,其中都有很多很好的 TeX 编程思想。)

这里的想法是提供两个测试:

\CompareMacro\MacroName{%
  \ComparisonMacroOne{Code for case one}%
  \ComparisonMacroTwo{Code for case two}%
  ...
}{Code for case 'else}

\CompareString{test-string}{%
  {comparison-string-one}{Code for case one}%
  {comparison-string-two}{Code for case two}%
  ...
}{Code for case 'else}

两者都可以嵌套。显然,对于基于宏的比较,您需要存储要测试的值,然后存储要检查的每个可能的值。

作为宏进行比较意味着字符('a'、'b' 等)及其类别代码('字母'、'其他' 等)都经过检查,并且必须一致。另一方面,作为字符串进行测试意味着对字符进行比较。(在大多数其他语言中,只存在字符串测试:类别代码似乎是 TeX 自身拥有的东西!)作为字符串进行比较(很容易)需要一个原始函数,而 Knuth 编写的 TeX 中没有这个函数。pdfTeX 调用它\pdfstrcmp,而 XeTeX 只使用。(LuaTeX 用户可能会在这里使用 Lua,但您可以使用包最轻松地\strcmp模拟)。\pdfstrcmppdftexcmds

首先,让我们来看看比较函数本身。作为宏进行比较,代码如下:

\newcommand\CompareMacro[2]{% But needs three arguments!
  \compare@macro#1#2\compare@tail?\compare@stop
}
\newcommand\compare@macro[3]{%
  \compare@if@tail@do{#2}{\@firstofone}%
  \ifx#1#2
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
    {\compare@end{#3}}%
    {\compare@macro#1}%
}

而对于字符串来说

\newcommand\CompareString[2]{% But needs three arguments!
  \compare@string{#1}#2\compare@tail?\compare@stop
}
\newcommand\compare@string[3]{%
  \compare@if@tail@do{#2}{\@firstofone}%
  \ifnum\pdfstrcmp{\unexpanded{#1}}{\unexpanded{#2}}%
    =\z@ % When two strings are equal \pdfstrcmp returns 0
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
    {\compare@end{#3}}%
    {\compare@string{#1}}%
}

支撑材料对两者而言都是相同的:

\newcommand\compare@if@tail@do[2]{%
  \expandafter\ifx\compare@if@tail@do@aux#1?\@nil\compare@tail\compare@tail
    \expandafter\compare@do@after@stop
  \else
    \expandafter\@gobble
  \fi
  {#2}%
}
\newcommand\compare@if@tail@do@aux{}
\long\def\compare@if@tail@do@aux#1#2\@nil\compare@tail{#1}
\newcommand\compare@do@after@stop{}
\long\def\compare@do@after@stop#1#2\compare@stop{#1}
\newcommand*\compare@tail{\compare@tail}
\newcommand\compare@end{}
\long\def\compare@end#1#2\compare@stop#3{#1}

这通过循环第二个参数来实现\Compare...。两种情况下的第一个测试是查看我们是否击中了标记\compare@tail,如果是,则查找“其他”情况并执行。另一方面,对于任何其他循环值,都会进行比较。如果成功,则所有其他情况都将被丢弃(使用第二个标记\compare@stop),并使用“成功”代码。另一方面,如果测试失败,则会出现循环。

以这种方式实现的测试可以嵌套。例如,你可以这样做

\CompareString{Hello}{%
  {Hello}{%
    \CompareString{Joseph}{%
      {Fred}{Nice to see you, Fred}%
      {Jane}{Nice to meet you, Jane}%
      {Joseph}{Oh, it's you!}%
    }{OH NO!}%
  }%
  {Goodbye}{See you}%
}{HELP!}

结果将是正确的。其次,测试是可扩展的。这可能并不总是相关的,但能够将\edef案例放入其中非常方便\write,而不必进行测试、保存结果然后应用它。

(对于那些愿意使用的人expl3,我已经描述了功能\tl_case:Nnn\str_case:nnn。您可以加载expl3和使用这些功能,而不必了解所有细节!)

答案2

也可以通过以下方式实现包裹xstring它提供\IfStrEqCase\IfEqCase,其中后者可用于测试数值等价性。

\documentclass{article}
\usepackage{xstring}

\begin{document}
    \IfStrEqCase{Jane}{%
      {Fred}{Nice to see you, Fred}%
      {Jane}{Nice to meet you, Jane}%
      {Joseph}{Oh, it's you, Joseph!}%
    }[OH NO, I don't know you!]%

    \IfEqCase{2.00}{%
      {1}{Number is same as: One}%
      {2}{Number is same as: Two}%
      {3}{Number is same as: Three}%
    }[OH NO, Unrecognized number.]%
\end{document}

生成:

在此处输入图片描述

也可以像 Joseph Wright 的例子那样进行嵌套。以下是等效代码xstring

\IfStrEqCase{Hello}{%
  {Hello}{%
    \IfStrEqCase{Joseph}{%
      {Fred}{Nice to see you, Fred}%
      {Jane}{Nice to meet you, Jane}%
      {Joseph}{Oh, it's you!}%
    }[OH NO!]%
  }%
  {Goodbye}{See you}%
}[HELP!]

答案3

您可以使用ifstrequalfrom etoolbox,但这对嵌套没有帮助。\ifthenelsefrom也一样ifthen

答案4

ConTeXt 提供了\processaction一些相关的宏,它们本质上等同于 case 语句。以下是来自syst-aux.mkiv

%D \macros
%D   {processaction,
%D    processfirstactioninset,
%D    processallactionsinset}
%D
%D \CONTEXT\ makes extensive use of a sort of case or switch
%D command. Depending of the presence of one or more provided
%D items, some actions is taken. These macros can be nested
%D without problems.
%D
%D \starttyping
%D \processaction           [x]     [a=>\a,b=>\b,c=>\c]
%D \processfirstactioninset [x,y,z] [a=>\a,b=>\b,c=>\c]
%D \processallactionsinset  [x,y,z] [a=>\a,b=>\b,c=>\c]
%D \stoptyping
%D
%D We can supply both a \type{default} action and an action
%D to be undertaken when an \type{unknown} value is met:
%D
%D \starttyping
%D \processallactionsinset
%D   [x,y,z]
%D   [      a=>\a,
%D          b=>\b,
%D          c=>\c,
%D    default=>\default,
%D    unknown=>\unknown{... \commalistelement ...}]
%D \stoptyping
%D
%D When \type{#1} is empty, this macro scans list \type{#2} for
%D the keyword \type{default} and executed the related action
%D if present. When \type{#1} is non empty and not in the list,
%D the action related to \type{unknown} is executed. Both
%D keywords must be at the end of list \type{#2}. Afterwards,
%D the actually found keyword is available in
%D \type{\commalistelement}. An advanced example of the use of
%D this macro can be found in \PPCHTEX, where we completely                                                                                                                                                        
%D rely on \TEX\ for interpreting user supplied keywords like
%D \type{SB}, \type{SB1..6}, \type{SB125} etc.

相关内容