19

The title pretty much says it all, I need a command to get the first word in a string.

Based on this answer to another question of mine, I tried this:

\documentclass{article}

\makeatletter
\newcommand\FirstWord[1]{\@firstword#1 \@nil}%
\newcommand\@firstword{}%
\def\@firstword#1 #2\@nil{#1\unskip}%
\makeatother

\begin{document}
    \FirstWord{John, Paul, George and Ringo}
\end{document}

It almost works, except for the fact it includes the comma. I get:

John,

While I want just:

John

So how can I do that?

PS: Ideally, if more than one word is inside braces, they should count as one. So \FirstWord{{John, Paul}, George and Ringo} should print "John, Paul".

  • Please clarify what constitutes a (single) "word" for your purposes. In particular, does "Jean-Pierre" constitute a single word or is it composed of two words (connected by a hyphen)? Separately, does the search string always commence with a word, or could it start off with punctuation and other non-alphabetic characters? – Mico Jun 29 '16 at 7:14
  • 1
    @Mico I'm working on a biblatex style and, when there's no author, entries should be cited using the first word in the title. If a book's title starts with a symbol, I guess ideally the command should ignore it. So what I need is something like... the first meaningful unit in the title that can stand on its own. But egreg's answer covers most cases, and for the exceptions the user can resort to fields like shorthand and shortauthor. Besides, I can't force possible users to use lualatex. So I'm going with his code, even though the lua option is really cool. :) – dbmrq Jun 29 '16 at 10:20
  • I've gone ahead and posted a second LuaLaTeX-based solution. – Mico Jun 29 '16 at 16:27
19

You're almost there, just remove the trailing comma

\documentclass{article}

\makeatletter
\newcommand\FirstWord[1]{\@firstword#1 \@nil}%
\newcommand\@firstword{}%
\newcommand\@removecomma{}%
\def\@firstword#1 #2\@nil{\@removecomma#1,\@nil}%
\def\@removecomma#1,#2\@nil{#1}
\makeatother

\begin{document}

X\FirstWord{John, Paul, George and Ringo}X

X\FirstWord{John}X

X\FirstWord{John and Paul}X

X\FirstWord{{John, Paul}, George and Ringo}X

\end{document}

enter image description here

You can add further tests for removing other delimiters

\documentclass{article}

\makeatletter
\newcommand\FirstWord[1]{\@firstword#1 \@nil}%
\def\@firstword#1 #2\@nil{\@removecomma#1,\@nil}%
\def\@removecomma#1,#2\@nil{\@removeperiod#1.\@nil}
\def\@removeperiod#1.#2\@nil{\@removesemicolon#1;\@nil}
\def\@removesemicolon#1;#2\@nil{#1}
\makeatother

\begin{document}

X\FirstWord{John; Paul; George; Ringo}X

X\FirstWord{John. Paul. George. Ringo}X

X\FirstWord{John}X

X\FirstWord{John and Paul}X

X\FirstWord{{John. Paul}. George. Ringo}X

\end{document}

If you don't need expandability, you can use l3regex:

\documentclass{article}
\usepackage{xparse,l3regex}

\ExplSyntaxOn
\NewDocumentCommand{\FirstWord}{m}
 {
  % split the argument at spaces
  \seq_set_split:Nnn \l_tmpa_seq { ~ } { #1 }
  % get the first item
  \tl_set:Nx \l_tmpa_tl { \seq_item:Nn \l_tmpa_seq { 1 } }
  % remove a trailing period, semicolon or comma (\Z matches the end)
  \regex_replace_once:nnN { [.;,]\Z } { } \l_tmpa_tl
  % output the result
  \tl_use:N \l_tmpa_tl
 }
\ExplSyntaxOff

\begin{document}

X\FirstWord{John, Paul, George and Ringo}X

X\FirstWord{John; Paul; George; Ringo}X

X\FirstWord{John. Paul. George. Ringo}X

X\FirstWord{John}X

X\FirstWord{John and Paul}X

X\FirstWord{{John, Paul}, George and Ringo}X

X\FirstWord{{John. Paul}. George. Ringo}X

\end{document}
  • Awesome! Just one thing… what if there's a period or a colon or a semicolon? I tried defining \@removeperiod, \@removecolon and so on, and adding one after the other in \def\@firstword, but I get "paragraph ended before \@removeperiod was complete". – dbmrq Jun 26 '16 at 20:41
  • @danielbmarques You can add further tests for removing the undesired trailing character. – egreg Jun 26 '16 at 20:43
10

Thanks for comment by Mico, this is the suggested pattern to use. PS. I am not a pattern matching expert and do not play one on TV, but the nice thing about lualatex is one can employ sophisticated pattern matching procedures if they are needed.

\documentclass{article}
\usepackage{luacode} % for '\luaexec' and '\luastring' macros

\newcommand{\FirstWord}[1]{\luaexec{tex.print(string.match(\luastring{#1}, '\%w+\%-?\%w*'))}}

\begin{document}
\def\lst{John, Paul, George and Ringo}
\textbf{\FirstWord{\lst}} is the first word in \{\lst\}

\def\lst{-John, Paul, George and Ringo}
\textbf{\FirstWord{\lst}} is the first word in \{\lst\}

\def\lst{Marie-Claire, Paul, George and Ringo}
\textbf{\FirstWord{\lst}} is the first word in \{\lst\}

\end{document}

gives

Mathematica graphics


Earlier version

lualatex solution

Updated with another variation of the call just for illustration.

\documentclass{article}
\usepackage{luacode}

\newcommand{\FirstWord}[1]{\luaexec{tex.print(string.match('#1', '([^,]+)'))}}

\begin{document}
\def\lst{John, Paul, George and Ringo}

\textbf{\FirstWord{\lst}} is the first word in \{\lst\}
\end{document}

The above does not handle special cases such as {{John, Paul}, George and Ringo}. It will still return John for the above.


Original answer

\documentclass{article}
\usepackage{luacode}
\begin{luacode*}
function FirstWord(arg)
tex.print(string.match(arg, '([^,]+)'))
end
\end{luacode*}
\newcommand{\FirstWord}[1]{\directlua{FirstWord("#1")}}

\begin{document}
\def\lst{John, Paul, George and Ringo}

   \textbf{\FirstWord{\lst}} is the first word in \{\lst\}

\end{document}

gives

Mathematica graphics

  • I've taken the liberty of making your \FirstWord macro more robust by writing \luastring{#1} instead of '#1'. (The latter bombs if the search string happens to contain a ' character.) – Mico Jun 29 '16 at 8:50
3

Admittedly a bit late to the game, but here's a second LuaLaTeX-based solution, which generalizes the earlier answer by @Nasser. This answer's pattern search algorithm satisfies the following criteria:

  • If the string to be searched starts with a substring that's delimited by matching curly braces, the entire substring is returned.

  • Otherwise, the first word is returned. Here, a "word" is taken to be either a collection of alphabetic characters -- e.g., "John" or "Nicolò" -- or a hyphenated pair of words -- e.g., "Kröller-Müller" and "Rhys-Davies". (Put differently, a hyphenated word is taken to be two single words that are joined by exactly one instance of -; the only restriction on the first word in the hyphenated pair is that it contain at least two characters.) Any non-alphabetic characters that precede the "word" in the full string are automatically discarded. The Lua code is unicode-aware, i.e., the words may contain non-ASCII alphabetic characters (such as ö, ü, and ò).

enter image description here

% !TEX TS-program = lualatex
\documentclass{article}
\usepackage{fontspec}
\usepackage{luacode} % for 'luacode' environment and '\luastring' macro
%% Lua-side code: A Lua function that does most of the work
\begin{luacode}
function fw ( s )
   if string.find ( s , '^%b{}' ) then
      first = string.sub ( string.match ( s , '%b{}' ), 2, -2 )
   else
      first = unicode.utf8.match ( s , '%w+%-?%w+' )
   end
   tex.sprint ( first ) 
end
\end{luacode}
%% TeX-side code: A macro that invokes the Lua function
\newcommand{\FW}[1]{\directlua{fw(\luastring{#1})}}

\begin{document}

\def\lst{{John and Paul} but not George or Ringo}
\FW{\lst}

\def\lst{'{Bay- Day} Hay}
\FW{\lst}

\def\lst{Kröller-Müller and Schwassmann-Wassmann}
\FW{\lst}

\end{document}

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.