20

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.

6
  • Do you supply \testchain with an explicit string or should the argument be expanded once to get the string?
    – egreg
    Sep 8, 2013 at 16:16
  • The string will be explicit (in the my code it will be like \testchain{Some string} ) Sep 8, 2013 at 16:19
  • Your latest edit needs more explanation. What do you want to do? Parsing? What? Replacing? Sep 8, 2013 at 16:33
  • I justed deleted the edit since it was more confusing. I just wanted to give some explanations why I needed such kind of code. In my case, the aim is to use some abbreviations. Sep 8, 2013 at 16:37
  • Since you have some responses below that seem to answer your question, please consider marking one of them as ‘Accepted’ by clicking on the tickmark below their vote count (see How do you accept an answer?). This shows which answer helped you most, and it assigns reputation points to the author of the answer (and to you!). It's part of this site's idea to identify good questions and answers through upvotes and acceptance of answers.
    – jub0bs
    Sep 15, 2013 at 12:00

5 Answers 5

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}
2
  • 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. Sep 8, 2013 at 16:37
  • @Qrrbrbirlbel Thanks. I had forgotten about \noexpandarg, etc.
    – jub0bs
    Sep 8, 2013 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}
0
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
4
  • I think it should be something like "^" .. char, not "^a". Otherwise, it will always test against "a" and not char.
    – svick
    Sep 8, 2013 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 8, 2013 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 8, 2013 at 19:56
  • @SuffixTreeMonkey You can use simple quotes "#1" as well, as long as you don't test a quoted string.
    – Marco
    Sep 8, 2013 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}
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}%

You must log in to answer this question.

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