4

I have a function that takes a parameters with other commands. Then I split the parameter by a second command that is in the parameter. Now I need to get the parameter that is given to this command. So I have a string that starts with a curly brackets and somewhere in the string it is closed. So I want to get this string that is in between these curly brackets and save it for example in a variable. For example

\edef\foo{{this is the string I want} This not}

Edit: Added the example

3
  • 4
    Welcome to TeX.SE! You'll need to provide some code, to be honest I'm not even you're you're talking about TeX...
    – campa
    Feb 22 at 18:00
  • Are you able to use LuaLaTeX?
    – Mico
    Feb 22 at 19:17
  • What are you trying to indicate by saying \edef\foo{...} instead of just \def\foo{...} ? (None of the tokens of the <replacement text> of your \edef is expandable. So in this case there is no difference between \def and \edef.) Feb 22 at 22:06

7 Answers 7

4

The text can be extracted with \regex_extract_once:nVN and the regular expression \{(.+)\}. Then the desired text is given by the second item in the sequence \l_tmpa_seq.

enter image description here

\documentclass[border=6pt]{standalone}
\begin{document}
\edef\foo{{this is the string I want} This not}
\ExplSyntaxOn
\cs_generate_variant:Nn \regex_extract_once:nnN { nVN }
\regex_extract_once:nVN { \{(.+)\} } \foo \l_tmpa_seq
\seq_item:Nn \l_tmpa_seq { 2 }
\ExplSyntaxOff
\end{document}

Edit: below, a command \extract is defined to work on user input.

\documentclass[border=6pt]{standalone}
\ExplSyntaxOn
\NewDocumentCommand \extract { m }
  {
    \regex_extract_once:nnN { \{(.+)\} } {#1} \l_tmpa_seq
    \seq_item:Nn \l_tmpa_seq { 2 }
  }
\ExplSyntaxOff
\begin{document}
\extract{{this is the string I want} This not}
\end{document}
5
  • Good solution, but how would you generalize it to get user input, such as \foo#1{#1}?
    – yannisl
    Feb 22 at 20:28
  • @yannisl How do you mean precisely? Feb 22 at 20:35
  • Assuming this is some author command \foo you would expect someone to type \foo{{this text}not this}. See my post for an example.
    – yannisl
    Feb 22 at 20:38
  • @yannisl I updated the answer with a command \extract to work on user input. Feb 22 at 21:58
  • Thanks. This is what I was suggesting.
    – yannisl
    Feb 23 at 3:30
6

As far as I can tell, you want some thing simpler than the answers suggested so far.


\def\defunwanted#1\foo{\def\unwanted{#1}}
\def\foo#1{%
  \afterassignment\defunwanted
  \def\wanted#1\foo}
  
  \foo{{this is the string I want} This not}

  \show\wanted

  \show\unwanted

  \bye

shows \wanted is this is the string I want and \unwanted is This not

3

You can do it with expl3. If the replacement text of the macro given as argument doesn't start with { (an explicit brace), only ?? is returned (it can be made into an error message).

If there's an optional argument, a macro name, it is defined to contain the tokens in the braced group.

\documentclass{article}

\ExplSyntaxOn

\NewDocumentCommand{\extractfirstgroup}{om}
 {
  \jonibue_extractfirst:o { #2 }
  \IfValueTF { #1 }
   { \tl_new:N #1 \tl_set_eq:NN #1 \l__jonibue_extractfirst_tl }
   { \tl_use:N \l__jonibue_extractfirst_tl }
 }

\tl_new:N \l__jonibue_extractfirst_tl

\cs_new_protected:Nn \jonibue_extractfirst:n
 {
  \tl_clear:N \l__jonibue_extractfirst_tl
  \tl_if_head_is_group:nTF { #1 }
   {
    \__jonibue_extractfirst:w #1 \q_stop
   }
   {
    ??
   }
 }
\cs_generate_variant:Nn \jonibue_extractfirst:n { o }

\cs_new_protected:Npn \__jonibue_extractfirst:w #1 #2 \q_stop
 {
  \tl_set:Nn \l__jonibue_extractfirst_tl { #1 }
 }

\ExplSyntaxOff

\begin{document}

\newcommand\foo{{this is the string I want} This not}
\newcommand\baz{No group}

\extractfirstgroup{\foo}

\extractfirstgroup[\xyz]{\foo}
\texttt{\meaning\xyz}

\extractfirstgroup{\baz}

\end{document}

enter image description here

2

The token cycle saves it in a token list, \cytoks.

\documentclass{article}
\usepackage{tokcycle}
\Characterdirective{}
\Groupdirective{\addcytoks{#1}}
\Macrodirective{}
\Spacedirective{}
\stripgroupingtrue

\edef\foo{Nor this {this is the string I want} This not}
\begin{document}
\expandafter\tokcyclexpress\expandafter{\foo}

\the\cytoks

Braces gone: \detokenize\expandafter{\the\cytoks}
\end{document}

enter image description here

2

If I had to do it on a traditional engine without the extensions of LuaTeX or eTeX and without expl3 programming layer, maybe I would do it like this:

\makeatletter
%%=============================================================================
%% PARAPHERNALIA:
%% \UD@firstoftwo, \UD@secondoftwo, \UD@PassFirstToSecond, \UD@Exchange,
%% \UD@removespace, \UD@stopromannumeral, \UD@CheckWhetherNull,
%% \UD@CheckWhetherBrace, \UD@CheckWhetherLeadingExplicitSpace,
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@Exchange[2]{#2#1}%
\@ifdefinable\UD@removespace{\UD@Exchange{ }{\def\UD@removespace}{}}%
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00 }%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is empty>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral\expandafter\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
  \expandafter\UD@stopromannumeral\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's first token is an explicit character token 
%% of category 1 (begin group):
%%.............................................................................
%% \UD@CheckWhetherBrace{<Argument which is to be checked>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked has a leading
%%                        explicit catcode-1-character-token>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked does not have a
%%                        leading explicit catcode-1-character-token>}%
\newcommand\UD@CheckWhetherBrace[1]{%
  \romannumeral\expandafter\UD@secondoftwo\expandafter{\expandafter{%
  \string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@firstoftwo}{%
  \expandafter\UD@stopromannumeral\UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument's first token is an explicit
%% space-token:
%%.............................................................................
%% \UD@CheckWhetherLeadingExplicitSpace{<Argument which is to be checked>}%
%%                                     {<Tokens to be delivered in case <argument
%%                                       which is to be checked> does have a
%%                                       leading explicit space-token>}%
%%                                     {<Tokens to be delivered in case <argument
%%                                       which is to be checked> does not have a
%%                                       a leading explicit space-token>}%
\newcommand\UD@CheckWhetherLeadingExplicitSpace[1]{%
  \romannumeral\UD@CheckWhetherNull{#1}%
  {\expandafter\UD@stopromannumeral\UD@secondoftwo}%
  {%
    % Let's nest things into \UD@firstoftwo{...}{} to make sure they are nested in braces
    % and thus do not disturb when the test is carried out within \halign/\valign:
    \expandafter\UD@firstoftwo\expandafter{%
      \expandafter\expandafter\expandafter\UD@stopromannumeral
      \romannumeral\expandafter\UD@secondoftwo
      \string{\UD@CheckWhetherLeadingExplicitSpaceB.#1 }{}%
    }{}%
  }%
}%
\@ifdefinable\UD@CheckWhetherLeadingExplicitSpaceB{%
  \long\def\UD@CheckWhetherLeadingExplicitSpaceB#1 {%
    \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
    {\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
    {\expandafter\expandafter\expandafter\UD@stopromannumeral
     \expandafter\expandafter\expandafter}%
     \expandafter\UD@secondoftwo\expandafter{\string}%
  }%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%
%%   \UD@ExtractFirstArgUnbraced{ABCDE} yields  A
%%
%%   \UD@ExtractFirstArgUnbraced{{AB}CDE} yields  AB
%%
%% Due to \romannumeral-expansion the result is delivered after two 
%% expansion-steps/after "hitting" \ExtractFirstArgUnbraced with 
%% \expandafter twice.
%%
%% \UD@ExtractFirstArgUnbraced's argument must not be blank.
%%
%% Use frozen-\relax as delimiter for speeding things up.
%% I chose frozen-\relax because David Carlisle pointed out in
%% <https://tex.stackexchange.com/a/578877>
%% that frozen-\relax cannot be (re)defined in terms of \outer and cannot be
%% affected by \uppercase/\lowercase.
%%
%% \ExtractFirstArgUnbraced's argument may contain frozen-\relax:
%% The only effect is that internally more iterations are needed for
%% obtaining the result.
%%
%%.............................................................................
\@ifdefinable\UD@RemoveTillFrozenrelax{%
  \expandafter\expandafter\expandafter\UD@Exchange
  \expandafter\expandafter\expandafter{%
  \expandafter\expandafter\ifnum0=0\fi}%
  {\long\def\UD@RemoveTillFrozenrelax#1#2}{{#1}}%
}%
\expandafter\UD@PassFirstToSecond\expandafter{%
  \romannumeral\expandafter
  \UD@PassFirstToSecond\expandafter{\romannumeral
    \expandafter\expandafter\expandafter\UD@Exchange
    \expandafter\expandafter\expandafter{%
    \expandafter\expandafter\ifnum0=0\fi}{\UD@stopromannumeral#1{}}%
  }{%
    \UD@stopromannumeral\romannumeral\UD@ExtractFirstArgUnbracedLoop
  }%
}{%
  \newcommand\UD@ExtractFirstArgUnbraced[1]%
}%
\newcommand\UD@ExtractFirstArgUnbracedLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
  {\expandafter\UD@stopromannumeral\UD@secondoftwo{}#1}%
  {\expandafter\UD@ExtractFirstArgUnbracedLoop
   \expandafter{\UD@RemoveTillFrozenrelax#1}}%
}%
%%=============================================================================
%% \UDExtractFirstBraceGroup
%%   {<tokens in case there is no brace group>}%
%%   {<argument where to extract content of 1st brace group>}%
%%
%% In case <argument where to extract content of 1st brace group> does not
%% contain a brace group you get <tokens in case there is no brace group>.
%% Otherwise you get the content of the 1st brace group of the <argument where
%% to extract content of 1st brace group>.
%% 
%% Due to \romannumeral-expansion the result can be obtained by triggering
%% two expansions on \UDExtractFirstBraceGroup
%%
%%=============================================================================
\newcommand\UDExtractFirstBraceGroup[2]{%
  \romannumeral\UD@ExtractFirstBraceGroupLoop{#2}{#1}%
}%
\newcommand\UD@ExtractFirstBraceGroupLoop[2]{%
  \UD@CheckWhetherNull{#1}{\UD@stopromannumeral#2}{%
    \UD@CheckWhetherBrace{#1}{%
      \expandafter\UD@firstoftwo\expandafter{\expandafter}%
      \UD@ExtractFirstArgUnbraced{#1}%
    }{%
      \UD@CheckWhetherLeadingExplicitSpace{#1}{%
        \expandafter\UD@ExtractFirstBraceGroupLoop\expandafter{\UD@removespace#1}%
      }{%
        \expandafter\UD@ExtractFirstBraceGroupLoop\expandafter{\UD@firstoftwo{}#1}%
      }%
      {#2}%
    }%
  }%
}%
%%=============================================================================
\makeatother

\documentclass{article}

\begin{document}

\ttfamily\frenchspacing

\UDExtractFirstBraceGroup{%
  Hey, there is no brace group in the string!!!%
}{%
  This not {This is the string I want} This not {This not} This not {This not}%
}%

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\foo
\expandafter\expandafter\expandafter{%
  \UDExtractFirstBraceGroup{%
    Hey, there is no brace group in the string!!!%
  }{%
    This not {This is the string I want} This not {This not} This not {This not}%
  }%
}%
\string\foo\space is: \meaning\foo

\edef\bar{%
  \UDExtractFirstBraceGroup{%
    Hey, there is no brace group in the string!!!%
  }{%
    This not {This is the string I want} This not {This not} This not {This not}%
  }%
}%
\string\bar\space is: \meaning\bar

\bigskip
\hrule
\bigskip

\UDExtractFirstBraceGroup{%
  Hey, there is no brace group in the string!!!%
}{%
  {This is the string I want} This not%
}%

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\bas
\expandafter\expandafter\expandafter{%
  \UDExtractFirstBraceGroup{%
    Hey, there is no brace group in the string!!!%
  }{%
    {This is the string I want} This not%
  }%
}%
\string\bas\space is: \meaning\bas

\edef\bat{%
  \UDExtractFirstBraceGroup{%
    Hey, there is no brace group in the string!!!%
  }{%
    {This is the string I want} This not%
  }%
}%
\string\bat\space is: \meaning\bat

\bigskip
\hrule
\bigskip

\UDExtractFirstBraceGroup{%
  Hey, there is no brace group in the string!!!%
}{%
  This is a string without brace group.%
}%

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\bau
\expandafter\expandafter\expandafter{%
  \UDExtractFirstBraceGroup{%
    Hey, there is no brace group in the string!!!%
  }{%
    This is a string without brace group.%
  }%
}%
\string\bau\space is: \meaning\bau

\edef\bav{%
  \UDExtractFirstBraceGroup{%
    Hey, there is no brace group in the string!!!%
  }{%
    This is a string without brace group.%
  }%
}%
\string\bav\space is: \meaning\bav

\end{document}

enter image description here

9
  • I think a solution with futurelet might be simpler for this case. Ala pgfparser?
    – yannisl
    Feb 22 at 20:21
  • 1
    @yannisl Yes. But \futurelet does not work out in the context of \edef-expansion. Feb 22 at 20:24
  • 1
    @yannisl I provided a command \UDExtractFirstBraceGroup{<tokens in case there is no brace group>}{<argument where to extract content of 1st brace group>} which also works in expansion contexts like \edef, \expanded, \csname..\endcsname, \write, \message, which does only with things available in Knuthian TeX-engines so that you don't need whatsoever extensions and where only \outer tokens are forbidden in the arguments and where even unbalanced \if/\else/\fi/\csname/\endcsname etc may occur within the arguments. How much more generalization do you need? And in which way? Feb 22 at 20:58
  • 1
    @yannisl And all code that might go into a package is between \makeatletter/\makeatother even before \documentclass. Feb 22 at 21:01
  • 1
    @yannisl And in my humble oponion another (not so important) pro for expansion-based solutions is that they won't break in case someone for some obscure reason plays around with the value of \globaldefs. Feb 23 at 12:53
1

A variant of David Carlisle's which does without delimited arguments so that \foo may occur in \foo's argument:

\long\def\GOBBLE#1{}%
\def\defunwanted{%
  \expandafter\expandafter\expandafter\def
  \expandafter\expandafter\expandafter\unwanted
  \expandafter\expandafter\expandafter{%
  \expandafter\GOBBLE\string}%
}%
\long\def\foo#1{%
  \expandafter\GOBBLE\string{%
  \afterassignment\defunwanted
  \def\wanted#1}%
}%
  
\foo{{this is the string I want} This not \foo  This not}

\show\wanted

\show\unwanted

\bye

In case you prefer \edef...{\the<token register>} in order to have hashes in the argument of \foo doubled rather than erroneously taken for paramaters of the macros \wanted/\unwanted:

\newtoks\scratchtoks
\long\def\GOBBLE#1{}%
\long\def\PASSFIRSTTOSECOND#1#2{#2{#1}}

\def\defunwanted{%
  \edef\unwanted{\the\scratchtoks}%
  \scratchtoks=%
}%
\def\defwanted{%
  \edef\wanted{\the\scratchtoks}%
  \afterassignment\defunwanted
  \scratchtoks=%
  \expandafter\expandafter\expandafter{%
  \expandafter\GOBBLE\string}%
}%
\long\def\foo#1{%
  \expandafter\PASSFIRSTTOSECOND\expandafter{\the\scratchtoks}{%
    \expandafter\GOBBLE\string{%
    \afterassignment\defwanted
    \scratchtoks=#1}%
  }%
}%

\scratchtoks={SomeThing}

\foo{{this is the string #1 I want} This not #2 \foo This not}

\show\wanted

\show\unwanted

\showthe\scratchtoks

\bye

In case \unexpanded is available with the engine in use and you prefer \edef...{\unexpanded{...}} in order to have hashes in the argument of \foo doubled rather than erroneously taken for paramaters of the macros \wanted/\unwanted:

\long\def\GOBBLE#1{}%
\chardef\stopromannumeral=`\^^00 %
\def\defunwanted{%
  \expandafter\edef
  \expandafter\unwanted
  \expandafter{%
  \expandafter\unexpanded
  \expandafter{%
  \romannumeral
  \expandafter\GOBBLE\string}%
  \expandafter\GOBBLE\string}%
  \stopromannumeral
}%
\long\def\defwanted#1{%
  \afterassignment\defunwanted
  \edef\wanted{\unexpanded{#1}}%
}%
\long\def\foo#1{%
  \expandafter\GOBBLE\string{%
  \expandafter\GOBBLE\string{\defwanted#1}}%
}%

\foo{{this is the string #1 I want} This not #2 \foo This not}

\show\wanted

\show\unwanted

\bye
0

The OP has not clarified, how user input is to be obtained. So this solution is based on a delimited macro.

\def\grab#1{\{#1\}}
\edef\foo #1!#2;{\grab{#1}}
\foo This is the string I want!not this;

The ! and ; delimit the user input, but then the \grab macro can be designed to decorate the capture in a more broad way. A more complicate parser can be written using \futurelet or the pgfparser

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .