我想编写一个命令\foo
,为了简单起见,它只做一件事,即确保其参数以点结尾。因此和都foo{hello world}
扩展foo{hello world.}
为hello world.
(或等效)。
我该怎么做?我可以使用某种“if-last-token”条件吗?
答案1
您可以定义一个\stripdot
命令,它执行与您要求的相反的操作 - 删除最后的“。” - 在两行中仅使用 TeX 原语:
\def\stripdot#1{\stripdotA#1\end.\end!{#1}}
\def\stripdotA#1.\end#2!#3{\ifx!#2!#3\else#1\fi}
给出这个命令,定义\foo
现在很简单:
\def\foo#1{\stripdot{#1}.}
\foo{Hello world.}
\foo{Hello world}
\bye
答案2
我怀疑这是一个 XY 问题。无论如何,这是一个可扩展的解决方案(根据需要添加到测试中)。
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewExpandableDocumentCommand{\foo}{m}
{
\str_case_x:nnF { \tl_item:fn { #1 } { -1 } }
{
{.}{Ends~with~period}
{,}{Ends~with~comma}
{?}{Ends~with~question~mark}
}
{No~punctuation}
}
\cs_generate_variant:Nn \tl_item:nn { f }
\ExplSyntaxOff
\newcommand{\withperiod}{This ends with a period.}
\newcommand{\noperiod}{This doesn't end with a period}
\begin{document}
\foo{This ends with a period.} (period)
\foo{\withperiod} (period)
\foo{This ends with a comma,} (comma)
\foo{This ends with a question mark?} (question mark)
\foo{Foo} (no punctuation)
\foo{\noperiod} (no punctuation)
\edef\test{\foo{\withperiod}}\texttt{\meaning\test}
\end{document}
这是一个可能更有用的实现:如果参数以 结尾.!?
,则不添加任何内容,否则添加句点。
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewExpandableDocumentCommand{\foo}{m}
{
\str_case_x:nnF { \tl_item:fn { #1 } { -1 } }
{
{.}{#1}
{?}{#1}
{!}{#1}
}
{#1.}
}
\cs_generate_variant:Nn \tl_item:nn { f }
\ExplSyntaxOff
\newcommand{\withperiod}{This ends with a period.}
\newcommand{\noperiod}{This will end with a period}
\begin{document}
\foo{This ends with a period.}
\foo{\withperiod}
\foo{This ends with an exclamation mark!}
\foo{Does this end with a question mark?}
\foo{This will end with a period}
\foo{\noperiod}
\edef\test{\foo{\withperiod}}\texttt{\meaning\test}
\end{document}
还有一种经典的方法可以做到这一点。
\documentclass{article}
\usepackage{amsthm} % for \@addpunct
\makeatletter
\newcommand{\foo}[1]{#1\@addpunct{.}}
\makeatother
\newcommand{\withperiod}{This ends with a period.}
\newcommand{\noperiod}{This will end with a period}
\begin{document}
\foo{This ends with a period.}
\foo{\withperiod}
\foo{This ends with an exclamation mark!}
\foo{Does this end with a question mark?}
\foo{This will end with a period}
\foo{\noperiod}
\end{document}
答案3
除了检测宏中句子开头的大写字母您还可以使用\IfEndWith
以下xstring
包:
\documentclass{article}
\usepackage{xstring}
\newcommand{\foo}[1]{\IfEndWith{#1}{.}{#1}{#1.}}
\begin{document}
\foo{hello world}
\foo{hello world.}
\end{document}
答案4
如果您希望查看形成\foo
未扩展参数的标记序列,您可以在该参数后添加一个尾随点,然后运行一个循环,删除以点分隔的参数,直到删除另一个以点分隔的参数产生空值。
然后检查剩余的以点分隔的参数是否为空。
如果是,则 的参数的最后一个标记\foo
是点。
如果不是,则 的参数的最后一个标记\foo
不是点。
\documentclass{article}
\makeatletter
%%----------------------------------------------------------------------
%% 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\@secondoftwo\string{\expandafter
\@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
\@secondoftwo\string}\expandafter\expandafter\@firstoftwo{ }{}%
\@secondoftwo}{\expandafter\expandafter\@firstoftwo{ }{}\@firstoftwo}%
}%
%%----------------------------------------------------------------------
%% Check whether argument's last token is a dot:
%%......................................................................
%% \UD@CheckWhetherTrailingDot{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% does have a last token which is a dot>}%
%% {<Tokens to be delivered in case that argument
%% does not have a last token which is a dot>}%
%%
\newcommand\UD@CheckWhetherTrailingDot[1]{%
\romannumeral0\UD@CheckWhetherNull{#1}%
{\@secondoftwo}%
{\UD@CheckWhetherTrailingDotGobbleToDotLoop{#1.}}%
{\expandafter\expandafter\@firstoftwo{ }{}\@firstoftwo}%
{\expandafter\expandafter\@firstoftwo{ }{}\@secondoftwo}%
}%
%%%%
\newcommand\UD@CheckWhetherTrailingDotGobbleToDotLoop[1]{%
\expandafter\UD@CheckWhetherNull
\expandafter{\UD@CheckWhetherTrailingDotGobbleToDot#1}{%
\UD@CheckWhetherTrailingDotFork{.}#1%
}{%
\expandafter\UD@CheckWhetherTrailingDotGobbleToDotLoop
\expandafter{\UD@CheckWhetherTrailingDotGobbleToDot#1}%
}%
}%
%%%%
\newcommand\UD@CheckWhetherTrailingDotGobbleToDot{}
\long\def\UD@CheckWhetherTrailingDotGobbleToDot#1.{}
%%%%
\newcommand\UD@CheckWhetherTrailingDotFork{}%
\long\def\UD@CheckWhetherTrailingDotFork#1.{%
\expandafter\UD@CheckWhetherNull\expandafter{\@gobble#1}%
}%
%%%%
\newcommand\foo[1]{%
\UD@CheckWhetherTrailingDot{#1}{#1}{#1.}%
}%
\makeatother
\begin{document}
With \verb|\foo| an empty argument is recogniced as an argument
that dose not have a trailing dot:
\verb|\foo{}|: \foo{}
\verb*|X\foo{ }X|: X\foo{ }X
\verb|\foo{.}|: \foo{.}
\verb|\foo{Bla. Bla.}|: \foo{Bla. Bla.}
\verb|\foo{Bla. Bla}|: \foo{Bla. Bla}
\verb|\foo{Bla. {Bla.}}|: \foo{Bla. {Bla.}}
\verb|\foo{hello world}|: \foo{hello world}
\verb|\foo{hello world.}|: \foo{hello world.}
With \verb|\foo| dots nested in braces will not count as trailing dots as
in this case the last token of the argument is not a dot but a closing brace:
\verb|\foo{hello world{.}}|: \foo{hello world{.}}
\end{document}
以下解决方案的要点要好得多,它来自于wipet 的答案:
选择一个<sentinel token>
不能出现在要检查的参数中的(除非嵌套在括号中)。
在获取由 分隔的第一个参数和获取由 分隔的第二个参数之前,在要检查的参数后面附加<sentinel token>.<sentinel token><delimiter>
(<delimiter>
除.
和除<sentinel token>
和除以外) 。.<sentinel token>
.<sentinel token>
<delimiter>
如果要检查的参数的最后一个标记是一个点,则该点和<sentinel token>
附加序列中的第一个点构成要获取的第一个参数的分隔符,并且第二个参数不为空而是包含序列.<sentinel token>
。
如果要检查的参数的最后一个标记不是点,则附加序列中的点和<sentinel token>
附加序列中的第二个点将形成要获取的第一个参数的分隔符,第二个参数为空。
\documentclass{article}
\makeatletter
\newcommand\Wipet@CheckWhetherTrailingDot[1]{%
\Wipet@CheckWhetherTrailingDotA#1\end.\end!%
}%
\newcommand\Wipet@CheckWhetherTrailingDotA{}%
\long\def\Wipet@CheckWhetherTrailingDotA#1.\end#2!{%
\ifx!#2!\expandafter\@secondoftwo\else\expandafter\@firstoftwo\fi
}%
\newcommand\foo[1]{%
\Wipet@CheckWhetherTrailingDot{#1}{#1}{#1.}%
}%
\makeatother
\begin{document}
With \verb|\foo| an empty argument is recogniced as an argument
that dose not have a trailing dot:
\verb|\foo{}|: \foo{}
\verb*|X\foo{ }X|: X\foo{ }X
\verb|\foo{.}|: \foo{.}
\verb|\foo{Bla. Bla.}|: \foo{Bla. Bla.}
\verb|\foo{Bla. Bla}|: \foo{Bla. Bla}
\verb|\foo{Bla. {Bla.}}|: \foo{Bla. {Bla.}}
\verb|\foo{hello world}|: \foo{hello world}
\verb|\foo{hello world.}|: \foo{hello world.}
With \verb|\foo| dots nested in braces will not count as trailing dots as
in this case the last token of the argument is not a dot but a closing brace:
\verb|\foo{hello world{.}}|: \foo{hello world{.}}
\end{document}
令我尴尬的是,当我写第一个解决方案时,它很麻烦,我没有记住wipet 展示的方法尽管它是众所周知的。
例如,在 20 世纪 90 年代初期的几年里迈克尔·唐斯提出了一系列 TeX 宏编程挑战,他称之为拐弯处。
更多相关信息可在 CTAN 上找到:https://ctan.org/pkg/around-the-bend。
包含所有编程挑战和答案的文档可以在以下位置找到:
https://ctan.org/tex-archive/info/challenges/AroBend/AroundTheBend.pdf。
在挑战 15 中 – 删除空格,第 15.2.3 节 – 关于问题范围的一些评论,第 66 页,wipet 展示的方法展示并解释了这一点。唯一的区别在于,该方法用于修剪尾随空格,而不是修剪尾随点:
在上一篇文章中,我讨论了通过扫描标记对来删除尾随空格的方法
<space><bizarre>
[...]