How to define a command that bolds the first letter of each word in the input argument?

I want to define a command that takes a sentence, a word or several words as input and bolds the first letter of each word. I need this mostly for abbreviations and so far I have done it manually in this way:

\documentclass[a4paper]{article}

\begin{document}
GNU is a recursive acronym for \emph{\bf{G}\normalfont{nu's} \bf{N}\normalfont{ot} \bf{U}\normalfont{nix}}'', chosen because GNU's design is Unix-like, but differs from Unix by being free software and containing no Unix code.
\end{document}


What I want to do, is to define a command like \BoldAbbrv that does the job. In other words:

\documentclass[a4paper]{article}
%\newcommand{\BoldAbbrv}...IMPLEMENTATION
\begin{document}
GNU is a recursive acronym for \BoldAbbrv{Gnu's Not Unix}'', chosen because GNU's design is Unix-like, but differs from Unix by being free software and containing no Unix code.
\end{document}


How can I do that? Moreover, a related question that came to my mind while thinking about this problem, is there such concept as wildcard in latex (e.g. "*" as zero or more characters and "_" as a single one)?

-
In your first example you are using a double negative to get a positive. ...for \textbf{G}nu's \textbf{N}ot \textbf{U}nix'' is sufficient. –  percusse Jan 18 '13 at 13:21
@percuse or just \textbf Not \textbf Unix, which may be easier. Of course, thing get more complicated when the words are in lists such as Not \texttt{Unix}, i.e. lists containing typeface-changing commands. Processing lists like this programmatically may pose problems. –  Marc van Dongen Jan 18 '13 at 16:02

Here's an idea using expl3:

\documentclass{article}
\usepackage[T1]{fontenc}

\usepackage{expl3,xparse}

% turn expl3 space on: :' and _' are letters now and spaces
% are ignored. To insert a space use ~'.
\ExplSyntaxOn
% declare a new sequence variable:
\seq_new:N \l_pouya_boldfirst_seq

% the internal command:
\cs_new:Npn \pouya_boldfirst:n #1
{
% split the input at every space and put the items in the sequence:
\seq_set_split:Nnn \l_pouya_boldfirst_seq { ~ } { #1 }
% map over every item of the sequence; each item is referred to with
% ##1' as we're inside of a command definition:
\seq_map_inline:Nn \l_pouya_boldfirst_seq
{
% use the head of the item in the argument of \textbf
% and put in the tail of the item after it
% followed by a space:
\textbf { \tl_head:n { ##1 } } \tl_tail:n { ##1 } ~
}
% undo the last space:
\unskip
}

% the document command:
\NewDocumentCommand\BoldFirst{m}
{ \pouya_boldfirst:n { #1 } }

% turn expl3 space off again:
\ExplSyntaxOff

\begin{document}

GNU is a recursive acronym for \BoldFirst{Gnu's Not Unix}'', chosen because GNU's
design is Unix-like, but differs from Unix by being free software and containing
no Unix code.

\end{document}

-
Thank you very much. It is working like a charm and here is my acceptance. Just for sake of my understanding, can you please briefly explain what your code is exactly doing? –  Pouya Jan 18 '13 at 13:48
@Pouya you're welcome! I've added some comments. –  clemens Jan 18 '13 at 13:54
Does this have any side effects with hyphenation of words; that is, when they are found at the end of the line? –  azetina Jan 18 '13 at 14:31
@azetina probably. The first letter of each word is an argument to \textbf so it should have the same side effects a manual solution would have. One would need to add discretionary hyphens manually –  clemens Jan 18 '13 at 14:39
@azetina you have to add them manually in cases like \textbf{s}oftware, too. My solution will also not work with accented letters (unlike @egreg's) or any other macro at the beginning of a word. One would need to add some checking... But then how often does one really need such a macro, anyway. –  clemens Jan 18 '13 at 14:46

Already three answers and one accepted, but let me try the following code. It does allow some flexibility in the user input, but certainly will probably fail against more treacherous situations.

Update to deal with line endings... (previous version did not do the expected thing; attached image of the output updated to show now it works.)

Update so that a space after the opening brace is significant (in the previous version two or more were significant and counted as one, but a single one was ignored).

I have also illustrated the limitations of the idea. Quite probably, this is not the good way to go for this problem.

Final update: I have improved bits and pieces. See the output below.

\documentclass[a4paper]{article}
%\newcommand{\BoldAbbrv}...IMPLEMENTATION

\usepackage{iftex}
\ifXeTeX\usepackage{fontspec}\fi
\ifLuaTeX\usepackage{fontspec}\fi
\ifPDFTeX\usepackage[utf8]{inputenc}\usepackage[T1]{fontenc}\fi

\def\LongJump#1#2#3#4{#2#3#4#1}
\makeatletter
{
\obeyspaces\catcode\^^M\active\relax%
%
\gdef\BoldAbbrv@sp{\ifx\BoldAbbrv@token\bgroup%
\ \expandafter\textbf\else%
\ifx\BoldAbbrv@token\egroup\ \else%
\expandafter\expandafter\expandafter\BoldAbbrv@aux\fi\fi}%
%
\gdef\BoldAbbrv@eol{\ifx\BoldAbbrv@token\bgroup%
\ \expandafter\textbf\else%
\ifx\BoldAbbrv@token\egroup\ \else%
\expandafter\expandafter\expandafter\BoldAbbrv@auxeol\fi\fi}%
%
\gdef\BoldAbbrv@aux#1{\ifx#1 \expandafter \else%
\ifx#1^^M\expandafter\expandafter\expandafter \else%
\ifcat\noexpand#1a{\ \bfseries#1}\else%
\LongJump{\ #1}\fi\fi\fi}%
%
\gdef\BoldAbbrv@auxeol#1{\ifx#1 \expandafter^^M\else%
\ifx#1^^M\expandafter\expandafter\expandafter\BoldAbbrv@initpar\else%
\ifcat\noexpand#1a{\ \bfseries#1}\else%
\LongJump{\ #1}\fi\fi\fi}%
%
\gdef\BoldAbbrv@inita{\def {\futurelet\BoldAbbrv@token\BoldAbbrv@sp}%
\def^^M{\futurelet\BoldAbbrv@token\BoldAbbrv@eol}}%
%
\gdef\BoldAbbrv@auxb#1{\ifx#1 \expandafter \else%
\ifx#1^^M\expandafter\expandafter\expandafter \else%
\ifcat\noexpand#1a{\bfseries#1}\else%
\LongJump{#1}\fi\fi\fi}%
%
\gdef\BoldAbbrv@nosp{\ifx\BoldAbbrv@token\bgroup%
\expandafter\textbf\else%
\ifx\BoldAbbrv@token\egroup\else%
\expandafter\expandafter\expandafter\BoldAbbrv@auxb\fi\fi}%
%
\gdef\BoldAbbrv@initb{\futurelet\BoldAbbrv@token\BoldAbbrv@nosp}%
}
%
\def\BoldAbbrv@initpar{\par\BoldAbbrv@initb}
\def\BoldAbbrv#1#{\bgroup\obeyspaces\catcode\^^M=\active
\BoldAbbrv@inita\afterassignment\BoldAbbrv@initb\let\next= }
\makeatother

\begin{document}\thispagestyle{empty}
GNU is a recursive acronym for \BoldAbbrv{Gnu's     Not Unix}'', chosen
because GNU's design is Unix-like, but differs from Unix by being free
software and containing no Unix code.

Space Torture test (\emph{n.b.} spaces or endlines just after
the opening brace
or just
before the closing one count as one):
GNU is a recursive acronym for \BoldAbbrv{Gnu's    Not      Unix'',
}%
\BoldAbbrv    {
chosen
because GNU's design      is Unix-like, but   differs from Unix
by being free
software     and containing     }no Unix code.

This new version does not bolden quotes, unfortunately it
doesn't either bolden the letter after the quote(s): \par
\BoldAbbrv{GNU is a recursive acronym for Gnu's Not Unix''}.

\BoldAbbrv{Things put {inside braces} in the argument will be
typeset {entirely bold.}

Accents need to be put inside braces. Omitting
braces does \emph{not} create an error. UTF8 letters in
PDFLaTeX need to be put inside braces for the macro to
operate, but this is not necessary with XeTeX. And in both
cases no compilation errors when braces are omitted.
Examples: Gnu's Not Unix --- Gnu's Not {\"U}nix (ok) ---
Gnu's Not \"Unix (no) --- Gnu's Not {Ü}nix (ok) --- Gnu's
Not Ünix (ok with Unicode engines). As ok followed a
parenthesis it was not boldened. The parenthesis not being a
letter wasn't boldened either.}

The new version has no effect on a macro even if it expands to
a string:
\newcommand*{\GNU}{Gnu's Not Unix}\BoldAbbrv{\GNU}.
This is sad but the new version is compatible with more
general input. It only boldens letters, or groups enclosed
within braces. \BoldAbbrv{{\GNU}}.

\BoldAbbrv{The macro {can} be applied to more than
one paragraph simultaneously. Yes, really.

The macro {can} be applied to more than
one paragraph simultaneously. Yes, really.

\BoldAbbrv{And it \BoldAbbrv{can be} nested. Although one does
not see the interest.}
}

An empty argument\BoldAbbrv{} is no error.

A space after the opening brace or before the closing brace
counts: \BoldAbbrv{ Gnu's Not {\"U}nix }X. Two or more are like
just one:\BoldAbbrv{   Gnu's Not {\"U}nix    }X.

This does not quite work: \BoldAbbrv{\emph{An emphasized
argument.}} And this neither: \BoldAbbrv{\itshape{}An
italicized argument,\/} where we got rid of the initial
superfluous space which is in this example:
\BoldAbbrv{\itshape An italicized argument with a spurious
initial space.} So one must do {\itshape\BoldAbbrv{This is
not argument to a macro, and it works.}\/}

\textsl{\BoldAbbrv {The macro does not work in the argument to
another macro (except itself... as it is not really a
macro with argument despite appearances!)}}

\end{document}


-

A TeX solution using delimited arguments.

Macros:

• \BoldAbbrv{<text>} that does not expand its argument, and
• \eBoldAbbrv{<text>} that does expand its argument before processing.

The \BoldAbbrv macro suffice if you only use plain text in its argument.

Check the output of \BoldAbbrv{\GNU} and \eBoldAbbrv{\GNU} for the difference, but also the different ways how accents are handled (they need at least to be enclosed in braces).

Code

\documentclass[a4paper]{article}
\makeatletter
\newcommand*{\BoldAbbrv}[1]{%
\qrr@BoldAbbrv#1 \relax
}
\newcommand*{\eBoldAbbrv}[1]{%
\edef\qrr@BoldAbbrv@Arg{#1 }%
\expandafter\qrr@BoldAbbrv\qrr@BoldAbbrv@Arg\relax
}
\def\qrr@BoldAbbrv#1 #2\relax{%
\textbf#1\relax
\ifx\relax#2\else
\space\qrr@BoldAbbrv#2\relax
\fi
}
\makeatother
\newcommand*{\GNU}{Gnu's Not Unix}
\begin{document}
\BoldAbbrv{Gnu's Not Unix} --- \BoldAbbrv{Gnu's Not {\"U}nix}

\BoldAbbrv{\GNU} --- \eBoldAbbrv{\GNU}

\BoldAbbrv{Gnu's Not {\d{U}}nix} --- \eBoldAbbrv{Gnu's Not {\noexpand\d{U}}nix}
\end{document}


Output

-
Nice! certainly more natural that my attempt. But with \BoldAbbrv{ Gnu's Not Unix} your macro throws an error. –  jfbu Jan 18 '13 at 17:29
@jfbu The Incomplete \ifmmode one? That was fixed in the last minor update where I added a \relax in \qrr@BoldAbbrv. –  Qrrbrbirlbel Jan 18 '13 at 17:35
yes I got that error when trying. Nice job, I think your idea is better than my attempt at using active spaces. –  jfbu Jan 18 '13 at 17:56

This seems to be a good application of the RegEx features of LaTeX3:

\documentclass{article}
\usepackage{xparse,l3regex}
\ExplSyntaxOn
\NewDocumentCommand{\BoldFirst}{ m }
{
\pouya_boldfirst:n { #1 }
}
\cs_new_protected:Npn \pouya_boldfirst:n #1
{
\tl_set:Nn \l_pouya_input_tl { #1 }
\regex_replace_once:nnN { \A(.*?[A-Za-z]) } { \c{textbf}\cB\{ \1 \cE\} } \l_pouya_input_tl
\regex_replace_all:nnN { (\s) (.*?[A-Za-z]) } { \1  \c{textbf}\cB\{ \2 \cE\} } \l_pouya_input_tl
\tl_use:N \l_pouya_input_tl
}
\ExplSyntaxOff

\begin{document}
\BoldFirst{Gnu's Not Unix}

\BoldFirst{Gnu's Not \'Unix}

\BoldFirst{Gnu's Not \v{U}nix}
\end{document}


Also accented characters work, provided the LICR is used (no UTF-8 input).

-
That's very nice! A pity I don't have votes left for today. I'll come back tomorrow. –  clemens Jan 18 '13 at 14:16
@cgnieder Thanks to Bruno Le Floch, of course. –  egreg Jan 18 '13 at 14:19
\BoldFirst{\itshape GNU's Not Unix} gives a surprising result. –  jfbu Jan 18 '13 at 18:52
Also {\itshape\BoldFirst{GNU's Not Unix}} has some strange spaces after the first letters when using LuaLaTeX and fontspec. No such spaces arise with PDFLaTeX. Perhaps this is just a font issue. Actually the problem does not seem to arise with XeTeX, only LuaLaTeX. –  jfbu Jan 18 '13 at 18:56
@jfbu The argument shouldn't have formatting instructions, only the words. The strange space with LuaLaTeX appears also with \textbf{G}nu's \textbf{N}ot \textbf{U}nix, so it isn't due to the macro. –  egreg Jan 18 '13 at 22:13