2

I have defined a new command that displays a box around whatever I want inside, and want it to act differently depending on whether its argument is just plain text or another command (e.g. \includegraphics).

To do so, I reckon it would be simple to check if the first character of the argument is \. I've tried with \StrChar from the xstring package, but it doesn't seem to be happy about it.

\newcommand{\extractFirst}[1]{
   \StrChar{#1}{1}[\FirstChar]
   The first character is: '\FirstChar'\par
}

Can this be done?

Ballu
  • 21

2 Answers2

2

Here, I make use of an internal macro from the tokcycle package.

The \count@stringtoks macro is used to count the tokens that make up the \string of the argument. We rely on the fact that the \string of a character token will be a single byte in length, whereas the \string of any macro token will be at least two bytes in length. Using this criterion, we can differentiate a macro from a character.

Best of all, \count@stringtoks is fully expandable.

\documentclass{article}
\usepackage{tokcycle}
\makeatletter
\newcommand\extractFirst[1]{\testfirst#1\relax\relax}
\def\testfirst#1#2\relax{\tctestifnum{\count@stringtoks{#1}>1}{Macro}{Character}}
\makeatother
\begin{document}
\extractFirst{abc}

\extractFirst{a}

\extractFirst{\abc ab\c}

\extractFirst{\a ab\c}
\end{document}

enter image description here

If you wanted to avoid loading the package, the extracted code necessary to recreate the \count@stringtoks macro is this:

\documentclass{article}
\makeatletter
\newcommand\extractFirst[1]{\testfirst#1\relax\relax}
\def\testfirst#1#2\relax{\tctestifnum{\count@stringtoks{#1}>1}{Macro}{Character}}
% FROM TOKCYCLE:
\long\def\count@stringtoks#1{\tc@earg\count@toks{\string#1}}
\long\def\count@toks#1{\the\numexpr-1\count@@toks#1.\tc@endcnt}
\long\def\count@@toks#1#2\tc@endcnt{+1\tc@ifempty{#2}{\relax}{\count@@toks#2\tc@endcnt}}
\def\tc@ifempty#1{\tc@testxifx{\expandafter\relax\detokenize{#1}\relax}}
\long\def\tc@earg#1#2{\expandafter#1\expandafter{#2}}
\long\def\tctestifnum#1{\tctestifcon{\ifnum#1\relax}}
\long\def\tctestifcon#1{#1\expandafter\tc@exfirst\else\expandafter\tc@exsecond\fi}
\long\def\tc@testxifx{\tc@earg\tctestifx}
\long\def\tctestifx#1{\tctestifcon{\ifx#1}}
\long\def\tc@exfirst#1#2{#1}
\long\def\tc@exsecond#1#2{#2}
\makeatother
\begin{document}
\extractFirst{abc}

\extractFirst{a}

\extractFirst{\abc ab\c}

\extractFirst{\a ab\c}
\end{document}
1

You can check the input with expl3 functions.

\documentclass{article}
\usepackage{xparse,xcolor}

\NewDocumentCommand{\mycommandmacro}{m}{%
  \fcolorbox{red}{white}{#1}%
}
\NewDocumentCommand{\mycommandnomacro}{m}{%
  \fcolorbox{blue}{black!10}{#1}%
}

\ExplSyntaxOn

\NewDocumentCommand{\mycommand}{m}
 {
  \tl_if_blank:nF { #1 }
   {
    \peek_catcode:NTF \c_group_begin_token
     {% argument starts with brace
      \ballu_mycommand_nomacro:w
     }
     {
      \ballu_mycommand_test:Nw
     }
    #1 \q_stop
   }
 }


\cs_new:Npn \ballu_mycommand_nomacro:w #1 \q_stop
 {
  \mycommandnomacro{#1}
 }

\cs_new:Npn \ballu_mycommand_test:Nw #1 #2 \q_stop
 {
  \token_if_cs:NTF #1
   {
    \mycommandmacro{#1#2}
   }
   {
    \mycommandnomacro{#1#2}
   }
 }

\ExplSyntaxOff

\begin{document}

\mycommand{abc}

\mycommand{{ab}c}

\mycommand{\mbox{abc}}

\mycommand{{\mbox{abc}}def}

X\mycommand{}X

X\mycommand{ }X

\end{document}

Blank (empty or just spaces) arguments do nothing at all; this might be changed, here it is used for defensive programming.

An initial brace triggers the “nomacro” variant.

enter image description here

egreg
  • 1,121,712