7

I am currently writing a document, that sometimes requires me to alternate the colors of the letters in a sentence. I thought it would be easy to find a way to create a macro that does that, but I've run into some problems.

MWE:

% !TeX program = xelatex
% !TeX spellcheck = en_US

\documentclass[11pt,a4paper,english,twoside,notitlepage]{book}

\usepackage{fontspec}
\usepackage{lmodern}
\usepackage[english,main=english]{babel}
\usepackage{xcolor}
\usepackage{etoolbox}


\definecolor{purple}{HTML}{2B0057}
\definecolor{candy}{HTML}{FF0000}

\newcounter{alternate} 

%the macro in question
\newcommand{\colset}[1]{%
    \ifnum\value{alternate}=0 {\color{candy}{#1}\setcounter{alternate}{1}}%
    \else{\color{purple}{#1}\setcounter{alternate}{0}}%
    \fi}


%snippet for the loop taken from https://tex.stackexchange.com/questions/359189/looping-over-strings
%iterates over the supplied string and replaces every letter with \colset{<letter>}
\def\gobblechar{\let\xchar= }
\def\assignthencheck{\afterassignment\xloop\gobblechar}
\def\xloop{%
    \ifx\relax\xchar
    \let\next=\relax
    \else
    \colset{\xchar}\let\next=\assignthencheck
    \fi
    \next}
\def\markletters#1{\setcounter{alternate}{0}\assignthencheck#1\relax}

\begin{document}
\markletters{Hello World}
\end{document}

Output: Output

The issue is that I have found no way to skip the spaces (i.e. the "W" in "World" should be purple, not red). I thought it would be easy to simply check if the argument is a space and just skip the rest of the macro, but after hours of searching and just trial-and-error (approaches included etoolbox's \ifblank{#1}, constructs like \ifx{#1}{ } or even \ifnum`#1=32, creating temporary macros [e.g. \edef\temp{#1}\ifblank{\temp}]) I am still no step closer, and I'm definitely out of my depth on this one.

Is there a way to check if the argument of a macro is only a space? If no, are there any other ways to construct this macro? (I'm using XeLaTeX via MiKTeX)

Thanks!

  • Thanks for the reference to my cuestion :-) – Raoul Kessels May 20 at 7:24
7

You are not iterating using macro arguments, so you do not need to test a macro argument, just test the token you have already:

enter image description here

\documentclass[11pt,a4paper,english,twoside,notitlepage]{book}

\usepackage{fontspec}
\usepackage{lmodern}
\usepackage[english,main=english]{babel}
\usepackage{xcolor}
\usepackage{etoolbox}


\definecolor{purple}{HTML}{2B0057}
\definecolor{candy}{HTML}{FF0000}

\newcounter{alternate} 

%the macro in question
\newcommand{\colset}[1]{%
    \ifnum\value{alternate}=0 {\color{candy}{#1}\setcounter{alternate}{1}}%
    \else{\color{purple}{#1}\setcounter{alternate}{0}}%
    \fi}

\makeatletter
%snippet for the loop taken from https://tex.stackexchange.com/questions/359189/looping-over-strings
%iterates over the supplied string and replaces every letter with \colset{<letter>}
\def\gobblechar{\let\xchar= }
\def\assignthencheck{\afterassignment\xloop\gobblechar}
\def\xloop{%
    \ifx\relax\xchar
    \let\next=\relax
    \else
    \ifx\@sptoken\xchar\setcounter{alternate}{\numexpr1-\value{alternate}}\fi
    \colset{\xchar}\let\next=\assignthencheck
    \fi
    \next}
\makeatother
\def\markletters#1{\setcounter{alternate}{0}\assignthencheck#1\relax}

\begin{document}
\markletters{Hello World}
\end{document}
4

You can substitute spaces with something that expands to a space.

\documentclass[11pt,a4paper,english,twoside,notitlepage]{book}

\usepackage{fontspec}
\usepackage{lmodern}
\usepackage[english,main=english]{babel}
\usepackage{xcolor}

\definecolor{purple}{HTML}{2B0057}
\definecolor{candy}{HTML}{FF0000}

\ExplSyntaxOn
\NewDocumentCommand{\markletters}{m}
 {
  \int_zero:N \l_tmpa_int
  \tl_set:Nn \l_tmpa_tl { #1 }
  % replace spaces with something different
  \tl_replace_all:Nnn \l_tmpa_tl { ~ } { \c_space_tl }
  \tl_map_inline:Nn \l_tmpa_tl
   {
    \tl_if_blank:eTF { ##1 }
     { ~ } % don't advance the counter and issue a space
     {
      \textcolor{ \int_if_odd:nTF { \l_tmpa_int } { purple } { candy } } { ##1 }
      \int_incr:N \l_tmpa_int
     }
   }
 }
\prg_generate_conditional_variant:Nnn \tl_if_blank:n { e } { T,F,TF,p }
\ExplSyntaxOff

\begin{document}

\markletters{Hello World}

\end{document}

enter image description here

0

You asked for a routine for checking whether a macro argument consists a single space although the \afterassignment-\let-loop in your example does not iterate macro-argument-wise but token-wise. Besides this, with that loop you cannot have (La)TeX "look" at tokens of whatsoever macro-arguments but you can have (La)TeX "look" at the meaning of the control word token \xchar. You cannot exactly deduce the kind of token from which \xchar got its meaning: This could indeed have been an explicit space character token. But this could as well have been an implicit space token, i.e., some control sequence which was \let equal to the explicit space character token, like \@sptoken.

Thus there is some likelihood that my answer/my example below is not of any use to you at all.
Nonetheless it might be of use to people who stumble over your question while indeed being in need of a routine for detecting (explicit) space tokens in macro arguments.

The example below provides two routines:

The macro \UD@CheckWhetherLeadingSpace can be used for finding out whether the very first token of a macro argument is an explicit space character token (character code 32, category code 10).
The gist of \UD@CheckWhetherLeadingSpace is: Append a space token (for ensuring that there is at least one), then gather everything till the first space token, then see whether "emptiness" was gathered. Some brace-hacking is used for removing the remainder.

The macro \UD@CheckWhetherSingleSpace can be used for finding out whether a macro argument consists of a single explicit space character token (character code 32, category code 10).
The gist of \UD@CheckWhetherSingleSpace is: Apply \UD@CheckWhetherLeadingSpace. In case there is a leading space, check whether you get emptiness after removing it.

These macros work only on explicit space character tokens (character code 32, category code 10). Neither do they work on implicit space characters, nor do they work on character tokens of character code 32 but category code differing from 10, nor do they work on so-called "funny spaces" (character code differing from 32, category code 10 - iirc "funny spaces" can come into being only by changing the \lccode or \uccode of the space character and then applying \lowercase respective \uppercase to the space character (and then probably applying \let for obtaining an implicit from an explicit funny space funny space)...).

These macros don't require e-TeX-extensions and are intended to work in full-expansion-contexts etc as well.

Due to \romannumeral-expansion you get the result after triggering two expansion-steps/after having \UD@CheckWhetherLeadingSpace/\UD@CheckWhetherSingleSpace "hit" by two \expandafter.

(By the way:

Be aware that (La)TeX skips explicit (non-funny) space tokens not nested in braces when gathering undelimited macro arguments. It does not skip them when gathering delimited macro arguments.

E.g., with \def\threeargs#1#2#3{#1#2#3} and \threeargs a b c you will get #1=a, #2=b, #3=c although there will be explicit space character tokens between a and b and between b and c.

I think that's one of the reasons why the loop in the example within your question does not iterate macro-argument-wise but token-wise.)

\documentclass[a4paper]{article}
\makeatletter
%%----------------------------------------------------------------------
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@exchange[2]{#2#1}%
\newcommand\UD@gobblespace{}%
\UD@firstoftwo{\def\UD@gobblespace}{} {}%
%%----------------------------------------------------------------------
%% Check whether argument is empty:
%%......................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is empty>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is not empty>}%
%%   The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%%   <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral0\expandafter\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
  \UD@secondoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% \UD@CheckWhetherLeadingSpace{<Argument which is to be checked>}%
%%                             {<Tokens to be delivered in case <argument
%%                               which is to be checked>'s 1st token is a
%%                               space-token>}%
%%                             {<Tokens to be delivered in case <argument
%%                               which is to be checked>'s 1st token is not
%%                               a space-token>}%
\newcommand\UD@CheckWhetherLeadingSpace[1]{%
  \romannumeral0\UD@CheckWhetherNull{#1}%
  {\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
  {\expandafter\UD@secondoftwo\string{\UD@CheckWhetherLeadingSpaceB.#1 }{}}%
}%
\newcommand\UD@CheckWhetherLeadingSpaceB{}%
\long\def\UD@CheckWhetherLeadingSpaceB#1 {%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@secondoftwo#1{}}%
  {\UD@exchange{\UD@firstoftwo}}{\UD@exchange{\UD@secondoftwo}}%
  {\UD@exchange{ }{\expandafter\expandafter\expandafter\expandafter
   \expandafter\expandafter\expandafter}\expandafter\expandafter
   \expandafter}\expandafter\UD@secondoftwo\expandafter{\string}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument consists only of a single space-token
%%.............................................................................
\newcommand\UD@CheckWhetherSingleSpace[1]{%
  \romannumeral0\UD@CheckWhetherLeadingSpace{#1}{%
    \expandafter\UD@CheckWhetherNull\expandafter{\UD@gobblespace#1}%
    {\UD@exchange{\UD@firstoftwo}}{\UD@exchange{\UD@secondoftwo}}%
  }{\UD@exchange{\UD@secondoftwo}}{\UD@exchange{ }{\expandafter}}%
}%
%\makeatother

\begin{document}

\UD@CheckWhetherLeadingSpace{ text}{Leading explicit space token}{No leading explicit space token}

\UD@CheckWhetherLeadingSpace{ }{Leading explicit space token}{No leading explicit space token}

\UD@CheckWhetherLeadingSpace{text}{Leading explicit space token}{No leading explicit space token}

% empty argument:    
\UD@CheckWhetherLeadingSpace{}{Leading explicit space token}{No leading explicit space token}

% two space tokens:
\expandafter\UD@CheckWhetherLeadingSpace\expandafter{\@firstofone{ } }%
                              {Leading explicit space token}{No leading explicit space token}

\noindent\hrulefill

\UD@CheckWhetherSingleSpace{ text}{Single explicit space token}{Not a single explicit space token}

\UD@CheckWhetherSingleSpace{ }{Single explicit space token}{Not a single explicit space token}

\UD@CheckWhetherSingleSpace{text}{Single explicit space token}{Not a single explicit space token}

% empty argument:
\UD@CheckWhetherSingleSpace{}{Single explicit space token}{Not a single explicit space token}

% two space tokens:
\expandafter\UD@CheckWhetherSingleSpace\expandafter{\@firstofone{ } }%
                           {Single explicit space token}{Not a single explicit space token}

\end{document}

enter image description here

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.