21

Is there an easy way to make a macro returning a string (e.g. 'true') if the string input begins by some fixed character, say 'a' ? I can't figure how to combine the different packages to do this.

\testchain{a} will return the string 'true' and nothing otherwise.

5 Answers5

11

The xstring package offers plenty of useful commands for string manipulation. However, if you're worried about performance for a very specific task, you may want to use lower-level commands, as in egreg's and Qrrbrbirlbel's answers.

Here's a solution using xstring; note that the argument to \testchain is fully expanded, if any expansion can be applied.

enter image description here

\documentclass{article}

\usepackage{xstring}

\newcommand\testchain[1]{%
  \StrLeft{#1}{1}[\firstchar]%
  \IfStrEq{\firstchar}{a}{true}{false}%
}

\begin{document}

\noindent
    Test 1: \testchain{adfgi7634r}\\
    Test 2: \testchain{sdfkhsdf}\\  
    \def\abc{abc}
    \def\ghi{ghi}
    Test 3: \testchain{\abc}\\
    Test 4: \testchain{\ghi}
\end{document}
David Carlisle
  • 757,742
jub0bs
  • 58,916
  • The macros of xstring expand their argument fully (\edef) by default. If this should not happen (i.e. fragile macros), check subsection 3.1.1 of the manual. – Qrrbrbirlbel Sep 08 '13 at 16:37
  • @Qrrbrbirlbel Thanks. I had forgotten about \noexpandarg, etc. – jub0bs Sep 08 '13 at 16:43
9

This is an expandable test, which means it can be used in an \edef as shown in the last line:

\documentclass{article}
\usepackage{xparse}

\makeatletter
\newcommand\testchain[1]{%
  \ifnum\pdfstrcmp{\unexpanded\expandafter{\@car#1\@nil}}{a}=\z@
    \expandafter\@firstofone
  \else
    \expandafter\@gobble
  \fi{true}%
}
\def\testchain@a{a}
\makeatother

\begin{document}
X\testchain{a}X

X\testchain{abc}X

X\testchain{x}X

\edef\exptest{\testchain{abc}}\texttt{\meaning\exptest}
\end{document}

enter image description here

It uses \pdfstrcmp and \unexpanded, so it's not available in "classic TeX". If you plan to use the macro with XeLaTeX or LuaLaTeX, then load the package pdftexcmds and use \@pdfstrcmp instead of \pdfstrcmp.


Here is a LaTeX3 version.

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\DeclareExpandableDocumentCommand{\testchain}{m}
 {
  \testchain_main:n { #1 }
 }
\cs_new:Npn \testchain_main:n #1
 {
  \str_if_eq_x:nnT { \tl_head:n { #1 } } { a } { true }
 }
\ExplSyntaxOff

\begin{document}
X\testchain{a}X

X\testchain{abc}X

X\testchain{x}X

\edef\exptest{\testchain{abc}}\texttt{\meaning\exptest}
\end{document}

A variant that allows to use a string buried into a macro, by calling \testchain*:

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\DeclareExpandableDocumentCommand{\testchain}{sm}
 {
  \IfBooleanTF{#1}
   { \testchain_main:o { #2 } }
   { \testchain_main:n { #2 } }
 }
\cs_new:Npn \testchain_main:n #1
 {
  \str_if_eq_x:nnT { \tl_head:n { #1 } } { a } { true }
 }
\cs_generate_variant:Nn \testchain_main:n { o }
\ExplSyntaxOff

\begin{document}
X\testchain{a}X

X\testchain{abc}X

X\testchain{x}X

\def\stringI{abc}
X\testchain{\stringI}X

% show it's fully expandable
\edef\exptest{\testchain*{\stringI}}\texttt{\meaning\exptest}
\end{document}
egreg
  • 1,121,712
7

I present a Lua solution which works with all macro packages, including plainTeX, ConTeXt and LaTex. It defines a simple Lua funktion testing for the first character. Then a TeX macro is defined calling the Lua funktion.

\def\beginsWith#1#2#3%
  {\directlua{userdata.beginsWith([===[#1]===],[===[#2]===],[===[#3]===])}}

\directlua{
  userdata = userdata or {}
  userdata.beginsWith = function(str, arg2, arg3)
    if string.find(str, "^a") then
      tex.print(arg2)
    else
      tex.print(arg3)
    end
  end}

\beginsWith{abcde}{true}{false}\par
\beginsWith{zyxwv}{true}{false}
\bye

The output:

true
false
Marco
  • 26,055
  • I think it should be something like "^" .. char, not "^a". Otherwise, it will always test against "a" and not char. – svick Sep 08 '13 at 18:52
  • No, char is (was) the test string, which is the first argument to \beginsWith. The character to test is hard coded. It can easily be made configurable or passed as argument as well. I renamed the variables to make it more clear. – Marco Sep 08 '13 at 19:00
  • As a person not acquainted with luaTeX yet: is the ugly [===[#1]===] syntax necessary? It looks like you have been trying to make sure some pattern serves as a delimeter, which happens very often in TeX but is avoided as a bad practice by virtually any other programming language out there. – M. B. Sep 08 '13 at 19:56
  • @SuffixTreeMonkey You can use simple quotes "#1" as well, as long as you don't test a quoted string. – Marco Sep 08 '13 at 22:38
6
\documentclass{article}
\makeatletter
\newcommand*{\eifstartswith}{\@expandtwoargs\ifstartswith}
\newcommand*{\ifstartswith}[2]{%
  \if\@car#1.\@nil\@car#2.\@nil
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi}
\makeatother
\begin{document}
\ifstartswith{a}{abba}{true}{false}

\ifstartswith{b}{abba}{true}{false}

\newcommand*{\stringA}{a}
\newcommand*{\stringB}{b}
\newcommand*{\stringC}{abba}

\eifstartswith{\stringA}{\stringC}{true}{false}

\eifstartswith{\stringB}{\stringC}{true}{false}
\end{document}
Qrrbrbirlbel
  • 119,821
3

The macro \isnextbyte of the stringstrings package compares its first [single-byte] argument to the first byte of its second argument. It will print a T or F unless invoked in quiet mode, in which case it stores its result in \theresult. The following MWE will produce a T, F, T, and null result.

\documentclass{article}
\usepackage{stringstrings}
\begin{document}
\isnextbyte{a}{another test}\par
\isnextbyte{a}{but not this one}\par

This will not print F for false:\par
\isnextbyte[q]{a}{and this is in quiet mode}
\if T\theresult T\fi
\isnextbyte[q]{a}{false result}
\if T\theresult T\fi
\end{document}%