Updated answer
For expl3
based partly on ideas raised here in my original approach and in Bruno's method we have now developed a set of expandable case-changing functions that implement case mappings as described by the Unicode Consortium:
\str_foldcase:n
\text_uppercase:n(n)
\text_lowercase:n(n)
\text_titlecase:n(n)
One important point to note is that they work with 'engine native' input, which means just ASCII for pdfTeX (the upper half of the 8-bit range is tricky). For XeTeX/LuaTeX the full Unicode range is covered.
The direct answer to the question is to use \text_uppercase:n
: it does expansion of input in a selective way, can deal with entries such as \aa
and with work inside an expansion context including 'f
-type' methods (expansion using \romannumeral
). In the current implementation there are features very similar to the textcase
package, for example selective skipping of input, skipping over math mode material, etc.
There are four types of function to cover different use cases:
'Removal' of case for use in non-text contexts. This looks rather like 'lower casing' and is a one-one mapping. As the data is string-like the function is called \str_foldcase:n
and does not skip or expand any input.
Uppercasing
Lowercasing
Making 'titlecase' (Unicode description): it covers only the first 'letter' of some text not the first letter of every word of some text (the latter is usually called title case in English)
The code includes the ability to handle context dependence (e.g. final-sigma in Greek) and also language-dependent versions such as \text_lowercase:nn { tr } { I }
to apply Turkish rules (here producing a dotless-i).
At the implementation level, the approach taken is to map over the input using a two-part strategy, first working out if the next token is a space, something braced or something else (what we call N
-type). Each type can be grabbed properly and then case changed as appropriate using a lookup table.
Note that using Lua in LuaTeX offers only a partial solution for two reasons. First, Lua does not work with TeX tokens meaning that for example skipping math mode input requires more effort. Secondly, the current Lua Unicode library available in LuaTeX is poorly documentation and does not cover context-dependent issues, non one-one mappings and so on. For example, a simple test case is
\documentclass{article}
\usepackage{fontspec}
\usepackage{expl3}
\begin{document}
\ExplSyntaxOn
\text_uppercase:n { Fußball }
\ExplSyntaxOff
\directlua{tex.print(unicode.utf8.upper("Fußball"))}
\end{document}
where no case changing occurs in the Lua-based case. (It is also not clear what Unicode version the Lua library follows.)
Original answer
For expl3
, I wrote the following as the most robust approach I could find
\documentclass{article}
\usepackage{expl3}
\ExplSyntaxOn
\cs_new:Npn \tl_to_upper_case:n #1
{ \exp_args:Nf \__tl_to_upper_case:n {#1} }
\cs_new:Npn \__tl_to_upper_case:n #1
{ \__tl_to_upper_case:w #1 ~ \q_no_value \q_stop }
\cs_new:Npn \__tl_to_upper_case:w #1 ~ #2 \q_stop
{
\quark_if_no_value:nTF {#2}
{
\tl_map_function:nN {#1} \__tl_to_upper_case_aux:N
\tl_trim_spaces:n { }
}
{ \__tl_to_upper_case:w #1 { ~ } #2 \q_stop }
}
\cs_new:Npn \__tl_to_upper_case_aux:N #1
{
\prg_case_str:nnn {#1}
{
{ a } { \__tl_to_case_aux:nw { A } }
{ b } { \__tl_to_case_aux:nw { B } }
{ c } { \__tl_to_case_aux:nw { C } }
{ d } { \__tl_to_case_aux:nw { D } }
{ e } { \__tl_to_case_aux:nw { E } }
{ f } { \__tl_to_case_aux:nw { F } }
{ g } { \__tl_to_case_aux:nw { G } }
{ h } { \__tl_to_case_aux:nw { H } }
{ i } { \__tl_to_case_aux:nw { I } }
{ j } { \__tl_to_case_aux:nw { J } }
{ k } { \__tl_to_case_aux:nw { K } }
{ l } { \__tl_to_case_aux:nw { L } }
{ m } { \__tl_to_case_aux:nw { M } }
{ n } { \__tl_to_case_aux:nw { N } }
{ o } { \__tl_to_case_aux:nw { O } }
{ p } { \__tl_to_case_aux:nw { P } }
{ q } { \__tl_to_case_aux:nw { Q } }
{ r } { \__tl_to_case_aux:nw { R } }
{ s } { \__tl_to_case_aux:nw { S } }
{ t } { \__tl_to_case_aux:nw { T } }
{ u } { \__tl_to_case_aux:nw { U } }
{ v } { \__tl_to_case_aux:nw { V } }
{ w } { \__tl_to_case_aux:nw { W } }
{ x } { \__tl_to_case_aux:nw { X } }
{ y } { \__tl_to_case_aux:nw { Y } }
{ z } { \__tl_to_case_aux:nw { Z } }
}
{ \__tl_to_case_aux:nw {#1 } }
}
\cs_new:Npn \__tl_to_case_aux:nw #1#2 \tl_trim_spaces:n #3
{
#2
\tl_trim_spaces:n { #3 #1 }
}
\cs_set_eq:NN \MakeExpandableUppercase \tl_to_upper_case:n
\ExplSyntaxOff
\begin{document}
\MakeExpandableUppercase{Hello World}
\edef\test{\MakeExpandableUppercase{Hello World}}
\show\test
\MakeExpandableUppercase{Hello {World}}
\edef\test{\MakeExpandableUppercase{Hello {World}}}
\show\test
\edef\test{Hello\space\space World}
\MakeExpandableUppercase{\test}
\edef\test{\MakeExpandableUppercase{\test}}
\end{document}
The reason for the space stripping at the end of the input is that you can't avoid it at the start of the string, so I felt the best you could do was say 'spaces at the ends are stripped'. Spaces should be retained within the input. You can implement a lower case function in the same way, and if you do nesting
\MakeExpandableUppercase{\MakeExpandableLowercase{Hello} World}
should work correctly. As illustrated by the last example, material is expanded before doing the case change. That applies even to protected macros, as the underlying expansion uses \romannumeral
. So the argument needs to be made up of purely expandable material.
(As a note, this can of course be implemented without expl3
.)
For completeness, a LuaTeX solution might read
\documentclass{article}
\usepackage{fontspec}
\newcommand*\MakeExpandableUppercase[1]{%
\scantokens\expandafter{%
\directlua{
tex.write(string.upper("\luatexluaescapestring{\unexpanded{#1}}"))
}%
\noexpand
}%
}%
\begin{document}
\MakeExpandableUppercase{hello world \oe}
\end{document}
(I'm no Lua expert: there may be a more efficient approach.)
\uppercase
isn't expandable, e.g.\edef\temp{\uppercase{abc}}
doesn't work, it is normally used like this\uppercase{\def\temp{abc}}
, which works fine. But you probably know this already.\uppercase
doesn't work with special letters like German Umlauts and such. It simply changes all character tokens to uppercase and ignores all macros and active characters.\uppercase
and\lowercase
are executed by the execution processor. They aren’t macro expansions like\number
. I don’t think you will find a pure-TeX solution.\uppercase
here.