2

I would like to write a command \foo which, for the sake of simplicity, does just one thing, which is make sure its argument ends with a dot. Thus both foo{hello world} and foo{hello world.} expand to hello world. (or equivalent).

How do I do this? Is there some kind of "if-last-token" conditional I could use?

2
  • Are you perhaps looking for a command that adds punctuation if not explicitly given in the argument? – egreg Jul 18 '18 at 8:19
  • For punctuation marks in particular there are good tricks to achieve this, but they may be impractical for other uses. I saw an answer by Ulrike Fischer about this a few days ago, maybe I can find it. Here its is: tex.stackexchange.com/q/4834/35864 – moewe Jul 18 '18 at 8:23
8

You can define a \stripdot command, which does the opposite of what you asked for - stripping a final "." - in two lines using only TeX primitives:

\def\stripdot#1{\stripdotA#1\end.\end!{#1}} 
\def\stripdotA#1.\end#2!#3{\ifx!#2!#3\else#1\fi} 

given this command, defining \foo is now trivial:

\def\foo#1{\stripdot{#1}.}
\foo{Hello world.}

\foo{Hello world}

\bye
4
  • Cool! You can use this for implementing a test for trailing dots where \end is the only sentinel-token that must not occur in the argument. ;-) – Ulrich Diez Jul 18 '18 at 20:11
  • \stripdot might strip one level of braces: \edef\test{\stripdot{{In Braces?}.}}{\tt\meaning\test}\bye – Ulrich Diez 53 mins ago – Ulrich Diez Jul 18 '18 at 21:35
  • @wipet, Do you have any idea why sharelatex's compiler doesn't like this code? – einpoklum Jul 21 '18 at 18:16
  • What says log file? Maybe sharelatex is only LaTeX specific... – wipet Jul 21 '18 at 18:21
4

I suspect this is an XY-question. Anyway, here's an expandable solution (add to the tests as needed).

\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}

enter image description here

Here's a possibly more useful implementation: if the argument ends with .!?, nothing is added, otherwise a period is added.

\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}

enter image description here

There is also a classical way to do this.

\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}

enter image description here

2

Apart from Detect beginning of a sentence in a macro for capitalization you could also use \IfEndWith from the xstring package:

\documentclass{article}
\usepackage{xstring}
\newcommand{\foo}[1]{\IfEndWith{#1}{.}{#1}{#1.}}
\begin{document}
\foo{hello world}

\foo{hello world.}
\end{document}

enter image description here

1

This uses listofitems to accomplish the task, and can be set up to intercept multiple qualifying end-punctuation (here ., ? and !).

\documentclass{article}
\usepackage{listofitems}
\newcommand\foo[1]{%
  \setsepchar{.||?||!}% <-- LIST ALL PUNCTUATION THAT QUALIFES AS ENDING
  \readlist\myarg{#1}%
  \foreachitem\x\in\myarg[]{%
    \x%
    \ifnum\xcnt=\listlen\myarg[]\relax%
      \if\relax\x\relax\else.\fi%
    \else%
      \myargsep[\xcnt]%
    \fi%
  }%
}
\begin{document}
\foo{Hello world}\par
\foo{Hello world.}\par
\foo{Stop. This. Now}\par
\foo{Stop. This. Now.}\par
\foo{Stop. This. Now!}\par
\foo{well...that is that}\par
\foo{well...that is that.}\par
\foo{well...how is that?}
\end{document}

enter image description here

1

If you wish to look at the sequence of tokens that forms the argument of \foo unexpanded, you can add a trailing dot to that argument and then run a loop which removes dot-delimited arguments until removing another dot-delimited argument would yield emptiness.

Then examine whether the remaining dot-delimited argument is empty.
If so, the last token of the argument of \foo was a dot.
If not so, the last token of the argument of \foo was not a dot.

\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}

enter image description here


The gist of the following solution, which is much better, comes from the answer of wipet :

Choose a <sentinel token> which must not occur within the argument to examine (unless nested in braces).

Append <sentinel token>.<sentinel token><delimiter> (<delimiter> other than . and other than <sentinel token> and other than .<sentinel token>) behind the argument to examine before fetching a first argument delimited by .<sentinel token> and fetching a second argument delimited by <delimiter>.

In case the last token of the argument to examine is a dot, that dot and the first <sentinel token> from the appended sequence form the delimiter for that first argument to fetch, and the second argument is not empty but holds the sequence .<sentinel token>.

In case the last token of the argument to examine is not a dot, the dot from the appended sequence and the second <sentinel token> from the appended sequence form the delimiter for that first argument to fetch, and the second argument is empty.

\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}

enter image description here

What's embarrassing for me is that when writing my first solution, which is cumbersome, I did not remember the method shown by wipet although it is well-known.

E.g., for several years in the early nineties the late Michael Downes presented a series of TeX macro programming challenges he called Around the Bend.

More information about this can be found on CTAN at https://ctan.org/pkg/around-the-bend.

The document holding all programming challenges and answers can be found at
https://ctan.org/tex-archive/info/challenges/AroBend/AroundTheBend.pdf .

In Challenge 15 – Space removal, section 15.2.3 – Some remarks about the domain of the problem, on page 66, the method shown by wipet is exhibited and explained. The only difference is that there the method is used for trimming a trailing space instead of trimming a trailing dot:

In the previous posting I discussed the method of removing a trailing space by scanning for a token pair <space><bizarre> [...]

3
  • +1 For a solution "from first principles" using no packages... :-) – einpoklum Jul 18 '18 at 18:58
  • @einpoklum wipet's solution is also a solution "from first principles using no packages" and much better. :-) – Ulrich Diez Jul 18 '18 at 21:18
  • ... @wipet posted it after you posted yours; and I wouldn't downvote you of course. I did accept his solution though. – einpoklum Jul 18 '18 at 22:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.