5

Is it possible to define such two macros \mymodule_start_collecting: and \mymodule_stop_collecting: so that the content between them can be passed to a third (given) macro as argument?

Motivation: I would like to have two macros that trims the whitespaces on the two sides of the content between them. For example, \MacroOne Te xt \MacroTwo should behave like Te xt. My idea is to collect the content and pass it to \tl_trim_spaces:n, hence this question.

Jinwen
  • 8,518

4 Answers4

6

If you want to protect against the loss of surrounding braces:

\cs_new:Npn \MacroOne
  { \mymodule_start_collecting:w \prg_do_nothing: }
\cs_new:Npn \mymodule_start_collecting:w #1 \MacroTwo
  { \exp_args:No \tl_trim_spaces:n {#1} }

If you don't care about surrounding braces, then it's much simpler:

\cs_new:Npn \MacroOne #1 \MacroTwo
  { \tl_trim_spaces:n {#1} }

Note that \mymodule_start_collecting: should have signature w.

  • 1
    Well, no. At the user level there can't be \mymodule_stop_collecting: (I suspect you mixed up names and also forgot to obey your own recommendation about the signature). – egreg Apr 04 '22 at 20:52
  • Thank you! May I ask what do you mean by "protect against the loss of surrounding braces"? I don't quite understand the description about \prg_do_nothing: in interface3.pdf. What is the difference with, say, \use_none:n? – Jinwen Apr 04 '22 at 20:56
  • @Jinwen Encountering the token \prg_do_nothing: during expansion causes TeX to remove that token from the token-stream. || Encountering the token \use_none:n during expansion causes TeX to remove that token and a subsequent undelimited (n-type-)argument from the token-stream. ;-) – Ulrich Diez Apr 05 '22 at 12:16
  • 3
    @Jinwen When TeX gathers a macro arg., be it a non-delimited one, be it one that is delimited, e.g., by the token \MacroTwo, a pair of matching curly braces surrounding the entire arg. gets stripped off if present. A trick for preventing the removal of such a pair of braces if present is prepending a token whose o-expansion yields emptiness before having TeX grab the delimited arg. This way the case of the entire delimited arg. being surrounded by curly braces that might get stripped off is eliminated while the token prepended can easily by removed by applying o-expansion. – Ulrich Diez Apr 05 '22 at 12:17
  • 2
    @Jinwen To illustrate what Ulrich is explaining, here's a code comparing both: https://pastebin.com/raw/R8R2VAeg. The behaviour of these two versions will differ only when there is a pair of braces surrounding the entire argument, as in \MacroOne{ hello world }\MacroTwo, otherwise they will behave exactly the same. This is useful, for example, if you want to trim spaces by default, but still want to allow the user to say “no, I do want spaces” by writing braces around the argument – Phelype Oleinik Apr 05 '22 at 12:24
  • @UlrichDiez and Phelype Oleinik: Thank you both for your detailed explanations! – Jinwen Apr 05 '22 at 12:27
  • @Jinwen If you are interested in TeX's subtleties when during expansion it comes to gathering undelimited or delimited macro arguments, the question How does TeX look for delimited arguments? might be of interest to you. – Ulrich Diez Apr 05 '22 at 13:18
  • 1
    @Jinwen I was about to comment on your (now deleted) question: That note about \unexpanded means that, for example, \edef\x{ \tl_trim_spaces:n { \document } } doesn't blow up on trying to expand \document (after that, \show\x would be \document). In other words: \tl_trim_spaces:n is expanded, but its result is not. I'll delete this comment afterwards to remain on-topic here :) – Phelype Oleinik Apr 06 '22 at 21:31
  • @PhelypeOleinik Thank you! Please keep this comment, I find it quite useful but due to my limited knowledge cannot fully understand it right now. – Jinwen Apr 06 '22 at 21:35
  • @Jinwen You can copy-paste the contents elsewhere: comments aren't really for discussions (let alone from other posts :). Here's a test document that redefines \tl_trim_spaces:n to not use \unexpanded. With \unexpanded, the spaces are trimmed, but nothing in the argument is expanded. Without \unexpanded, spaces are trimmed and macros in the argument of \tl_trim_spaces:n are expanded further (and that leads to an error if the macro does not work expandably, like \document). https://pastebin.com/raw/3Vz13ZVY – Phelype Oleinik Apr 06 '22 at 21:45
  • @PhelypeOleinik May I ask how should your code be compiled? Do I need to paste it into a complete document or is there someway to compile it directly? The \stop makes it rather look like plain TeX and I always wonder how should one compile that. – Jinwen Apr 06 '22 at 21:50
  • @Jinwen It's a LaTeX document. \stop is a LaTeX command to end the run (useful for test documents that produce no output). You run it as you would run any LaTeX document. I prefer running pdflatex <name-of-the-file>.tex on the command line, but if you use an IDE, that should work too. If you use the command line, you'll see output in the terminal, otherwise you can look at the generated .log file – Phelype Oleinik Apr 06 '22 at 21:57
  • @Jinwen In "full-expansion-contexts" like \edef, \message, \write, \expanded and with expansion driven by things like expl3's e- or x-expansion usually expandable tokens get expanded until only non-expandable tokens remain. In such contexts expanding \unexpanded{<set of tokens>} yields that the token \unexpanded and the curly braces surrounding <set of tokens> vanish while all the tokens belonging to <set of tokens> remain in place untouched/will not be expanded further during the expansion-context in question. ... – Ulrich Diez Apr 07 '22 at 12:02
  • @Jinwen ... \tl_trim_spaces:n { #1 } after removing spaces at the left and at the right wraps the result in \unexpanded{...} to ensure that in such full-expansion-contexts none of the tokens forming the result of removing spaces gets expanded further. \exp_not:n is just the expl3-name of \unexpanded. Thus, if you wish to have the result of \tl_trim_spaces:n { #1 } without needing to care about the amount of expansion-steps that need to be triggered until the result is there, you can wrap \tl_trim_spaces:n { #1 } into an e- or x-type argument. – Ulrich Diez Apr 07 '22 at 12:10
  • @Jinwen If this answer or one of the others solves your problem, could you please mark one of them as accepted? Thanks! – Phelype Oleinik Apr 11 '22 at 17:15
  • @PhelypeOleinik The reason I didn't accept any answer is that they are equally good to me and it would be very difficult to decide which one to accept. Too bad I can only accept one of them :( – Jinwen Apr 11 '22 at 17:18
  • @Jinwen Then go for the one you used in your particular case. We don't hold hard feelings for internet points :) – Phelype Oleinik Apr 11 '22 at 17:23
6

You can, but the usual latex idiom would be to use an environment, the b argument type does exactly this.

enter image description here

\documentclass{article}

\NewDocumentEnvironment{myenv}{b} {\fbox{#1}} {} \begin{document}

\begin{myenv} This text will be gathered and passed to fbox. \end{myenv}

\end{document}

David Carlisle
  • 757,742
5

If your \MacroOne is always followed by \MacroTwo, you just do

\NewExpandableDocumentCommand{\MacroOne}{}
 {
  \mymodule_start_collecting:w
 }
\cs_new:Npn \mymodule_start_collecting:w #1 \MacroTwo
 {
  \tl_trim_spaces:n { #1 }
 }

Note that you must have explicitly \MacroTwo in the definition, because TeX won't expand tokens when absorbing arguments (delimited or undelimited).

If your use cases include something like

\MacroOne { Te xt} \MacroTwo

and you want to preserve the space after the brace, then use

\NewExpandableDocumentCommand{\MacroOne}{}
 {
  \mymodule_start_collecting:w \prg_do_nothing:
 }
\cs_new:Npn \mymodule_start_collecting:w #1 \MacroTwo
 {
  \exp_args:No \tl_trim_spaces:n { #1 }
 }

(the first item in #1 would be \prg_do_nothing:, whose expansion is empty).

If you also want that \MacroTwo is executed, add it back at the end

\NewExpandableDocumentCommand{\MacroOne}{}
 {
  \mymodule_start_collecting:w
 }
\cs_new:Npn \mymodule_start_collecting:w #1 \MacroTwo
 {
  \tl_trim_spaces:n { #1 } \MacroTwo
 }

(or the brace preserving modification).

egreg
  • 1,121,712
2

Phelype Oleinik and egreg provide brace-preserving variants of \MacroOne ... \MacroTwo.

With these variants sequences of explicit space-tokens, if present, are removed at the left and at the right of the set of tokens that forms the \MacroTwo-delimited argument. If the resulting set of tokens can be considered a set of tokens surrounded by an outermost pair of matching curly braces, that pair of matching curly braces will be left in place and sequences of explicit space tokens inside the curly braces will be left in place, too.

Thus a surrounding pair of matching curly braces can be used for preventing removal of spaces.

But this surrounding pair of matching curly braces for preventing removal of spaces, if present, will be left in place.

I can think of situations where using a surrounding pair of matching curly braces for preventing removal of spaces with stuff inside it is desirable, but where you prefer the curly braces that serve the purpose of preventing removal of spaces to be stripped off because the braces have fulfilled their purpose and are not needed any longer when space-removal is done and hereby removal of spaces inside the braces was prevented.

The following code provides a variant of \MacroA...\MAcroB where space-tokens at the left and at the right of the \MacroB-delimited argument, if present, and -afterwards, if present- the outermost pair of matching curly braces surrounding all the other tokens of the argument are removed but spaces surrounding things inside the outermost pair of matching surrounding curly braces, if present, are preserved.

This way one level of curly braces "surrounding everything but leading and trailing spaces" of the argument in any case is taken for a "space-token-removal preventer" which in any case is stripped off in the process.

Due to \romannumeral/\exp:w-expansion the result can be obtained by triggering two expansion steps on \MacroOne.

\ExplSyntaxOn
\cs_new:Npn \MacroOne
 {
   \exp:w \__mymodule_start_collecting:w \prg_do_nothing:
 }
\cs_new:Npn \__mymodule_start_collecting:w #1 \MacroTwo
 {
   \exp_args:Ne \__mymodule_RemoveSurroundungBraces:n { \tl_trim_spaces:o { #1 } }
 }
\cs_new:Nn\__mymodule_RemoveSurroundungBraces:n 
 {
   \tl_if_blank:nTF { #1 } 
                    { \exp_end: #1 }
                    {
                      \exp_args:No \tl_if_empty:nTF { \use_none:n #1 }
                                                    { \use:nn {\exp_end:} #1 }
                                                    { \exp_end:#1 }
                    }
 }
%----------------------------------------------------------------------------------
% \InsertSpacesAndCallMacroOne{<Argument>} defines the macro \test from the result 
% of triggering two expansion-steps on \MacroOne<Argument>\MacroTwo and shows the
% meaning of \test on the console. Instances of #1 within <Argument> are replaced
% by explicit tokens before expanding \MacroOne.
%
\cs_new:Npn \InsertSpacesAndCallMacroOne #1#2 
 {
   \cs_gset:Npn \InsertSpacesAndCallMacroOne ##1
    {
      \cs_set:Npn \test ####1 
       {
         \exp_args:NNf \cs_set:Npn \test {
           \exp_after:wN \exp_after:wN \exp_after:wN |
           \MacroOne##1\MacroTwo|~is~the~result~of~|#1##1#2|
         }
         \tex_show:D \test
       }
      \test{~}
    }
 }
\exp_args:Noo \InsertSpacesAndCallMacroOne{\token_to_str:N \MacroOne}{\token_to_str:N \MacroTwo}
\ExplSyntaxOff

% #1 within the argument of \InsertSpacesAndCallMacroOne denotes space token

\InsertSpacesAndCallMacroOne{#1#1#1{#1Te#1xt#1}#1#1#1}%

\InsertSpacesAndCallMacroOne{{#1Te#1xt#1}#1#1#1}%

\InsertSpacesAndCallMacroOne{#1#1#1{#1Te#1xt#1}}%

\InsertSpacesAndCallMacroOne{#1#1#1#1Te#1xt#1#1#1#1}%

\InsertSpacesAndCallMacroOne{#1#1#1{#1Te}#1xt#1#1#1#1}%

\InsertSpacesAndCallMacroOne{#1#1#1{#1{#1Te}#1xt#1}#1#1#1}%

% For this minimal example, no document environment was needed and % therefore not loaded. So now we end the LaTeX run with the % "sledgehammer method", i.e., with the command \stop:

\stop

Excerpt from console output:

| Te xt | is the result of |\MacroOne   { Te xt }   \MacroTwo|.

| Te xt | is the result of |\MacroOne{ Te xt } \MacroTwo|.

| Te xt | is the result of |\MacroOne { Te xt }\MacroTwo|.

|Te xt| is the result of |\MacroOne Te xt \MacroTwo|.

|{ Te} xt| is the result of |\MacroOne { Te} xt \MacroTwo|.

| { Te} xt | is the result of |\MacroOne { { Te} xt } \MacroTwo|.

Ulrich Diez
  • 28,770