% \iffalse meta-comment % %% File: l3text-case.dtx % % Copyright (C) 2020-2022 The LaTeX Project % % It may be distributed and/or modified under the conditions of the % LaTeX Project Public License (LPPL), either version 1.3c of this % license or (at your option) any later version. The latest version % of this license is in the file % % https://www.latex-project.org/lppl.txt % % This file is part of the "l3kernel bundle" (The Work in LPPL) % and all files in that bundle must be distributed together. % % ----------------------------------------------------------------------- % % The development version of the bundle can be found at % % https://github.com/latex3/latex3 % % for those people who are interested. % %<*driver> \documentclass[full,kernel]{l3doc} \begin{document} \DocInput{\jobname.dtx} \end{document} % % \fi % % \title{^^A % The \textsf{l3text-case} package: text processing (case changing)^^A % } % % \author{^^A % The \LaTeX{} Project\thanks % {^^A % E-mail: % \href{mailto:latex-team@latex-project.org} % {latex-team@latex-project.org}^^A % }^^A % } % % \date{Released 2022-04-10} % % \maketitle % % \begin{documentation} % % \end{documentation} % % \begin{implementation} % % \section{\pkg{l3text-case} implementation} % % \begin{macrocode} %<*package> % \end{macrocode} % % \begin{macrocode} %<@@=text> % \end{macrocode} % % \subsection{Case changing} % % \begin{variable}{\l_text_titlecase_check_letter_bool} % Needed to determine the route used in titlecasing. % \begin{macrocode} \bool_new:N \l_text_titlecase_check_letter_bool \bool_set_true:N \l_text_titlecase_check_letter_bool % \end{macrocode} % \end{variable} % % \begin{macro}[EXP] % { % \text_lowercase:n, % \text_uppercase:n, % \text_titlecase:n, % \text_titlecase_first:n % } % \begin{macro}[EXP] % { % \text_lowercase:nn, % \text_uppercase:nn, % \text_titlecase:nn, % \text_titlecase_first:nn % } % The user level functions here are all wrappers around the internal % functions for case changing. % \begin{macrocode} \cs_new:Npn \text_lowercase:n #1 { \@@_change_case:nnn { lower } { } {#1} } \cs_new:Npn \text_uppercase:n #1 { \@@_change_case:nnn { upper } { } {#1} } \cs_new:Npn \text_titlecase:n #1 { \@@_change_case:nnn { title } { } {#1} } \cs_new:Npn \text_titlecase_first:n #1 { \@@_change_case:nnn { titleonly } { } {#1} } \cs_new:Npn \text_lowercase:nn #1#2 { \@@_change_case:nnn { lower } {#1} {#2} } \cs_new:Npn \text_uppercase:nn #1#2 { \@@_change_case:nnn { upper } {#1} {#2} } \cs_new:Npn \text_titlecase:nn #1#2 { \@@_change_case:nnn { title } {#1} {#2} } \cs_new:Npn \text_titlecase_first:nn #1#2 { \@@_change_case:nnn { titleonly } {#1} {#2} } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[EXP]{\@@_change_case:nnn, \@@_change_case_aux:nnn} % \begin{macro}[EXP] % { % \@@_change_case_store:n, \@@_change_case_store:o, % \@@_change_case_store:V, \@@_change_case_store:v, % \@@_change_case_store:e % } % \begin{macro}[EXP]{\@@_change_case_store:nw} % \begin{macro}[EXP]{\@@_change_case_result:n} % \begin{macro}[EXP]{\@@_change_case_end:w} % \begin{macro}[EXP]{\@@_change_case_loop:nnw} % \begin{macro}[EXP]{\@@_change_case_break:w} % \begin{macro}[EXP] % { % \@@_change_case_group_lower:nnn , % \@@_change_case_group_upper:nnn , % \@@_change_case_group_title:nnn , % \@@_change_case_group_titleonly:nnn % } % \begin{macro}[EXP]{\@@_change_case_space:nnw} % \begin{macro}[EXP]{\@@_change_case_N_type:nnN, \@@_change_case_N_type_aux:nnN} % \begin{macro}[EXP]{\@@_change_case_N_type:nnnN} % \begin{macro}[EXP]{\@@_change_case_math_search:nnNNN} % \begin{macro}[EXP]{\@@_change_case_math_loop:nnNw} % \begin{macro}[EXP]{\@@_change_case_math_N_type:nnNN} % \begin{macro}[EXP]{\@@_change_case_math_group:nnNn} % \begin{macro}[EXP]{\@@_change_case_math_space:nnNw} % \begin{macro}[EXP]{\@@_change_case_cs_check:nnN} % \begin{macro}[EXP]{\@@_change_case_exclude:nnN} % \begin{macro}[EXP]{\@@_change_case_exclude:nnnN} % \begin{macro}[EXP]{\@@_change_case_exclude:nnNN} % \begin{macro}[EXP]{\@@_change_case_exclude:nnNw} % \begin{macro}[EXP]{\@@_change_case_exclude:nnNnn} % \begin{macro}[EXP] % { % \@@_change_case_letterlike_lower:nnN , % \@@_change_case_letterlike_upper:nnN , % \@@_change_case_letterlike_title:nnN , % \@@_change_case_letterlike_titleonly:nnN % } % \begin{macro}[EXP]{\@@_change_case_letterlike:nnnnN} % \begin{macro}[EXP] % {\@@_change_case_char_lower:nnN, \@@_change_case_char_upper:nnN} % \begin{macro}[EXP]{\@@_change_case_lower_sigma:nnnN} % \begin{macro}[EXP]{\@@_change_case_lower_sigma:nnNw} % \begin{macro}[EXP]{\@@_change_case_lower_sigma:NnnN} % \begin{macro}[EXP]{\@@_change_case_lower_sigma:nnnNN} % \begin{macro}[EXP]{\@@_change_case_lower_sigma:nnw} % \begin{macro}[EXP]{\@@_change_case_lower_sigma:nnN} % \begin{macro}[EXP] % {\@@_change_case_char_title:nnN, \@@_change_case_char_titleonly:nnN} % \begin{macro}[EXP] % {\@@_change_case_char_title:nN, \@@_change_case_char_titleonly:nN} % \begin{macro}[EXP]{\@@_change_case_char_title:nnnN} % \begin{macro}[EXP] % { % \@@_change_case_char:nnnN , % \@@_change_case_char_auxi:nnnN , % \@@_change_case_char_auxii:nnnN % } % \begin{macro}[EXP]{\@@_change_case_char_UTFviii:nnnNN} % \begin{macro}[EXP]{\@@_change_case_char_UTFviii:nnnNNN} % \begin{macro}[EXP]{\@@_change_case_char_UTFviii:nnnNNNN} % \begin{macro}[EXP]{\@@_change_case_char_UTFviii:nnnn} % \begin{macro}[EXP] % { % \@@_change_case_char_next_lower:nn , % \@@_change_case_char_next_upper:nn , % \@@_change_case_char_next_title:nn , % \@@_change_case_char_next_titleonly:nn % } % \begin{macro}[EXP]{\@@_change_case_char_next_end:nn} % As for the expansion code, the business end of case changing is the % handling of \texttt{N}-type tokens. First, we expand the input fully % (so the loops here don't need to worry about awkward look-aheads and the % like). Then we split into the different paths. % % The code here needs to be \texttt{f}-type expandable to deal with the % situation where case changing is applied in running text. There, we % might have case changing as a document command and the text containing % other non-expandable document commands. % \begin{verbatim} % \cs_set_eq:NN \MakeLowercase \text_lowercase % ... % \MakeLowercase{\enquote*{A} text} % \end{verbatim} % If we use an \texttt{e}-type expansion and wrap each token in % \cs{exp_not:n}, that would explode: the document command grabs % \cs{exp_not:n} as an argument, and things go badly wrong. So we have to % wrap the entire result in exactly one \cs{exp_not:n}, or rather in the % kernel version. % \begin{macrocode} \cs_new:Npn \@@_change_case:nnn #1#2#3 { \__kernel_exp_not:w \exp_after:wN { \exp:w \exp_args:Ne \@@_change_case_aux:nnn { \text_expand:n {#3} } {#1} {#2} } } \cs_new:Npn \@@_change_case_aux:nnn #1#2#3 { \group_align_safe_begin: \cs_if_exist_use:c { @@_change_case_boundary_ #2 _ #3 :Nnnw } \@@_change_case_loop:nnw {#2} {#3} #1 \q_@@_recursion_tail \q_@@_recursion_stop \@@_change_case_result:n { } } % \end{macrocode} % As for expansion, collect up the tokens for future use. % \begin{macrocode} \cs_new:Npn \@@_change_case_store:n #1 { \@@_change_case_store:nw {#1} } \cs_generate_variant:Nn \@@_change_case_store:n { o , e , V , v } \cs_new:Npn \@@_change_case_store:nw #1#2 \@@_change_case_result:n #3 { #2 \@@_change_case_result:n { #3 #1 } } \cs_new:Npn \@@_change_case_end:w #1 \@@_change_case_result:n #2 { \group_align_safe_end: \exp_end: #2 } % \end{macrocode} % The main loop is the standard \texttt{tl action} type. % \begin{macrocode} \cs_new:Npn \@@_change_case_loop:nnw #1#2#3 \q_@@_recursion_stop { \tl_if_head_is_N_type:nTF {#3} { \@@_change_case_N_type:nnN } { \tl_if_head_is_group:nTF {#3} { \use:c { @@_change_case_group_ #1 :nnn } } { \@@_change_case_space:nnw } } {#1} {#2} #3 \q_@@_recursion_stop } \cs_new:Npn \@@_change_case_break:w #1 \q_@@_recursion_tail \q_@@_recursion_stop { \@@_change_case_store:n {#1} \@@_change_case_end:w } % \end{macrocode} % For a group, we \emph{could} worry about whether this contains a character % or not. However, that would make life very complex for little gain: exactly % what a first character is is rather weakly-defined anyway. So if there is % a group, we simply assume that a character has been seen, and for title % case we switch to the \enquote{rest of the tokens} situation. To avoid % having too much testing, we use a two-step process here to allow the % titlecase functions to be separate. % \begin{macrocode} \cs_new:Npn \@@_change_case_group_lower:nnn #1#2#3 { \@@_change_case_store:o { \exp_after:wN { \exp:w \@@_change_case_aux:nnn {#3} {#1} {#2} } } \@@_change_case_loop:nnw {#1} {#2} } \cs_new_eq:NN \@@_change_case_group_upper:nnn \@@_change_case_group_lower:nnn \cs_new:Npn \@@_change_case_group_title:nnn #1#2#3 { \@@_change_case_store:o { \exp_after:wN { \exp:w \@@_change_case_aux:nnn {#3} {#1} {#2} } } \@@_change_case_loop:nnw { lower } {#2} } \cs_new:Npn \@@_change_case_group_titleonly:nnn #1#2#3 { \@@_change_case_store:o { \exp_after:wN { \exp:w \@@_change_case_aux:nnn {#3} {#1} {#2} } } \@@_change_case_break:w } \use:x { \cs_new:Npn \exp_not:N \@@_change_case_space:nnw ##1##2 \c_space_tl } { \@@_change_case_store:n { ~ } \cs_if_exist_use:c { @@_change_case_boundary_ #1 _ #2 :Nnnw } \@@_change_case_loop:nnw {#1} {#2} } % \end{macrocode} % The first step of handling \texttt{N}-type tokens is to filter out the % end-of-loop. That has to be done separately from the first real step % as otherwise we pick up the wrong delimiter. The loop here is the same % as the \texttt{expand} one, just passing the additional data long. If no % close-math token is found then the final clean-up is forced % (i.e.~there is no assumption of \enquote{well-behaved} input in terms of % math mode). % \begin{macrocode} \cs_new:Npn \@@_change_case_N_type:nnN #1#2#3 { \@@_if_recursion_tail_stop_do:Nn #3 { \@@_change_case_end:w } \@@_change_case_N_type_aux:nnN {#1} {#2} #3 } \cs_new:Npn \@@_change_case_N_type_aux:nnN #1#2#3 { \exp_args:NV \@@_change_case_N_type:nnnN \l_text_math_delims_tl {#1} {#2} #3 } \cs_new:Npn \@@_change_case_N_type:nnnN #1#2#3#4 { \@@_change_case_math_search:nnNNN {#2} {#3} #4 #1 \q_@@_recursion_tail \q_@@_recursion_tail \q_@@_recursion_stop } \cs_new:Npn \@@_change_case_math_search:nnNNN #1#2#3#4#5 { \@@_if_recursion_tail_stop_do:Nn #4 { \@@_change_case_cs_check:nnN {#1} {#2} #3 } \token_if_eq_meaning:NNTF #3 #4 { \@@_use_i_delimit_by_q_recursion_stop:nw { \@@_change_case_store:n {#3} \@@_change_case_math_loop:nnNw {#1} {#2} #5 } } { \@@_change_case_math_search:nnNNN {#1} {#2} #3 } } \cs_new:Npn \@@_change_case_math_loop:nnNw #1#2#3#4 \q_@@_recursion_stop { \tl_if_head_is_N_type:nTF {#4} { \@@_change_case_math_N_type:nnNN } { \tl_if_head_is_group:nTF {#4} { \@@_change_case_math_group:nnNn } { \@@_change_case_math_space:nnNw } } {#1} {#2} #3 #4 \q_@@_recursion_stop } \cs_new:Npn \@@_change_case_math_N_type:nnNN #1#2#3#4 { \@@_if_recursion_tail_stop_do:Nn #4 { \@@_change_case_end:w } \@@_change_case_store:n {#4} \token_if_eq_meaning:NNTF #4 #3 { \@@_change_case_loop:nnw {#1} {#2} } { \@@_change_case_math_loop:nnNw {#1} {#2} #3 } } \cs_new:Npn \@@_change_case_math_group:nnNn #1#2#3#4 { \@@_change_case_store:n { {#4} } \@@_change_case_math_loop:nnNw {#1} {#2} #3 } \use:x { \cs_new:Npn \exp_not:N \@@_change_case_math_space:nnNw ##1##2##3 \c_space_tl } { \@@_change_case_store:n { ~ } \@@_change_case_math_loop:nnNw {#1} {#2} #3 } % \end{macrocode} % Once potential math-mode cases are filtered out the next stage is to % test if the token grabbed is a control sequence: the two routes the code % may take are then very different. % \begin{macrocode} \cs_new:Npn \@@_change_case_cs_check:nnN #1#2#3 { \token_if_cs:NTF #3 { \@@_change_case_exclude:nnN } { \use:c { @@_change_case_char_ #1 :nnN } } {#1} {#2} #3 } % \end{macrocode} % To deal with a control sequence there is first a need to test if it is % on the list which indicate that case changing should be skipped. That's % done using a loop as for the other special cases. If a hit is found then % the argument is grabbed and passed through as-is. % \begin{macrocode} \cs_new:Npn \@@_change_case_exclude:nnN #1#2#3 { \exp_args:Ne \@@_change_case_exclude:nnnN { \exp_not:V \l_text_math_arg_tl \exp_not:V \l_text_case_exclude_arg_tl } {#1} {#2} #3 } \cs_new:Npn \@@_change_case_exclude:nnnN #1#2#3#4 { \@@_change_case_exclude:nnNN {#2} {#3} #4 #1 \q_@@_recursion_tail \q_@@_recursion_stop } \cs_new:Npn \@@_change_case_exclude:nnNN #1#2#3#4 { \@@_if_recursion_tail_stop_do:Nn #4 { \use:c { @@_change_case_letterlike_ #1 :nnN } {#1} {#2} #3 } \str_if_eq:nnTF {#3} {#4} { \@@_use_i_delimit_by_q_recursion_stop:nw { \@@_change_case_exclude:nnNw {#1} {#2} #3 } } { \@@_change_case_exclude:nnNN {#1} {#2} #3 } } \cs_new:Npn \@@_change_case_exclude:nnNw #1#2#3#4# { \@@_change_case_exclude:nnNnn {#1} {#2} {#3} {#4} } \cs_new:Npn \@@_change_case_exclude:nnNnn #1#2#3#4#5 { \@@_change_case_store:n { #3#4 {#5} } \@@_change_case_loop:nnw {#1} {#2} } % \end{macrocode} % Letter-like commands may still be present: they are set up using a simple % lookup approach, so can easily be handled with no loop. If there is no % hit, we are at the end of the process: we loop around. Letter-like chars % are all available only in upper- and lowercase, so titlecasing maps to the % uppercase version. % \begin{macrocode} \cs_new:Npn \@@_change_case_letterlike_lower:nnN #1#2#3 { \@@_change_case_letterlike:nnnnN {#1} {#1} {#1} {#2} #3 } \cs_new_eq:NN \@@_change_case_letterlike_upper:nnN \@@_change_case_letterlike_lower:nnN \cs_new:Npn \@@_change_case_letterlike_title:nnN #1#2#3 { \@@_change_case_letterlike:nnnnN { upper } { lower } {#1} {#2} #3 } \cs_new:Npn \@@_change_case_letterlike_titleonly:nnN #1#2#3 { \@@_change_case_letterlike:nnnnN { upper } { end } {#1} {#2} #3 } \cs_new:Npn \@@_change_case_letterlike:nnnnN #1#2#3#4#5 { \cs_if_exist:cTF { c_@@_ #1 case_ \token_to_str:N #5 _tl } { \@@_change_case_store:v { c_@@_ #1 case_ \token_to_str:N #5 _tl } \use:c { @@_change_case_char_next_ #2 :nn } {#2} {#4} } { \@@_change_case_store:n {#5} \cs_if_exist:cTF { c_@@_ \str_if_eq:nnTF {#1} { lower } { upper } { lower } case_ \token_to_str:N #5 _tl } { \use:c { @@_change_case_char_next_ #2 :nn } {#2} {#4} } { \@@_change_case_loop:nnw {#3} {#4} } } } % \end{macrocode} % For upper- and lowercase changes, once we get to this stage there are only % a couple of questions remaining: is there a language-specific mapping and % is there the special case of a terminal sigma. If not, then we pass to % a simple character mapping. % \begin{macrocode} \cs_new:Npn \@@_change_case_char_lower:nnN #1#2#3 { \cs_if_exist_use:cF { @@_change_case_lower_ #2 :nnnN } { \@@_change_case_lower_sigma:nnnN } {#1} {#1} {#2} #3 } \cs_new:Npn \@@_change_case_char_upper:nnN #1#2#3 { \cs_if_exist_use:cF { @@_change_case_upper_ #2 :nnnN } { \@@_change_case_char:nnnN } {#1} {#1} {#2} #3 } % \end{macrocode} % If the current character is an uppercase sigma, the a check is made on the % next item in the input. If it is \texttt{N}-type and not a control sequence % then there is a look-ahead phase: the logic here is simply based on letters. % The one exception is Dutch: see below. % \begin{macrocode} \bool_lazy_or:nnTF { \sys_if_engine_luatex_p: } { \sys_if_engine_xetex_p: } { \cs_new:Npn \@@_change_case_lower_sigma:nnnN #1#2#3#4 { \int_compare:nNnTF { `#4 } = { "03A3 } { \@@_change_case_lower_sigma:nnNw {#2} {#3} #4 } { \@@_change_case_char:nnnN {#1} {#2} {#3} #4 } } \cs_new:Npn \@@_change_case_lower_sigma:nnNw #1#2#3#4 \q_@@_recursion_stop { \tl_if_head_is_N_type:nTF {#4} { \@@_change_case_lower_sigma:NnnN #3 } { \@@_change_case_store:e { \char_generate:nn { "03C2 } { \@@_char_catcode:N #3 } } \@@_change_case_loop:nnw } {#1} {#2} #4 \q_@@_recursion_stop } \cs_new:Npn \@@_change_case_lower_sigma:NnnN #1#2#3#4 { \@@_change_case_store:e { \token_if_letter:NTF #4 { \char_generate:nn { "03C3 } { \@@_char_catcode:N #1 } } { \char_generate:nn { "03C2 } { \@@_char_catcode:N #1 } } } \@@_change_case_loop:nnw {#2} {#3} #4 } } % \end{macrocode} % In the $8$-bit engines, we have to look ahead once we find the first byte of the % possible hit. % \begin{macrocode} { \cs_new:Npn \@@_change_case_lower_sigma:nnnN #1#2#3#4 { \int_compare:nNnTF { `#4 } = { "CE } { \@@_change_case_lower_sigma:nnnNN } { \@@_change_case_char:nnnN } {#1} {#2} {#3} #4 } \cs_new:Npn \@@_change_case_lower_sigma:nnnNN #1#2#3#4#5 { \int_compare:nNnTF { `#5 } = { "A3 } { \@@_change_case_lower_sigma:nnw {#2} {#3} } { \@@_change_case_char:nnnN {#1} {#2} {#3} #4#5 } } \cs_new:Npn \@@_change_case_lower_sigma:nnw #1#2#3 \q_@@_recursion_stop { \tl_if_head_is_N_type:nTF {#3} { \@@_change_case_lower_sigma:nnN } { \@@_change_case_store:V \c_@@_final_sigma_tl \@@_change_case_loop:nnw } {#1} {#2} #3 \q_@@_recursion_stop } \cs_new:Npn \@@_change_case_lower_sigma:nnN #1#2#3 { \bool_lazy_or:nnTF { \token_if_letter_p:N #3 } { \bool_lazy_and_p:nn { \token_if_active_p:N #3 } { \int_compare_p:nNn { `#3 } > { "80 } } } { \@@_change_case_store:V \c_@@_sigma_tl } { \@@_change_case_store:V \c_@@_final_sigma_tl } \@@_change_case_loop:nnw {#1} {#2} #3 } } % \end{macrocode} % For titlecasing, we need to fully expand the new character to see if it % is a letter (or active) But that means looking ahead in the $8$-bit case, so % we have to grab the required tokens up-front. Life is a lot easier for Unicode % \TeX{}'s, where we just have one token to worry about. The one wrinkle here % is that for look-ahead we'd get into trouble: luckily, only Dutch has that % issue. % \begin{macrocode} \cs_new:Npx \@@_change_case_char_title:nnN #1#2#3 { \exp_not:N \bool_if:NTF \l_text_titlecase_check_letter_bool { \bool_lazy_or:nnTF { \sys_if_engine_luatex_p: } { \sys_if_engine_xetex_p: } { \exp_not:N \token_if_letter:NTF #3 } { \exp_not:N \bool_lazy_or:nnTF { \exp_not:N \token_if_letter_p:N #3 } { \exp_not:N \token_if_active_p:N #3 } } { \exp_not:N \use:c { @@_change_case_char_ #1 :nN } } { \exp_not:N \@@_change_case_char_title:nnnN { title } {#1} } } { \exp_not:N \use:c { @@_change_case_char_ #1 :nN } } {#2} #3 } \cs_new_eq:NN \@@_change_case_char_titleonly:nnN \@@_change_case_char_title:nnN \cs_new:Npn \@@_change_case_char_title:nN #1#2 { \@@_change_case_char_title:nnnN { title } { lower } {#1} #2 } \cs_new:Npn \@@_change_case_char_titleonly:nN #1#2 { \@@_change_case_char_title:nnnN { title } { end } {#1} #2 } \cs_new:Npn \@@_change_case_char_title:nnnN #1#2#3#4 { \cs_if_exist_use:cF { @@_change_case_title_ #3 :nnnN } { \cs_if_exist_use:cF { @@_change_case_upper_ #3 :nnnN } { \@@_change_case_char:nnnN } } {#1} {#2} {#3} #4 } % \end{macrocode} % For Unicode engines we can handle all characters directly. However, for % the $8$-bit engines the aim is to deal with (a subset of) Unicode (UTF-8) % input. They deal with that by making the upper half of the range active, % so we look for that and if found work out how many UTF-8 octets there % are to deal with. Those can then be grabbed to reconstruct the full % Unicode character, which is then used in a lookup. (As will become % obvious below, there is no intention here of covering all of Unicode.) % For (u)p-\TeX{} there are a limited number of tokens we can touch. % \begin{macrocode} \cs_new:Npn \@@_change_case_char:nnnN #1#2#3#4 { \token_if_active:NTF #4 { \@@_change_case_store:n {#4} } { \@@_change_case_store:e { \use:c { char_ #1 case :N } #4 } } \use:c { @@_change_case_char_next_ #2 :nn } {#2} {#3} } \bool_lazy_or:nnF { \sys_if_engine_luatex_p: } { \sys_if_engine_xetex_p: } { \cs_new_eq:NN \@@_change_case_char_auxi:nnnN \@@_change_case_char:nnnN \cs_gset:Npn \@@_change_case_char:nnnN #1#2#3#4 { \int_compare:nNnTF { `#4 } > { "80 } { \int_compare:nNnTF { `#4 } < { "E0 } { \@@_change_case_char_UTFviii:nnnNN } { \@@_change_case_char_auxii:nnnN } } { \@@_change_case_char_auxi:nnnN } {#1} {#2} {#3} #4 } \sys_if_engine_pdftex:TF { \cs_new:Npn \@@_change_case_char_auxii:nnnN #1#2#3#4 { \int_compare:nNnTF { `#4 } < { "F0 } { \@@_change_case_char_UTFviii:nnnNNN } { \@@_change_case_char_UTFviii:nnnNNNN } {#1} {#2} {#3} #4 } } { \cs_new:Npn \@@_change_case_char_auxii:nnnN #1#2#3#4 { \@@_change_case_store:n {#4} \use:c { @@_change_case_char_next_ #2 :nn } {#2} {#3} } } \cs_new:Npn \@@_change_case_char_UTFviii:nnnNN #1#2#3#4#5 { \@@_change_case_char_UTFviii:nnnn {#1} {#2} {#3} {#4#5} } \cs_new:Npn \@@_change_case_char_UTFviii:nnnNNN #1#2#3#4#5#6 { \@@_change_case_char_UTFviii:nnnn {#1} {#2} {#3} {#4#5#6} } \cs_new:Npn \@@_change_case_char_UTFviii:nnnNNNN #1#2#3#4#5#6#7 { \@@_change_case_char_UTFviii:nnnn {#1} {#2} {#3} {#4#5#6#7} } \cs_new:Npn \@@_change_case_char_UTFviii:nnnn #1#2#3#4 { \cs_if_exist:cTF { c__kernel_ #1 case_ \tl_to_str:n {#4} _tl } { \@@_change_case_store:v { c__kernel_ #1 case_ \tl_to_str:n {#4} _tl } } { \@@_change_case_store:n {#4} } \use:c { @@_change_case_char_next_ #2 :nn } {#2} {#3} } } \cs_new:Npn \@@_change_case_char_next_lower:nn #1#2 { \@@_change_case_loop:nnw {#1} {#2} } \cs_new_eq:NN \@@_change_case_char_next_upper:nn \@@_change_case_char_next_lower:nn \cs_new_eq:NN \@@_change_case_char_next_title:nn \@@_change_case_char_next_lower:nn \cs_new_eq:NN \@@_change_case_char_next_titleonly:nn \@@_change_case_char_next_lower:nn \cs_new:Npn \@@_change_case_char_next_end:nn #1#2 { \@@_change_case_break:w } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}[EXP]{\@@_change_case_upper_de-alt:nnnN} % \begin{macro}[EXP]{\@@_change_case_upper_de-alt:nnnNN} % A simple alternative version for German. % \begin{macrocode} \bool_lazy_or:nnTF { \sys_if_engine_luatex_p: } { \sys_if_engine_xetex_p: } { \cs_new:cpn { @@_change_case_upper_de-alt:nnnN } #1#2#3#4 { \int_compare:nNnTF { `#4 } = { "00DF } { \@@_change_case_store:e { \char_generate:nn { "1E9E } { \@@_char_catcode:N #4 } } \use:c { @@_change_case_char_next_ #2 :nn } {#2} {#3} } { \@@_change_case_char:nnnN {#1} {#2} {#3} #4 } } } { \cs_new:cpx { @@_change_case_upper_de-alt:nnnN } #1#2#3#4 { \exp_not:N \int_compare:nNnTF { `#4 } = { "00C3 } { \exp_not:c { @@_change_case_upper_de-alt:nnnNN } {#1} {#2} {#3} #4 } { \exp_not:N \@@_change_case_char:nnnN {#1} {#2} {#3} #4 } } \cs_new:cpn { @@_change_case_upper_de-alt:nnnNN } #1#2#3#4#5 { \int_compare:nNnTF { `#5 } = { "009F } { \@@_change_case_store:V \c_@@_grosses_Eszett_tl \use:c { @@_change_case_char_next_ #2 :nn } {#2} {#3} } { \@@_change_case_char:nnnN {#1} {#2} {#3} #4#5 } } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[EXP]{\@@_change_case_upper_el:nnnN} % \begin{macro}[EXP]{\@@_change_case_upper_el:nnn} % \begin{macro}[EXP]{\@@_change_case_upper_el:nnNw} % \begin{macro}[EXP]{\@@_change_case_upper_el:NnnN} % \begin{macro}[EXP]{\@@_change_case_upper_el_dialytika:nnN} % \begin{macro}[EXP]{\@@_change_case_upper_el_dialytika:N} % \begin{macro}[EXP]{\@@_change_case_upper_el_hiatus:nnNw} % \begin{macro}[EXP]{\@@_change_case_upper_el_hiatus:nnN} % \begin{macro}[EXP]{\@@_change_case_upper_el_gobble:nnw} % \begin{macro}[EXP]{\@@_change_case_upper_el_gobble:nnN} % \begin{macro}[EXP,TF]{\@@_change_case_if_greek:n} % \begin{macro}[EXP,pTF]{\@@_change_case_if_greek:n} % \begin{macro}[EXP,pTF]{\@@_change_case_if_greek_accent:n} % \begin{macro}[EXP,pTF]{\@@_change_case_if_greek_diacritic:n} % \begin{macro}[EXP,TF]{\@@_change_case_if_takes_dialytika:n} % For Greek uppercasing, we need to know if characters \emph{in the Greek % range} have accents. That means doing a \textsc{nfd} conversion first, then % starting a search. As described by the Unicode \textsc{cldr}, Greek accents % need to be found \emph{after} any U+0308 (diaeresis) and are done in two % groups to allow for the canonical ordering. The implementation here follows % the data and examples from ICU % (\url{https://sites.google.com/site/icusite/design/case/greek-upper}), % although necessarily the implementation is somewhat different. % \begin{macrocode} \bool_lazy_or:nnT { \sys_if_engine_luatex_p: } { \sys_if_engine_xetex_p: } { \cs_new:Npn \@@_change_case_upper_el:nnnN #1#2#3#4 { \@@_change_case_if_greek:nTF { `#4 } { \exp_args:Ne \@@_change_case_upper_el:nnn { \char_to_nfd:N #4 } {#2} {#3} } { \@@_change_case_char:nnnN {#1} {#2} {#3} #4 } } \cs_new:Npn \@@_change_case_upper_el:nnn #1#2#3 { \@@_change_case_upper_el:nnNw {#2} {#3} #1 } % \end{macrocode} % At this stage we have the first NFD codepoint as |#3|. What we need to know % is whether after that we have another character token, either from the NFD or % directly in the input. If not, we store the changed character at this stage. % \begin{macrocode} \cs_new:Npn \@@_change_case_upper_el:nnNw #1#2#3#4 \q_@@_recursion_stop { \tl_if_head_is_N_type:nTF {#4} { \@@_change_case_upper_el:NnnN #3 } { \@@_change_case_store:e { \char_uppercase:N #3 } \@@_change_case_loop:nnw } {#1} {#2} #4 \q_@@_recursion_stop } % \end{macrocode} % Now, we check the detail of the next codepoint: again we filter out the % not-a-char cases, before checking if it's an dialytika, accent or diacritic. % (The latter do not have the same hiatus behavior as accents.) % \begin{macrocode} \cs_new:Npn \@@_change_case_upper_el:NnnN #1#2#3#4 { \token_if_cs:NTF #4 { \@@_change_case_store:e { \char_uppercase:N #1 } \@@_change_case_loop:nnw {#2} {#3} #4 } { \int_compare:nNnTF { `#4 } = { "0308 } { \@@_change_case_upper_el_dialytika:nnN {#2} {#3} #1 } { \@@_change_case_if_greek_accent:nTF { `#4 } { \@@_change_case_upper_el_hiatus:nnNw {#2} {#3} #1 } { \@@_change_case_if_greek_diacritic:nTF { `#4 } { \@@_change_case_store:e { \char_uppercase:N #1 } \@@_change_case_loop:nnw {#2} {#3} } { \@@_change_case_store:e { \char_uppercase:N #1 } \@@_change_case_loop:nnw {#2} {#3} #4 } } } } } % \end{macrocode} % We handle \emph{dialytika} in parts as it's also needed for the hiatus. % We know only two letters take it, so we can shortcut here on the second % part of the tests. % \begin{macrocode} \cs_new:Npn \@@_change_case_upper_el_dialytika:nnN #1#2#3 { \@@_change_case_if_takes_dialytika:nTF { `#3 } { \@@_change_case_upper_el_dialytika:N #3 } { \@@_change_case_store:e { \char_uppercase:N #3 } } \@@_change_case_upper_el_gobble:nnw {#1} {#2} } \cs_new:Npn \@@_change_case_upper_el_dialytika:N #1 { \@@_change_case_store:e { \bool_lazy_or:nnTF { \int_compare_p:nNn { `#1 } = { "0399 } } { \int_compare_p:nNn { `#1 } = { "03B9 } } { \char_generate:nn { "03AA } { \@@_char_catcode:N #1 } } { \char_generate:nn { "03AB } { \@@_char_catcode:N #1 } } } } % \end{macrocode} % Adding a hiatus needs some of the same ideas, but if there is not one we % skip this code point, hence needing a separate function. % \begin{macrocode} \cs_new:Npn \@@_change_case_upper_el_hiatus:nnNw #1#2#3#4 \q_@@_recursion_stop { \@@_change_case_store:e { \char_uppercase:N #3 } \tl_if_head_is_N_type:nTF {#4} { \@@_change_case_upper_el_hiatus:nnN } { \@@_change_case_loop:nnw } {#1} {#2} #4 \q_@@_recursion_stop } \cs_new:Npn \@@_change_case_upper_el_hiatus:nnN #1#2#3 { \token_if_cs:NTF #3 { \@@_change_case_loop:nnw {#1} {#2} #3 } { \@@_change_case_if_takes_dialytika:nTF { `#3 } { \@@_change_case_upper_el_dialytika:N #3 \@@_change_case_upper_el_gobble:nnw {#1} {#2} } { \@@_change_case_loop:nnw {#1} {#2} #3 } } } % \end{macrocode} % For clearing out trailing combining marks after we have dealt with % the first one. % \begin{macrocode} \cs_new:Npn \@@_change_case_upper_el_gobble:nnw #1#2#3 \q_@@_recursion_stop { \tl_if_head_is_N_type:nTF {#3} { \@@_change_case_upper_el_gobble:nnN } { \@@_change_case_loop:nnw } {#1} {#2} #3 \q_@@_recursion_stop } \cs_new:Npn \@@_change_case_upper_el_gobble:nnN #1#2#3 { \bool_lazy_or:nnTF { \token_if_cs_p:N #3 } { ! \bool_lazy_or_p:nn { \@@_change_case_if_greek_accent_p:n { `#3 } } { \@@_change_case_if_greek_diacritic_p:n { `#3 } } } { \@@_change_case_loop:nnw {#1} {#2} #3 } { \@@_change_case_upper_el_gobble:nnw {#1} {#2} } } } % \end{macrocode} % Luckily the Greek range is limited and clear. % \begin{macrocode} \prg_new_conditional:Npnn \@@_change_case_if_greek:n #1 { TF } { \if_int_compare:w #1 < "0370 \exp_stop_f: \prg_return_false: \else: \if_int_compare:w #1 > "03FF \exp_stop_f: \if_int_compare:w #1 < "1F00 \exp_stop_f: \prg_return_false: \else: \if_int_compare:w #1 > "1FFF \exp_stop_f: \prg_return_false: \else: \prg_return_true: \fi: \fi: \else: \prg_return_true: \fi: \fi: } % \end{macrocode} % We follow ICU in adding a few extras to the accent list here. % \begin{macrocode} \prg_new_conditional:Npnn \@@_change_case_if_greek_accent:n #1 { TF , p } { \if_int_compare:w #1 = "0300 \exp_stop_f: \prg_return_true: \else: \if_int_compare:w #1 = "0301 \exp_stop_f: \prg_return_true: \else: \if_int_compare:w #1 = "0342 \exp_stop_f: \prg_return_true: \else: \if_int_compare:w #1 = "0302 \exp_stop_f: \prg_return_true: \else: \if_int_compare:w #1 = "0303 \exp_stop_f: \prg_return_true: \else: \if_int_compare:w #1 = "0311 \exp_stop_f: \prg_return_true: \else: \prg_return_false: \fi: \fi: \fi: \fi: \fi: \fi: } \prg_new_conditional:Npnn \@@_change_case_if_greek_diacritic:n #1 { TF , p } { \if_int_compare:w #1 = "0304 \exp_stop_f: \prg_return_true: \else: \if_int_compare:w #1 = "0306 \exp_stop_f: \prg_return_true: \else: \if_int_compare:w #1 = "0313 \exp_stop_f: \prg_return_true: \else: \if_int_compare:w #1 = "0314 \exp_stop_f: \prg_return_true: \else: \if_int_compare:w #1 = "0343 \exp_stop_f: \prg_return_true: \else: \prg_return_false: \fi: \fi: \fi: \fi: \fi: } \prg_new_conditional:Npnn \@@_change_case_if_takes_dialytika:n #1 { TF } { \if_int_compare:w #1 = "0399 \exp_stop_f: \prg_return_true: \else: \if_int_compare:w #1 = "03B9 \exp_stop_f: \prg_return_true: \else: \if_int_compare:w #1 = "03A5 \exp_stop_f: \prg_return_true: \else: \if_int_compare:w #1 = "03C5 \exp_stop_f: \prg_return_true: \else: \prg_return_false: \fi: \fi: \fi: \fi: } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \begin{macro}[EXP]{\@@_change_case_boundary_upper_el:Nnnw} % \begin{macro}[EXP]{\@@_change_case_boundary_upper_el:nnN} % \begin{macro}[EXP]{\@@_change_case_boundary_upper_el:nnNw} % \begin{macro}[EXP]{\@@_change_case_boundary_upper_el:NnnN} % There is one special case in Greek that needs to be picked up based on % being an isolated letter. We do that using a test similar to final sigma, % but it has to fire off from the space grabber. % \begin{macrocode} \bool_lazy_or:nnT { \sys_if_engine_luatex_p: } { \sys_if_engine_xetex_p: } { \cs_new:Npn \@@_change_case_boundary_upper_el:Nnnw #1#2#3#4 \q_@@_recursion_stop { \tl_if_head_is_N_type:nTF {#4} { \@@_change_case_boundary_upper_el:nnN } { \@@_change_case_loop:nnw } {#2} {#3} #4 \q_@@_recursion_stop } \cs_new:Npn \@@_change_case_boundary_upper_el:nnN #1#2#3 { \bool_lazy_or:nnTF { \token_if_cs_p:N #3 } { ! \bool_lazy_or_p:nn { \int_compare_p:nNn { `#3 } = { "03AE } } { \int_compare_p:nNn { `#3 } = { "1F22 } } } { \@@_change_case_loop:nnw } { \@@_change_case_boundary_upper_el:nnNw } {#1} {#2} #3 } \cs_new:Npn \@@_change_case_boundary_upper_el:nnNw #1#2#3#4 \q_@@_recursion_stop { \tl_if_head_is_N_type:nTF {#4} { \@@_change_case_boundary_upper_el:NnnN #3 } { \@@_change_case_store:e { \char_generate:nn { "0389 } { \@@_char_catcode:N #3 } } \@@_change_case_loop:nnw } {#1} {#2} #4 \q_@@_recursion_stop } \cs_new:Npn \@@_change_case_boundary_upper_el:NnnN #1#2#3#4 { \token_if_letter:NTF #4 { \@@_change_case_loop:nnw {#2} {#3} #1#4 } { \@@_change_case_store:e { \char_generate:nn { "0389 } { \@@_char_catcode:N #1 } } \@@_change_case_loop:nnw {#2} {#3} #4 } } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \begin{macro}[EXP]{\@@_change_case_title_el:nnnN} % Titlecasing retains accents, but to prevent the uppercasing code % from kicking in, there has to be an explicit function here. % \begin{macrocode} \bool_lazy_or:nnT { \sys_if_engine_luatex_p: } { \sys_if_engine_xetex_p: } { \cs_new:Npn \@@_change_case_title_el:nnnN #1#2#3#4 { \@@_change_case_char:nnnN {#1} {#2} {#3} #4 } } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP] % { % \@@_change_cases_lower_lt:nnnN , % \@@_change_cases_lower_lt_auxi:nnnN , % \@@_change_cases_lower_lt_auxii:nnnN % } % \begin{macro}[rEXP]{\@@_change_case_lower_lt:nnw} % \begin{macro}[rEXP]{\@@_change_case_lower_lt:nnN} % For Lithuanian, the issue to be dealt with is dots over lower case % letters: these should be present if there is another accent. The first step % is a simple match attempt: look for the three uppercase accented letters % which should gain a dot-above char in their lowercase form. % \begin{macrocode} \bool_lazy_or:nnT { \sys_if_engine_luatex_p: } { \sys_if_engine_xetex_p: } { \cs_new:Npn \@@_change_case_lower_lt:nnnN #1#2#3#4 { \exp_args:Ne \@@_change_case_lower_lt_auxi:nnnN { \int_case:nn { `#4 } { { "00CC } { "0300 } { "00CD } { "0301 } { "0128 } { "0303 } } } {#2} {#3} #4 } % \end{macrocode} % If there was a hit, output the result with the dot-above and move on. % Otherwise, look for one of the three letters that can take a combining % accent: I, J and I-ogonek. % \begin{macrocode} \cs_new:Npn \@@_change_case_lower_lt_auxi:nnnN #1#2#3#4 { \tl_if_blank:nTF {#1} { \exp_args:Ne \@@_change_case_lower_lt_auxii:nnnN { \int_case:nn { `#4 } { { "0049 } { "0069 } { "004A } { "006A } { "012E } { "012F } } } {#2} {#3} #4 } { \@@_change_case_store:e { \char_generate:nn { "0069 } { \@@_char_catcode:N #4 } \char_generate:nn { "0307 } { \@@_char_catcode:N #4 } \char_generate:nn {#1} { \@@_char_catcode:N #4 } } \@@_change_case_loop:nnw {#2} {#3} } } % \end{macrocode} % Again, branch depending on a hit. If there is one, we output the character % then need to look for a combining accent: as usual, we need to be aware of % the loop situation. % \begin{macrocode} \cs_new:Npn \@@_change_case_lower_lt_auxii:nnnN #1#2#3#4 { \tl_if_blank:nTF {#1} { \@@_change_case_lower_sigma:nnnN {#2} {#2} {#3} #4 } { \@@_change_case_store:e { \char_generate:nn {#1} { \@@_char_catcode:N #4 } } \@@_change_case_lower_lt:nnw {#2} {#3} } } \cs_new:Npn \@@_change_case_lower_lt:nnw #1#2#3 \q_@@_recursion_stop { \tl_if_head_is_N_type:nTF {#3} { \@@_change_case_lower_lt:nnN } { \@@_change_case_loop:nnw } {#1} {#2} #3 \q_@@_recursion_stop } \cs_new:Npn \@@_change_case_lower_lt:nnN #1#2#3 { \bool_lazy_and:nnT { ! \token_if_cs_p:N #3 } { \bool_lazy_any_p:n { { \int_compare_p:nNn { `#3 } = { "0300 } } { \int_compare_p:nNn { `#3 } = { "0301 } } { \int_compare_p:nNn { `#3 } = { "0303 } } } } { \@@_change_case_store:e { \char_generate:nn { "0307 } { \@@_char_catcode:N #3 } } } \@@_change_case_loop:nnw {#1} {#2} #3 } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \begin{macro}[EXP] % { % \@@_change_cases_upper_lt:nnnN , % \@@_change_cases_upper_lt_aux:nnnN % } % \begin{macro}[rEXP]{\@@_change_case_upper_lt:nnw} % \begin{macro}[rEXP]{\@@_change_case_upper_lt:nnN} % The uppercasing version: first find i/j/i-ogonek, then look for the % combining char: drop it if present. % \begin{macrocode} \bool_lazy_or:nnT { \sys_if_engine_luatex_p: } { \sys_if_engine_xetex_p: } { \cs_new:Npn \@@_change_case_upper_lt:nnnN #1#2#3#4 { \exp_args:Ne \@@_change_case_upper_lt_aux:nnnN { \int_case:nn { `#4 } { { "0069 } { "0049 } { "006A } { "004A } { "012F } { "012E } } } {#2} {#3} #4 } \cs_new:Npn \@@_change_case_upper_lt_aux:nnnN #1#2#3#4 { \tl_if_blank:nTF {#1} { \@@_change_case_char:nnnN { upper } {#2} {#3} #4 } { \@@_change_case_store:e { \char_generate:nn {#1} { \@@_char_catcode:N #4 } } \@@_change_case_upper_lt:nnw {#2} {#3} } } \cs_new:Npn \@@_change_case_upper_lt:nnw #1#2#3 \q_@@_recursion_stop { \tl_if_head_is_N_type:nTF {#3} { \@@_change_case_upper_lt:nnN } { \use:c { @@_change_case_char_next_ #1 :nn } } {#1} {#2} #3 \q_@@_recursion_stop } \cs_new:Npn \@@_change_case_upper_lt:nnN #1#2#3 { \bool_lazy_and:nnTF { ! \token_if_cs_p:N #3 } { \int_compare_p:nNn { `#3 } = { "0307 } } { \use:c { @@_change_case_char_next_ #1 :nn } {#1} {#2} } { \use:c { @@_change_case_char_next_ #1 :nn } {#1} {#2} #3 } } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}[EXP]{\@@_change_case_title_nl:nnnN} % \begin{macro}[EXP]{\@@_change_case_title_nl:nnw} % \begin{macro}[EXP]{\@@_change_case_title_nl:nnN} % For Dutch, there is a single look-ahead test for \texttt{ij} when % title casing. If the appropriate letters are found, produce \texttt{IJ} % and gobble the \texttt{j}/\texttt{J}. % \begin{macrocode} \cs_new:Npn \@@_change_case_title_nl:nnnN #1#2#3#4 { \bool_lazy_or:nnTF { \int_compare_p:nNn { `#4 } = { "0049 } } { \int_compare_p:nNn { `#4 } = { "0069 } } { \@@_change_case_store:e { \char_generate:nn { "0049 } { \@@_char_catcode:N #4 } } \@@_change_case_title_nl:nnw {#2} {#3} } { \@@_change_case_char:nnnN {#1} {#2} {#3} #4 } } \cs_new:Npn \@@_change_case_title_nl:nnw #1#2#3 \q_@@_recursion_stop { \tl_if_head_is_N_type:nTF {#3} { \@@_change_case_title_nl:nnN } { \use:c { @@_change_case_char_next_ #1 :nn } } {#1} {#2} #3 \q_@@_recursion_stop } \cs_new:Npn \@@_change_case_title_nl:nnN #1#2#3 { \bool_lazy_and:nnTF { ! \token_if_cs_p:N #3 } { \bool_lazy_or_p:nn { \int_compare_p:nNn { `#3 } = { "004A } } { \int_compare_p:nNn { `#3 } = { "006A } } } { \@@_change_case_store:e { \char_generate:nn { "004A } { \@@_char_catcode:N #3 } } \use:c { @@_change_case_char_next_ #1 :nn } {#1} {#2} } { \use:c { @@_change_case_char_next_ #1 :nn } {#1} {#2} #3 } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}[EXP]{\@@_change_case_lower_tr:nnnN} % \begin{macro}[EXP]{\@@_change_case_lower_tr:nnNw} % \begin{macro}[EXP]{\@@_change_case_lower_tr:NnnN} % \begin{macro}[EXP]{\@@_change_case_lower_tr:nnnNN} % The Turkic languages need special treatment for dotted-i and dotless-i. % The lower casing rule can be expressed in terms of searching first for % either a dotless-I or a dotted-I. In the latter case the mapping is % easy, but in the former there is a second stage search. % \begin{macrocode} \bool_lazy_or:nnTF { \sys_if_engine_luatex_p: } { \sys_if_engine_xetex_p: } { \cs_new:Npn \@@_change_case_lower_tr:nnnN #1#2#3#4 { \int_compare:nNnTF { `#4 } = { "0049 } { \@@_change_case_lower_tr:nnNw {#1} {#3} #4 } { \int_compare:nNnTF { `#4 } = { "0130 } { \@@_change_case_store:e { \char_generate:nn { "0069 } { \@@_char_catcode:N #4 } } \@@_change_case_loop:nnw {#1} {#3} } { \@@_change_case_lower_sigma:nnnN {#1} {#2} {#3} #4 } } } % \end{macrocode} % After a dotless-I there may be a dot-above character. If there is then % a dotted-i should be produced, otherwise output a dotless-i. When the % combination is found both the dotless-I and the dot-above char have to % be removed from the input. % \begin{macrocode} \cs_new:Npn \@@_change_case_lower_tr:nnNw #1#2#3#4 \q_@@_recursion_stop { \tl_if_head_is_N_type:nTF {#4} { \@@_change_case_lower_tr:NnnN #3 } { \@@_change_case_store:e { \char_generate:nn { "0131 } { \@@_char_catcode:N #3 } } \@@_change_case_loop:nnw } {#1} {#2} #4 \q_@@_recursion_stop } \cs_new:Npn \@@_change_case_lower_tr:NnnN #1#2#3#4 { \bool_lazy_or:nnTF { \token_if_cs_p:N #4 } { ! \int_compare_p:nNn { `#4 } = { "0307 } } { \@@_change_case_store:e { \char_generate:nn { "0131 } { \@@_char_catcode:N #1 } } \@@_change_case_loop:nnw {#2} {#3} #4 } { \@@_change_case_store:e { \char_generate:nn { "0069 } { \@@_char_catcode:N #1 } } \@@_change_case_loop:nnw {#2} {#3} } } } % \end{macrocode} % For $8$-bit engines, dot-above is not available so there is a simple % test for an upper-case I. Then we can look for the UTF-8 representation of % an upper case dotted-I without the combining char. If it's not there, % preserve the UTF-8 sequence as-is. With $8$bit engines, we cannot % completely preserve category codes, so we have to make some assumptions: % output a \enquote{normal} \texttt{i} for the dotted case. As the original % character here is catcode-$13$, we have to make a choice about handling % of |i|: generate a \enquote{normal} one. % \begin{macrocode} { \cs_new:Npn \@@_change_case_lower_tr:nnnN #1#2#3#4 { \int_compare:nNnTF { `#4 } = { "0049 } { \@@_change_case_store:V \c_@@_dotless_i_tl \@@_change_case_loop:nnw {#1} {#3} } { \int_compare:nNnTF { `#4 } = { "00C4 } { \@@_change_case_lower_tr:nnnNN {#1} {#2} {#3} #4 } { \@@_change_case_char:nnnN {#1} {#2} {#3} #4 } } } \cs_new:Npn \@@_change_case_lower_tr:nnnNN #1#2#3#4#5 { \int_compare:nNnTF { `#5 } = { "00B0 } { \@@_change_case_store:e { \char_generate:nn { "0069 } { \char_value_catcode:n { "0069 } } } \@@_change_case_loop:nnw {#1} {#3} } { \@@_change_case_char:nnnN {#1} {#2} {#3} #4#5 } } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \begin{macro}[EXP]{\@@_change_case_upper_tr:nnnN} % Uppercasing is easier: just one exception with no context. % \begin{macrocode} \cs_new:Npx \@@_change_case_upper_tr:nnnN #1#2#3#4 { \exp_not:N \int_compare:nNnTF { `#4 } = { "0069 } { \bool_lazy_or:nnTF { \sys_if_engine_luatex_p: } { \sys_if_engine_xetex_p: } { \exp_not:N \@@_change_case_store:e { \exp_not:N \char_generate:nn { "0130 } { \exp_not:N \@@_char_catcode:N #4 } } } { \exp_not:N \@@_change_case_store:V \exp_not:N \c_@@_dotted_I_tl } \exp_not:N \use:c { @@_change_case_char_next_ #2 :nn } {#2} {#3} } { \exp_not:N \@@_change_case_char:nnnN {#1} {#2} {#3} #4 } } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP] % {\@@_change_case_lower_az:nnnN, \@@_change_case_upper_az:nnnN} % Straight copies. % \begin{macrocode} \cs_new_eq:NN \@@_change_case_lower_az:nnnN \@@_change_case_lower_tr:nnnN \cs_new_eq:NN \@@_change_case_upper_az:nnnN \@@_change_case_upper_tr:nnnN % \end{macrocode} % \end{macro} % % \subsection{Case changing data for $8$-bit engines} % % \begin{variable} % { % \c_@@_dotless_i_tl , % \c_@@_dotted_I_tl , % \c_@@_i_ogonek_tl , % \c_@@_I_ogonek_tl , % \c_@@_final_sigma_tl , % \c_@@_sigma_tl , % \c_@@_grosses_Eszett_tl % } % For cases where there is an $8$-bit option in the |T1| font set up, % a variant is provided in both cases. There are also a few extras for % |LGR|. % \begin{macrocode} \group_begin: \bool_lazy_or:nnF { \sys_if_engine_luatex_p: } { \sys_if_engine_xetex_p: } { \cs_set_protected:Npn \@@_tmp:w #1#2 { \group_begin: \cs_set_protected:Npn \@@_tmp:w ##1##2##3##4 { \tl_const:Nx #1 { \exp_after:wN \exp_after:wN \exp_after:wN \exp_not:N \char_generate:nn {##1} { 13 } \exp_after:wN \exp_after:wN \exp_after:wN \exp_not:N \char_generate:nn {##2} { 13 } \tl_if_blank:nF {##3} { \exp_after:wN \exp_after:wN \exp_after:wN \exp_not:N \char_generate:nn {##3} { 13 } } } } \use:x { \@@_tmp:w \char_to_utfviii_bytes:n { "#2 } } \group_end: } \@@_tmp:w \c_@@_dotless_i_tl { 0131 } \@@_tmp:w \c_@@_dotted_I_tl { 0130 } \@@_tmp:w \c_@@_i_ogonek_tl { 012F } \@@_tmp:w \c_@@_I_ogonek_tl { 012E } \@@_tmp:w \c_@@_final_sigma_tl { 03C2 } \@@_tmp:w \c_@@_sigma_tl { 03C3 } \@@_tmp:w \c_@@_grosses_Eszett_tl { 1E9E } } \group_end: % \end{macrocode} % \end{variable} % % For $8$-bit engines we now need to define the case-change data for % the multi-octet mappings. This data is here not in the \pkg{char} module % as the multi-byte nature means they are never |N|-type. As this data is % needed both for text and string work, it is stored as |kernel|: it remains % internal as it is not really suitable for wider exposure. % % The first set of codepoints are those in |T1|: all of these are two bytes. % \begin{macrocode} \group_begin: \bool_lazy_or:nnF { \sys_if_engine_luatex_p: } { \sys_if_engine_xetex_p: } { \cs_set_protected:Npn \@@_loop:nn #1#2 { \quark_if_recursion_tail_stop:n {#1} \use:x { \@@_tmp:w \char_to_utfviii_bytes:n { "#1 } \char_to_utfviii_bytes:n { "#2 } } \@@_loop:nn } \cs_set_protected:Npn \@@_tmp:nnnn #1#2#3#4#5 { \tl_const:cx { c__kernel_ #1 case_ \char_generate:nn {#2} { 12 } \char_generate:nn {#3} { 12 } _tl } { \exp_after:wN \exp_after:wN \exp_after:wN \exp_not:N \char_generate:nn {#4} { 13 } \exp_after:wN \exp_after:wN \exp_after:wN \exp_not:N \char_generate:nn {#5} { 13 } } } \cs_set_protected:Npn \@@_tmp:w #1#2#3#4#5#6#7#8 { \tl_const:cx { c__kernel_lowercase_ \char_generate:nn {#1} { 12 } \char_generate:nn {#2} { 12 } _tl } { \exp_after:wN \exp_after:wN \exp_after:wN \exp_not:N \char_generate:nn {#5} { 13 } \exp_after:wN \exp_after:wN \exp_after:wN \exp_not:N \char_generate:nn {#6} { 13 } } \@@_tmp:nnnn { upper } {#5} {#6} {#1} {#2} \@@_tmp:nnnn { title } {#5} {#6} {#1} {#2} } \@@_loop:nn { 00C0 } { 00E0 } { 00C1 } { 00E1 } { 00C2 } { 00E2 } { 00C3 } { 00E3 } { 00C4 } { 00E4 } { 00C5 } { 00E5 } { 00C6 } { 00E6 } { 00C7 } { 00E7 } { 00C8 } { 00E8 } { 00C9 } { 00E9 } { 00CA } { 00EA } { 00CB } { 00EB } { 00CC } { 00EC } { 00CD } { 00ED } { 00CE } { 00EE } { 00CF } { 00EF } { 00D0 } { 00F0 } { 00D1 } { 00F1 } { 00D2 } { 00F2 } { 00D3 } { 00F3 } { 00D4 } { 00F4 } { 00D5 } { 00F5 } { 00D6 } { 00F6 } { 00D8 } { 00F8 } { 00D9 } { 00F9 } { 00DA } { 00FA } { 00DB } { 00FB } { 00DC } { 00FC } { 00DD } { 00FD } { 00DE } { 00FE } { 0100 } { 0101 } { 0102 } { 0103 } { 0104 } { 0105 } { 0106 } { 0107 } { 0108 } { 0109 } { 010A } { 010B } { 010C } { 010D } { 010E } { 010F } { 0110 } { 0111 } { 0112 } { 0113 } { 0114 } { 0115 } { 0116 } { 0117 } { 0118 } { 0119 } { 011A } { 011B } { 011C } { 011D } { 011E } { 011F } { 0120 } { 0121 } { 0122 } { 0123 } { 0124 } { 0125 } { 0128 } { 0129 } { 012A } { 012B } { 012C } { 012D } { 012E } { 012F } { 0132 } { 0133 } { 0134 } { 0135 } { 0136 } { 0137 } { 0139 } { 013A } { 013B } { 013C } { 013E } { 013F } { 0141 } { 0142 } { 0143 } { 0144 } { 0145 } { 0146 } { 0147 } { 0148 } { 014A } { 014B } { 014C } { 014D } { 014E } { 014F } { 0150 } { 0151 } { 0152 } { 0153 } { 0154 } { 0155 } { 0156 } { 0157 } { 0158 } { 0159 } { 015A } { 015B } { 015C } { 015D } { 015E } { 015F } { 0160 } { 0161 } { 0162 } { 0163 } { 0164 } { 0165 } { 0168 } { 0169 } { 016A } { 016B } { 016C } { 016D } { 016E } { 016F } { 0170 } { 0171 } { 0172 } { 0173 } { 0174 } { 0175 } { 0176 } { 0177 } { 0178 } { 00FF } { 0179 } { 017A } { 017B } { 017C } { 017D } { 017E } { 01CD } { 01CE } { 01CF } { 01D0 } { 01D1 } { 01D2 } { 01D3 } { 01D4 } { 01E2 } { 01E3 } { 01E6 } { 01E7 } { 01E8 } { 01E9 } { 01EA } { 01EB } { 01F4 } { 01F5 } { 0218 } { 0219 } { 021A } { 021B } % \end{macrocode} % Add |T2| (Cyrillic) : again two bytes. % \begin{macrocode} { 0400 } { 0450 } { 0401 } { 0451 } { 0402 } { 0452 } { 0403 } { 0453 } { 0404 } { 0454 } { 0405 } { 0455 } { 0406 } { 0456 } { 0407 } { 0457 } { 0408 } { 0458 } { 0409 } { 0459 } { 040A } { 045A } { 040B } { 045B } { 040C } { 045C } { 040D } { 045D } { 040E } { 045E } { 040F } { 045F } { 0410 } { 0430 } { 0411 } { 0431 } { 0412 } { 0432 } { 0413 } { 0433 } { 0414 } { 0434 } { 0415 } { 0435 } { 0416 } { 0436 } { 0417 } { 0437 } { 0418 } { 0438 } { 0419 } { 0439 } { 041A } { 043A } { 041B } { 043B } { 041C } { 043C } { 041D } { 043D } { 041E } { 043E } { 041F } { 043F } { 0420 } { 0440 } { 0421 } { 0441 } { 0422 } { 0442 } { 0423 } { 0443 } { 0424 } { 0444 } { 0425 } { 0445 } { 0426 } { 0446 } { 0427 } { 0447 } { 0428 } { 0448 } { 0429 } { 0449 } { 042A } { 044A } { 042B } { 044B } { 042C } { 044C } { 042D } { 044D } { 042E } { 044E } { 042F } { 044F } % \end{macrocode} % Greek support: everything in the two-octet range. % \begin{macrocode} { 0370 } { 0371 } { 0372 } { 0373 } { 0376 } { 0377 } { 03FD } { 037B } { 03FE } { 037C } { 03FF } { 037D } { 0386 } { 03AC } { 0388 } { 03AD } { 0389 } { 03AE } { 038A } { 03AF } { 0391 } { 03B1 } { 0392 } { 03B2 } { 0393 } { 03B3 } { 0394 } { 03B4 } { 0395 } { 03B5 } { 0396 } { 03B6 } { 0397 } { 03B7 } { 0398 } { 03B8 } { 0399 } { 03B9 } { 039A } { 03BA } { 039B } { 03BB } { 039C } { 03BC } { 039D } { 03BD } { 039E } { 03BE } { 039F } { 03BF } { 03A0 } { 03C0 } { 03A1 } { 03C1 } { 03A3 } { 03C3 } { 03A4 } { 03C4 } { 03A5 } { 03C5 } { 03A6 } { 03C6 } { 03A7 } { 03C7 } { 03A8 } { 03C8 } { 03A9 } { 03C9 } { 03AA } { 03CA } { 03AB } { 03CB } { 038C } { 03CC } { 038E } { 03CD } { 038F } { 03CE } { 03CF } { 03D7 } { 03D8 } { 03D9 } { 03DA } { 03DB } { 03DC } { 03DD } { 03DE } { 03DF } { 03E0 } { 03E1 } { 03E2 } { 03E3 } { 03E4 } { 03E5 } { 03E6 } { 03E7 } { 03E8 } { 03E9 } { 03EA } { 03EB } { 03EC } { 03ED } { 03EE } { 03EF } { 03F9 } { 03F2 } { 037F } { 03F3 } { 03F7 } { 03F8 } { 03FA } { 03FB } \q_recursion_tail ? \q_recursion_stop % \end{macrocode} % Odds and ends for Greek; mainly symbols that are for compatibility, % but also things like the terminal sigma. Almost all are uppercase % mappings, but there is one that is not! % \begin{macrocode} \cs_set_protected:Npn \@@_tmp:w #1#2#3 { \group_begin: \cs_set_protected:Npn \@@_tmp:w ##1##2##3##4##5##6##7##8 { \tl_const:cx { c__kernel_ #3 case_ \char_generate:nn {##1} { 12 } \char_generate:nn {##2} { 12 } _tl } { \exp_after:wN \exp_after:wN \exp_after:wN \exp_not:N \char_generate:nn {##5} { 13 } \exp_after:wN \exp_after:wN \exp_after:wN \exp_not:N \char_generate:nn {##6} { 13 } } } \use:x { \@@_tmp:w \char_to_utfviii_bytes:n { "#1 } \char_to_utfviii_bytes:n { "#2 } } \group_end: } \@@_tmp:w { 0345 } { 0399 } { upper } \@@_tmp:w { 03C2 } { 03A3 } { upper } \@@_tmp:w { 03D0 } { 0392 } { upper } \@@_tmp:w { 03D1 } { 0398 } { upper } \@@_tmp:w { 03D5 } { 03A6 } { upper } \@@_tmp:w { 03D6 } { 03A0 } { upper } \@@_tmp:w { 03F0 } { 039A } { upper } \@@_tmp:w { 03F1 } { 03A1 } { upper } \@@_tmp:w { 03F4 } { 03B8 } { lower } \@@_tmp:w { 03F5 } { 0395 } { upper } % \end{macrocode} % Odds and ends that are not simple one-to-one mappings. These % are still two-octet code points. % \begin{macrocode} \cs_set_protected:Npn \@@_tmp:w #1#2#3 { \group_begin: \cs_set_protected:Npn \@@_tmp:w ##1##2##3##4 { \tl_const:cn { c__kernel_ #3 case_ \char_generate:nn {##1} { 12 } \char_generate:nn {##2} { 12 } _tl } {#2} } \use:x { \@@_tmp:w \char_to_utfviii_bytes:n { "#1 } } \group_end: } \@@_tmp:w { 00DF } { SS } { upper } \@@_tmp:w { 00DF } { Ss } { title } \@@_tmp:w { 0131 } { I } { upper } % \end{macrocode} % Greek support: the three-octet code points. % \begin{macrocode} \cs_set_protected:Npn \@@_tmp:nnnnnn #1#2#3#4#5#6#7 { \tl_const:cx { c__kernel_ #1 case_ \char_generate:nn {#2} { 12 } \char_generate:nn {#3} { 12 } \char_generate:nn {#4} { 12 } _tl } { \exp_after:wN \exp_after:wN \exp_after:wN \exp_not:N \char_generate:nn {#5} { 13 } \exp_after:wN \exp_after:wN \exp_after:wN \exp_not:N \char_generate:nn {#6} { 13 } \exp_after:wN \exp_after:wN \exp_after:wN \exp_not:N \char_generate:nn {#7} { 13 } } } \cs_set_protected:Npn \@@_tmp:w #1#2#3#4#5#6#7#8 { \tl_const:cx { c__kernel_lowercase_ \char_generate:nn {#1} { 12 } \char_generate:nn {#2} { 12 } \char_generate:nn {#3} { 12 } _tl } { \exp_after:wN \exp_after:wN \exp_after:wN \exp_not:N \char_generate:nn {#5} { 13 } \exp_after:wN \exp_after:wN \exp_after:wN \exp_not:N \char_generate:nn {#6} { 13 } \exp_after:wN \exp_after:wN \exp_after:wN \exp_not:N \char_generate:nn {#7} { 13 } } \@@_tmp:nnnnnn { upper } {#5} {#6} {#7} {#1} {#2} {#3} \@@_tmp:nnnnnn { title } {#5} {#6} {#7} {#1} {#2} {#3} } \@@_loop:nn { 1F08 } { 1F00 } { 1F09 } { 1F01 } { 1F0A } { 1F02 } { 1F0B } { 1F03 } { 1F0C } { 1F04 } { 1F0D } { 1F05 } { 1F0E } { 1F06 } { 1F0F } { 1F07 } { 1F18 } { 1F10 } { 1F19 } { 1F11 } { 1F1A } { 1F12 } { 1F1B } { 1F13 } { 1F1C } { 1F14 } { 1F1D } { 1F15 } { 1F28 } { 1F20 } { 1F29 } { 1F21 } { 1F2A } { 1F22 } { 1F2B } { 1F23 } { 1F2C } { 1F24 } { 1F2D } { 1F25 } { 1F2E } { 1F26 } { 1F2F } { 1F27 } { 1F38 } { 1F30 } { 1F39 } { 1F31 } { 1F3A } { 1F32 } { 1F3B } { 1F33 } { 1F3C } { 1F34 } { 1F3D } { 1F35 } { 1F3E } { 1F36 } { 1F3F } { 1F37 } { 1F48 } { 1F40 } { 1F49 } { 1F41 } { 1F4A } { 1F42 } { 1F4B } { 1F43 } { 1F4C } { 1F44 } { 1F4D } { 1F45 } { 1F59 } { 1F51 } { 1F5B } { 1F53 } { 1F5D } { 1F55 } { 1F5F } { 1F57 } { 1F68 } { 1F60 } { 1F69 } { 1F61 } { 1F6A } { 1F62 } { 1F6B } { 1F63 } { 1F6C } { 1F64 } { 1F6D } { 1F65 } { 1F6E } { 1F66 } { 1F6F } { 1F67 } { 1FBA } { 1F70 } { 1FBB } { 1F71 } { 1FC8 } { 1F72 } { 1FC9 } { 1F73 } { 1FCA } { 1F74 } { 1FCB } { 1F75 } { 1FDA } { 1F76 } { 1FDB } { 1F77 } { 1FF8 } { 1F78 } { 1FF9 } { 1F79 } { 1FEA } { 1F7A } { 1FEB } { 1F7B } { 1FFA } { 1F7C } { 1FFB } { 1F7D } { 1F88 } { 1F80 } { 1F89 } { 1F81 } { 1F8A } { 1F82 } { 1F8B } { 1F83 } { 1F8C } { 1F84 } { 1F8D } { 1F85 } { 1F8E } { 1F86 } { 1F8F } { 1F87 } { 1F98 } { 1F90 } { 1F99 } { 1F91 } { 1F9A } { 1F92 } { 1F9B } { 1F93 } { 1F9C } { 1F94 } { 1F9D } { 1F95 } { 1F9E } { 1F96 } { 1F9F } { 1F97 } { 1FA8 } { 1FA0 } { 1FA9 } { 1FA1 } { 1FAA } { 1FA2 } { 1FAB } { 1FA3 } { 1FAC } { 1FA4 } { 1FAD } { 1FA5 } { 1FAE } { 1FA6 } { 1FAF } { 1FA7 } { 1FB8 } { 1FB0 } { 1FB9 } { 1FB1 } { 1FBC } { 1FB3 } { 1FCC } { 1FC3 } { 1FD8 } { 1FD0 } { 1FD9 } { 1FD1 } { 1FE8 } { 1FE0 } { 1FE9 } { 1FE1 } { 1FEC } { 1FE5 } { 1FFC } { 1FF3 } \q_recursion_tail ? \q_recursion_stop % \end{macrocode} % One three-octet special case for Greek: it also moves to two-octets! % \begin{macrocode} \cs_set_protected:Npn \@@_tmp:w #1#2#3 { \group_begin: \cs_set_protected:Npn \@@_tmp:w ##1##2##3##4##5##6##7##8 { \tl_const:cx { c__kernel_ #3 case_ \char_generate:nn {##1} { 12 } \char_generate:nn {##2} { 12 } \char_generate:nn {##3} { 12 } _tl } { \exp_after:wN \exp_after:wN \exp_after:wN \exp_not:N \char_generate:nn {##5} { 13 } \exp_after:wN \exp_after:wN \exp_after:wN \exp_not:N \char_generate:nn {##6} { 13 } } } \use:x { \@@_tmp:w \char_to_utfviii_bytes:n { "#1 } \char_to_utfviii_bytes:n { "#2 } } \group_end: } \@@_tmp:w { 1FBE } { 0399 } { upper } } \group_end: % \end{macrocode} % % The (fixed) look-up mappings for letter-like control sequences. % \begin{macrocode} \group_begin: \cs_set_protected:Npn \@@_change_case_setup:NN #1#2 { \quark_if_recursion_tail_stop:N #1 \tl_const:cn { c_@@_lowercase_ \token_to_str:N #1 _tl } { #2 } \tl_const:cn { c_@@_uppercase_ \token_to_str:N #2 _tl } { #1 } \@@_change_case_setup:NN } \@@_change_case_setup:NN \AA \aa \AE \ae \DH \dh \DJ \dj \IJ \ij \L \l \NG \ng \O \o \OE \oe \SS \ss \TH \th \q_recursion_tail ? \q_recursion_stop \tl_const:cn { c_@@_uppercase_ \token_to_str:N \i _tl } { I } \tl_const:cn { c_@@_uppercase_ \token_to_str:N \j _tl } { J } \group_end: % \end{macrocode} % % To deal with possible encoding-specific extensions to \tn{@uclclist}, % we check at the end of the preamble. This will therefore only apply % to \LaTeXe{} package mode. % \begin{macrocode} \tl_if_exist:NT \@expl@finalise@setup@@@@ { \tl_gput_right:Nn \@expl@finalise@setup@@@@ { \tl_gput_right:Nn \@kernel@after@begindocument { \group_begin: \cs_set_protected:Npn \@@_change_case_setup:Nn #1#2 { \quark_if_recursion_tail_stop:N #1 \tl_if_single_token:nT {#2} { \cs_if_exist:cF { c_@@_uppercase_ \token_to_str:N #1 _tl } { \tl_const:cn { c_@@_uppercase_ \token_to_str:N #1 _tl } { #2 } } \cs_if_exist:cF { c_@@_lowercase_ \token_to_str:N #2 _tl } { \tl_const:cn { c_@@_lowercase_ \token_to_str:N #2 _tl } { #1 } } } \@@_change_case_setup:Nn } \exp_after:wN \@@_change_case_setup:Nn \@uclclist \q_recursion_tail ? \q_recursion_stop \group_end: } } } % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \end{implementation} % % \PrintIndex