% \iffalse meta-comment
%
%% File: l3text-case.dtx
%
% Copyright (C) 2020,2021 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}
%</driver>
% \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 2021-02-18}
%
% \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:nnNn}
% \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_aux:nnnN}
% \begin{macro}[EXP]{\@@_change_case_char_UTFviii:nnnNN}
% \begin{macro}[EXP]{\@@_change_case_char_UTFviii:nnnNNN}
% \begin{macro}[EXP]{\@@_change_case_char_UTFviii:nnnNNNNN}
% \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:nnNn {#1} {#2} #3 }
      }
      { \@@_change_case_exclude:nnNN {#1} {#2} #3 }
  }
\cs_new:Npn \@@_change_case_exclude:nnNn #1#2#3#4
  {
    \@@_change_case_store:n { #3 {#4} }
    \@@_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.)
%    \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_aux: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 }
              {
                \int_compare:nNnTF { `#4 } < { "F0 }
                  { \@@_change_case_char_UTFviii:nnnNNN }
                  { \@@_change_case_char_UTFviii:nnnNNNN }
              }
                {#1} {#2} {#3} #4
          }
          { \@@_change_case_char_aux:nnnN {#1} {#2} {#3} #4 }
       }
    \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:nnnNNNNN #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_@@_ #1 case_ \tl_to_str:n {#4} _tl }
          {
            \@@_change_case_store:v
              { c_@@_ #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}
%
% \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.
% These need a list of what code points are
% doable in |T1| so the list is hard coded (there's no saving in loading
% the mappings dynamically). All of the straight-forward ones have two
% octets, so that is taken as read.
%    \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_@@_ #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_@@_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) as this is doable using a classical \tn{MakeUppercase}
% approach.
%    \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_@@_ #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_@@_ #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_@@_ #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_@@_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_@@_ #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}
\cs_if_exist:cT { @uclclist }
  {
    \AtBeginDocument
      {
        \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}
%</package>
%    \end{macrocode}
%
% \end{implementation}
%
% \PrintIndex
