3

This took me longer than I'd like to accomplish what should have been a very simple task. I needed a way to turn a tokenlist lowercase and remove the spaces. But str_lowercase:N doesn't exist! And trying to generate the variant failed. Any combination I tried of \MakeLowercase and \tl_remove_all:Nn didn't play nice together. I ended up using two helper functions to get the job done. The solution works so far. I am making this question two fold, for one to document my solution and two, to ask if there is a better way, preferably using expl3 only.

% this function exists to simplify the call for the other, reducing the number of arguments to 1.
\cs_new_protected:Npn \tl_lowercase:N #1
    { \bob_tl_lowercase:No #1 #1 }

% this function and its generated varient cause arguement 2 to expand once % so it can be accepted by \str_lowercase:n, then sets it to itself \cs_new_protected:Npn \bob_tl_lowercase:Nn #1#2 { \tl_set:Nx #1 { \str_lowercase:n {#2} } } \cs_generate_variant:Nn \bob_tl_lowercase:Nn { No }

% just a example use case function \tl_new:N \itemfilename \NewDocumentCommand{\getfilename}{s+m} { \tl_set:Nn \itemfilename {#2} \tl_remove_all:Nn \itemfilename {~} \tl_lowercase:N \itemfilename \tl_show:N \itemfilename }

Bob
  • 1,270
  • 9
  • 14
  • Ah, okay, I think I understood what your issue is. Expl3 has two main kind of macros, one that returns the result and another that sets a tl variable to the result. You can trivially construct the second type from the first type (just \tl_set:Nx \something {\str_lowercase:V \something}) but cannot construct the first type from the second type (because TeX has the concept of expandability). (I don't know if this is a "common issue"? Did the expl3 documentation make it clear how the macros should be used?) – In that respect, the first (unexpandable) type can compute everything but … – user202729 Jan 08 '22 at 09:38
  • … it's somewhat like assembly that you have to place temporary intermediate result into temp variables. — (continued) Because of that, expl3 provides only the unexpandable type for things that requires unexpandable computations, and only the expandable type for things that can be done entirely expandably. — On that note, another thing that should be understood is that the expandable functions make sure that you can usefully use the output, so they either • have the result unexpandable, • wrap the result in exp_not:n, or • document the exact number of expansion steps required, so you don't… – user202729 Jan 08 '22 at 09:39
  • … need to know the exact number of expansion steps most of the time, e.g. https://tex.stackexchange.com/q/101833/250119 ; in other words the exact number of expansion step will only be documented when it's important. – user202729 Jan 08 '22 at 09:44
  • The general question is duplicate of programming - How to place expl3 function output to variable not in stream? - TeX - LaTeX Stack Exchange — side note: \tl_remove_all does not operate recursively (does not act of inner braced group), as (probably?) documented in a recent version of interface3. – user202729 Jan 08 '22 at 09:46
  • @user202729 Thank you for your words on this matter. I would say that the linked question is not really a duplicate as it its answer doesn't fully answer this question either, but it is a good read and relevant information. – Bob Jan 08 '22 at 16:10

3 Answers3

3

There is \str_lowercase:n and it's fully expandable, but (as Joseph rightly suggests) there's the better \str_foldcase:n.

\str_new:N \l_bob_itemfilename_str

\NewDocumentCommand{\getfilename}{m} { \str_set:Nx \l_bob_itemfilename_str { \str_foldcase:n { #1 } } \str_replace_all:Nnn \l_bob_itemfilename_str { ~ } { } \str_show:N \l_bob_itemfilename_str }

\getfilename{SaMPleName WITH upperCase and SPACES}

The console shows

> \l_bob_itemfilename_str=samplenamewithuppercaseandspaces.

An improved version with the possibility to manage an already built token list or string:

\documentclass{article}

\ExplSyntaxOn

\NewDocumentCommand{\getfilename}{sm} { \IfBooleanTF { #1 } { \bob_filename_get:V #2 } { \bob_filename_get:n { #2 } } }

\str_new:N \l_bob_filename_item_str

\cs_new_protected:Nn \bob_filename_get:n { \str_set:Nx \l_bob_filename_item_str { \str_foldcase:n { #1 } } \str_replace_all:Nnn \l_bob_filename_item_str { ~ } { } \str_show:N \l_bob_filename_item_str } \cs_generate_variant:Nn \bob_filename_get:n { V }

\getfilename{SaMPleName WITH upperCase and SPACES}

\def\test{SaMPleName WITH upperCase and SPACES}

\getfilename*{\test}

In either case the console will have

> \l_bob_filename_item_str=samplenamewithuppercaseandspaces.
egreg
  • 1,121,712
  • Your answer is similar to mine but it doesn’t handle the case of needing to alter a preexisting tokenlist. Additionally I am curious as to why you changed the data type to string, and what advantages and possibly disadvantages that has compared to a tokenlist. – Bob Jan 08 '22 at 01:17
  • @Bob Your question doesn't give many hints about the intended usage and from \getfilename I guess you're dealing with file names, which are better treated as strings. About how to process already given token lists/strings, the procedure is standard; I'll add it with the *-variant you have and don't use. – egreg Jan 08 '22 at 09:25
  • @Bob TeX tokens don't have meaning to the file system, so it's usually more sensible to switch to a string here. – Joseph Wright Jan 08 '22 at 09:45
  • @egreg Im sorry but you are missing the point of the question sort of. The \getfilename command was only added as an example use case so its exact implimentation is not really relevant. The important part of the question is the functionality one would infer from the name \tl_lowercase:N. So ignoring the \getfilename and focusing on \bob_filename_get, your implimentation strategy is the same as mine, in that you used a shell \bob_filename_get:V in order to pass the value of a tokenlist down to \str_foldcase:n {#1}. – Bob Jan 08 '22 at 15:50
  • @Bob Sorry, but I can't understand what the point is. It's you who should reveal what the macro is meant for. If I see “file name” what should I think? – egreg Jan 08 '22 at 16:05
  • @Bob It's important what the use case is because case changing (typesetting) tokens needs some functionality that is orthogonal to changing material to be used as bytes, such as filenames. – Joseph Wright Jan 08 '22 at 21:00
0

Having looked around I think that this is the proper answer to the question.

 \tl_set:Nx \itemfilename { \str_foldcase:V \itemfilename }

A important piece of information is with \str_foldcase "The result of this process is left in the input stream." This also seems true for str_lowercase even though it is not said in the manual.

A bit of more careful reading in the manual shows that "the [str_lowercase:n] functions should not be used for caseless comparisons".

However I was also successful with the following.

\cs_generate_variant:Nn \str_lowercase:n {V}
\tl_set:Nx \itemfilename { \str_lowercase:V \itemfilename }.
Bob
  • 1,270
  • 9
  • 14
0

Here is a way without expl3...rather, it uses a token cycle to remove spaces and direct characters to the macro \explowercase.

Here it is set up to pre-expand its argument, so that it works with a string, or a \def of a string.

\documentclass{article}
\def\explowerchar#1{%
  \ifcase\numexpr`#1-`A\relax
   a\or b\or c\or d\or e\or f\or g\or h\or i\or j\or k\or l\or m\or
   n\or o\or p\or q\or r\or s\or t\or u\or v\or w\or x\or y\or z\else
   #1\fi
}
\usepackage{tokcycle}
\Characterdirective{\addcytoks[x]{\explowerchar{#1}}}
\Spacedirective{}
\newcommand\getfilename[1]{\expandedtokcyclexpress{#1}\the\cytoks}
\begin{document}
\getfilename{SaMPleName WITH upperCase and SPACES}

\def\test{SaMPleName WITH upperCase and SPACES} \getfilename{\test} \end{document}

enter image description here