\expandafter 和 xparse 测试的问题

\expandafter 和 xparse 测试的问题

我正在尝试使用xparse一个宏来构建,该宏可与现有包中的另一个宏进行接口。后者采用一个选项[d],我想使用 的s参数类型用星号来表示它xparse

为了避免明确引用原始包中的宏,让我们考虑一个\Foo根据星号的存在改变行为的基本宏:

\NewDocumentCommand{\Foo}{s}
  {\IfBooleanTF{#1}
    {foo bar}
    {foo baz}}

现在我想\Foo与以下简洁的实现进行交互:

\NewDocumentCommand{\FooInterfaceBad}{s}
  {\Foo \IfBooleanT{#1}{*}}

顾名思义,此实现不起作用,我怀疑这是由于IfBooleanT宏未展开造成的。但是,以下也不起作用:

\NewDocumentCommand{\FooInterfaceAlsoBad}{s}
  {\expandafter \Foo \IfBooleanT{#1}{*}}

当然我可以按照以下方式来实现,这种方法是可行的:

\NewDocumentCommand{\FooInterfaceGood}{s}
  {\IfBooleanTF{#1}
    {\Foo*}
    {\Foo}}

但是,如果我将来添加其他选项\Foo,我将需要用一堆来处理每个可能的选项组合\IfBooleanTF,从而导致代码重复率呈指数级增长。这直接适用于我最初设想的情况,即我尝试将复杂的宏与许多选项进行交互。

因此我的问题是:有没有办法可以完成FooInterfaceBad工作,或者至少有一种可以避免大量测试的替代方法?

以下是完整的 MWE:

\documentclass{article}
\usepackage{xparse}

\NewDocumentCommand{\Foo}{s}
  {\IfBooleanTF{#1}
    {foo bar}
    {foo baz}}
\NewDocumentCommand{\FooInterfaceBad}{s}
  {\Foo \IfBooleanT{#1}{*}}
\NewDocumentCommand{\FooInterfaceAlsoBad}{s}
  {\expandafter \Foo \IfBooleanT{#1}{*}}
\NewDocumentCommand{\FooInterfaceGood}{s}
  {\IfBooleanTF{#1}
    {\Foo*}
    {\Foo}}

\begin{document}

\Foo*

\FooInterfaceBad*

\FooInterfaceAlsoBad*

\FooInterfaceGood*

\end{document}

答案1

我猜你想要的是\Foo执行\othercommandfrompackage并且\Foo*而不是\othercommandfrompackage[d]

\documentclass{article}
\usepackage{xparse}

% this emulates the command from the package
\newcommand{\othercommandfrompackage}[1][]{%
  \if d#1%
    there was the optional argument%
  \else
    there was no optional argument%
  \fi
}

\NewDocumentCommand{\Foo}{s}{%
  \expandafter\othercommandfrompackage\expanded{\IfBooleanT{#1}{[d]}}%
}

\begin{document}

\Foo

\Foo*

\end{document}

在此处输入图片描述

有关真实的问题,可以编写一些更好的代码。

答案2

你可以选择一个处理器,如果存在星号则设置参数,*如果没有星号则保留为空:

\documentclass[]{article}

\usepackage{xparse}
\newcommand\mystarprocessor[1]
  {%
    \edef\ProcessedArgument{\IfBooleanT{#1}{*}}%
  }
\NewDocumentCommand\foo{>{\mystarprocessor}s}
  {%
    Was there a star? (#1)%
  }

\begin{document}
\foo

\foo*
\end{document}

或者,你可以用技巧来控制扩展步骤的数量\romannumeral。以下将扩展所有内容,直到它碰到一个空间标记,然后将吞噬该标记(或者如果碰到另一个不可扩展的标记,扩展将停止):

\documentclass[]{article}

\usepackage{xparse}
\newcommand\mystarprocessor[1]
  {%
    \edef\ProcessedArgument{\IfBooleanT{#1}{*}}%
  }
\NewDocumentCommand\foo{>{\mystarprocessor}s}
  {%
    Was there a star? (#1)%
  }
\NewDocumentCommand\fooexpanded{s}
  {%
    \expandafter\foo\romannumeral`\^^@\IfBooleanTF{#1}{ *}{ }%
  }

\begin{document}
\fooexpanded

\fooexpanded*
\end{document}

答案3

如果允许使用辅助宏,您可以执行以下操作:

\newcommand\exchange[2]{#2#1}

\NewDocumentCommand{\FooInterfaceNotSoBad}{s}
  {\IfBooleanT{#1}{\exchange{*}}\Foo}

您还可以使用\romannumeral0-expansion:

\NewDocumentCommand{\FooInterfaceAlsoNotSoBad}{s}
  {\expandafter\Foo\romannumeral0\IfBooleanTF{#1}{ *}{ }}

请注意,在对控制字标记进行标记之后,\Foo(La)TeX 的读取设备处于状态 S(跳过空格),这意味着 .tex 输入文件中出现的后续空格将被丢弃,而不是产生插入到标记流中的标记,而在对 catcode-12(other) 字符标记进行标记之后,*(La)TeX 的读取设备处于状态 M(行中间),这意味着后续空格通常被标记化为插入到标记流中的空格标记,并且在水平模式下可能会产生水平粘合。如果 after\Foo和 after均\Foo*不再由 (La)TeX 收集非分隔/括号嵌套参数,那么您可能希望在存在星号的情况下添加。如果要在/\ignorespaces之后收集非分隔/括号嵌套参数,则这不是必需的,因为 (La)TeX 始终会丢弃非分隔/括号嵌套参数之前的空格标记。\Foo\Foo*

\documentclass{article}
\usepackage{xparse}

\NewDocumentCommand{\Foo}{s}
  {\IfBooleanTF{#1}
    {foo bar\ignorespaces}
    {foo baz}}
\newcommand\exchange[2]{#2#1}
\NewDocumentCommand{\FooInterfaceNotSoBad}{s}
  {\IfBooleanT{#1}{\exchange{*}}\Foo}
\NewDocumentCommand{\FooInterfaceAlsoNotSoBad}{s}
  {\expandafter\Foo\romannumeral0\IfBooleanTF{#1}{ *}{ }}
% In Exol-Syntax you use ~ for denoting a space-token:
%\ExplSyntaxOn
%\NewDocumentCommand{\FooInterfaceAlsoNotSoBad}{s}
%  {\expandafter\Foo\romannumeral0\IfBooleanTF{#1}{~*}{~}}
%\ExplSyntaxOff
\NewDocumentCommand{\FooInterfaceGood}{s}
  {\IfBooleanTF{#1}
    {\Foo*}
    {\Foo}}

\begin{document}

(\Foo* )

(\Foo  )

(\FooInterfaceNotSoBad* )

(\FooInterfaceNotSoBad )

(\FooInterfaceAlsoNotSoBad* )

(\FooInterfaceAlsoNotSoBad )

(\FooInterfaceGood* )

(\FooInterfaceGood )

\end{document}

相关内容