How might one write a macro that makes slovenly abbreviations respectable?

\documentclass{article}
% \newcommand\abr{...}
\begin{document}
I've seen it in the \abr{OED}.
\end{document}

What I mean is that OED should be rendered as O.\,E.\,D..

As Mico points out, there should never be two contiguous points. I suppose one might use \@ifnextchar. to prevent that.

  • Are you sure that you want two . punctuation marks after the (non-slovenly) version of OED? – Mico Nov 8 at 16:41
  • 1
    @Mico, I'm sure I don't; edited. – Toothrot Nov 8 at 19:42
  • 1
    Note however that the norm is that abbreviations should be typeset without any dots. I don't have the reference to the norm right now, but I'll post it later on. – Massimo Ortolano Nov 9 at 7:44

All in all the same as the answer by Steven B. Segletes, but expandable. Also almost everything as contents should be fine (except the really unlikely \endabr@).

\documentclass[]{article}

\makeatletter
\newcommand\abr[1]
  {%
    \abr@#1\endabr@
  }
\def\abr@#1#2\endabr@
  {%
    #1.%
    \if\relax\detokenize{#2}\relax
      \@%
      \expandafter\@gobble
    \else
      \,%
      \expandafter\@firstofone
    \fi
    {\abr@#2\endabr@}%
  }
\makeatother

\begin{document}
Single letter:
\abr{eg}

Multi letter: 
\abr{{th}e}
\end{document}
  • 1
    I believe mine is fully expandable, as well. – Steven B. Segletes Nov 8 at 12:59
  • @StevenB.Segletes \def is not expandable. – Skillmon Nov 8 at 13:00
  • Got it. I meant to say, mine can be placed in an \edef. But you are right, the contents of the \edef are not the final expansion. – Steven B. Segletes Nov 8 at 13:01
  • @StevenB.Segletes It can't be placed inside an \edef as the \edef would try to expand \next before it gets defined. – Skillmon Nov 8 at 13:02
  • 1
    @StevenB.Segletes no. Just try \edef\tmp{\abr{OED}} right after the definition of \abr and \abraux in your MWE. It'll throw an error. – Skillmon Nov 8 at 13:05

Here is the simplest form of my original approach. It can be placed in an \edef. Its only drawback is that it can blow the stack if the argument is too long (maybe 256 characters??)

\documentclass{article}
\newcommand\abr[1]{\abraux#1\relax\relax}
\def\abraux#1#2\relax{#1.\ifx\relax#2\relax\@\else\,\abraux#2\relax\fi}
\begin{document}
Here is \abr{OED} abbreviation.

Here is \abr{XO} abbreviation.
\end{document}

enter image description here

If that really were an issue, here is an alternative that doesn't have that problem.

There seems to be a misunderstanding that this definition of \abr cannot be placed into an \edef. It can. Naturally, the expansion is not necessarily pretty, but it will yield the proper typesetting. The only proviso is that \@MyOwnMacro is not used elsewhere in your document.

\documentclass{article}
\usepackage[T1]{fontenc}
\makeatletter
\let\@MyOwnMacro\relax
\newcommand\abr[1]{\abraux#1\relax\relax}
\def\abraux#1#2\relax{%
  #1.\ifx\relax#2\relax\def\@MyOwnMacro{\@}\else\def\@MyOwnMacro{\,\abraux#2\relax}\fi%
  \@MyOwnMacro%
}
\makeatother
\begin{document}
Here is \abr{OED} abbreviation.

Here is \abr{XO} abbreviation.

Can be edef'ed:

\edef\tmp{\abr{OED}} \detokenize\expandafter{\tmp}

expands to \tmp
\end{document}

enter image description here

  • 1
    Cheers to you :-) for your good solution. I have removed my comment. Is it correct in English language? – Sebastiano Nov 8 at 12:51
  • 1
    @Sebastiano 5:5 (loud and clear). Excellent English. Saluti! – Steven B. Segletes Nov 8 at 12:58
  • It can't be \edefed if it is used for the first time! It'll throw an error. – Skillmon Nov 8 at 13:55
  • @Skillmon OK, \let\next\relax fixes that. – Steven B. Segletes Nov 8 at 14:16
  • 2
    @StevenB.Segletes as soon as anything else uses \next you'll get problems. In general your macro can't be fully expandable as long as it contains anything changing the definition of anything. – Skillmon Nov 8 at 14:21

Here's a LuaLaTeX-based solution.

  • Cases such as \abr{OED} or \abr{IMF} work just as expected. If the acronym contains both uppercase and lowercase letters, dots are inserted only before the uppercase letters in the interior of the acronym. E.g., \abr{MSc} generates M.\,Sc., and \abr{PhD} generates Ph.\,D..

  • It can handle mixed-case acronyms such as "PhD" directly -- no need to write \abr{{Ph}D}.

  • If a "slovenly abbreviation" ends a sentence, one should place the "." punctuation mark inside the argument of \abr. The code takes care to insert a \@ "space factor* directive before the final . character. This, in turn, informs LaTeX that that . character should be treated as ending a sentence.

  • The code returns nothing if the argument of \abr is either empty or expands to return nothing. E.g., \def\ttt{} /\abr{\ttt}/ returns //. If the code encounters non-letter characters -- say, ( and ) -- no periods are inserted before or after them.

  • The code is expandable in the sense that \abr can be included in the argument of an \edef directive.

enter image description here

\documentclass{article}
\usepackage{luacode}   % for 'luacode' environment

%% Lua-side code:
\begin{luacode}
function abr ( s )
  n = string.len ( s )
  -- Do nothing unless "s" is non-empty.
  if n>0 then 
    s_mod = ""  -- initialize the string

    -- Process the first n-1 characters in "s"
    for i=1, n-1 do
      s12 = string.sub ( s  , i, i+1 )
      s1  = string.sub ( s12, 1, 1 )
      if string.match ( s12 , "%a%u" ) then
        s_mod = s_mod .. s1 .. ".\\,"
      else
        s_mod = s_mod .. s1
      end
    end

    -- Process the final character in "s"
    s_n = string.sub ( s , n)
    if     string.match (s_n, "%.") then   -- "." char.
      s_mod = s_mod .. "\\@." 
    elseif string.match (s_n, "%l") then   -- lowercase letter
      s_mod = s_mod .. s_n .. ".\\hbox{}"
    elseif string.match (s_n, "%u") then   -- uppercase letter
      s_mod = s_mod .. s_n .. "."
    else                    -- Any other character:
      s_mod = s_mod .. s_n  -- don't add anything after 's_n'
    end
    -- Print the modified string
    tex.sprint ( s_mod )
  end
end

\end{luacode}

%% LaTeX-side code: macro that calls the Lua function
\newcommand\abr[2]{\directlua{abr("#1")}}

\begin{document}
\abr{OED}, \abr{PhD}, \abr{DPhil}, \abr{MSc}, \abr{()}

\smallskip
% Two calls to "\abr" with an empty argument (upon expansion):
.\abr{}. \quad 
\def\ttt{} .\abr{\ttt}. 

\bigskip
\edef\tmp{\abr{MA}}   \detokenize\expandafter{\tmp}

\edef\tmp{\abr{MA.}}  \detokenize\expandafter{\tmp}

expands to: \tmp

\smallskip
\edef\tmp{\abr{MSc}}  \detokenize\expandafter{\tmp}

\edef\tmp{\abr{MSc.}} \detokenize\expandafter{\tmp}

expands to: \tmp

\bigskip
Some tests of spacing after punctuation marks:

\smallskip
a \abr{PhD} candidate --- good

a Ph.\,D. candidate   --- just to verify

\smallskip
an \abr{MSc} candidate --- good

an M.\,Sc.\ candidate  --- just to verify

\smallskip
She has a \abr{PhD.} So do I. --- good

She has a Ph.\,D\@.  So do I. --- just to verify

\smallskip
He has an \abr{MSc.} So do I. --- good

He has an M.\,Sc.    So do I.  --- just to verify

\smallskip
Does he have an \abr{MSc.}? Really?! --- good

Does he have an M.\,Sc.?    Really?!  --- just to verify
\end{document} 
  • I wonder if it weren't more correct to put a sentence-ending point after the macro rather than into the argument, seeing as the full-stop is not part of the abbreviation. – Toothrot Nov 8 at 22:16
  • @Toothrot - A major issue is: How does one inform LaTeX whether a sentence ends with a slovenly abbreviation? If one writes I like the \abr{OED}., one ends up with two "dots" -- not good. The only way I can think of indicating reliably to LaTeX that a sentence ends right after some \abr{...} directive is to include the period in the argument of \abr. I've come up with an update to the code that allows \abbr{MSc} and \abr{MSc.} to be typeset differently. I'll post the updated code shortly. – Mico Nov 8 at 22:49
  • How about \@ifnextchar.{\spacefactor3000\@gobble} or something like that? – Toothrot Nov 8 at 22:55
  • @Toothrot - Using \@ifnextchar is a potentially interesting idea. I'll have to think about some more; unfortunately, I won't be able to get to work on it until this evening at the earliest. Maybe somebody else will come up with a good solution in the meantime... – Mico Nov 9 at 6:11

Let TeX do the recursion:

\documentclass{article}
\usepackage{xparse}
\usepackage{etoolbox}

\robustify{\,} % just in order it doesn't expand in \edef

\ExplSyntaxOn
\NewExpandableDocumentCommand{\abr}{m}
 {
  \tl_map_function:fN { \tl_range:nnn { #1 } { 1 } { -2 } } \__toothrot_abr:n
  \tl_range:nnn { #1 } { -1 } { -1 } .
 }
\cs_generate_variant:Nn \tl_map_function:nN { f }
\cs_new:Nn \__toothrot_abr:n { #1.\, }
\ExplSyntaxOff

\begin{document}

\abr{OED}

\abr{{Ph}D}

\edef\test{\abr{OED}}
\texttt{\meaning\test}

\edef\test{\abr{}}
\texttt{\meaning\test}

\end{document}

If a part of the argument is braced, it is considered as a single item.

One might check whether the argument is empty in order to print nothing at all, but it doesn't seem so important a feature.

enter image description here

With \tl_range:nnn { #1 } { 1 } { -2 } we extract all items but the last; \tl_range:nnn { #1 } { -1 } { -1 } extracts the last item.

\documentclass{article}
\usepackage{xinttools}
\newcommand\abr[1]{\xintListWithSep{.\,}{#1}.}
\begin{document}
I've seen it in the \abr{OED}.

I got my \abr{{Ph}D}.
\end{document}

enter image description here

Updated (à la Mico, but without LuaLaTeX)

The syntax here is to use \abr{PhD.} for example at end of a sentence, and \abr{PhD} if not at end of a sentence.

\documentclass{article}
\usepackage{xinttools}
\makeatletter
\newcommand\abr[1]
   {\expandafter\@gobbletwo\romannumeral0\xintapplyunbraced\abr@aux{#1}.\@}
\def\abr@sep{.\,}
\def\abr@aux#1{\if.#1\expandafter\abr@end
               \else
                \if1\ifnum`#1<`A 0\fi\ifnum`#1>`Z 0\fi1%
                   \expandafter\expandafter\expandafter\abr@sep
                \fi
               \fi#1}%
\def\abr@end.{ \abr@@end}
\def\abr@@end.\@{\@.}
\makeatother

\begin{document}%\ttfamily
I've seen it in the \abr{OED}, and if located at end of a sentence
just insert a dot in the \verb|\abr| argument: \abr{OED.} It ended a
sentence and in non-French spacing mode, TeX inserted the extra
space.

\texttt{We can see it better with monospace font: \abr{OED.} See?}

I got my \abr{PhD} and even my \abr{PhilD}, leniency ruled
in those days.

\texttt{The dots are added in a smart way: \abr{AaaaBbbbCccc.} But it is
assumed that the first letter is \abr{Uppercased.} That's it.}

\texttt{Notice that neitger \abr{Aaaa} nor \abr{AaA} trigger an end of
  sentence spacing after the dot, which is \abr{Good.} Isn't it?}
\end{document}

enter image description here

Again updated, for automatic end of sentence dot detection after abbreviation

Here an end of sentence dot will be detected automatically.

Of course we can't use \@ifnextchar for that, as it swallows spaces.

I added some comments about expandability, which seems to have drawn great attention in other answers :).

\documentclass{article}
\usepackage{shortvrb}\MakeShortVerb{\|}
\usepackage{xinttools}

\makeatletter
\protected\def\abrsep{.\,}% maybe redefined even after \edef\foo{\abr{DPhil}}...
\protected\def\abrend{\futurelet\@let@token\abr@end}
\def\abr@end{\ifx.\@let@token\@\else.\@\fi}
\newcommand\abr[1]
   {\expandafter\@gobble\romannumeral0\xintapplyunbraced\abr@aux{#1}\abrend}
\def\abr@aux#1{\if1\ifnum`#1<`A 0\fi\ifnum`#1>`Z 0\fi1%
                   \expandafter\abr@sep
               \fi#1}%
\def\abr@sep{ \abrsep}
\makeatother

\begin{document}%\ttfamily
I've seen it in the \abr{OED}, and if located at end of a sentence
it will detect it automatically: \abr{OED}. There was no double dot.
Besides, TeX applied its end of sentence extra space.

\texttt{We can see it better with monospace font: \abr{OED}. See?}

\texttt{We can see it better with monospace font: \abr{OED}, See?}

I got a \abr{MSc}, a \abr{PhD} and even a \abr{DPhil}. Leniency ruled
in those days. 

{The abbreviation dots are added in a smart way, after the last
  lowercase letter following an uppercase letter:
  \abr{AaaaBbbbCccc}. But it is \emph{assumed} that the first letter is
  \abr{Uppercased}. That's it.}

\texttt{Notice that neither \abr{DPhil} nor \abr{PhilD} get \TeX\ to
  consider the inserted final dot as signaling  an end of
  sentence spacing after the dot, which is \abr{Good}. Isn't it?}

About expandability, the correct way for \LaTeX2e's users would be to use
|\protected@edef|, not a naked |\edef|; although nowadays some
\LaTeX2e\ users have heard about |\edef|, they might not know
about |\protected@edef|, which requires a cumbersome extra
|\makeatletter| for its usage. Anyway, none of that is described in
\textsc{Lamport} book, so I wonder if \LaTeX2e users are really
\emph{allowed} into using |\edef| to start with.

But as it seems they know about |\edef|, we as macro programmers need
better to use the e-\TeX's |\protected| prefix and not the \LaTeX2e
|\DeclareRobustCommand|, because users will not do |\protected@edef|.

This is what I have done here for a macro |\abrsep| (why haven't we all
used |\abbr| by the way?) which is deliberately |\protected|,
allowing it to be redefined at location of use, long after some macro
will have been defined via |\edef\foo{\abr{ABCDEFGH}}|.

\edef\foo{\abr{ABCDEFGH}}\texttt{\string\foo\ is \meaning\foo}

\edef\foo{\abr{AaaBccCcc}}\texttt{\string\foo\ is \meaning\foo}

The |\abrend| is also |\protected|, anyway as its expansion will be
context dependent (it detects if a dot follows), it had to not expand
in the |\edef|.
\end{document}

enter image description here

Notice that my proposals v2 and v3 will work only with ascii uppercase letters, no diacritics.

  • @Mico I plagiarized your input syntax for end of sentence... – jfbu Nov 8 at 17:07
  • 1
    @Mico I have again updated as I read OP is pushing towards automatic dot detection. One can not use LaTeX \@ifnextchar which swallows spaces. – jfbu Nov 9 at 8:21
  • Outstanding! :-) Incidentally, I've gone ahead and deleted my earlier comments are they're no longer relevant, or even understandable, for readers of the current version of your answer. – Mico Nov 9 at 8:39
  • (not to be told publicly: of course I could remove all usage of xinttools but where is the fun without it?) well, in fact usage of a an xinttools macro facilitates a fixed number of expansion steps to get final result, here 3 steps, I could reduce to 2 steps. But doesn't matter for an \edef and anyhow the tokens of the input are subjected to full-first expansion via \ifnum test etc... – jfbu Nov 9 at 9:47
\documentclass{article}
\newcommand\abr[1]{\abraux#1..}
\def\abraux#1#2#3{%
  #1%
  \ifnum`#2>91\relax% we have a lowercase letter following
    \def\next{\abraux#2#3}%
  \else
    .\ifx.#2\def\next{\@}\else\,\def\next{\abraux#2#3}\fi
  \fi
  \next
}
\begin{document}
    Here is \abr{OED} abbreviation.

    Here is \abr{XO} abbreviation.

    Here is \abr{XOOED} abbreviation.

    \abr{MSc}  

    \abr{DPhil}
\end{document}

enter image description here

  • Can your code be extended to handle the lazy/slovenly punctuation of MSc (correct: M.\,Sc.) and DPhil (correct: D.\,Phil.)? – Mico Nov 9 at 6:20
  • 1
    Sure, if it is not an uppercase letter then go the next char. – Herbert Nov 9 at 19:02

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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