From b3225c4301e7724de481f410444fef5463799b9d Mon Sep 17 00:00:00 2001
From: Norbert Preining <norbert@preining.info>
Date: Tue, 14 Jan 2020 03:00:43 +0000
Subject: CTAN sync 202001140300

---
 macros/latex/contrib/l3kernel/l3text-case.dtx | 1421 +++++++++++++++++++++++++
 1 file changed, 1421 insertions(+)
 create mode 100644 macros/latex/contrib/l3kernel/l3text-case.dtx

(limited to 'macros/latex/contrib/l3kernel/l3text-case.dtx')

diff --git a/macros/latex/contrib/l3kernel/l3text-case.dtx b/macros/latex/contrib/l3kernel/l3text-case.dtx
new file mode 100644
index 0000000000..3c608eacb9
--- /dev/null
+++ b/macros/latex/contrib/l3kernel/l3text-case.dtx
@@ -0,0 +1,1421 @@
+% \iffalse meta-comment
+%
+%% File: l3text-case.dtx
+%
+% Copyright (C) 2020 The LaTeX3 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 \LaTeX3 Project\thanks
+%    {^^A
+%      E-mail:
+%        \href{mailto:latex-team@latex-project.org}
+%          {latex-team@latex-project.org}^^A
+%    }^^A
+% }
+%
+% \date{Released 2020-01-12}
+%
+% \maketitle
+%
+% \begin{documentation}
+%
+% \end{documentation}
+%
+% \begin{implementation}
+%
+% \section{\pkg{l3text-case} implementation}
+%
+%    \begin{macrocode}
+%<*initex|package>
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%<@@=text>
+%    \end{macrocode}
+%
+% \subsection{Case changing}
+%
+% \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}
+% \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_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_char: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:
+    \@@_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 { ~ }
+    \@@_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
+  {
+    \quark_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
+  {
+    \quark_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
+  {
+    \quark_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
+  {
+    \quark_if_recursion_tail_stop_do:Nn #4
+      { \use:c { @@_change_case_letterlike_ #1 :nnN } {#1} {#2} #3 }
+    \cs_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:Npx \@@_change_case_char_lower:nnN #1#2#3
+  {
+    \exp_not:N \cs_if_exist_use:cF { @@_change_case_lower_ #2 :nnnN }
+      {
+        \bool_lazy_or:nnTF
+          { \sys_if_engine_luatex_p: }
+          { \sys_if_engine_xetex_p: }
+          { \exp_not:N \@@_change_case_lower_sigma:nnnN }
+          { \exp_not:N \@@_change_case_char: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:nnT
+  { \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}
+%   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
+  {
+    \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} }
+        {#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}
+\bool_lazy_or:nnTF
+  { \sys_if_engine_luatex_p: }
+  { \sys_if_engine_xetex_p: }
+  {
+    \cs_new:Npn \@@_change_case_char:nnnN #1#2#3#4
+      {
+        \@@_change_case_store:e
+          { \use:c { char_ #1 case :N } #4 }
+        \use:c { @@_change_case_char_next_ #2 :nn } {#2} {#3}
+      }
+  }
+  {
+    \cs_new: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_store:e{ \use:c { char_ #1 case :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: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}
+%
+% \begin{macro}[EXP]{\@@_change_case_upper_de-alt:nnnN}
+%   A simple alternative version for German.
+%    \begin{macrocode}
+\bool_lazy_or:nnT
+  { \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 }
+      }
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_change_case_upper_el:nnnN}
+% \begin{macro}[EXP]{\@@_change_case_upper_el:nnnn}
+% \begin{macro}[EXP]{\@@_change_case_upper_el_aux:nnnN}
+% \begin{macro}[EXP]{\@@_change_case_upper_el_loop:nnw}
+% \begin{macro}[EXP]{\@@_change_case_upper_el:nnN}
+% \begin{macro}[EXP]{\@@_change_case_if_greek:nTF}
+%   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.
+%    \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:nnnn
+              { \char_to_nfd:N #4 } {#1} {#2} {#3}
+          }
+          { \@@_change_case_char:nnnN {#1} {#2} {#3} #4 }
+      }
+    \cs_new:Npn \@@_change_case_upper_el:nnnn #1#2#3#4
+      { \@@_change_case_upper_el_aux:nnnN {#2} {#3} {#4} #1 }
+    \cs_new:Npn \@@_change_case_upper_el_aux:nnnN #1#2#3#4
+      {
+        \@@_change_case_store:e { \use:c { char_ #1 case:N } #4 }
+        \@@_change_case_upper_el_loop:nnw {#2} {#3}
+      }
+    \cs_new:Npn \@@_change_case_upper_el_loop:nnw
+      #1#2#3 \q_recursion_stop
+      {
+        \tl_if_head_is_N_type:nTF {#3}
+          { \@@_change_case_upper_el:nnN }
+          { \@@_change_case_loop:nnw }
+            {#1} {#2} #3 \q_recursion_stop
+      }
+%    \end{macrocode}
+%   In addition to the Greek accents, we list three cases here where an
+%   accent outside the Greek range has a \text{nfd} that would make it
+%   equivalent. That includes U+0344, which has to insert U+0308.
+%    \begin{macrocode}
+    \cs_new:Npn \@@_change_case_upper_el:nnN #1#2#3
+      {
+        \token_if_cs:NTF #3
+          { \@@_change_case_loop:nnw {#1} {#2} #3 }
+          {
+            \int_compare:nNnTF { `#3 } = { "0308 }
+              {
+                \@@_change_case_store:n {#3}
+                \@@_change_case_upper_el_loop:nnw {#1} {#2}
+              }
+              {
+                \bool_lazy_any:nTF
+                  {
+                    { \int_compare_p:nNn { `#3 } = { "0300 } }
+                    { \int_compare_p:nNn { `#3 } = { "0301 } }
+                    { \int_compare_p:nNn { `#3 } = { "0304 } }
+                    { \int_compare_p:nNn { `#3 } = { "0306 } }
+                    { \int_compare_p:nNn { `#3 } = { "0308 } }
+                    { \int_compare_p:nNn { `#3 } = { "0313 } }
+                    { \int_compare_p:nNn { `#3 } = { "0314 } }
+                    { \int_compare_p:nNn { `#3 } = { "0342 } }
+                    { \int_compare_p:nNn { `#3 } = { "0340 } }
+                    { \int_compare_p:nNn { `#3 } = { "0341 } }
+                    { \int_compare_p:nNn { `#3 } = { "0343 } }
+                  }
+                  { \@@_change_case_upper_el_loop:nnw {#1} {#2} }
+                  {
+                    \int_compare:nNnTF { `#3 } = { "0344 }
+                      {
+                        \@@_change_case_store:e
+                          {
+                            \char_generate:nn { "0308 }
+                              { \@@_char_catcode:N #3 }
+                          }
+                        \@@_change_case_upper_el_loop:nnw {#1} {#2}
+                      }
+                      {
+                        \int_compare:nNnTF { `#3 } = { "0345 }
+                          { \@@_change_case_loop:nnw {#1} {#2} }
+                          { \@@_change_case_loop:nnw {#1} {#2} #3 }
+                      }
+                  }
+              }
+          }
+      }
+    \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}
+% \end{macro}
+% \end{macro}
+% \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:nnN}
+% \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:nnN }
+          {
+            \@@_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:nnN #1#2#3
+      {
+        \bool_lazy_or:nnTF
+          { \token_if_cs_p:N #3 }
+          { ! \int_compare_p:nNn { `#3 } = { "0307 } }
+          {
+            \@@_change_case_store:e 
+              { \char_generate:nn { "0131 } { \@@_char_catcode:N #3 } }
+            \@@_change_case_loop:nnw {#1} {#2} #3
+          }
+          {
+            \@@_change_case_store:e
+              { \char_generate:nn { "0069 } { \@@_char_catcode:N #3 } }
+            \@@_change_case_loop:nnw {#1} {#2}
+          }
+      }
+  }
+%    \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
+%   }
+%  For cases where there is an $8$-bit option in the |T1| font set up,
+%  a variant is provided in both cases.
+%    \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 }
+                  }
+              }
+            \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 }
+    }
+\group_end:
+%    \end{macrocode}
+% \end{variable}
+%
+% For $8$-bit engines we now need to define the case-change data for
+% the multi-octet mappings. 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 }
+        \q_recursion_tail ?
+        \q_recursion_stop
+      \cs_set_protected:Npn \@@_tmp:w #1#2#3
+        {
+          \group_begin:
+            \cs_set_protected:Npn \@@_tmp:w ##1##2##3##4
+              {
+                \tl_const:cx
+                  {
+                    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 }
+    }
+  \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}
+%
+%    \begin{macrocode}
+%</initex|package>
+%    \end{macrocode}
+%
+% \end{implementation}
+%
+% \PrintIndex
-- 
cgit v1.2.3

