# Test if an argument starts with a particular string

Is there a way in tex to test if an argument starts with some string or not? And then extract the text that follows that start?

As an example, I want to define a command that makes a text bold if it starts with my and italic otherwise.

\transform{my big world}
% Returns "\textbf{big world}".

\transform{our small world}
% Returns "\textit{small world}".

% Returns \textit{world}

% It would be great if the test is case insensitive
\transform{My world}
% Returns \textbf{world}


If possible, I prefer a method not using expl3 (I don't understand it enough to tinker with it).

The answers here gobbles a single character, which I can use for every character of my string. But that would get unwieldy rather quickly for long strings.

If your first word is always separated from the rest by a space, you can do with the following code:

\documentclass{article}

\makeatletter
\newcommand{\transform}[1]{\transform@#1\@nil}
\def\transform@@prefix{my}
\def\transform@#1 #2\@nil{%
\lowercase{\def\transform@@start{#1}}%
\ifx\transform@@start\transform@@prefix
\textbf{#2}%
\else
\textit{#2}%
\fi
}
\makeatother

\begin{document}

\transform{my big world} should return \textbf{big world}.

\transform{our small world} should return \textit{small world}.

\transform{My world} should return \textbf{world}

\end{document}


The first word is isolated and its lowercase version is matched with my.

Since you want a colon, rather than a space, it's possible to manage the absence of a prefix.

\documentclass{article}

\makeatletter
\newcommand{\transform}[1]{\transform@#1::\@nil}
\def\transform@@prefix{my}
\def\transform@#1:#2:#3\@nil{%
\if\relax\detokenize{#2}\relax
% no colon in #1
\textit{#1}%
\else
\lowercase{\def\transform@@start{#1}}%
\ifx\transform@@start\transform@@prefix
\textbf{#2}%
\else
\textit{#2}%
\fi
\fi
}
\makeatother

\begin{document}

\transform{my:big world} should return \textbf{big world}.

\transform{our:small world} should return \textit{small world}.

\transform{your:world} should return \textit{world}

\transform{My:world} should return \textbf{world}

\transform{world} should return \textit{world}

\end{document}


An expl3 version, which is also (almost) fully expandable (it is not really because of \textit and \textbf, but we can use \text_expand:n to take care of that.

\documentclass{article}

\ExplSyntaxOn

% just for the final test
\cs_new_eq:NN \textexpand \text_expand:n
%%%

\NewExpandableDocumentCommand{\transform}{sm}
{
\IfBooleanTF{#1}
{
\tohiko_transform:V #2
}
{
\tohiko_transform:n { #2 }
}
}

\cs_new:Npx \tohiko_transform:n #1
{% colons are special in expl3
\exp_not:N \__tohiko_transform:w #1 \c_colon_str \c_colon_str \exp_not:N \q_stop
}
\cs_generate_variant:Nn \tohiko_transform:n { V }

\use:x
{% colons are special in expl3
\cs_new:Npn \exp_not:N \__tohiko_transform:w
##1 \c_colon_str ##2 \c_colon_str ##3 \exp_not:N \q_stop
}
{
\tl_if_empty:nTF { #2 }
{
\__tohiko_transformed:Nn \textit { #1 }
}
{
\__tohiko_transform:nn { #1 } { #2 }
}
}

\cs_new:Npn \__tohiko_transform:nn #1 #2
{
\str_if_eq:eeTF { \str_lowercase:n { #1 } } { my }
{
\__tohiko_transformed:Nn \textbf { #2 }
}
{
\__tohiko_transformed:Nn \textit { #2 }
}
}

\cs_new:Npn \__tohiko_transformed:Nn #1 #2
{
#1{\tl_trim_spaces:n{#2}}
}

\ExplSyntaxOff

\newcommand{\testA}{my:hello world}
\newcommand{\testB}{our:hello world}

\begin{document}

\transform{my: big world} should return \textbf{big world}.

\transform{our:small world} should return \textit{small world}.

\transform{your:world } should return \textit{world}.

\transform{My:world} should return \textbf{world}.

\transform{world} should return \textit{world}.

\transform*{\testA} should return \textbf{hello world}.

\transform*{\testB} should return \textit{hello world}.

\edef\test{\textexpand{\transform{my: big world}}}
\texttt{\meaning\test}

\edef\test{\textexpand{\transform*{\testB}}}
\texttt{\meaning\test}

\end{document}


• In my case, the separator character is a colon but this solution can be adapted easily to that case. Thanks! Nov 10, 2021 at 11:22
• Would it be difficult to adapt it so that if no space is found, the whole is italicized? So that \transform{world} changes to \textit{world}? Nov 10, 2021 at 12:00
• @Tohiko With the colon it's easier; with spaces it would be more complicated. I added the code. Nov 10, 2021 at 13:10
• Thanks. I just figured out that to \transform accept a command which evaluates to a string I also need to define \newcommand{\transform}[1]{\expandafter\transform@#1::\@nil} Nov 10, 2021 at 13:42

Here's a LuaLaTeX-based solution. It recognizes both : and whitespace as possible delimiters.

\documentclass{article}
\usepackage{luacode}
\begin{luacode}

function transform ( s )
if s:find ( "^[Mm]y" ) then
s = s:gsub  ( "^.-[%s:]+(.*)" , "\\textbf{%1}")
elseif s:find ( "^.-[%s:]+" ) then
s = s:gsub  ( "^.-[%s:]+(.*)" , "\\textit{%1}")
else
s = "\\textit{"..s.."}"
end
tex.sprint ( s )
end

\end{luacode}
\newcommand\transform[1]{\directlua{transform(\luastring{#1})}}

\begin{document}
\obeylines
\transform{my:big world}   should return \textbf{big world}.
\transform{my   big world} should return \textbf{big world}.
\transform{my: big world}  should return \textbf{big world}.

\smallskip
\transform{My:big world}   should return \textbf{big world}.
\transform{My   big world} should return \textbf{big world}.
\transform{My: big world}  should return \textbf{big world}.

\smallskip
\transform{our:big world}   should return \textit{big world}.
\transform{our big world}   should return \textit{big world}.
\transform{our:  big world} should return \textit{big world}.

\smallskip
\transform{world} should return \textit{world}
\end{document}


Not using expl3 I can offer a generic mechanism \UD@CheckWhetherLeadingTokens for detecting whether a macro argument's leading tokens form a specific token-sequence.

\UD@CheckWhetherLeadingTokens requires an ⟨internal token-check-macro⟩ that processes a macro-argument delimited by that specific token-sequence.

The fact that the specific token-sequence is used as an argument-delimiter implies that the specific token-sequence cannot contain explicit character tokens of category 1(begin group) or 2(end group) or 6(parameter).

Syntax is:

\UD@CheckWhetherLeadingTokens{⟨argument which is to be checked⟩}%
{⟨a ⟨token sequence⟩ without explicit
character tokens of category 1 or 2
or 6⟩}%
{⟨internal token-check-macro⟩}%
{⟨tokens to be delivered in case
⟨argument which is to be checked⟩ has
{⟨tokens to be delivered in case
⟨argument which is to be checked⟩
does not have ⟨token sequence⟩ as
leading tokens⟩}%

For defining the ⟨internal token-check-macro⟩ with the delimited argument you can use

\UD@internaltokencheckdefiner{⟨internal token-check-macro⟩}%
{⟨token-sequence-gobble-macro⟩}%
{⟨token sequence⟩}%

⟨token-sequence-gobble-macro⟩ is not used by \UD@CheckWhetherLeadingTokens but it can be used for gobbling the leading token-sequence.

Here is the MWE:

\documentclass{article}

\makeatletter
%%=============================================================================
%% Paraphernalia:
%%    \UD@firstoftwo, \UD@secondoftwo, \UD@Exchange, \UD@stopromannumeral,
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@Exchange[2]{#2#1}%
\@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:
\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 leading tokens form a specific
%% token-sequence that does not contain explicit character tokens of
%% category 1 or 2 or 6:
%%.............................................................................
%% \UD@CheckWhetherLeadingTokens{<argument which is to be checked>}%
%%                              {<a <token sequence> without explicit
%%                                character tokens of category 1 or 2
%%                                or 6>}%
%%                              {<internal token-check-macro>}%
%%                              {<tokens to be delivered in case
%%                                <argument which is to be checked> has
%%                                <token sequence> as leading tokens>}%
%%                              {<tokens to be delivered in case
%%                                <argument which is to be checked>
%%                                does not have <token sequence> as
\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@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}%
}%
%%-----------------------------------------------------------------------------
%% \UD@internaltokencheckdefiner{<internal token-check-macro>}%
%%                              {<token-sequence-gobble-macro>}%
%%                              {<token sequence>}%
%% - Defines <internal token-check-macro> to snap everything
%%   until reaching <token sequence> and spit that out,
%%   nested in braces. <token sequence> is discarded.
%% - Defines <token-sequence-gobble-macro> to gobble/discard
%%   everything until reaching <token sequence>.
%%   <token sequence> is discarded, too.
%% <token sequence> must not contain explicit character tokens
%% of category 1 or 2 or 6.
%%-----------------------------------------------------------------------------
\newcommand\UD@internaltokencheckdefiner[3]{%
\@ifdefinable#1{\long\def#1##1#3{{##1}}}%
\@ifdefinable#2{\def#2#3{}}%
}%
%------------------------------------------------------------------------------
\UD@internaltokencheckdefiner{\UD@Checkmyspace}{\UD@Gobblemyspace}{my }%
\UD@internaltokencheckdefiner{\UD@CheckMyspace}{\UD@GobbleMyspace}{My }%
\UD@internaltokencheckdefiner{\UD@CheckmYspace}{\UD@GobblemYspace}{mY }%
\UD@internaltokencheckdefiner{\UD@CheckMYspace}{\UD@GobbleMYspace}{MY }%
%\UD@internaltokencheckdefiner{\UD@Checkmy}{\UD@Gobblemy}{my}%
%\UD@internaltokencheckdefiner{\UD@CheckMy}{\UD@GobbleMy}{My}%
%\UD@internaltokencheckdefiner{\UD@CheckmY}{\UD@GobblemY}{mY}%
%\UD@internaltokencheckdefiner{\UD@CheckMY}{\UD@GobbleMY}{MY}%
%------------------------------------------------------------------------------
\@ifdefinable\UD@gobbletospace{\long\def\UD@gobbletospace#1 {}}%
\newcommand\UD@CheckWhetherNoSpace[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@gobbletospace#1 }%
}%
%------------------------------------------------------------------------------
\newcommand\transform[1]{%
\textit{\UD@CheckWhetherNoSpace{#1}{}{\UD@gobbletospace}#1}%
%                }%
%              }%
%            }%
%          }%
}%
}%
}%
}%
}%
%------------------------------------------------------------------------------
\makeatother

\begin{document}

\verb|\transform{my big world}|: \transform{my big world}

\verb|\transform{our small world}|: \transform{our small world}

\verb|\transform{My world}|: \transform{My world}

\verb|\transform{My }|: \transform{My }

\verb|\transform{My}|: \transform{My}

\verb|\transform{Bla}|: \transform{Bla}

\end{document}


You did not specify desired behavior in case the argument contains no spaces. In this case the argument is delivered in \textit without anything being removed.

If you need different behavior let me know via a comment and I will edit my answer accordingly.

\UD@CheckWhetherLeadingTokens does not change the letter-casing of any component of its first argument and thus \transform does not change the letter-casing of any component of its argument.

Occurrence of marker-tokens/sentinel-tokens like \@nil within the argument of \transform/within the first argument of \UD@CheckWhetherLeadingTokens is not forbidden.

Unmatched \if.. or \else or \or or \fi or \csname or \endcsname may occur within the first argument of \UD@CheckWhetherLeadingTokens. However with \transform things are passed on to the LaTeX 2ε-kernel-macros\textit/\textbf which don't handle such unmatched things.

\UD@CheckWhetherLeadingTokens and thus \transform itself work by means of expansion only. This means carrying out any of the macros belonging to the mechanism for finding out whether the argument has a leading token-sequence my⟨space⟩/My⟨space⟩ /mY⟨space⟩ /MY⟨space⟩ does not trigger performing whatsoever temporary assignment.
\UD@CheckWhetherLeadingTokens can be used with things like \edef, \expanded, \write, \message, \csname..\endcsname, \number, \romannumeral, \ifcsname..\endcsname, \numexpr etc as well.

However, the LaTeX 2ε-kernel-macros \textbf and \textit themselves are not fully expandable and cannot reliably be used with such things. With \textbf and \textit both the LaTeX 2ε-kernel's \protect-mechanism and the TeX-engine's protected-mechanism and with old LaTeX 2ε-kernels not applying any protection-mechanism to \textbf and \textit at all will at least disturb things like \csname..\endcsname, \number, \romannumeral \ifcsname..\endcsname, and \numexpr.

You can easily adapt this to using colon (:) instad of space but be aware that there are packages which turn the colon into an active character while the mechanism below is defined while the category code of : is 12(other).

\documentclass{article}

\makeatletter
%%=============================================================================
%% Paraphernalia:
%%    \UD@firstoftwo, \UD@secondoftwo, \UD@Exchange, \UD@stopromannumeral,
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@Exchange[2]{#2#1}%
\@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:
\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 leading tokens form a specific
%% token-sequence that does not contain explicit character tokens of
%% category 1 or 2 or 6:
%%.............................................................................
%% \UD@CheckWhetherLeadingTokens{<argument which is to be checked>}%
%%                              {<a <token sequence> without explicit
%%                                character tokens of category 1 or 2
%%                                or 6>}%
%%                              {<internal token-check-macro>}%
%%                              {<tokens to be delivered in case
%%                                <argument which is to be checked> has
%%                                <token sequence> as leading tokens>}%
%%                              {<tokens to be delivered in case
%%                                <argument which is to be checked>
%%                                does not have <token sequence> as
\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@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}%
}%
%%-----------------------------------------------------------------------------
%% \UD@internaltokencheckdefiner{<internal token-check-macro>}%
%%                              {<token-sequence-gobble-macro>}%
%%                              {<token sequence>}%
%% - Defines <internal token-check-macro> to snap everything
%%   until reaching <token sequence> and spit that out,
%%   nested in braces. <token sequence> is discarded.
%% - Defines <token-sequence-gobble-macro> to gobble/discard
%%   everything until reaching <token sequence>.
%%   <token sequence> is discarded, too.
%% <token sequence> must not contain explicit character tokens
%% of category 1 or 2 or 6.
%%-----------------------------------------------------------------------------
\newcommand\UD@internaltokencheckdefiner[3]{%
\@ifdefinable#1{\long\def#1##1#3{{##1}}}%
\@ifdefinable#2{\def#2#3{}}%
}%
%------------------------------------------------------------------------------
\UD@internaltokencheckdefiner{\UD@Checkmycolon}{\UD@Gobblemycolon}{my:}%
\UD@internaltokencheckdefiner{\UD@CheckMycolon}{\UD@GobbleMycolon}{My:}%
\UD@internaltokencheckdefiner{\UD@CheckmYcolon}{\UD@GobblemYcolon}{mY:}%
\UD@internaltokencheckdefiner{\UD@CheckMYcolon}{\UD@GobbleMYcolon}{MY:}%
%------------------------------------------------------------------------------
\@ifdefinable\UD@gobbletocolon{\long\def\UD@gobbletocolon#1:{}}%
\newcommand\UD@CheckWhetherNoColon[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@gobbletocolon#1:}%
}%
%------------------------------------------------------------------------------
\newcommand\transform[1]{%
\textit{\UD@CheckWhetherNoColon{#1}{}{\UD@gobbletocolon}#1}%
}%
}%
}%
}%
}%
%------------------------------------------------------------------------------
\makeatother

\begin{document}

\verb|\transform{my:big world}| yields \transform{my:big world}

\verb|\transform{our:small world}| yields \transform{our:small world}

\verb|\transform{your:world}| yields \transform{your:world}

\verb|\transform{My:world}| yields \transform{My:world}

\verb|\transform{My:}| yields \transform{My:}

\verb|\transform{My}| yields \transform{My}

\verb|\transform{Bla}| yields \transform{Bla}

\end{document}


For completeness an xstring solution. First lower case the argument, then find the first space (default 0 if no space is present), split in left and right part (left part empty if no space is present), compare the left part to my  (including the space), print the right part in bold or italics accordingly.

\documentclass{article}
\usepackage{xstring}
\newcommand{\transform}[1]{%
\lowercase{\def\arglc{#1}}%
\StrPosition{\arglc}{ }[\spacepos]%
\StrSplit{\arglc}{\spacepos}{\leftpart}{\rightpart}%
\IfEq{\leftpart}{my }{\textbf{\rightpart}}{\textit{\rightpart}}%
}
\begin{document}
\transform{my big world}
% Returns "\textbf{big world}".

\transform{our small world}
% Returns "\textit{small world}".

% Returns \textit{world}

% It would be great if the test is case insensitive
\transform{My world}
% Returns \textbf{world}

\transform{world}
\end{document}


It seems that the \getword macro from here would work well too.

\documentclass{article}

% https://tex.stackexchange.com/a/616582/
\protected\def\getword#1{%
\begingroup \toks0{}\toks1{#1}%
\futurelet\next\getwordA
}
\def\getwordA{\ifcat A\noexpand\next \expandafter\getwordB \else \expandafter\getwordC \fi}
\def\getwordB#1{\toks0\expandafter{\the\toks0 #1}\futurelet\next\getwordA}
\def\getwordC{\edef\tmp{\endgroup \the\toks1{\the\toks0}}\tmp}

\def\transformA#1{%
\lowercase{\ifnum\pdfstrcmp{#1}{my}=0 \bfseries
\else \ifnum\pdfstrcmp{#1}{our}=0 \itshape \fi\fi}%
\ignorespaces
}
\newcommand\transform[1]{\begingroup \getword\transformA#1\endgroup}

\begin{document}

\transform{my big world}

\transform{our small world}