32

I want to implement an if-then-else depending on whether the argument contains a special character or not. Should I be looking here: http://www.tug.org/TUGboat/Articles/tb28-1/tb88glister.pdf, or does anyone know a simpler way?

lockstep
  • 250,273
oleks
  • 619

4 Answers4

31
\makeatletter
\def\instring#1#2{TT\fi\begingroup
  \edef\x{\endgroup\noexpand\in@{#1}{#2}}\x\ifin@}
\makeatother

\def\mystring{abcdef}

\if\instring{a}{abcdef}\message{YES}\else\message{NO}\fi
\if\instring{a}{\mystring}\message{YES}\else\message{NO}\fi

LaTeX should show "YES" in both cases. However, it depends on the kind of "special character" you are interested in.

A much more powerful macro with expl3:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\instringTF}{mmmm}
 {
  \oleks_instring:nnnn { #1 } { #2 } { #3 } { #4 }
 }

\tl_new:N \l__oleks_instring_test_tl

\cs_new_protected:Nn \oleks_instring:nnnn
 {
  \tl_set:Nn \l__oleks_instring_test_tl { #1 }
  \regex_match:nnTF { \u{l__oleks_instring_test_tl} } { #2 } { #3 } { #4 }
 }

\ExplSyntaxOff

\begin{document}

\instringTF{=}{a=b}{true}{false} (should be true)

\instringTF{=}{ab}{true}{false} (should be false)

\instringTF{à}{città}{true}{false} (should be true)

\instringTF{à}{mela}{true}{false} (should be false)

\end{document}

enter image description here

An even more powerful version; the \instringxTF macro has one optional argument, for choosing the match (default 1, first match). The first mandatory argument is the text to look for, the second is the input.

The third mandatory argument can contain #1 that stands for the text before the chosen match and #2 for the text following the match. Similarly, the fourth mandatory argument can contain #1 for the input text.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\instringxTF}{O{1}mmmm}
 {% #1 = number of match
  % #2 = test tokens
  % #3 = input to check for match
  % #4 = code to execute for matches
  %      #1 stands for the pre-match
  %      #2 for the post-match
  % #5 = code to execute for no match
  %      #1 for the input text
  \oleks_instring:nnnnn { #1 } { #2 } { #3 } { #4 } { #5 }
 }

\cs_generate_variant:Nn \seq_use:Nn { NV }

\tl_new:N \l__oleks_instring_test_tl
\seq_new:N \l__oleks_instring_parts_tl
\seq_new:N \l__oleks_instring_pre_seq
\seq_new:N \l__oleks_instring_post_seq
\cs_new_protected:Nn \__oleks_instring_match:nn {}
\cs_generate_variant:Nn \__oleks_instring_match:nn { ff }
\cs_new_protected:Nn \__oleks_instring_nomatch:n {}

\cs_new_protected:Nn \oleks_instring:nnnnn
 {
  \tl_set:Nn \l__oleks_instring_test_tl { #2 }
  \regex_match:nnTF { \u{l__oleks_instring_test_tl} } { #3 }
   { \__oleks_instring_match_do:nnn { #1 } { #3 } { #4 } }
   {
    \cs_set_protected:Nn \__oleks_instring_nomatch:n { #5 }
    \__oleks_instring_nomatch:n { #3 }
   }
 }
\cs_new_protected:Nn \__oleks_instring_match_do:nnn
 {
  \cs_set_protected:Nn \__oleks_instring_match:nn { #3 }
  \regex_split:nnN
   { \u{l__oleks_instring_test_tl} }
   { #2 }
   \l__oleks_instring_parts_seq
  \seq_clear:N \l__oleks_instring_pre_seq
  \seq_clear:N \l__oleks_instring_post_seq
  \int_step_inline:nnnn { 1 } { 1 } { #1 }
   {
    \seq_put_right:Nx \l__oleks_instring_pre_seq
     { \seq_item:Nn \l__oleks_instring_parts_seq { ##1 } }
   }
  \int_step_inline:nnnn { #1 + 1 } { 1 } { \seq_count:N \l__oleks_instring_parts_seq }
   {
    \seq_put_right:Nx \l__oleks_instring_post_seq
     { \seq_item:Nn \l__oleks_instring_parts_seq { ##1 } }
   }
  \__oleks_instring_match:ff
   { \seq_use:NV \l__oleks_instring_pre_seq \l__oleks_instring_test_tl }
   { \seq_use:NV \l__oleks_instring_post_seq \l__oleks_instring_test_tl }
 }
\ExplSyntaxOff

\begin{document}

\instringxTF{=}{a=b=c}{pre: #1, post: #2}{no match: #1}

\instringxTF[2]{=}{a=b=c}{pre: #1, post: #2}{no match: #1}

\instringxTF{=}{abc}{pre: #1, post: #2}{no match: #1}

\instringxTF{é}{abécdéf}{pre: #1, post: #2}{no match: #1}

\end{document}

enter image description here

egreg
  • 1,121,712
  • The specific character was "=". This works great, thanks! I was actually just around the corner from a similar solution, all thanks to http://www.tex.ac.uk/tex-archive/info/macros2e/macros2e.pdf, but the guide isn't so clear around what an "\ifin@ switch" is, do you happen to have a good guide to doing "advanced" stuff like this? – oleks Aug 29 '11 at 15:30
  • Nice. I didn't know about \in@ and \ifin@ ... – cgnieder May 25 '13 at 11:05
  • @cgnieder They're well hidden in the LaTeX kernel. ;-) Of course expl3 makes better tools available. – egreg May 25 '13 at 11:07
28
\documentclass{article}
\usepackage{xstring}
\begin{document}

\IfSubStr{Rotterdam}{otter}{ true }{ false }
\end{document}
yegor256
  • 12,021
  • I needed to use \IfSubStr{Rotterdam}{otter}{\def\result{true}}{\def\result{false}} to use it with href like \href{\result}{some link} as using it directly caused a tex capacity exceeded sorry input stack size=5000 error. – arekolek Aug 17 '16 at 22:13
  • How do you write if you want to match a regex instead of a substring? Is that possible? – StrawberryFieldsForever Aug 13 '17 at 13:26
5

\pdfmatch{<pattern>}{<string>} is provided by pdfTeX. This command implements pattern matching (using the syntax of POSIX regular expressions), searching for <pattern> in <string>.The command expands to -1 if the pattern is invalid, to 0 if no match is found, and to 1 if a match is found. The primitive was introduced in pdfTeX 1.30.0.

Yes
Yes
No

\documentclass{article}

\newcommand{\instring}[4]{%
  % \instring{<pattern>}{<string>}{<true>}{<false>}
  %   pattern = sought after string
  %   string  = string to search
  \ifnum\pdfmatch{#1}{#2}=1
    #3%
  \else
    #4%
  \fi
}

\begin{document}

\def\mystring{abcdef}

\instring{a}{abcdef}{Yes}{No}% Yes

\instring{abc}{\mystring}{Yes}{No}% Yes

\instring{acb}{\mystring}{Yes}{No}% No

\end{document}
Werner
  • 603,163
4

In OpTeX, we have \isinlist macro for these purposes. The syntax is

\isinlist <list>{<substring>}\iftrue true branch\else false branch\fi

The <list> can be implicit (like \macro) or explicit (like {text}. Example:

\def\mystring{abcdef}

\isinlist {abcdef}{a}\iftrue \message{YES}\else\message{NO}\fi \isinlist \mystring{a}\iftrue \message{YES}\else\message{NO}\fi

\bye

The result is YES in both cases.

wipet
  • 74,238