summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/source/latex/l3kernel/l3candidates.dtx
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2015-03-02 22:00:39 +0000
committerKarl Berry <karl@freefriends.org>2015-03-02 22:00:39 +0000
commit31370a61758b71ae992cb45a6e475e5d0c4d921f (patch)
treebf7f3c65116794b8a57092935cbe1960e0b1cdb5 /Master/texmf-dist/source/latex/l3kernel/l3candidates.dtx
parentd085026f8b51358f65d4f81412f6af6a5094e14a (diff)
l3kernel (2mar15)
git-svn-id: svn://tug.org/texlive/trunk@36423 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/source/latex/l3kernel/l3candidates.dtx')
-rw-r--r--Master/texmf-dist/source/latex/l3kernel/l3candidates.dtx1227
1 files changed, 712 insertions, 515 deletions
diff --git a/Master/texmf-dist/source/latex/l3kernel/l3candidates.dtx b/Master/texmf-dist/source/latex/l3kernel/l3candidates.dtx
index 5e6956729dc..41e883c9397 100644
--- a/Master/texmf-dist/source/latex/l3kernel/l3candidates.dtx
+++ b/Master/texmf-dist/source/latex/l3kernel/l3candidates.dtx
@@ -38,7 +38,7 @@
\documentclass[full]{l3doc}
%</driver>
%<*driver|package>
-\GetIdInfo$Id: l3candidates.dtx 5523 2015-02-12 22:10:34Z joseph $
+\GetIdInfo$Id: l3candidates.dtx 5544 2015-03-01 18:30:00Z joseph $
{L3 Experimental additions to l3kernel}
%</driver|package>
%<*driver>
@@ -806,7 +806,7 @@
% \end{texnote}
% \end{function}
%
-% \begin{function}[rEXP, added = 2014-06-30, updated = 2015-02-12]^^A
+% \begin{function}[EXP, added = 2014-06-30, updated = 2015-02-18]^^A
% {
% \tl_lower_case:n, \tl_upper_case:n, \tl_mixed_case:n,
% \tl_lower_case:nn, \tl_upper_case:nn, \tl_mixed_case:nn
@@ -820,32 +820,60 @@
% change the case of characters as discussed below. The character code of
% the characters replaced may be arbitrary: the replacement characters will
% have standard document-level category codes ($11$ for letters, $12$ for
-% letter-like characters which can also be case-changed).
+% letter-like characters which can also be case-changed). Begin-group and
+% end-group characters in the \meta{tokens} are normalized and become |{|
+% and |}|, respectively.
+%
+% Importantly, notice that these functions are intended for working with
+% user text for typesetting. For case changing programmatic data see the
+% \pkg{l3str} module and discussion there of \cs{str_lower_case:n},
+% \cs{str_upper_case:n} and \cs{str_fold_case:n}.
% \end{function}
%
-% The functions are \texttt{x}-type expandable and will expand input in an
-% analogous way (though they are themselves expandable). In particular, input
-% in the form of token lists or expandable functions will be expanded
+% The functions perform expansion on the input in most cases. In particular,
+% input in the form of token lists or expandable functions will be expanded
% \emph{unless} it falls within one of the special handling classes described
-% below. As such, in package mode \LaTeXe{} \enquote{robust} commands should
-% be converted to engine-protected versions using for example the
-% \tn{robustify} command from the \pkg{etoolbox} package. Begin-group and
-% end-group characters in the \meta{tokens} are normalized and become |{| and
-% |}|, respectively. Case changing will take place inside groups but not in
-% math mode material so for example
+% below. This expansion approach means that in general the result of case
+% changing will match the \enquote{natural} outcome expected from a
+% \enquote{functional} approach to case modification. For example
% \begin{verbatim}
-% \tl_upper_case:n { Some~text~$y = mx + c$~with~{Braces} }
+% \tl_set:Nn \l_tmpa_tl { hello }
+% \tl_upper_case:n { \l_tmpa_tl \c_space_tl world }
% \end{verbatim}
-% will become
+% will produce
% \begin{verbatim}
-% SOME~TEXT~$y = mx + c$~WITH~{BRACES}
+% HELLO WORLD
% \end{verbatim}
+% The expansion approach taken means that in package mode any \LaTeXe{}
+% \enquote{robust} commands which may appear in the input should be converted
+% to engine-protected versions using for example the \tn{robustify} command
+% from the \pkg{etoolbox} package.
+%
+% \begin{variable}{\l_tl_case_change_math_tl}
+% Case changing will not take place within math mode material so for example
+% \begin{verbatim}
+% \tl_upper_case:n { Some~text~$y = mx + c$~with~{Braces} }
+% \end{verbatim}
+% will become
+% \begin{verbatim}
+% SOME TEXT $y = mx + c$ WITH {BRACES}
+% \end{verbatim}
+% Material inside math mode is left entirely unchanged: in particular, no
+% expansion is undertaken.
+%
+% Detection of math mode is controlled by the list of tokens in
+% \cs{l_tl_case_change_math_tl}, which should be in open--close pairs. In
+% package mode the standard settings is
+% \begin{verbatim}
+% $ $ \( \)
+% \end{verbatim}
+% \end{variable}
%
% \begin{variable}{\l_tl_case_change_exclude_tl}
% Case changing can be prevented by using any command on the list
% \cs{l_tl_case_change_exclude_tl}. Each entry should be a function
-% to be followed by one argument: the latter will be preserved as-is.
-% Thus for example following
+% to be followed by one argument: the latter will be preserved as-is
+% with no expansion. Thus for example following
% \begin{verbatim}
% \tl_put_right:Nn \l_tl_case_change_exclude_tl { \NoChangeCase }
% \end{verbatim}
@@ -856,34 +884,24 @@
% \end{verbatim}
% will result in
% \begin{verbatim}
-% SOME~TEXT~$y = mx + c$~WITH~\NoChangeCase {Protection}
+% SOME TEXT $y = mx + c$ WITH \NoChangeCase {Protection}
% \end{verbatim}
% Notice that the case changing mapping preserves the inclusion of
% the escape functions: it is left to other code to provide suitable
-% definitions (typically equivalent to \cs{use:n}). In particular, these
-% functions are protected from further expansion (returned using
-% \cs{exp_not:n}) as so will not be lost inside an \texttt{x}-type expansion.
+% definitions (typically equivalent to \cs{use:n}). In particular, the
+% result of case changing is returned protected by \cs{exp_not:n}.
%
% When used with \LaTeXe{} the commands |\cite|, |\ensuremath|, |\label|
% and |\ref| are automatically included in the list for exclusion from
% case changing.
% \end{variable}
%
-% \begin{variable}{\l_tl_case_change_math_tl}
-% Case changing is automatically skipped in math mode, as shown in the
-% demonstrations above. This is controlled by the list of tokens in
-% \cs{l_tl_case_change_math_tl}, which should be in open--close pairs. In
-% package mode the standard settings is
-% \begin{verbatim}
-% $ $ \( \)
-% \end{verbatim}
-% \end{variable}
-%
-% In addition to this functionality, the case change system will also
+% In package mode, the case change system will also
% convert text stored using the \LaTeXe{} \enquote{LICR} approach. This
% will upper/lower case tokens as implemented for the font encodings
% \texttt{T1}, \texttt{T2}, \texttt{T5} and \texttt{LGR} (see the behaviour of
-% the \LaTeXe{} command \tn{MakeUppercase}).
+% the \LaTeXe{} command \tn{MakeUppercase}). Note that these commands will
+% automatically be protected from expansion.
%
% \enquote{Mixed} case conversion may be regarded informally as converting the
% first character of the \meta{tokens} to upper case and the rest to lower
@@ -929,8 +947,10 @@
% (for example in the mapping i to I-dot in Turkish): in these cases the
% input is left unchanged.
%
-% Context-sensitive mappings are enabled: language-dependent cases are
-% discussed below.
+% Context-sensitive mappings are enabled: language-dependent cases are
+% discussed below. All context detection works before any expansion of
+% following tokens and thus any control sequences are taken as the end of
+% the current \enquote{word}/character context.
%
% \begin{variable}{\l_tl_change_case_after_final_sigma_tl}
% The \enquote{final sigma} rule for Greek letters is enabled and active for
@@ -3059,23 +3079,16 @@
% to allow for all of the special cases. These functions also require the
% appropriate data extracted from the Unicode documentation (either manually
% or automatically), which is covered by \pkg{l3unicode-data}.
-%
-% \begin{macro}[EXP]{\str_case:nVF, \str_case:nvF, \str_case:nvTF}
-% \begin{macro}[EXP]{\tl_if_blank:fTF}
+%
% \begin{macro}[EXP]{\tl_if_head_eq_catcode:oNTF}
-% Required variants.
+% Extra variants.
% \begin{macrocode}
-\cs_generate_variant:Nn \str_case:nnF { nV , nv }
-\cs_generate_variant:Nn \str_case:nnTF { nv }
-\cs_generate_variant:Nn \tl_if_blank:nTF { f }
\cs_generate_variant:Nn \tl_if_head_eq_catcode:nNTF { o }
% \end{macrocode}
% \end{macro}
-% \end{macro}
-% \end{macro}
-%
-% \begin{macro}[rEXP]{\tl_lower_case:n, \tl_upper_case:n, \tl_mixed_case:n}
-% \begin{macro}[rEXP]{\tl_lower_case:nn, \tl_upper_case:nn, \tl_mixed_case:nn}
+%
+% \begin{macro}[EXP]{\tl_lower_case:n, \tl_upper_case:n, \tl_mixed_case:n}
+% \begin{macro}[EXP]{\tl_lower_case:nn, \tl_upper_case:nn, \tl_mixed_case:nn}
% The user level functions here are all wrappers around the internal
% functions for case changing. Note that \cs{tl_mixed_case:nn} could be
% done without an internal, but this way the logic is slightly clearer as
@@ -3092,56 +3105,64 @@
% \end{macro}
%
% \begin{macro}[aux, EXP]{\@@_change_case:nnn}
+% \begin{macro}[aux, EXP]{\@@_change_case_aux:nnn}
% \begin{macro}[aux, EXP]{\@@_change_case_loop:wnn}
-% \begin{macro}[aux, EXP]{\@@_change_case_N_type:Nwnn}
-% \begin{macro}[aux, EXP]{\@@_change_case_N_type:NNN}
-% \begin{macro}[aux, EXP]{\@@_change_case_N_type:Nnnn}
+% \begin{macro}[aux, EXP]
+% {
+% \@@_change_case_output:nwn ,
+% \@@_change_case_output:Vwn ,
+% \@@_change_case_output:own ,
+% \@@_change_case_output:fwn ,
+% }
+% \begin{macro}[aux, EXP]{\@@_change_case_end:wn}
% \begin{macro}[aux, EXP]{\@@_change_case_group:nwnn}
% \begin{macro}[aux, EXP]{\@@_change_case_space:wnn}
+% \begin{macro}[aux, EXP]{\@@_change_case_N_type:Nwnn}
+% \begin{macro}[aux, EXP]{\@@_change_case_N_type:NNNnnn}
+% \begin{macro}[aux, EXP]{\@@_change_case_math:NNNnnn}
+% \begin{macro}[aux, EXP]{\@@_change_case_math_loop:wNNnn}
+% \begin{macro}[aux, EXP]{\@@_change_case_math:NwNNnn}
+% \begin{macro}[aux, EXP]{\@@_change_case_math_group:nwNNnn}
+% \begin{macro}[aux, EXP]{\@@_change_case_math_space:wNNnn}
+% \begin{macro}[aux, EXP]{\@@_change_case_N_type:Nnnn}
+% \begin{macro}[aux, EXP]{\@@_change_case_char:Nnn}
+% \begin{macro}[aux, EXP]{\@@_change_case_char:Nn}
% \begin{macro}[aux, EXP]{\@@_change_case_char:NNNNNNNNn}
-% \begin{macro}[aux, EXP]{\@@_change_case_math:NNNn}
-% \begin{macro}[aux, EXP]{\@@_change_case_math_loop:wNN}
-% \begin{macro}[aux, EXP]{\@@_change_case_math:NwNN}
-% \begin{macro}[aux, EXP]{\@@_change_case_math_group:nwNN}
-% \begin{macro}[aux, EXP]{\@@_change_case_math_space:wNN}
% \begin{macro}[aux, EXP]{\@@_change_case_cs:Nnnn}
% \begin{macro}[aux, EXP]{\@@_change_case_cs:nNnnn}
-% \begin{macro}[aux, EXP]{\@@_change_case_cs_three:w}
-% \begin{macro}[aux, EXP]{\@@_change_case_cs_four:w}
-% \begin{macro}[aux, EXP]{\@@_change_case_cs:Nnnnn}
+% \begin{macro}[aux, EXP]{\@@_change_case_cs_three:NNNw}
+% \begin{macro}[aux, EXP]{\@@_change_case_cs_four:NNNNw}
% \begin{macro}[aux, EXP]{\@@_change_case_cs_cyr:NnNNNNw}
+% \begin{macro}[aux, EXP]{\@@_change_case_cs_type:Nnnnn}
+% \begin{macro}[aux, EXP]{\@@_change_case_cs_type:nnn}
% \begin{macro}[aux, EXP]{\@@_change_case_cs:N}
% \begin{macro}[aux, EXP]{\@@_change_case_cs:NN}
% \begin{macro}[aux, EXP]{\@@_change_case_cs:NNn}
-% \begin{macro}[aux, EXP]{\@@_change_case_cs_expand:N}
+% \begin{macro}[aux, EXP]{\@@_change_case_cs_expand:Nnw}
% \begin{macro}[aux, EXP]{\@@_change_case_cs_expand:NN}
-% \begin{macro}[aux, EXP]^^A
-% {
-% \@@_change_case_lower_sigma:Nnn ,
-% \@@_change_case_upper_sigma:Nnn ,
-% \@@_change_case_mixed_sigma:Nnn
-% }
-% \begin{macro}[aux]{\@@_change_case_lower_sigma:Nw}
-% \begin{macro}[aux]{\@@_change_case_lower_sigma_loop:NN}
-% The mechanism for the core conversion of case is based on the approach used
-% in \cs{@@_act:NNNnn}. Thus the idea is to use a loop which is will grab
-% the entire token list plus a quark: the latter is used as an end marker and
-% to avoid any brace stripping. Depending on the nature of the first item
-% in the grabbed argument, it can either processed as a single token,
-% treated as a group or treated as a space (the latter requires special
-% treatment). In contrast to \cs{@@_act:NNNnn}, there is no need for this
-% process to be \texttt{f}-type expandable: things are done using only
-% \texttt{x}-type requirements. Also, for \enquote{normal} tokens there is
-% a bit more work to do here. To allow math mode escape for paired tokens,
-% there is first a search to see if we've picked up on of the possible
-% openers. If we have, there is a separate route to follow. Otherwise
-% we need to choose between control sequences and characters: only the
-% latter allow character code to be used for the conversion lookup.
+% \begin{macro}[aux, EXP]{\@@_change_case_cs_expand:NNnw}
+% The mechanism for the core conversion of case is based on the idea that
+% we can use a loop to grab the entire token list plus a quark: the latter is
+% used as an end marker and to avoid any brace stripping. Depending on the
+% nature of the first item in the grabbed argument, it can either processed
+% as a single token, treated as a group or treated as a space. These
+% different cases all work by re-reading |#1| in the appropriate way, hence
+% the repetition of |#1 \q_recursion_stop|.
% \begin{macrocode}
\cs_new:Npn \@@_change_case:nnn #1#2#3
{
- \@@_change_case_loop:wnn #3
- \q_recursion_tail \q_recursion_stop {#1} {#2}
+ \etex_unexpanded:D \exp_after:wN
+ {
+ \tex_romannumeral:D
+ \@@_change_case_aux:nnn {#1} {#2} {#3}
+ }
+ }
+\cs_new:Npn \@@_change_case_aux:nnn #1#2#3
+ {
+ \group_align_safe_begin:
+ \@@_change_case_loop:wnn
+ #3 \q_recursion_tail \q_recursion_stop {#1} {#2}
+ \@@_change_case_result:n { }
}
\cs_new:Npn \@@_change_case_loop:wnn #1 \q_recursion_stop
{
@@ -3154,27 +3175,140 @@
}
#1 \q_recursion_stop
}
+% \end{macrocode}
+% Earlier versions of the code where only \texttt{x}-type expandable rather
+% than \texttt{f}-type: this causes issues with nesting and so the slight
+% performance hit is taken for a better outcome in usability terms. Setting
+% up for \texttt{f}-type expandability has two requirements: a marker
+% token after the main loop (see above) and a mechanism to \enquote{load}
+% and finalise the result. That is handled in the code below, which includes
+% the necessary material to end the \tn{romannumeral} expansion.
+% \begin{macrocode}
+\cs_new:Npn \@@_change_case_output:nwn #1#2 \@@_change_case_result:n #3
+ { #2 \@@_change_case_result:n { #3 #1 } }
+\cs_generate_variant:Nn \@@_change_case_output:nwn { V , o , f }
+\cs_new:Npn \@@_change_case_end:wn #1 \@@_change_case_result:n #2
+ {
+ \group_align_safe_end:
+ \c_zero
+ #2
+ }
+% \end{macrocode}
+% Handling for the cases where the current argument is a brace group or
+% a space is relatively easy. For the brace case, the routine works
+% recursively, using the expandability of the mechanism to ensure that the
+% result is finalised before storage. For the space case it is simply a
+% question of removing the space in the input and storing it in the output.
+% In both cases, and indeed for the \texttt{N}-type grabber, after removing
+% the current item from the input \cs{@@_change_case_loop:wnn} is inserted
+% in front of the remaining tokens.
+% \begin{macrocode}
+\cs_new:Npn \@@_change_case_group:nwnn #1#2 \q_recursion_stop #3#4
+ {
+ \@@_change_case_output:own
+ {
+ \exp_after:wN
+ {
+ \tex_romannumeral:D
+ \@@_change_case_aux:nnn {#3} {#4} {#1}
+ }
+ }
+ \@@_change_case_loop:wnn #2 \q_recursion_stop {#3} {#4}
+ }
+\exp_last_unbraced:NNo \cs_new:Npn \@@_change_case_space:wnn \c_space_tl
+ {
+ \@@_change_case_output:nwn { ~ }
+ \@@_change_case_loop:wnn
+ }
+% \end{macrocode}
+% For \texttt{N}-type arguments there are several stages to the approach.
+% First, a simply check for the end-of-input marker, which if found triggers
+% the final clean up and output step. Assuming that is not the case, the
+% first check is for math-mode escaping: this test can encompass control
+% sequences or other \texttt{N}-type tokens so is handled up front.
+% \begin{macrocode}
\cs_new:Npn \@@_change_case_N_type:Nwnn #1#2 \q_recursion_stop
{
- \quark_if_recursion_tail_stop_do:Nn #1 { \use_none:nnn }
- \exp_after:wN \@@_change_case_N_type:NNN
+ \quark_if_recursion_tail_stop_do:Nn #1
+ { \@@_change_case_end:wn }
+ \exp_after:wN \@@_change_case_N_type:NNNnnn
\exp_after:wN #1 \l_tl_case_change_math_tl
\q_recursion_tail ? \q_recursion_stop {#2}
}
-\cs_new:Npn \@@_change_case_N_type:NNN #1#2#3
+% \end{macrocode}
+% Looking for math mode escape first requires a loop over the possible
+% token pairs to see if the current input (|#1|) matches an open-math case
+% (|#2|). If if does then this test loop is ended and a new input-gathering
+% one is begun. The latter simply transfers material from the input to the
+% output without any expansion, testing each \texttt{N}-type token to see
+% if it matches the close-math case required. If that is the situation then
+% the \enquote{math loop} stops and resumes the main loop: as that might
+% be either the standard case-changing one or the mixed-case alternative,
+% it is not hard-coded into the math loop but is rather passed as argument
+% |#3| to \cs{@@_change_case_math:NNNnnn}. If no close-math token is found
+% then the final clean-up will be forced (\emph{i.e.}~there is no assumption
+% of \enquote{well-behaved} code in terms of math mode).
+% \begin{macrocode}
+\cs_new:Npn \@@_change_case_N_type:NNNnnn #1#2#3
{
\quark_if_recursion_tail_stop_do:Nn #2
- { \@@_change_case_N_type:Nnnn #1 }
+ { \@@_change_case_N_type:Nnnn #1 }
\token_if_eq_meaning:NNTF #1 #2
{
\use_i_delimit_by_q_recursion_stop:nw
{
- \@@_change_case_math:NNNn #1 #3
- \@@_change_case_loop:wnn
+ \@@_change_case_math:NNNnnn
+ #1 #3 \@@_change_case_loop:wnn
}
}
- { \@@_change_case_N_type:NNN #1 }
+ { \@@_change_case_N_type:NNNnnn #1 }
}
+\cs_new:Npn \@@_change_case_math:NNNnnn #1#2#3#4
+ {
+ \@@_change_case_output:nwn {#1}
+ \@@_change_case_math_loop:wNNnn #4 \q_recursion_stop #2 #3
+ }
+\cs_new:Npn \@@_change_case_math_loop:wNNnn #1 \q_recursion_stop
+ {
+ \tl_if_head_is_N_type:nTF {#1}
+ { \@@_change_case_math:NwNNnn }
+ {
+ \tl_if_head_is_group:nTF {#1}
+ { \@@_change_case_math_group:nwNNnn }
+ { \@@_change_case_math_space:wNNnn }
+ }
+ #1 \q_recursion_stop
+ }
+\cs_new:Npn \@@_change_case_math:NwNNnn #1#2 \q_recursion_stop #3#4
+ {
+ \token_if_eq_meaning:NNTF \q_recursion_tail #1
+ { \@@_change_case_end:wn }
+ {
+ \@@_change_case_output:nwn {#1}
+ \token_if_eq_meaning:NNTF #1 #3
+ { #4 #2 \q_recursion_stop }
+ { \@@_change_case_math_loop:wNNnn #2 \q_recursion_stop #3#4 }
+ }
+ }
+\cs_new:Npn \@@_change_case_math_group:nwNNnn #1#2 \q_recursion_stop
+ {
+ \@@_change_case_output:nwn { {#1} }
+ \@@_change_case_math_loop:wNNnn #2 \q_recursion_stop
+ }
+\exp_last_unbraced:NNo
+ \cs_new:Npn \@@_change_case_math_space:wNNnn \c_space_tl
+ {
+ \@@_change_case_output:nwn { ~ }
+ \@@_change_case_math_loop:wNNnn
+ }
+% \end{macrocode}
+% Once potential math-mode cases are filtered out the next stage is to
+% test if the token grabbed is a control sequence: they cannot be used in
+% the lookup table and also may require expansion. At this stage the loop
+% code starting \cs{@@_change_case_loop:wnn} is inserted: all subsequent
+% steps in the code which need a look-ahead are coded to rely on this and
+% thus have \texttt{w}-type arguments if they may do a look-ahead.
+% \begin{macrocode}
\cs_new:Npn \@@_change_case_N_type:Nnnn #1#2#3#4
{
\token_if_cs:NTF #1
@@ -3188,97 +3322,46 @@
{ \@@_change_case_cs:N #1 }
}
%</package>
- {
- \cs_if_exist_use:cF { @@_change_case_ #3 _ #4 :Nnn }
- { \use_iii:nnn }
- #1 {#2}
- {
- \use:c { @@_change_case_ #3 _ sigma:Nnn } #1 {#2}
- {
- \exp_after:wN \@@_change_case_char:NNNNNNNNn
- \int_use:N \__int_eval:w 1000000 + `#1 \__int_eval_end:
- #1 {#3}
- }
- }
- }
+ { \@@_change_case_char:Nnn #1 {#3} {#4} }
\@@_change_case_loop:wnn #2 \q_recursion_stop {#3} {#4}
}
-\cs_new:Npn \@@_change_case_group:nwnn #1#2 \q_recursion_stop #3#4
- {
- {
- \@@_change_case_loop:wnn #1
- \q_recursion_tail \q_recursion_stop {#3} {#4}
- }
- \@@_change_case_loop:wnn #2 \q_recursion_stop {#3} {#4}
- }
-\exp_last_unbraced:NNo \cs_new:Npn \@@_change_case_space:wnn \c_space_tl
- {
- \c_space_tl
- \@@_change_case_loop:wnn
- }
% \end{macrocode}
-% Actually look for the char in the appropriate table. This code is a
-% compromise between performance and hash name use. The fastest approach
-% would be to create one token list for each char (and could cover special
-% case control sequences too) and use a hash table lookup. That is
-% quite \enquote{expensive}, so instead a \cs{str_case:nvF}
-% approach is used. One big list would be most efficient in name usage
-% but then gets slow for entries later in the list. Doing a split into
-% $100$ bins falls somewhere between the two extremes.
+% For character tokens there are a couple of potential special cases to
+% handle then the core idea of the loop: a lookup table. The latter uses
+% the character code to spilt what would otherwise be a very long list into
+% $100$ manageable blocks (this is a balance between hash table usage and
+% performance). Notice that the special case code may do a look-ahead so
+% requires a final \texttt{w}-type argument whereas the core lookup table
+% does not and also guarantees an output so \texttt{f}-type expansion may
+% be used to obtain the case-changed result.
% \begin{macrocode}
-\cs_new:Npn \@@_change_case_char:NNNNNNNNn #1#2#3#4#5#6#7#8#9
+\cs_new:Npn \@@_change_case_char:Nnn #1#2#3
{
- \str_case:nvF #8
- { c_@@_ #9 _ #6 _X_ #7 _tl }
- { \exp_not:n {#8} }
- }
-% \end{macrocode}
-% When dealing with a token that can open math mode, the idea is to
-% loop to find the closing token and to preserve everything as-is.
-% The loop therefore looks much the same as the normal one, but with
-% a check to look for end-of-math mode. Once that is found or once we
-% hit the end of the input, the \enquote{normal} loop is restarted. That
-% is done so that the code here works for both standard case changing and
-% for the mixed case version: the clean up needed is different but the
-% loop functions passes (as |#4| to \cs{@@_change_case_math:NNNn}) deal
-% with that.
-% \begin{macrocode}
-\cs_new:Npn \@@_change_case_math:NNNn #1#2#3#4
- {
- \exp_not:n {#1}
- \@@_change_case_math_loop:wNN #4 \q_recursion_stop #2 #3
+ \cs_if_exist_use:cF { @@_change_case_ #2 _ #3 :Nnw }
+ { \use_ii:nn }
+ #1
+ {
+ \use:c { @@_change_case_ #2 _ sigma:Nnw } #1
+ { \@@_change_case_char:Nn #1 {#2} }
+ }
}
-\cs_new:Npn \@@_change_case_math_loop:wNN #1 \q_recursion_stop
+\cs_new:Npn \@@_change_case_char:Nn #1#2
{
- \tl_if_head_is_N_type:nTF {#1}
- { \@@_change_case_math:NwNN }
+ \@@_change_case_output:fwn
{
- \tl_if_head_is_group:nTF {#1}
- { \@@_change_case_math_group:nwNN }
- { \@@_change_case_math_space:wNN }
- }
- #1 \q_recursion_stop
- }
-\cs_new:Npn \@@_change_case_math:NwNN #1#2 \q_recursion_stop #3#4
- {
- \token_if_eq_meaning:NNTF \q_recursion_tail #1
- { #4 \q_recursion_tail \q_recursion_stop }
- {
- \exp_not:n {#1}
- \token_if_eq_meaning:NNTF #1 #3
- { #4 #2 \q_recursion_stop }
- { \@@_change_case_math_loop:wNN #2 \q_recursion_stop #3#4 }
+ \str_case:nvF #1 { c__unicode_ #2 _exceptions_tl }
+ {
+ \exp_after:wN \@@_change_case_char:NNNNNNNNn
+ \int_use:N \__int_eval:w 1000000 + `#1 \__int_eval_end:
+ #1 {#2}
+ }
}
}
-\cs_new:Npn \@@_change_case_math_group:nwNN #1#2 \q_recursion_stop
- {
- { \exp_not:n {#1} }
- \@@_change_case_math_loop:wNN #2 \q_recursion_stop
- }
-\exp_last_unbraced:NNo \cs_new:Npn \@@_change_case_math_space:wNN \c_space_tl
+\cs_new:Npn \@@_change_case_char:NNNNNNNNn #1#2#3#4#5#6#7#8#9
{
- \c_space_tl
- \@@_change_case_math_loop:wNN
+ \str_case:nvF #8
+ { c__unicode_ #9 _ #6 _X_ #7 _tl }
+ { \exp_stop_f: #8 }
}
% \end{macrocode}
% If a control sequence has been given as the argument and it is not on
@@ -3319,17 +3402,17 @@
{ \@@_change_case_cs_type:Nnnnn #2 { latin } {#3} }
{
\str_if_eq_x:nnTF
- { \@@_change_case_cs_three:w #1 \q_nil }
+ { \@@_change_case_cs_three:NNNw #1 \q_nil }
{ \str_if_eq:nnTF {#3} { lower } { CYR } { cyr } }
{ \@@_change_case_cs_cyr:NnNNNNw #2 {#3} #1 \q_stop }
{
\str_if_eq_x:nnTF
- { \@@_change_case_cs_three:w #1 \q_nil }
+ { \@@_change_case_cs_three:NNNw #1 \q_nil }
{ acc }
{ \@@_change_case_cs_type:Nnnnn #2 { acc } {#3} }
{
\str_if_eq_x:nnTF
- { \@@_change_case_cs_four:w #1 \q_nil }
+ { \@@_change_case_cs_four:NNNNw #1 \q_nil }
{ text }
{ \@@_change_case_cs_type:Nnnnn #2 { greek } {#3} }
{ \@@_change_case_cs_type:Nnnnn #2 { misc } {#3} }
@@ -3337,13 +3420,8 @@
}
}
}
-\cs_new:Npn \@@_change_case_cs_three:w #1#2#3#4 \q_nil { #1#2#3 }
-\cs_new:Npn \@@_change_case_cs_four:w #1#2#3#4#5 \q_nil { #1#2#3#4 }
-\cs_new:Npn \@@_change_case_cs_type:Nnnnn #1#2#3
- {
- \str_case:nvTF #1
- { c_@@_change_case_ #2 _ #3 _ tl }
- }
+\cs_new:Npn \@@_change_case_cs_three:NNNw #1#2#3#4 \q_nil { #1#2#3 }
+\cs_new:Npn \@@_change_case_cs_four:NNNNw #1#2#3#4#5 \q_nil { #1#2#3#4 }
\cs_new:Npn \@@_change_case_cs_cyr:NnNNNNw #1#2#3#4#5#6#7 \q_stop
{
\@@_change_case_cs_type:Nnnnn #1
@@ -3361,15 +3439,31 @@
}
}
}
+\cs_new:Npn \@@_change_case_cs_type:Nnnnn #1#2#3
+ {
+ \exp_args:Nf \@@_change_case_cs_type:nnn
+ {
+ \str_case:nvF #1
+ { c_@@_change_case_ #2 _ #3 _ tl }
+ { \exp_stop_f: }
+ }
+ }
+\cs_new:Npn \@@_change_case_cs_type:nnn #1#2#3
+ {
+ \tl_if_blank:nTF {#1}
+ {#3}
+ {
+ \@@_change_case_output:nwn {#1}
+ #2
+ }
+ }
%</package>
% \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 we do \cs{tl_if_in:N(TF)} expandably. If a hit is
-% found then the argument is grabbed: that comes \emph{after} the loop
-% function which is therefore also picked up and rearranged. This means
-% that the same code can be used for mixed case, where an escaped part
-% is ignored for the purposes of identifying a leading letter.
+% done using a loop as for the other special cases. If a hit is found then
+% the argument is grabbed: that comes \emph{after} the loop function which
+% is therefore rearranged.
% \begin{macrocode}
\cs_new:Npn \@@_change_case_cs:N #1
{
@@ -3380,7 +3474,10 @@
\cs_new:Npn \@@_change_case_cs:NN #1#2
{
\quark_if_recursion_tail_stop_do:Nn #2
- { \@@_change_case_cs_expand:N #1 }
+ {
+ \@@_change_case_cs_expand:Nnw #1
+ { \@@_change_case_output:nwn {#1} }
+ }
\str_if_eq:nnTF {#1} {#2}
{
\use_i_delimit_by_q_recursion_stop:nw
@@ -3390,7 +3487,7 @@
}
\cs_new:Npn \@@_change_case_cs:NNn #1#2#3
{
- \exp_not:n { #1 {#3} }
+ \@@_change_case_output:nwn { #1 {#3} }
#2
}
% \end{macrocode}
@@ -3399,7 +3496,7 @@
% function is grabbed as part of the clean-up and reinserted before the
% now expanded material.
% \begin{macrocode}
-\cs_new:Npn \@@_change_case_cs_expand:N #1
+\cs_new:Npn \@@_change_case_cs_expand:Nnw #1#2
{
\bool_if:nTF
{
@@ -3408,82 +3505,301 @@
&& ! \token_if_protected_long_macro_p:N #1
}
{ \@@_change_case_cs_expand:NN #1 }
- { \exp_not:n {#1} }
+ { #2 }
}
\cs_new:Npn \@@_change_case_cs_expand:NN #1#2
{ \exp_after:wN #2 #1 }
+\cs_new:Npn \@@_change_case_cs_expand:NNnw #1#2#3
+ { \@@_change_case_cs_expand:Nnw #2 {#3} #1 }
% \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}
+%
+% \begin{macro}[aux, EXP]{\@@_change_case_lower_sigma:Nnw}
+% \begin{macro}[aux, EXP]{\@@_change_case_lower_sigma:w}
+% \begin{macro}[aux, EXP]{\@@_change_case_lower_sigma:Nw}
+% \begin{macro}[aux, EXP]{\@@_change_case_lower_sigma_loop:NN}
+% \begin{macro}[aux, EXP]{\@@_change_case_upper_sigma:Nnw}
% If the current char is an upper case sigma, the a check is made on the next
-% item in the input. If it is another \texttt{N}-type token then further tests
-% are needed to decide what to do.
+% item in the input. If it is \texttt{N}-type and not a control sequence
+% then there is a look-ahead phase.
% \begin{macrocode}
-\cs_new:Npn \@@_change_case_lower_sigma:Nnn #1#2
+\cs_new:Npn \@@_change_case_lower_sigma:Nnw #1#2#3#4 \q_recursion_stop
{
\int_compare:nNnTF { `#1 } = { "03A3 }
{
- \tl_if_head_is_N_type:nTF {#2}
- { \@@_change_case_lower_sigma:Nw #2 \q_recursion_stop }
- {
- \tl_if_head_is_group:nTF {#2}
- { \c_@@_std_sigma_tl }
- { \c_@@_final_sigma_tl }
- }
+ \@@_change_case_output:fwn
+ { \@@_change_case_lower_sigma:w #4 \q_recursion_stop }
}
+ {#2}
+ #3 #4 \q_recursion_stop
+ }
+\cs_new:Npn \@@_change_case_lower_sigma:w #1 \q_recursion_stop
+ {
+ \tl_if_head_is_N_type:nTF {#1}
+ { \@@_change_case_lower_sigma:Nw #1 \q_recursion_stop }
+ { \c__unicode_final_sigma_tl }
}
-% \end{macrocode}
-% Assuming the next token is not a control sequence, a loop is used to test
-% if the next char is something that can be interpreted as the end of a word.
-% Rather than use all of the Unicode data for this, the simplifying
-% assumption is made that in real text the end of a word will be indicated by
-% a small number of chars. As this may have to be extended over time to other
-% cases, the easiest handling is offered by using the numerical values for
-% these chars. Notice that if the next token is a control sequence there is
-% a need to make sure it's not the end-of-loop, as that will happen if
-% the word in question is at the end of the current list.
-% \begin{macrocode}
\cs_new:Npn \@@_change_case_lower_sigma:Nw #1#2 \q_recursion_stop
{
\token_if_cs:NTF #1
- {
- \token_if_eq_meaning:NNTF #1 \q_recursion_tail
- { \c_@@_final_sigma_tl }
- { \c_@@_std_sigma_tl }
- }
+ { \c__unicode_final_sigma_tl }
{
\exp_after:wN \@@_change_case_lower_sigma_loop:NN
\exp_after:wN #1 \l_tl_case_change_after_final_sigma_tl
\q_recursion_tail \q_recursion_stop
}
}
+% \end{macrocode}
+% Assuming the next token is not a control sequence, a loop is used to test
+% if the next char is something that can be interpreted as the end of a word.
+% Rather than use all of the Unicode data for this, the simplifying
+% assumption is made that in real text the end of a word will be indicated by
+% a small number of chars.
+% \begin{macrocode}
\cs_new:Npn \@@_change_case_lower_sigma_loop:NN #1#2
{
- \quark_if_recursion_tail_stop_do:nn {#2}
- { \c_@@_std_sigma_tl }
+ \quark_if_recursion_tail_stop_do:Nn #2
+ { \c__unicode_std_sigma_tl }
\int_compare:nNnT { `#1 } = { `#2 }
- { \use_i_delimit_by_q_recursion_stop:nw { \c_@@_final_sigma_tl } }
+ { \use_i_delimit_by_q_recursion_stop:nw { \c__unicode_final_sigma_tl } }
\@@_change_case_lower_sigma_loop:NN #1
}
-\cs_new_eq:NN \@@_change_case_upper_sigma:Nnn \use_iii:nnn
-\cs_new_eq:NN \@@_change_case_mixed_sigma:Nnn \use_iii:nnn
+% \end{macrocode}
+% Simply skip to the final step for upper casing.
+% \begin{macrocode}
+\cs_new_eq:NN \@@_change_case_upper_sigma:Nnw \use_ii:nn
% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
+%
+% \begin{macro}[aux, EXP]{\@@_change_case_lower_tr:Nnw}
+% \begin{macro}[aux, EXP]{\@@_change_case_lower_tr_auxi:Nw}
+% \begin{macro}[aux, EXP]{\@@_change_case_lower_tr_auxii:Nw}
+% \begin{macro}[aux, EXP]{\@@_change_case_upper_tr:Nnw}
+% \begin{macro}[aux, EXP]{\@@_change_case_lower_az:Nnw}
+% \begin{macro}[aux, EXP]{\@@_change_case_upper_az:Nnw}
+% 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}
+\cs_new:Npn \@@_change_case_lower_tr:Nnw #1#2
+ {
+ \int_compare:nNnTF { `#1 } = { "0049 }
+ { \@@_change_case_lower_tr_auxi:Nw }
+ {
+ \int_compare:nNnTF { `#1 } = { "0130 }
+ { \@@_change_case_output:nwn { i } }
+ {#2}
+ }
+ }
+% \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, which is done by the \cs{use_i:nn}
+% (it grabs \cs{@@_change_case_loop:wn} and the dot-above char and
+% discards the latter).
+% \begin{macrocode}
+\cs_new:Npn \@@_change_case_lower_tr_auxi:Nw #1#2 \q_recursion_stop
+ {
+ \tl_if_head_is_N_type:nTF {#2}
+ { \@@_change_case_lower_tr_auxii:Nw #2 \q_recursion_stop }
+ { \@@_change_case_output:Vwn \c__unicode_dotless_i_tl }
+ #1 #2 \q_recursion_stop
+ }
+\cs_new:Npn \@@_change_case_lower_tr_auxii:Nw #1#2 \q_recursion_stop
+ {
+ \bool_if:nTF
+ {
+ \token_if_cs_p:N #1
+ || ! ( \int_compare_p:nNn { `#1 } = { "0307 } )
+ }
+ { \@@_change_case_output:Vwn \c__unicode_dotless_i_tl }
+ {
+ \@@_change_case_output:nwn { i }
+ \use_i:nn
+ }
+ }
+% \end{macrocode}
+% Upper casing is easier: just one exception with no context.
+% \begin{macrocode}
+\cs_new:Npn \@@_change_case_upper_tr:Nnw #1#2
+ {
+ \int_compare:nNnTF { `#1 } = { "0069 }
+ { \@@_change_case_output:Vwn \c__unicode_dotted_I_tl }
+ {#2}
+ }
+% \end{macrocode}
+% Straight copies.
+% \begin{macrocode}
+\cs_new_eq:NN \@@_change_case_lower_az:Nnw \@@_change_case_lower_tr:Nnw
+\cs_new_eq:NN \@@_change_case_upper_az:Nnw \@@_change_case_upper_tr:Nnw
+% \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}
+%
+% \begin{macro}[aux, EXP]{\@@_change_case_lower_lt:Nnw}
+% \begin{macro}[aux, EXP]{\@@_change_case_lower_lt:nNnw}
+% \begin{macro}[aux, EXP]{\@@_change_case_lower_lt:nnw}
+% \begin{macro}[aux, EXP]{\@@_change_case_lower_lt:Nw}
+% \begin{macro}[aux, EXP]{\@@_change_case_lower_lt:NNw}
+% \begin{macro}[aux, EXP]{\@@_change_case_upper_lt:Nnw}
+% \begin{macro}[aux, EXP]{\@@_change_case_upper_lt:nnw}
+% \begin{macro}[aux, EXP]{\@@_change_case_upper_lt:Nw}
+% \begin{macro}[aux, EXP]{\@@_change_case_upper_lt:NNw}
+% For Lithuanian, the issue to be dealt with is dots over lower case
+% letters: these should be present if there is another accent. That means
+% that there is some work to do when lower casing I and J. The first step
+% is a simple match attempt: \cs{c_@@_accents_lt_tl} contains
+% accented upper case letters which should gain a dot-above char in their
+% lower case form. This is done using \texttt{f}-type expansion so only one
+% pass is needed to find if it works or not. If there was no hit, the second
+% stage is to check for I, J and I-ogonek, and if the current char is a
+% match to look for a following accent.
+% \begin{macrocode}
+\cs_new:Npn \@@_change_case_lower_lt:Nnw #1
+ {
+ \exp_args:Nf \@@_change_case_lower_lt:nNnw
+ { \str_case:nVF #1 \c__unicode_accents_lt_tl \exp_stop_f: }
+ #1
+ }
+\cs_new:Npn \@@_change_case_lower_lt:nNnw #1#2
+ {
+ \tl_if_blank:nTF {#1}
+ {
+ \exp_args:Nf \@@_change_case_lower_lt:nnw
+ {
+ \int_case:nnF {`#2}
+ {
+ { "0049 } i
+ { "004A } j
+ { "012E } \c__unicode_i_ogonek_tl
+ }
+ \exp_stop_f:
+ }
+ }
+ {
+ \@@_change_case_output:nwn {#1}
+ \use_none:n
+ }
+ }
+\cs_new:Npn \@@_change_case_lower_lt:nnw #1#2
+ {
+ \tl_if_blank:nTF {#1}
+ {#2}
+ {
+ \@@_change_case_output:nwn {#1}
+ \@@_change_case_lower_lt:Nw
+ }
+ }
+% \end{macrocode}
+% Grab the next char and see if it is one of the accents used in Lithuanian:
+% if it is, add the dot-above char into the output.
+% \begin{macrocode}
+\cs_new:Npn \@@_change_case_lower_lt:Nw #1#2 \q_recursion_stop
+ {
+ \tl_if_head_is_N_type:nT {#2}
+ { \@@_change_case_lower_lt:NNw }
+ #1 #2 \q_recursion_stop
+ }
+\cs_new:Npn \@@_change_case_lower_lt:NNw #1#2#3 \q_recursion_stop
+ {
+ \bool_if:nT
+ {
+ ! \token_if_cs_p:N #2
+ &&
+ (
+ \int_compare_p:nNn { `#2 } = { "0300 }
+ || \int_compare_p:nNn { `#2 } = { "0301 }
+ || \int_compare_p:nNn { `#2 } = { "0303 }
+ )
+ }
+ { \@@_change_case_output:Vwn \c__unicode_dot_above_tl }
+ #1 #2#3 \q_recursion_stop
+ }
+% \end{macrocode}
+% For upper casing, the test required is for a dot-above char after an I,
+% J or I-ogonek. First a test for the appropriate letter, and if found a
+% look-ahead and potentially one token dropped.
+% \begin{macrocode}
+\cs_new:Npn \@@_change_case_upper_lt:Nnw #1
+ {
+ \exp_args:Nf \@@_change_case_upper_lt:nnw
+ {
+ \int_case:nnF {`#1}
+ {
+ { "0069 } I
+ { "006A } J
+ { "012F } \c__unicode_I_ogonek_tl
+ }
+ \exp_stop_f:
+ }
+ }
+\cs_new:Npn \@@_change_case_upper_lt:nnw #1#2
+ {
+ \tl_if_blank:nTF {#1}
+ {#2}
+ {
+ \@@_change_case_output:nwn {#1}
+ \@@_change_case_upper_lt:Nw
+ }
+ }
+\cs_new:Npn \@@_change_case_upper_lt:Nw #1#2 \q_recursion_stop
+ {
+ \tl_if_head_is_N_type:nT {#2}
+ { \@@_change_case_upper_lt:NNw }
+ #1 #2 \q_recursion_stop
+ }
+\cs_new:Npn \@@_change_case_upper_lt:NNw #1#2#3 \q_recursion_stop
+ {
+ \bool_if:nTF
+ {
+ ! \token_if_cs_p:N #2
+ && \int_compare_p:nNn { `#2 } = { "0307 }
+ }
+ { #1 }
+ { #1 #2 }
+ #3 \q_recursion_stop
+ }
+% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
@@ -3495,32 +3811,50 @@
% \end{macro}
%
% \begin{macro}[aux, EXP]{\@@_mixed_case:nn}
+% \begin{macro}[aux, EXP]{\@@_mixed_case_aux:nn}
% \begin{macro}[aux, EXP]{\@@_mixed_case_loop:wn}
-% \begin{macro}[aux, EXP]{\@@_mixed_case_N_type:Nwn}
-% \begin{macro}[aux, EXP]{\@@_mixed_case_N_type:NNN}
-% \begin{macro}[aux, EXP]{\@@_mixed_case_N_type:Nnn}
-% \begin{macro}[aux, EXP]{\@@_mixed_case_skip:Nwn}
-% \begin{macro}[aux, EXP]{\@@_mixed_case_skip_tidy:nNwn}
% \begin{macro}[aux, EXP]{\@@_mixed_case_group:nwn}
% \begin{macro}[aux, EXP]{\@@_mixed_case_space:wn}
-% Mixed (title) casing needs an entire set of functions to itself. These are
-% more or less the same as those for general case changes but the
-% requirements here are subtly different. What is needed is a loop which
-% looks for the first \enquote{real} char in the input (skipping any
-% pre-letter chars). Once one is found, it is case changed to upper case but
-% first checking that there is not an entry in the exceptions list. Once that
-% process is done, all remaining chars are lower cased so there is a switch
-% to the normal system. Note that simply grabbing the first token in the
-% input is no good here: it can't handle pre-letter tokens or any special
-% treatment of the first letter found (\emph{e.g.}~words starting with
-% \texttt{i} in Turkish). Spaces at the start of the input are passed
-% through without counting as being the \enquote{start} of the first word,
-% while a brace group forces a switch to the standard lower casing routine.
+% \begin{macro}[aux, EXP]{\@@_mixed_case_N_type:Nwn}
+% \begin{macro}[aux, EXP]{\@@_mixed_case_N_type:NNNnn}
+% \begin{macro}[aux, EXP]{\@@_mixed_case_N_type:Nnn}
+% \begin{macro}[aux, EXP]{\@@_mixed_case_char:Nn}
+% \begin{macro}[aux, EXP]{\@@_mixed_case_skip:N}
+% \begin{macro}[aux, EXP]{\@@_mixed_case_skip:NN}
+% \begin{macro}[aux, EXP]{\@@_mixed_case_skip_tidy:Nwn}
+% \begin{macro}[aux, EXP]{\@@_mixed_case_char:nN}
+% Mixed (title) casing requires some custom handling of the case changing
+% of the first letter in the input followed by a switch to the normal
+% lower casing routine. That could be covered by passing a set of functions
+% to generic routines, but at the cost of making the process rather opaque.
+% Instead, the approach taken here is to use a dedicated set of functions
+% which keep the different loop requirements clearly separate.
+%
+% The main loop looks for the first \enquote{real} char in the input
+% (skipping any pre-letter chars). Once one is found, it is case changed to
+% upper case but first checking that there is not an entry in the exceptions
+% list. Note that simply grabbing the first token in the input is no good
+% here: it can't handle pre-letter tokens or any special treatment of the
+% first letter found (\emph{e.g.}~words starting with \texttt{i} in
+% Turkish). Spaces at the start of the input are passed through without
+% counting as being the \enquote{start} of the first word, while a brace
+% group is assumed to be contain the first char with everything after the
+% brace therefore lower cased.
% \begin{macrocode}
\cs_new:Npn \@@_mixed_case:nn #1#2
{
- \@@_mixed_case_loop:wn #2
- \q_recursion_tail \q_recursion_stop {#1}
+ \etex_unexpanded:D \exp_after:wN
+ {
+ \tex_romannumeral:D
+ \@@_mixed_case_aux:nn {#1} {#2}
+ }
+ }
+\cs_new:Npn \@@_mixed_case_aux:nn #1#2
+ {
+ \group_align_safe_begin:
+ \@@_mixed_case_loop:wn
+ #2 \q_recursion_tail \q_recursion_stop {#1}
+ \@@_change_case_result:n { }
}
\cs_new:Npn \@@_mixed_case_loop:wn #1 \q_recursion_stop
{
@@ -3533,14 +3867,32 @@
}
#1 \q_recursion_stop
}
+\cs_new:Npn \@@_mixed_case_group:nwn #1#2 \q_recursion_stop #3
+ {
+ \@@_change_case_output:own
+ {
+ \exp_after:wN
+ {
+ \tex_romannumeral:D
+ \@@_mixed_case_aux:nn {#3} {#1}
+ }
+ }
+ \@@_change_case_loop:wnn #2 \q_recursion_stop { lower } {#3}
+ }
+\exp_last_unbraced:NNo \cs_new:Npn \@@_mixed_case_space:wn \c_space_tl
+ {
+ \@@_change_case_output:nwn { ~ }
+ \@@_mixed_case_loop:wn
+ }
\cs_new:Npn \@@_mixed_case_N_type:Nwn #1#2 \q_recursion_stop
{
- \quark_if_recursion_tail_stop_do:Nn #1 { \use_none:nn }
- \exp_after:wN \@@_mixed_case_N_type:NNN
+ \quark_if_recursion_tail_stop_do:Nn #1
+ { \@@_change_case_end:wn }
+ \exp_after:wN \@@_mixed_case_N_type:NNNnn
\exp_after:wN #1 \l_tl_case_change_math_tl
\q_recursion_tail ? \q_recursion_stop {#2}
}
-\cs_new:Npn \@@_mixed_case_N_type:NNN #1#2#3
+\cs_new:Npn \@@_mixed_case_N_type:NNNnn #1#2#3
{
\quark_if_recursion_tail_stop_do:Nn #2
{ \@@_mixed_case_N_type:Nnn #1 }
@@ -3548,18 +3900,22 @@
{
\use_i_delimit_by_q_recursion_stop:nw
{
- \@@_change_case_math:NNNn #1 #3
- \@@_mixed_case_loop:wn
+ \@@_change_case_math:NNNnnn
+ #1 #3 \@@_mixed_case_loop:wn
}
}
- { \@@_mixed_case_N_type:NNN #1 }
+ { \@@_mixed_case_N_type:NNNnn #1 }
}
+% \end{macrocode}
+% The business end of the loop is here: there is first a need to deal
+% with any control sequence cases before looking for characters to skip.
+% \begin{macrocode}
\cs_new:Npn \@@_mixed_case_N_type:Nnn #1#2#3
{
\token_if_cs:NTF #1
%<*initex>
{
- \@@_change_case_cs:N #1
+ \@@_change_case_cs:N #1
\@@_mixed_case_loop:wn #2 \q_recursion_stop {#3}
}
%</initex>
@@ -3577,74 +3933,58 @@
}
%</package>
{
- \cs_if_exist_use:cF { @@_change_case_mixed_ #3 :Nnn }
- {
- \cs_if_exist_use:cF { @@_change_case_upper_ #3 :Nnn }
- { \use_iii:nnn }
- }
- #1 {#2}
- {
- \exp_after:wN \@@_mixed_case_skip:NNn
- \exp_after:wN #1 \l_tl_mixed_case_ignore_tl
- \q_recursion_tail \q_recursion_stop
- {
- \str_case:nVF #1 \c_@@_mixed_exceptions_tl
- {
- \exp_after:wN \@@_change_case_char:NNNNNNNNn
- \int_use:N
- \__int_eval:w 1000000 + `#1 \__int_eval_end:
- #1 { upper }
- }
- }
- }
+ \@@_mixed_case_char:Nn #1 {#3}
\@@_change_case_loop:wnn #2 \q_recursion_stop { lower } {#3}
}
}
% \end{macrocode}
-% Looking for chars to skip when title casing uses the standard \enquote{loop
-% around a list} approach. If there is a hit, there is a bit of tidying up
-% to do: retain the char and switch the looping system to stick with the
-% title loop rather than the lower case one. That means swapping an
-% auxiliary and removing a trailing |{ lower }|, which is easiest to do
-% with a dedicated function. When dealing with a brace group there is a
-% need to test if there is any content in it: that affects whether to keep
-% looking for the first character or not after the group. If there is
-% anything non-expandable at all in it then that will be treated as the
-% first \enquote{character}.
+% As detailed above, handling a mixed case char means first looking for
+% exceptions then treating as an upper cased letter, but with a list of
+% tokens to skip over too.
% \begin{macrocode}
-\cs_new:Npn \@@_mixed_case_skip:NNn #1#2
+\cs_new:Npn \@@_mixed_case_char:Nn #1#2
{
- \quark_if_recursion_tail_stop_do:nn {#2} { \use:n }
- \int_compare:nNnT { `#1 } = { `#2 }
+ \cs_if_exist_use:cF { @@_change_case_mixed_ #2 :Nnw }
{
- \use_i_delimit_by_q_recursion_stop:nw
- {
- #1
- \@@_mixed_case_skip_tidy:nNwn
- }
+ \cs_if_exist_use:cF { @@_change_case_upper_ #2 :Nnw }
+ { \use_ii:nn }
}
- \@@_mixed_case_skip:NNn #1
+ #1
+ { \@@_mixed_case_skip:N #1 }
}
-\cs_new:Npn \@@_mixed_case_skip_tidy:nNwn #1#2#3 \q_recursion_stop #4
+\cs_new:Npn \@@_mixed_case_skip:N #1
{
- \@@_mixed_case_loop:wn #3 \q_recursion_stop
+ \exp_after:wN \@@_mixed_case_skip:NN
+ \exp_after:wN #1 \l_tl_mixed_case_ignore_tl
+ \q_recursion_tail \q_recursion_stop
}
-\cs_new:Npn \@@_mixed_case_group:nwn #1#2 \q_recursion_stop #3
+\cs_new:Npn \@@_mixed_case_skip:NN #1#2
{
- \tl_if_blank:fTF {#1}
- { { } \@@_mixed_case_loop:wn #2 \q_recursion_stop {#3} }
+ \quark_if_recursion_tail_stop_do:nn {#2}
{
- {
- \@@_mixed_case_loop:wn #1
- \q_recursion_tail \q_recursion_stop {#3}
- }
- \@@_change_case_loop:wnn #2 \q_recursion_stop { lower } {#3}
+ \exp_args:Nf \@@_mixed_case_char:nN
+ { \str_case:nVF #1 \c__unicode_mixed_exceptions_tl \exp_stop_f: }
+ #1
+ }
+ \int_compare:nNnT { `#1 } = { `#2 }
+ {
+ \use_i_delimit_by_q_recursion_stop:nw
+ {
+ \@@_change_case_output:nwn {#1}
+ \@@_mixed_case_skip_tidy:Nwn
+ }
}
+ \@@_mixed_case_skip:NN #1
}
-\exp_last_unbraced:NNo \cs_new:Npn \@@_mixed_case_space:wn \c_space_tl
+\cs_new:Npn \@@_mixed_case_skip_tidy:Nwn #1#2 \q_recursion_stop #3
{
- \c_space_tl
- \@@_mixed_case_loop:wn
+ \@@_mixed_case_loop:wn #2 \q_recursion_stop
+ }
+\cs_new:Npn \@@_mixed_case_char:nN #1#2
+ {
+ \tl_if_blank:nTF {#1}
+ { \@@_change_case_char:Nn #2 { upper } }
+ { \@@_change_case_output:nwn {#1} }
}
% \end{macrocode}
% \end{macro}
@@ -3656,191 +3996,58 @@
% \end{macro}
% \end{macro}
% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
%
-% \begin{macro}[aux]^^A
-% {
-% \@@_change_case_lower_tr:Nnn ,
-% \@@_change_case_lower_az:Nnn
-% }
-% \begin{macro}[aux]{\@@_change_case_lower_tr:Nw}
-% \begin{macro}[aux]^^A
-% {
-% \@@_change_case_upper_tr:Nnn ,
-% \@@_change_case_upper_az:Nnn
-% }
-% 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}
-\cs_new:Npn \@@_change_case_lower_tr:Nnn #1#2#3
- {
- \int_compare:nNnTF { `#1 } = { "0049 }
- {
- \tl_if_head_is_N_type:nTF {#2}
- { \@@_change_case_lower_tr:Nw #2 \q_recursion_stop }
- { \c_@@_dotless_i_tl }
- }
- {
- \int_compare:nNnTF { `#1 } = { "0130 }
- { i }
- {#3}
- }
- }
-\cs_new_nopar:Npn \@@_change_case_lower_az:Nnn
- { \@@_change_case_lower_tr:Nnn }
-% \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, which is done by the \cs{use_i:nn}
-% (it grabs \cs{@@_change_case_loop:wn} and the dot-above char and
-% discards the latter).
+% \begin{macro}[aux, EXP]{\@@_change_case_mixed_nl:Nnw}
+% \begin{macro}[aux, EXP]{\@@_change_case_mixed_nl:Nw}
+% \begin{macro}[aux, EXP]{\@@_change_case_mixed_nl:NNw}
+% 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_lower_tr:Nw #1#2 \q_recursion_stop
+\cs_new:Npn \@@_change_case_mixed_nl:Nnw #1
{
\bool_if:nTF
{
- \token_if_cs_p:N #1
- || ! ( \int_compare_p:nNn { `#1 } = { "0307 } )
+ \int_compare_p:nNn { `#1 } = { `i }
+ || \int_compare_p:nNn { `#1 } = { `I }
}
- { \c_@@_dotless_i_tl }
{
- i
- \use_i:nn
+ \@@_change_case_output:nwn { I }
+ \@@_change_case_mixed_nl:Nw
}
}
-% \end{macrocode}
-% Upper casing is easier: just one exception with no context.
-% \begin{macrocode}
-\cs_new:Npn \@@_change_case_upper_tr:Nnn #1#2#3
- {
- \int_compare:nNnTF { `#1 } = { "0069 }
- { \c_@@_dotted_I_tl }
- {#3}
- }
-\cs_new_nopar:Npn \@@_change_case_upper_az:Nnn
- { \@@_change_case_upper_tr:Nnn }
-% \end{macrocode}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \begin{macro}[aux]{\@@_change_case_lower_lt:Nnn}
-% \begin{macro}[aux]{\@@_change_case_lower_lt:Nw}
-% \begin{macro}[aux]{\@@_change_case_upper_lt:Nnn}
-% \begin{macro}[aux]{\@@_change_case_upper_lt:Nw}
-% For Lithuanian, the issue to be dealt with is dots over lower case
-% letters: these should be present if there is another accent. That means
-% that there is some work to do when lower casing I and J. The first step
-% is a simple match attempt: \cs{c_@@_accents_lt_tl} contains
-% accented upper case letters which should gain a dot-above char in their
-% lower case form. The second stage is to check for I, J and I-ogonek, and
-% if the current char is a match to look for a following accent. As the
-% current char is still needed in case-changed form, the standard code is
-% inserted before hunting for an accent.
-% \begin{macrocode}
-\cs_new:Npn \@@_change_case_lower_lt:Nnn #1#2#3
+\cs_new:Npn \@@_change_case_mixed_nl:Nw #1#2 \q_recursion_stop
{
- \str_case:nVF #1 \c_@@_accents_lt_tl
- {
- #3
- \bool_if:nT
- {
- \int_compare_p:nNn { `#1 } = { "0049 }
- || \int_compare_p:nNn { `#1 } = { "004A }
- || \int_compare_p:nNn { `#1 } = { "012E }
- }
- {
- \tl_if_head_is_N_type:nT {#2}
- { \@@_change_case_lower_lt:Nw #2 \q_recursion_stop }
- }
- }
+ \tl_if_head_is_N_type:nT {#2}
+ { \@@_change_case_mixed_nl:NNw }
+ #1 #2 \q_recursion_stop
}
-% \end{macrocode}
-% Grab the next char and see if it is one of the accents used in Lithuanian:
-% if it is, add the dot-above char into the output.
-% \begin{macrocode}
-\cs_new:Npn \@@_change_case_lower_lt:Nw #1#2 \q_recursion_stop
+\cs_new:Npn \@@_change_case_mixed_nl:NNw #1#2#3 \q_recursion_stop
{
- \bool_if:nT
+ \bool_if:nTF
{
- ! ( \token_if_cs_p:N #1 )
+ ! ( \token_if_cs_p:N #2 )
&&
(
- \int_compare_p:nNn { `#1 } = { "0300 }
- || \int_compare_p:nNn { `#1 } = { "0301 }
- || \int_compare_p:nNn { `#1 } = { "0303 }
+ \int_compare_p:nNn { `#2 } = { `j }
+ || \int_compare_p:nNn { `#2 } = { `J }
)
}
- { \c_@@_dot_above_tl }
- }
-% \end{macrocode}
-% For upper casing, the chars themselves are always converted as normal.
-% The test required here is for a dot-above char after an I, J or I-ogonek.
-% If there is one, it's removed using \cs{use_i:nn} (which preserves the
-% loop and discards the char).
-% \begin{macrocode}
-\cs_new:Npn \@@_change_case_upper_lt:Nnn #1#2#3
- {
- #3
- \bool_if:nT
- {
- \tl_if_head_is_N_type_p:n {#2}
- &&
- (
- \int_compare_p:nNn { `#1 } = { "0069 }
- || \int_compare_p:nNn { `#1 } = { "006A }
- || \int_compare_p:nNn { `#1 } = { "012F }
- )
- }
- { \@@_change_case_upper_lt:Nw #2 \q_recursion_stop }
- }
-\cs_new:Npn \@@_change_case_upper_lt:Nw #1#2 \q_recursion_stop
- {
- \bool_if:nT
{
- ! ( \token_if_cs_p:N #1 )
- &&
- \int_compare_p:nNn { `#1 } = { "0307 }
+ \@@_change_case_output:nwn { J }
+ #1
}
- { \use_i:nn }
+ { #1 #2 }
+ #3 \q_recursion_stop
}
% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
-% \end{macro}
-% \begin{macro}[aux]{\@@_change_case_mixed_nl:Nnn}
-% \begin{macro}[aux]{\@@_change_case_mixed_nl:Nw}
-% 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}.
-% \begin{macrocode}
-\cs_new:Npn \@@_change_case_mixed_nl:Nnn #1#2
- {
- \int_compare:nNnTF { `#1 } = { `i }
- {
- I
- \tl_if_head_is_N_type:nT {#2}
- { \@@_change_case_mixed_nl:Nw #2 \q_recursion_stop }
- }
- }
-\cs_new:Npn \@@_change_case_mixed_nl:Nw #1#2 \q_recursion_stop
- {
- \bool_if:nT
- {
- ! ( \token_if_cs_p:N #1 )
- &&
- \int_compare_p:nNn { `#1 } = { `j }
- }
- {
- J
- \use_i:nn
- }
- }
-% \end{macrocode}
-% \end{macro}
-% \end{macro}
%
% \begin{variable}{\l_tl_case_change_math_tl}
% The list of token pairs which are treated as math mode and so
@@ -3923,6 +4130,7 @@
% lower case mappings are not identical so special end cases are needed
% to get everything set up correctly with minimal repetition.
% \begin{macrocode}
+%<*package>
\group_begin:
\cs_set_protected:Npn \@@_change_case_setup:nnnn #1#2#3#4
{
@@ -3940,24 +4148,14 @@
\cs_set:Npn \@@_change_case_map:NN #1#2
{
\quark_if_recursion_tail_stop:N #1
-%<*initex>
- \exp_not:N #1 \exp_not:N #2
-%</initex>
-%<*package>
- \exp_not:N #1 \exp_not:n { { \exp_not:n {#2} } }
-%</package>
+ \exp_not:N #1 \exp_not:n { { \exp_stop_f: #2 } }
\@@_change_case_map:NN
}
\cs_set:Npn \@@_change_case_map:NNN #1#2#3
{
\str_if_eq:nnT {#1} {#2}
{ \use_none_delimit_by_q_recursion_stop:w }
-%<*initex>
- \exp_not:N #3 \exp_not:N #2
-%</initex>
-%<*package>
- \exp_not:N #3 \exp_not:n { { \exp_not:n {#2} } }
-%</package>
+ \exp_not:N #3 \exp_not:n { { \exp_stop_f: #2 } }
\@@_change_case_map:NNN #1
}
\@@_change_case_setup:nnnn
@@ -3978,7 +4176,6 @@
\j J
}
{ \i }
-%<*package>
\@@_change_case_setup:nnnn
{ cyrillic }
{ _i }
@@ -4140,50 +4337,50 @@
{ \textsigma }
\tl_const:Nn \c_@@_change_case_acc_upper_tl
{
- \accdasia { \exp_not:n { \LGR@accdropped } }
- \accdasiaoxia { \exp_not:n { \LGR@hiatus } }
- \accdasiavaria { \exp_not:n { \LGR@accdropped } }
- \accdasiaperispomeni { \exp_not:n { \LGR@accdropped } }
- \accpsili { \exp_not:n { \LGR@hiatus } }
- \accpsilioxia { \exp_not:n { \LGR@hiatus } }
- \accpsilivaria { \exp_not:n { \LGR@hiatus } }
- \accpsiliperispomeni { \exp_not:n { \LGR@accdropped } }
- \acctonos { \exp_not:n { \LGR@hiatus } }
- \accvaria { \exp_not:n { \LGR@accdropped } }
- \accdialytikatonos { \exp_not:n { \LGR@accDialytika } }
- \accdialytikavaria { \exp_not:n { \LGR@accDialytika } }
- \accdialytikaperispomeni { \exp_not:n { \LGR@accDialytika } }
- \accperispomeni { \exp_not:n { \LGR@accdropped } }
+ \accdasia { \exp_stop_f: \LGR@accdropped }
+ \accdasiaoxia { \exp_stop_f: \LGR@hiatus }
+ \accdasiavaria { \exp_stop_f: \LGR@accdropped }
+ \accdasiaperispomeni { \exp_stop_f: \LGR@accdropped }
+ \accpsili { \exp_stop_f: \LGR@hiatus }
+ \accpsilioxia { \exp_stop_f: \LGR@hiatus }
+ \accpsilivaria { \exp_stop_f: \LGR@hiatus }
+ \accpsiliperispomeni { \exp_stop_f: \LGR@accdropped }
+ \acctonos { \exp_stop_f: \LGR@hiatus }
+ \accvaria { \exp_stop_f: \LGR@accdropped }
+ \accdialytikatonos { \exp_stop_f: \LGR@accDialytika }
+ \accdialytikavaria { \exp_stop_f: \LGR@accDialytika }
+ \accdialytikaperispomeni { \exp_stop_f: \LGR@accDialytika }
+ \accperispomeni { \exp_stop_f: \LGR@accdropped }
}
\tl_const:Nn \c_@@_change_case_acc_lower_tl { }
\tl_const:Nn \c_@@_change_case_misc_upper_tl
{
- \ypogegrammeni { \exp_not:n { \prosgegrammeni } }
- \abreve { \exp_not:n { \Abreve } }
- \acircumflex { \exp_not:n { \Acircumflex } }
- \ecircumflex { \exp_not:n { \Ecircumflex } }
- \ocircumflex { \exp_not:n { \Ocircumflex } }
- \ohorn { \exp_not:n { \Ohorn } }
- \uhorn { \exp_not:n { \Uhorn } }
+ \ypogegrammeni { \exp_stop_f: \prosgegrammeni }
+ \abreve { \exp_stop_f: \Abreve }
+ \acircumflex { \exp_stop_f: \Acircumflex }
+ \ecircumflex { \exp_stop_f: \Ecircumflex }
+ \ocircumflex { \exp_stop_f: \Ocircumflex }
+ \ohorn { \exp_stop_f: \Ohorn }
+ \uhorn { \exp_stop_f: \Uhorn }
}
\tl_const:Nn \c_@@_change_case_misc_lower_tl
{
- \prosgegrammeni { \exp_not:n { \ypogegrammeni } }
- \Abreve { \exp_not:n { \abreve } }
- \Acircumflex { \exp_not:n { \acircumflex } }
- \Ecircumflex { \exp_not:n { \ecircumflex } }
- \Ocircumflex { \exp_not:n { \ocircumflex } }
- \Ohorn { \exp_not:n { \ohorn } }
- \Uhorn { \exp_not:n { \uhorn } }
- \ABREVE { \exp_not:n { \abreve } }
- \ACIRCUMFLEX { \exp_not:n { \acircumflex } }
- \ECIRCUMFLEX { \exp_not:n { \ecircumflex } }
- \OCIRCUMFLEX { \exp_not:n { \ocircumflex } }
- \OHORN { \exp_not:n { \ohorn } }
- \UHORN { \exp_not:n { \uhorn } }
+ \prosgegrammeni { \exp_stop_f: \ypogegrammeni }
+ \Abreve { \exp_stop_f: \abreve }
+ \Acircumflex { \exp_stop_f: \acircumflex }
+ \Ecircumflex { \exp_stop_f: \ecircumflex }
+ \Ocircumflex { \exp_stop_f: \ocircumflex }
+ \Ohorn { \exp_stop_f: \ohorn }
+ \Uhorn { \exp_stop_f: \uhorn }
+ \ABREVE { \exp_stop_f: \abreve }
+ \ACIRCUMFLEX { \exp_stop_f: \acircumflex }
+ \ECIRCUMFLEX { \exp_stop_f: \ecircumflex }
+ \OCIRCUMFLEX { \exp_stop_f: \ocircumflex }
+ \OHORN { \exp_stop_f: \ohorn }
+ \UHORN { \exp_stop_f: \uhorn }
}
-%</package>
\group_end:
+%</package>
% \end{macrocode}
% \end{variable}
%