[Tex/LaTex] How to repeat over all characters in a string

loopsmacrosstrings

I have a command that formats a single character, let's call it \d{}. (In my case, \d{} adds a dot under a Chinese character, as this is the custom to emphasize Chinese texts).

I want to define a new command \ds{} such that it applies \d{} over every letter of the string.

For example \ds{abc} would be equivalent to \d{a}\d{b}\d{c}.

Thanks!

Best Answer

You can use \@tfor. I provide also a better redefinition of the dot under according to your wish:

\documentclass{article}
\usepackage{graphicx}

\let\d\relax
\DeclareRobustCommand{\d}[1]{%
  \oalign{#1\cr\hidewidth\scalebox{0.5}{\textbullet}\hidewidth\cr}%
}
\makeatletter
\newcommand{\ds}[1]{%
  \@tfor\next:=#1\do{\d{\next}}%
}
\makeatother

\begin{document}

x\d{d}\d{s}\d{a}x

x\ds{dsa}x

\end{document}

enter image description here

What does \@tfor do? Its syntax is

\@tfor<scratch macro>:=<tokens>\do{<code>}

The scratch macro is traditionally \next, but it can be anything. The <tokens> part is any (brace balanced) list of tokens. In the loop, LaTeX essentially does \def<scratch macro>{<next token>}, so

\@tfor\next:=dsa\do{\d{\next}}

will perform

\def\next{d}\d{\next}\def\next{s}\d{\next}\def\next{a}\d{\next}

However, with \@tfor\next:=d {sa}\do{\d{\next}} we will just obtain

\def\next{d}\d{\next}\def\next{sa}\d{\next}

Explicit space tokens are ignored and braced groups of tokens are treated as one.

The expl3 analog is \tl_map_inline:nn:

\documentclass{article}
\usepackage{xparse}
\usepackage{graphicx}

\let\d\relax
\DeclareRobustCommand{\d}[1]{%
  \oalign{#1\cr\hidewidth\scalebox{0.5}{\textbullet}\hidewidth\cr}%
}

\ExplSyntaxOn
\NewDocumentCommand{\ds}{m}
 {
  \tl_map_inline:n { #1 } { \d { ##1 } }
 }
\ExplSyntaxOff

\begin{document}

x\d{d}\d{s}\d{a}x

x\ds{dsa}x

x\ds{d sa{bc}}x

\end{document}

No scratch macro is used: the current item in the loop is denoted by #1 (which becomes ##1 in the body of a definition, as usual).

In this particular case where just a single command is applied with the current item as argument, one can use \tl_map_function:nN:

\NewDocumentCommand{\ds}{m}
 {
  \tl_map_function:n { #1 } \d
 }

which has the same effect and is shorter. It can also appear in a full expansion context (not for this particular case, because of \d).

enter image description here

Related Question