diff options
Diffstat (limited to 'Master/texmf-dist/source/latex/l3kernel/l3candidates.dtx')
-rw-r--r-- | Master/texmf-dist/source/latex/l3kernel/l3candidates.dtx | 1227 |
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} % |