我正在尝试使用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}