# Macro Improvements

I am looking to simplify and improve on the code below:

\documentclass{article}
\usepackage{filecontents}
\begin{filecontents}{test.txt}
a an and the this by on of
\end{filecontents}

\begin{document}
\makeatletter

\in@false
\def\upfirst#1#2\upfirst{%
\in@{#1#2}{a and of that this}%
\ifin@{\LARGE#1#2}\else {\Huge\MakeUppercase#1}{\LARGE#2 }\fi
}

\def\smallcaps#1\upfirst{\textsc{#1} }

\def\boldfirst#1#2\upfirst{\textbf{\uppercase{#1}}#2 }

\def\everytoken#1#2{%
\def\everytoken@##1{%
\@tempcntb=0
\@tfor \i :=##1  \do{\expandafter#2\i\upfirst
}}
\everytoken@{#1}%
}

\everytoken {{the} {battle} {and} {the} {resistance} {of} {france}}{\upfirst}

\everytoken {{Life} {death} {and} {the} {Universe}}{\smallcaps}

\everytoken {{The} {battle} {and} {the} {Resistance} }{\boldfirst}

\makeatother
\end{document}


What the code does it styles titles, by upper casing the first letter (if the word is not in a bag of reserved words) or styles in smallcaps or bolds and uppercases the first letter.

I am looking for improvements in the interface (currently the title words must be enclosed in braces) and for ways to input a file with the reserved words rather than just typing them in. (See the code around in@.

-
I can't write a full answer, as the question is rather vague. Maybe you could split it into several more specific questions? Anyway, your use of \in@ seems questionable, as "an" for instance will also be found in your list. \in@ just looks for a substring, it does not care for words. –  Stephan Lehmke Jan 21 at 18:48
Can we assume that spaces can be used as delimiters for 'words'? (BTW, in the demo I assume \in@false should be inside \upfirst.) –  Joseph Wright Jan 21 at 18:49
@StephanLehmke I am trying to find a way to say everytoken{The battle} rather than everytoken{{The} {Battle}..} and I am not happy in general with what I have done probably an l3 solution? Also to input{bag of reserved words} couldn't get the expansion right. You also right about the limitations of the \in@ for example The must be capitalized if it is the first word but not in between. –  Yiannis Lazarides Jan 21 at 18:52
@JosephWright \in@false is redundant because \in@ sets either true or false. –  Stephan Lehmke Jan 21 at 18:56
There seems to be a space missing in \ifin@{\LARGE#1#2}. –  cgnieder Jan 21 at 19:17
show 8 more comments

## 2 Answers

Of course there's also a LaTeX3 version waiting. :)

\begin{filecontents*}{\jobname.dat}
that this
the
\end{filecontents*}
\documentclass{article}
\usepackage{xparse,l3regex}

\ExplSyntaxOn
\NewDocumentCommand{\everytoken}{O{}mm}
{
\group_begin:
#1 % #1 are global formatting instructions
\yiannis_everytoken:Nn #2 { #3 }
\group_end:
}

\seq_new:N \l__yiannis_words_seq
\seq_new:N \l__yiannis_final_seq
\seq_new:N \g_yiannis_reserved_words_seq
\tl_new:N \l__yiannis_first_word_tl
\cs_generate_variant:Nn \seq_put_left:Nn { Nf }

\cs_new_protected:Npn \yiannis_everytoken:Nn #1 #2
{
\seq_set_split:Nnn \l__yiannis_words_seq { ~ } { #2 }
\seq_pop_left:NN \l__yiannis_words_seq \l__yiannis_first_word_tl
\seq_clear:N \l__yiannis_final_seq
\seq_map_inline:Nn \l__yiannis_words_seq
{
\yiannis_if_reserved:nTF { ##1 }
{ \seq_put_right:Nn \l__yiannis_final_seq { ##1 } }
{ \seq_put_right:Nn \l__yiannis_final_seq { #1 { ##1 } } }
}
\seq_put_left:Nf \l__yiannis_final_seq { \exp_args:NNV \exp_not:N #1 \l__yiannis_first_word_tl }
\seq_use:Nnnn \l__yiannis_final_seq { ~ } { ~ } { ~ }
}

\prg_new_conditional:Npnn \yiannis_if_reserved:n #1 { TF }
{
\seq_if_in:NnTF \g_yiannis_reserved_words_seq { #1 }
{ \prg_return_true: }
{ \prg_return_false: }
}

%%% Simple version
\NewDocumentCommand{\Hugeupfirst}{m}
{
\tl_to_uppercase:n { { \Huge \tl_head:n { #1 } } }
\tl_tail:n { #1 }
}

%%% Complex but more robust version (uncomment the following four lines)
%\RenewDocumentCommand{\Hugeupfirst}{m}
% {
%  \yiannis_Hugeupfirst:n { #1 }
% }
\cs_new_protected:Npn \yiannis_hugeupfirst:n #1
{
\tl_set:Nn \l__yiannis_temp_tl { #1 }
\regex_replace_once:nnN
{ \A (.*? [A-Za-z]) }
{ \c{tl_to_uppercase:n} \cB\{ \cB\{ \c{Huge} \1 \cE\} \cE\} }
\l__yiannis_temp_tl
\tl_use:N \l__yiannis_temp_tl
}
%%% end of more complex version

%%% Simple version
\NewDocumentCommand{\upfirst}{m}
{
\tl_to_uppercase:n { \tl_head:n { #1 } }
\tl_tail:n { #1 }
}

%%% Complex but more robust version (uncomment the following four lines)
%\RenewDocumentCommand{\upfirst}{m}
% {
%  \yiannis_upfirst:n { #1 }
% }
\cs_new_protected:Npn \yiannis_upfirst:n #1
{
\tl_set:Nn \l__yiannis_temp_tl { #1 }
\regex_replace_once:nnN
{ \A (.*? [A-Za-z]) }
{ \c{tl_to_uppercase:n} \cB\{ \1 \cE\} }
\l__yiannis_temp_tl
\tl_use:N \l__yiannis_temp_tl
}
%%% end of more complex version

\NewDocumentCommand{\setreservedwords}{m}
{
\clist_map_inline:nn { #1 }
{ \seq_gput_right:Nn \g_yiannis_reserved_words_seq { ##1 } }
}

\ior_new:N \l_yiannis_input_ior
\NewDocumentCommand{\readreservedwords}{m}
{
\yiannis_read_reserved_words:n { #1 }
}
\cs_new_protected:Npn \yiannis_read_reserved_words:n #1
{
\ior_open:Nn \l_yiannis_input_ior { #1 }
\tl_clear:N \l_tempa_tl
\ior_map_inline:Nn \l_yiannis_input_ior
{
\tl_put_right:Nn \l_tempa_tl { ##1 ~ }
}
\seq_set_split:NnV \l_tempa_seq { ~ } \l_tempa_tl
\seq_gconcat:NNN \g_yiannis_reserved_words_seq \l_tempa_seq \g_yiannis_reserved_words_seq
\seq_gremove_all:Nn \g_yiannis_reserved_words_seq { }
}

\ExplSyntaxOff

\setreservedwords{a, and, of}
\readreservedwords{\jobname.dat}

\begin{document}

\everytoken{\Hugeupfirst}{The battle and the resistance of France}

\everytoken[\scshape]{\Hugeupfirst}{the battle and the resistance of france}

\everytoken[\scshape]{\upfirst}{the battle and the resistance of france}

\everytoken{\upfirst}{the battle and the resistance of france}

\end{document}


For adding to the list of reserved words you have two ways: either a comma separated list to feed as argument to \setreservedwords or a file where the separator is either a space or an end of line given as argument to \readreservedwords. You can use them in any order and each command adds to the list that is initially empty.

The syntax of \everytoken is

\everytoken[<global formatting>]{<first letter>}{<words>}


where

• <global formatting> is any set of formatting instructions to be applied to all letters (for instance, \bfseries or \scshape)
• <first letter> is the macro to be applied to the first letter of non reserved word and to the first word
• <words> is the list of words

I've given examples for \Hugeupfirst and \upfirst both in "easy" version (no accented characters) and "complex" one. With the "complex" version you can manage \'equipe de secours which you can't with the easy version.

-
In the regex you can use a single group \A (.*? [A-Za-z]). –  Bruno Le Floch Jan 21 at 22:04
@BrunoLeFloch Yes, of course. Thanks. Feel free to add other improvements. –  egreg Jan 21 at 22:07
@egreg Piece de r\'esistance works, but Pièce de résistance swallows the accented characters. I guess the regex needs to be modified. Very good solution and a motivation for me to seriously look at l3 this year:) –  Yiannis Lazarides Jan 22 at 3:07
I don't see the issue. However, the caveat is about the first character in the word: using there an accented character can give unpredictable results, unless the LICR form is used (\'e instead of é, for instance) and the RegEx version. –  egreg Jan 22 at 7:36
@YiannisLazarides To support É as a first character, maybe replace [A-Za-z] by \cL. in egreg's answer. This will match a catcode-letter (\cL) token with any character code (.). Perhaps \c[LO]. (matching any letter or other token) is better. –  Bruno Le Floch Jan 22 at 13:36
show 4 more comments

This reads the words from the files,, fixes the missing space in the in@ test and avoids needing to brace each word.

The output isn't quite as you show as the version of the word list in the file includes the

\documentclass{article}
\usepackage{filecontents}
\begin{filecontents*}{test.txt}
a an and the this by on of
\end{filecontents*}

\begin{document}
\makeatletter

\newread\testtxt
\immediate\openin\testtxt test.txt

\immediate\read\testtxt to \foo

\def\upfirst#1#2 {%
\edef\tmp{\noexpand\in@{#1#2}{\foo}}\tmp%
\ifin@{\LARGE#1#2 }\else {\Huge\MakeUppercase#1}{\LARGE#2 }\fi
}

\def\smallcaps#1 {\textsc{#1} }

\def\boldfirst#1#2 {\textbf{\uppercase{#1}}#2 }

\def\everytoken#1#2{\xeverytoken#2#1 \relax}

\def\xeverytoken#1#2 #3{%
#1#2
\ifx\relax#3%
\else
\expandafter\xeverytoken\expandafter#1%
\fi
#3}

\everytoken {the battle and the resistance of france}{\upfirst}

\everytoken {Life death and the Universe}{\smallcaps}

\everytoken {The battle and the Resistance}{\boldfirst}

\makeatother
\end{document}


To force the first word always to be processed change some of the macros a bit

\def\upfirst#1#2 {%
\edef\tmp{\noexpand\in@{#1#2}{\foox}}\tmp%
\ifin@{\LARGE#1#2 }\else {\Huge\MakeUppercase#1}{\LARGE#2 }\fi
}

\def\everytoken#1#2{%
\let\foox\@empty
\xeverytoken#2#1 \relax}

\def\xeverytoken#1#2 #3{%
#1#2
\let\foox\foo
\ifx\relax#3%
\else
\expandafter\xeverytoken\expandafter#1%
\fi
#3}
`
-
Thanks. The initial word (if it is in the bag of words is a problem) was thinking though of a simple solution, where the title is reprocessed and the first letter is capitalized for any word. –  Yiannis Lazarides Jan 21 at 20:11
updated answer with one way of handling the first word –  David Carlisle Jan 21 at 20:30
add comment