![关于删除前导和尾随空格的说明](https://linux22.com/image/390298/%E5%85%B3%E4%BA%8E%E5%88%A0%E9%99%A4%E5%89%8D%E5%AF%BC%E5%92%8C%E5%B0%BE%E9%9A%8F%E7%A9%BA%E6%A0%BC%E7%9A%84%E8%AF%B4%E6%98%8E.png)
这是一个小挑战。目标是创建一个接受两个或三个参数的命令,而不使用方括号来表示可选参数。第一个和第二个参数用逗号和空格分隔,而第二个和第三个(如果存在)用破折号分隔。这意味着以下两个输入都应该是可接受的
\mycommand{arg_1, arg_2}
和
\mycommand{arg_1, arg_2-arg_3}
这两个/三个参数的定义\mycommand
与问题无关。
答案1
你可以这样做xparse
:
\documentclass{article}
\usepackage{xparse}
% split at the comma
\NewDocumentCommand{\mycommand}{ >{\SplitArgument{1}{,}}m }{%
\mycommandA#1%
}
% do something with #1 and split the second part at the hyphen
\NewDocumentCommand{\mycommandA}{ m >{\SplitArgument{1}{-}}m }{%
Main is #1%
\IfNoValueTF{#2}{.}{; \mycommandB#2}%
}
% do something with the second part
\NewDocumentCommand{\mycommandB}{mm}{%
Secondary is #1\IfValueT{#2}{ plus #2}.%
}
\begin{document}
\mycommand{arg1}
\mycommand{arg1, arg2}
\mycommand{arg1, arg2-arg3}
\end{document}
一个优点:如果您忘记了逗号后的空格,这也没关系。
答案2
答案3
一些手工钩编/针织(或其他)... ;-)
我想这个问题是在你思考另一个问题时产生的:自定义 \cite 命令。在那个问题中,您要求使用\cite
相同语法的自定义命令。
是因为它可能。
挑战在于以某种方式进行,即删除所需的空格,但不会删除不必要的参数括号。
例如,\mycommand{{arg_1}a, arg_2}
括号之类的东西arg_1
应该保留,而括号之类的东西\mycommand{{arg_1} , arg_2}
应该删除。
此外,可能只有空格标记围绕全部的参数/围绕这些逗号和用于参数分隔符的破折号应该被删除。
下面的方法中,逗号优先于破折号,最左边的未隐藏逗号将第一个参数与其余参数分隔开,逗号后面的第一个未隐藏破折号将第二个参数与第三个参数分隔开。
如果在删除周围的空格后,花括号包围了整个参数,则将删除一对匹配的花括号,因为在这种情况下,假定花括号用于隐藏逗号/破折号/空格/空白。在所有其他情况下,花括号将被保留。
例如,\mycommand{ {arg_1} , {arg_2-2} - arg_3}
您应该得到以下内容:
第一个参数:arg_1
第二个参数:arg_2-2
第三个参数:arg_3
。
例如,\mycommand{ {{arg 1,a} - arg 1b} , {arg 2-2} , 2 - arg 3a - arg 3b}
您应该得到如下内容:
第一个参数(从第一个未隐藏的逗号开始,删除周围的空格和围绕整个参数的最外层括号级别):{arg 1,a} - arg 1b
第二个参数(从第一个未隐藏的逗号开始,从逗号后第一个未隐藏的破折号开始,左边):{arg 2-2} , 2
第三个参数(余数,从逗号后第一个未隐藏的破折号开始)arg 3a - arg 3b
:。
例如,\mycommand{{arg 1}, {arg 2-2} , 2 - arg 3a - arg 3b}
您应该得到如下内容:
第一个参数(从第一个未隐藏的逗号开始左边):arg 1
第二个参数(从第一个未隐藏的逗号开始右边,从逗号后第一个未隐藏的破折号开始左边):{arg 2-2} , 2
第三个参数(余数,从逗号后第一个未隐藏的破折号开始右边)arg 3a - arg 3b
:。
您可能不熟悉删除参数周围的空格标记的方法,因此引用我已弃用的 labelcas-package 手册中的一段内容:
关于删除前导和尾随空格的说明
关于如何从(几乎)任意的标记序列中删除尾随空格的问题,Michael Downes 在《Around the Bend #15, answers》一文中进行了详细阐述,这是一份互联网讨论摘要,主要在他的指导下在 INFO-TEX 列表中进行,但也在 comp.text.tex (usenet) 和通过私人电子邮件进行;1993 年 12 月。在线存档于http://www.tug.org/tex-archive/info/arobend/answer.015。
其中建议的一个基本方法是使用 TeX 对分隔参数的扫描来检测并丢弃参数的结束空格:
... 扫描一对标记:一个空格标记和一个精心挑选的奇怪标记,这些标记不可能出现在扫描的文本中。如果您将奇怪的标记放在文本末尾,并且文本后面有一个空格,那么 TeX 的分隔符匹配将在该点匹配,而不是在之前,因为较早出现的空格没有该对中必需的其他成员。
接下来考虑尾随空格缺失的可能性:TeX 将继续扫描该对, 直到找到它们或决定放弃并发出“参数失控?”错误信号。因此,您必须添加一个停止对来捕获失控参数的可能性:奇怪标记的第二个实例,前面有一个空格。如果 TeX 在第一个奇怪标记处找不到匹配项,它会在第二个标记处找到。
⟨space⟩⟨bizarre⟩
(查找David Carlisle 的 keyval 包中的宏
\KV@@sp@def
、和,\KV@@sp@b
了解这种方法的有趣变化。)\KV@@sp@c
\KV@@sp@d
当扫描参数 序列时: ,你可以分叉两种情况:
##1⟨space⟩⟨bizarre⟩##2⟨B1⟩
⟨stuff where to remove trail-space⟩⟨bizarre⟩⟨space⟩⟨bizarre⟩⟨B1⟩
尾随空格:
##1
=⟨stuff where to remove trail-space⟩
,但删除了空格。(并且可能删除了一个大括号级别!)
##2
= 。⟨space⟩⟨bizarre⟩
没有尾随空格:
##1
= .为空。⟨stuff where to remove trail-space⟩⟨bizarre⟩
##2
因此,可以根据 的空性来实现分叉
##2
。
您可以轻松防止在第一种情况下删除括号,例如,通过在 前面添加(然后删除)某些内容(例如,空格标记)⟨stuff where to remove trail-space⟩
。你可以选择。
⟨B1⟩=⟨bizarre⟩⟨space⟩
引文中的链接已过期。
如今,您可以在以下网址找到整个“Around the bend”系列http://mirrors.ctan.org/info/challenges/AroBend/AroundTheBend.pdf。
如您所见,上面引用中展示的空格删除方法确实依赖于一些在参数中不能出现的标记序列。
在下面的例子中,它依赖于\UD@seldom
参数中未出现的标记。
换句话说:\UD@seldom
您不能在您的参数中使用令牌。
如果您不喜欢这种限制,那么我可以提供没有这种限制的空间删除例程,但它们的速度较慢并且会形成另一大段代码。
\documentclass{article}
\makeatletter
%%-----------------------------------------------------------------------------
%% Paraphernalia ;-) :
%%.............................................................................
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@exchange[2]{#2#1}%
\newcommand\UD@removespace{}\UD@firstoftwo{\def\UD@removespace}{} {}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is empty>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not empty>}%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
%%.............................................................................
\newcommand\UD@CheckWhetherNull[1]{%
\romannumeral0\expandafter\UD@secondoftwo\string{\expandafter
\UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
\UD@secondoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% \UD@CheckWhetherLeadingSpace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is a
%% space-token>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is not
%% a space-token>}%
\newcommand\UD@CheckWhetherLeadingSpace[1]{%
\romannumeral0\UD@CheckWhetherNull{#1}%
{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
{\expandafter\UD@secondoftwo\string{\UD@CheckWhetherLeadingSpaceB.#1 }{}}%
}%
\newcommand\UD@CheckWhetherLeadingSpaceB{}%
\long\def\UD@CheckWhetherLeadingSpaceB#1 {%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@secondoftwo#1{}}%
{\UD@exchange{\UD@firstoftwo}}{\UD@exchange{\UD@secondoftwo}}%
{\UD@exchange{ }{\expandafter\expandafter\expandafter\expandafter
\expandafter\expandafter\expandafter}\expandafter\expandafter
\expandafter}\expandafter\UD@secondoftwo\expandafter{\string}%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%.............................................................................
%% \UD@ExtractFirstArg{ABCDE} yields {A}
%%
%% \UD@ExtractFirstArg{{AB}CDE} yields {AB}
%%
%% !!! The argument of \UD@ExtractFirstArg must not be empty. !!!
%% You can check for emptiness via \UD@CheckWhetherNull before applying
%% \UD@ExtractFirstArg.
%%.............................................................................
\newcommand\UD@RemoveTillUD@SelDOm{}%
\long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
\newcommand\UD@ExtractFirstArg[1]{%
\romannumeral0%
\UD@ExtractFirstArgLoop{#1\UD@SelDOm}%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{ #1}%
{\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%-----------------------------------------------------------------------------
%% \UD@RemoveSpacesAndOneLevelOfBraces{<argument>} removes leading and
%% trailing spaces from <argument>.
%% If after that the <argument> is something that is entirely nested
%% between at least one pair of matching curly braces, the outermost
%% pair of these braces will be removed.
%%
%% !!!! <argument> must not contain the token \UD@seldom !!!!
%%.............................................................................
\begingroup
\newcommand\UD@RemoveSpacesAndOneLevelOfBraces[1]{%
\endgroup
\newcommand\UD@RemoveSpacesAndOneLevelOfBraces[1]{%
\romannumeral0%
\UD@trimtrailspaceloop#1##1\UD@seldom#1\UD@seldom\UD@seldom#1{##1}%
}%
\newcommand\UD@trimtrailspaceloop{}%
\long\def\UD@trimtrailspaceloop##1#1\UD@seldom##2\UD@seldom#1##3{%
\UD@CheckWhetherNull{##2}{%
\UD@trimleadspaceloop{##3}%
}{%
\UD@trimtrailspaceloop##1\UD@seldom#1\UD@seldom\UD@seldom#1{##1}%
}%
}%
}%
\UD@RemoveSpacesAndOneLevelOfBraces{ }%
\newcommand\UD@trimleadspaceloop[1]{%
\UD@CheckWhetherLeadingSpace{#1}{%
\expandafter\UD@trimleadspaceloop\expandafter{\UD@removespace#1}%
}{%
\UD@CheckWhetherNull{#1}{ }{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}{%
\UD@exchange{ }{\expandafter}\UD@secondoftwo{}#1%
}{ #1}%
}%
}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument contains no comma which is not nested
%% in braces:
%%.............................................................................
%% \UD@CheckWhetherNoComma{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% contains no comma>}%
%% {<Tokens to be delivered in case that argument
%% contains comma>}%
%%
\newcommand\UD@GobbleToComma{}\long\def\UD@GobbleToComma#1,{}%
\newcommand\UD@CheckWhetherNoComma[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@GobbleToComma#1,}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument contains no dash which is not nested
%% in braces:
%%.............................................................................
%% \UD@CheckWhetherNoDash{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% contains no dash>}%
%% {<Tokens to be delivered in case that argument
%% contains dash>}%
%%
\newcommand\UD@GobbleToDash{}\long\def\UD@GobbleToDash#1-{}%
\newcommand\UD@CheckWhetherNoDash[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@GobbleToDash#1-}%
}%
%%-----------------------------------------------------------------------------
%% Take a comma-delimited/dash-delimited argument where a space was
%% prepended for preventing brace-removal and wrap it in curly braces.
%%.............................................................................
\newcommand\UD@SplitCommaArg{}%
\long\def\UD@SplitCommaArg#1,{%
\romannumeral0\UD@exchange{ }{\expandafter}\expandafter{\UD@removespace#1}%
}%
\newcommand\UD@SplitDashArg{}%
\long\def\UD@SplitDashArg#1-{%
\romannumeral0\UD@exchange{ }{\expandafter}\expandafter{\UD@removespace#1}%
}%
%%-----------------------------------------------------------------------------
%% Now we have the tools for creating the desired \mycommand-macro:
%%.............................................................................
\newcommand\mycommand[1]{%
\romannumeral0%
\UD@CheckWhetherNoComma{#1}{%
\expandafter\mycommand@oneargument\expandafter{%
\romannumeral0\UD@exchange{ }{\expandafter\expandafter\expandafter}%
\UD@RemoveSpacesAndOneLevelOfBraces{#1}%
}%
}{%
\expandafter\UD@exchange\expandafter{\expandafter
{\UD@GobbleToComma#1}%
}{%
\expandafter\@mycommand
\romannumeral0%
\UD@exchange{ }{%
\expandafter\expandafter\expandafter\expandafter
\expandafter\expandafter\expandafter
}%
\expandafter\UD@ExtractFirstArg\expandafter{%
\romannumeral0%
\UD@exchange{ }{%
\expandafter\expandafter\expandafter\expandafter
\expandafter\expandafter\expandafter
}%
\expandafter\UD@SplitCommaArg
\UD@firstoftwo{ }{}#1}%
}%
}%
}%
\newcommand\@mycommand[2]{%
\UD@CheckWhetherNoDash{#2}{%
\expandafter\UD@exchange\expandafter{\expandafter
{%
\romannumeral0%
\UD@exchange{ }{\expandafter\expandafter\expandafter}%
\UD@RemoveSpacesAndOneLevelOfBraces{#2}%
}%
}{%
\expandafter\mycommand@twoarguments\expandafter{%
\romannumeral0%
\UD@exchange{ }{\expandafter\expandafter\expandafter}%
\UD@RemoveSpacesAndOneLevelOfBraces{#1}%
}%
}%
}{%
\expandafter\UD@exchange\expandafter{\expandafter%
{\UD@GobbleToDash#2}%
}{%
\expandafter\@@mycommand
\romannumeral0%
\UD@exchange{ }{%
\expandafter\expandafter\expandafter\expandafter
\expandafter\expandafter\expandafter
}%
\expandafter\UD@ExtractFirstArg\expandafter{%
\romannumeral0%
\UD@exchange{ }{%
\expandafter\expandafter\expandafter\expandafter
\expandafter\expandafter\expandafter
}%
\expandafter\UD@SplitDashArg
\UD@firstoftwo{ }{}#2}%
}%
{#1}%
}%
}%
\newcommand\@@mycommand[3]{%
\expandafter\UD@exchange\expandafter{%
\expandafter{%
\romannumeral0%
\UD@exchange{ }{\expandafter\expandafter\expandafter}%
\UD@RemoveSpacesAndOneLevelOfBraces{#2}%
}%
}{%
\expandafter\UD@exchange\expandafter{%
\expandafter{%
\romannumeral0%
\UD@exchange{ }{\expandafter\expandafter\expandafter}%
\UD@RemoveSpacesAndOneLevelOfBraces{#1}%
}%
}{%
\expandafter\mycommand@threearguments\expandafter{%
\romannumeral0%
\UD@exchange{ }{\expandafter\expandafter\expandafter}%
\UD@RemoveSpacesAndOneLevelOfBraces{#3}%
}%
}%
}%
}%
\newcommand\mycommand@oneargument[1]{%
\UD@secondoftwo{%
%We need a space to stop \romannumeral-expansion.
}{ }%
There is no comma or only empty args after the comma, thus:\\
argument 1 = \printmeaning{#1}%
}%
\newcommand\mycommand@twoarguments[2]{%
\UD@CheckWhetherNull{#2}{%
\mycommand@oneargument{#1}%
}{%
\UD@secondoftwo{%
%We need a space to stop \romannumeral-expansion.
}{ }%
There is a comma but either no dash or only one non-empty argument near
the dash, thus:\\
argument 1 = \printmeaning{#1}\\
argument 2 = \printmeaning{#2}%
}%
}%
\newcommand\mycommand@threearguments[3]{%
\UD@CheckWhetherNull{#2}{%
\UD@CheckWhetherNull{#3}{%
\mycommand@oneargument{#1}%
}{%x
\mycommand@twoarguments{#1}{#3}%
}%
}{%
\UD@CheckWhetherNull{#3}{%
\mycommand@twoarguments{#1}{#2}%
}{%
\UD@secondoftwo{%
%We need a space to stop \romannumeral-expansion.
}{ }%
There is a comma and a dash, thus:\\
argument 1 = \printmeaning{#1}\\
argument 2 = \printmeaning{#2}\\
argument 3 = \printmeaning{#3}%
}%
}%
}%
\newcommand\printmeaning[1]{%
\def\UD@tempa{#1}%
\fbox{\texttt{\expandafter\strip@prefix\meaning\UD@tempa}}%
}%
\makeatother
\parindent=0ex
\parskip=\smallskipamount
\pagestyle{empty}%
\begin{document}
\vspace*{-3.5cm}%
\enlargethispage{7cm}%
\verb|\mycommand{ arg 1 }|:\\
\mycommand{ arg 1 }\\
\null\hrulefill\null
\verb|\mycommand{ arg 1 , }|:\\
\mycommand{ arg 1 , }\\
\null\hrulefill\null
\verb|\mycommand{ arg 1 , - }|:\\
\mycommand{ arg 1 , - }\\
\null\hrulefill\null
\verb|\mycommand{ arg 1 , {{arg 2}} }|:\\
\mycommand{ arg 1 , {{arg 2}} }\\
(Be aware that (only) the outermost braces get removed from
an argument if surrounding the entire argument after removal
of surrounding spaces.)\\
\null\hrulefill\null
\verb|\mycommand{ arg 1 , arg 2 - }|:\\
\mycommand{ arg 1 , arg 2 - }\\
\null\hrulefill\null
\verb|\mycommand{ arg 1 , arg 2 - arg 3}|:\\
\mycommand{ arg 1 , arg 2 - arg 3}\\
\null\hrulefill\null
\verb|\mycommand{ arg 1 , - arg 3}|:\\
\mycommand{ arg 1 , - arg 3}\\
\null\hrulefill\null
\verb|\mycommand{ {arg 1a} - arg 1b , {arg 2-2} , 2 - arg 3a - arg 3b}|:\\
\mycommand{ {arg 1a} - arg 1b , {arg 2-2} , 2 - arg 3a - arg 3b}\\
\null\hrulefill\null
\verb|\mycommand{ {arg 1} , {arg 2-2} , 2 - arg 3a - arg 3b}|:\\
\mycommand{ {arg 1} , {arg 2-2} , 2 - arg 3a - arg 3b}
\end{document}