我怎样才能对最后一个标记进行调节?

我怎样才能对最后一个标记进行调节?

我想编写一个命令\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>[...]

相关内容