Although, as Phelype says, the current code relies on 'sensible' definitions of active chars, it is quite possible to selectively-expand material. The last check-in with the old code is a03c651350. It has the following for the name-sanitizing code:
% \begin{macro}[EXP]{\__kernel_file_name_sanitize:n} % \begin{macro}[EXP]{\__kernel_file_name_expand_loop:w} % \begin{macro}[EXP]{\__kernel_file_name_expand_N_type:Nw} % \begin{macro}[EXP]{\__kernel_file_name_expand_group:nw} % \begin{macro}[EXP]{\__kernel_file_name_expand_space:w} % \begin{macro}[EXP]{\__kernel_file_name_strip_quotes:n} % \begin{macro}[EXP]{\__kernel_file_name_strip_quotes:nnnw} % \begin{macro}[EXP]{\__kernel_file_name_strip_quotes:nnn} % \begin{macro}[EXP]{\__kernel_file_name_trim_spaces:n} % \begin{macro}[EXP]{\__kernel_file_name_trim_spaces:nw} % \begin{macro}[EXP]{\__kernel_file_name_trim_spaces_aux:n} % \begin{macro}[EXP]{\__kernel_file_name_trim_spaces_aux:w} % Expanding the file name without expanding active characters is done % using the same token-by-token approach as for example case changing. % The finale outcome only need be \texttt{e}-type expandable, so there % is no need for the shuffling that is seen in other locations. % \begin{macrocode} \cs_new:Npn \__kernel_file_name_sanitize:n #1 { \exp_args:Ne \__kernel_file_name_trim_spaces:n { \exp_args:Ne \__kernel_file_name_strip_quotes:n { \__kernel_file_name_expand_loop:w #1 \q_@@_recursion_tail \q_@@_recursion_stop } } } \cs_new:Npn \__kernel_file_name_expand_loop:w #1 \q_@@_recursion_stop { \tl_if_head_is_N_type:nTF {#1} { \__kernel_file_name_expand_N_type:Nw } { \tl_if_head_is_group:nTF {#1} { \__kernel_file_name_expand_group:nw } { \__kernel_file_name_expand_space:w } } #1 \q_@@_recursion_stop } \cs_new:Npn \__kernel_file_name_expand_N_type:Nw #1 { \@@_if_recursion_tail_stop:N #1 \bool_lazy_and:nnTF { \token_if_expandable_p:N #1 } { \bool_not_p:n { \bool_lazy_any_p:n { { \token_if_protected_macro_p:N #1 } { \token_if_protected_long_macro_p:N #1 } { \token_if_active_p:N #1 } } } } { \exp_after:wN \__kernel_file_name_expand_loop:w #1 } { \token_to_str:N #1 \__kernel_file_name_expand_loop:w } } \cs_new:Npx \__kernel_file_name_expand_group:nw #1 { \c_left_brace_str \exp_not:N \__kernel_file_name_expand_loop:w #1 \c_right_brace_str } \exp_last_unbraced:NNo \cs_new:Npx \__kernel_file_name_expand_space:w \c_space_tl { \c_space_tl \exp_not:N \__kernel_file_name_expand_loop:w } % \end{macrocode} % Quoting file name uses basically the same approach as for % \texttt{luaquotejobname}: count the |"| tokens and remove them. % \begin{macrocode} \cs_new:Npn \__kernel_file_name_strip_quotes:n #1 { \__kernel_file_name_strip_quotes:nnnw {#1} { 0 } { } #1 " \q_@@_recursion_tail " \q_@@_recursion_stop } \cs_new:Npn \__kernel_file_name_strip_quotes:nnnw #1#2#3#4 " { \@@_if_recursion_tail_stop_do:nn {#4} { \__kernel_file_name_strip_quotes:nnn {#1} {#2} {#3} } \__kernel_file_name_strip_quotes:nnnw {#1} { #2 + 1 } { #3#4 } } \cs_new:Npn \__kernel_file_name_strip_quotes:nnn #1#2#3 { \int_if_even:nT {#2} { \__kernel_msg_expandable_error:nnn { kernel } { unbalanced-quote-in-filename } {#1} } #3 } % \end{macrocode} % Spaces need to be trimmed from the start of the name and from the end of % any extension. However, the name we are passed might not have an extension: % that means we have to look for one. If there is no extension, we still use % the standard trimming function but deliberately prevent any spaces being % removed at the end. % \begin{macrocode} \cs_new:Npn \__kernel_file_name_trim_spaces:n #1 { \__kernel_file_name_trim_spaces:nw {#1} #1 . \q_@@_nil . \s_@@_stop } \cs_new:Npn \__kernel_file_name_trim_spaces:nw #1#2 . #3 . #4 \s_@@_stop { \@@_quark_if_nil:nTF {#3} { \exp_args:Ne \__kernel_file_name_trim_spaces_aux:n { \tl_trim_spaces:n { #1 \s_@@_stop } } } { \tl_trim_spaces:n {#1} } } \cs_new:Npn \__kernel_file_name_trim_spaces_aux:n #1 { \__kernel_file_name_trim_spaces_aux:w #1 } \cs_new:Npn \__kernel_file_name_trim_spaces_aux:w #1 \s_@@_stop {#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}
As shown, this uses a 'tl action' loop to grab each token/group in turn (which normalises {/} tokens). We can then examine each N-type token and filter out protected macros and active tokens.
The downside to this approach is it's quite slow. The current code is written to be faster, using a \csname to expand tokens and relying on the fact that UTF-8 and similar active-char support includes an \ifincsname in the definition of the active character tokens.
\csname <filename>\endcsnameto expand<filename>(thus active tokens are expanded, and that documentation is outdated)\stringthemselves if used in\csname.\csname..\endcsnamething I saw in source3.pdf. But I didn`t find anything there that indicated special treatment of active characters. Thus I assumed I was missing something about the code. Too bad: I thought I was onto an interesting trick. :-) Shall I delete the question?\ifincsname. Other active chars have to be dealt with on a case-by-case basis (for example, somewhere in the filename processing in the kernel we do\edef~{\string~}before passing the file name to\csname..\endcsname