8

Is there an easy way to define a command that takes a word (an initialism) and inserts a period after every character? That is, I would like to define a command \init{abc} that outputs "A.B.C." (making no assumption on the number of characters in the input argument).

The intended use is to be able to switch the output of \init{#1} to \uppercase{#1} or \textsc{#1} depending on the desired style, or to distinguish between initialism like C.P.U. (each letter pronounced separately) and NASA (pronounced as one word) like the New Yorker does.

Tor
  • 336

2 Answers2

4

You can split the input at each token and then deliver them separated by periods; with \initformat you can choose the formatting, the argument should be a one parameter macro such as \MakeUppercase or \textsc.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\init}{m}
 {
  \tanh_init:n { #1 }
 }
\NewDocumentCommand{\initformat}{m}
 {
  \cs_set_eq:NN \tanh_init_format:n #1
 }

\seq_new:N \l_tanh_init_seq

\cs_new_protected:Nn \tanh_init:n
 {
  \seq_set_split:Nnn \l_tanh_init_seq { } { #1 }
  \tanh_init_format:n { \seq_use:Nn \l_tanh_init_seq { . } .\@ }
 }
\ExplSyntaxOff

\initformat{\MakeUppercase} % initialize

\begin{document}

\init{abc}

\initformat{\textsc}

\init{abc}

\end{document}

enter image description here

If you want to remove the trailing period when the initialism is followed by a period, you can check for it and take a decision. In the example I used \xspaceskip to make the effect more visible.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\init}{m}
 {
  \tanh_init:n { #1 }
 }
\NewDocumentCommand{\initformat}{m}
 {
  \cs_set_eq:NN \tanh_init_format:n #1
 }

\seq_new:N \l_tanh_init_seq

\cs_new_protected:Nn \tanh_init:n
 {
  \seq_set_split:Nnn \l_tanh_init_seq { } { #1 }
  \tanh_init_format:n { \seq_use:Nn \l_tanh_init_seq { . } }
  \tanh_init_period:
 }
\cs_new_protected:Nn \tanh_init_period:
 {
  \peek_charcode:NTF . { \@ } { \tanh_init_format:n { . } \@ }
 }
\ExplSyntaxOff

\xspaceskip=20pt

\initformat{\MakeUppercase} % initialize

\begin{document}

\init{abc} whatever

\init{abc}. A period!

\initformat{\textsc}

\init{abc} whatever

\init{abc}. A period!

\end{document}

enter image description here

If you want to preserve kerning, you need to make the test about . beforehand:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\init}{m}
 {
  \tanh_init:n { #1 }
 }
\NewDocumentCommand{\initformat}{m}
 {
  \cs_set_eq:NN \tanh_init_format:n #1
 }

\seq_new:N \l_tanh_init_seq

\cs_new_protected:Nn \tanh_init:n
 {
  \seq_set_split:Nnn \l_tanh_init_seq { } { #1 }
  \tanh_init_period:
 }
\cs_new_protected:Nn \tanh_init_period:
 {
  \peek_charcode_remove:NTF .
   {
    \tanh_init_format:n { \seq_use:Nn \l_tanh_init_seq { . } . } \spacefactor\sfcode`.~ 
   }
   {
    \tanh_init_format:n { \seq_use:Nn \l_tanh_init_seq { . } . } \@
   }
 }
\ExplSyntaxOff

\xspaceskip=20pt

\initformat{\MakeUppercase} % initialize

\begin{document}

\init{abp} whatever

\init{abp}. A period!

\initformat{\textsc}

\init{abp} whatever

\init{abp}. A period!

\textsc{a.b.p.} A period!

\end{document}

enter image description here

egreg
  • 1,121,712
  • That's a really nice solution, but to me it's a little less readable than what jarauh suggested. Does it offer any advantages besides the ability to change the format (\initformat{}) anywhere in the document? – Tor Nov 27 '15 at 17:38
  • @tanh Apart from the clearer syntax \init{abc} instead of \init abc\relax{}, you mean? – egreg Nov 27 '15 at 18:01
  • That is clearer, but I got around that by wrapping the \init command in @jarauh's answer above like this: \newcommand{\myinit}[1]{\init#1\relax{}}. I tried adding functionality for handling the case of an initialism ending a sentence (manually It is based on G.N.U\@.) by replacing .\@ toward the end of your code with
    \@ifnextchar.{\@.}{.\@}
    but I then get "IFNEXTCHAR" as part of the printed output. (I realize it must be obvious why this does not work if you know xparse package, but I couldn't figure it out.)
    – Tor Nov 29 '15 at 03:19
  • 1
    @tanh I added the code for peeking at a following period. – egreg Nov 29 '15 at 10:46
  • That's perfect -- thank you so much! – Tor Dec 02 '15 at 09:31
  • I just realized this breaks the kerning in P.P., for instance -- compare \init{pp} and P.P.. With \init{pp}, the space between the first P and the first period is correctly kerned, whereas the second P and the second period are separated by a bigger space. I was able to make out that \@. breaks kerning (P\@. is not kerned correctly), but have not found a way to modify the space factor inside the macro definition. – Tor Dec 08 '15 at 18:08
  • @tanh I'll give a look. – egreg Dec 08 '15 at 18:58
  • 1
    @tanh I'm not sure I see any difference; what font are you using? – egreg Dec 08 '15 at 19:25
  • That's strange. I use Latin Modern and Palatino with pdflatex on the latest TeXLive distribution on a Mac. Compiling the bottom code you included above, with the only difference being I add \usepackage{mathpazo} and replace the body with
    \begin{document}
    \fontfamily{lmr}\selectfont
    P.P. \par
    \init{pp} \par
    \fontfamily{ppl}\selectfont
    P.P. \par
    \init{pp}
    \end{document}
    produces this output (vertical lines added manually): http://i.imgur.com/NiUN79p.png. (Sorry about the formatting!)
    – Tor Dec 09 '15 at 14:22
  • It's not a big deal of course, but pretty noticeable with certain combinations of letters. – Tor Dec 09 '15 at 14:30
3

One way to do it:

\documentclass{article}

\begin{document}
\newcommand{\init}[1]{%
  \let\nextinit=\relax
  \if\relax\noexpand#1\relax\else#1.\let\nextinit=\init\fi\nextinit}

\init IBM\relax{} is an abbreviation, as is
\init GNU\relax.  What is \init SPECTRE{}?
\end{document}

\init is now defined as a function that takes a single argument. Thus, in \init IBM, first, only the I will be the argument to \init. The idea is that \init checks whether its argument equals \relax. If yes, it does nothing and stops. If not, then it prints the argument plus a ., and it replicates itself. Thus it continues, until it meets a \relax. It also stops at {} (even though that's not strictly speeking a \relax, but well).

You could also try to implement other stopping criteria, such as a special character, using \@ifnextchar, see Understanding \@ifnextchar.

jarauh
  • 2,762
  • 15
  • 27
  • That's very concise and works really well. I wrapped your \init command an a new command \myinit:
    \newcommand{\myinit}[1]{\init #1\relax{}} That way, I can can call \myinit{gnu} and make it easier to redefine the output format later. Thank you!
    (Edit: repeatedly and accidentally pressing return.)
    – Tor Nov 27 '15 at 17:20
  • I guess that should be
    \newcommand{\myinit}[1]{\init#1\relax{}\@} where the added \@ ensures a non-end-of-sentence space, as far as I know.
    – Tor Nov 27 '15 at 17:34
  • 1
    However, the \@ will also add a space if you write it at the end of a sentence before the punctuation mark. – jarauh Nov 27 '15 at 18:49
  • True. I guess the right thing to do is to print a \@. if the argument equals . (indicating the abbreviation is at the end of a sentence, as in It is based on \init GNU., which should expand to It is based on G.N.U\@.), and if not do the above. I tried modifying your code to do that, but do not have the skills. – Tor Nov 29 '15 at 03:02