18

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.

  • Do you supply \testchain with an explicit string or should the argument be expanded once to get the string? – egreg Sep 8 '13 at 16:16
  • The string will be explicit (in the my code it will be like \testchain{Some string} ) – Laurent Dudok de Wit Sep 8 '13 at 16:19
  • Your latest edit needs more explanation. What do you want to do? Parsing? What? Replacing? – Qrrbrbirlbel Sep 8 '13 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. – Laurent Dudok de Wit Sep 8 '13 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. – jubobs Sep 15 '13 at 12:00
9

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}
  • 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 8 '13 at 16:37
  • @Qrrbrbirlbel Thanks. I had forgotten about \noexpandarg, etc. – jubobs Sep 8 '13 at 16:43
8

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}
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
  • I think it should be something like "^" .. char, not "^a". Otherwise, it will always test against "a" and not char. – svick Sep 8 '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 8 '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 8 '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 8 '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}
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}%

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.