4

I wish to define such a command (say \test) to apply an operation (say \action) to text separated by \\, for example \test{A\\B\\C} would become

\action{A}\\\action{B}\\\action{C}

This is similar to this question, but I was't able to adapt the answer there to achieve what I want here. How could I define such a macro?

Jinwen
  • 8,518

4 Answers4

5

There is classical (and probably most simple) method based on TeX primitives:

\def\test#1{\testA#1\\\end\\}
\def\testA#1\\{\ifx\end#1\empty\else\action{#1}\expandafter\testA\fi}
\def\action#1{parameter is: #1\par}

\test{A\B\C}

If we need to insert something between actions, for example \actionbetween, then the macro is slightly more complicated but it is fully expandable too:

\def\test#1{\testA#1\\\end\\\\}
\def\testA#1\\#2\\{\ifx\end#1\empty 
   \else\action{#1}\ifx\end#2\empty \else \actionbetween \fi
   \afterfi{\testA#2\\}\fi}
\def\afterfi#1#2\fi{\fi#1}

\def\action#1{parameter is #1\par} \def\actionbetween{\string\}

\test{A\B\C}

wipet
  • 74,238
  • It seems to translate A\\B\\C into \action{A}\action{B}\action{C}, not \action{A}\\\action{B}\\\action{C}. – Jinwen Feb 03 '21 at 10:29
  • @Jinwen OK, I added second version of the macro. – wipet Feb 03 '21 at 10:46
  • It's fantastic, thanks for this! By the way, I'm a little confused about \afterfi, it seems to need 2 parameters, but only {\testA#2\\} is passed to it, what's going on? – Jinwen Feb 03 '21 at 11:08
  • @Jinwen \afterfi is my macro, I am using it in the context \if.. do-something\afterfi{xx}\else do-something-else\afterfi{yy}\fi. The \else part is completely ignored by \afterfi{xx} in this case. LateX uses different approach \firstoftwo, \secondoftwo, but I have only single macro. – wipet Feb 03 '21 at 11:21
  • I just asked a question, in which I used your first version of \test, do you think the problem there has to do with the definition of this macro? – Jinwen Feb 03 '21 at 12:11
4

\DeclareListParser from etoolbox is made for this, it allows you to define a list parser with a custom list separator. (EDIT: fixed a shortcoming, thanks to cgnieder!)

\documentclass{article}
\usepackage{etoolbox}
\DeclareListParser{\mydocsvlist}{\\}
\newif\ifFirstItem
\newcommand{\action}[1]{\textbf{#1}}
\newcommand{\test}[1]{\begingroup
\FirstItemtrue
\renewcommand*{\do}[1]{\ifFirstItem\FirstItemfalse\else\\\fi\action{##1}}%
\mydocsvlist{#1}%
\endgroup}
\parindent0pt
\begin{document}
\test{A\\B\\C}

\renewcommand{\action}[1]{\emph{#1}!}% \test{A\B\C} \end{document}

enter image description here

4
\documentclass{arlticle}
\usepackage{listofitems}
\newcommand\action[1]{%
  \setsepchar{\\}%
  \readlist\myparse{#1}%
  \foreachitem\z\in\myparse[]{%
    \expandafter\theaction\expandafter{\z}\myparsesep[\zcnt]%
  }%
}
\newcommand\theaction{\textit}
\begin{document}
\action{A\\B\\C plus more}

\renewcommand\theaction{\textsc}
\action{A\\B\\C plus more}
\end{document}

enter image description here

To make it even more general, you can set it up to specify the separator, as well. Note, in the 2nd example, the separator tokens are not subjected to the small caps action.

\documentclass{arlticle}
\usepackage{listofitems}
\newcommand\action[1]{%
  \readlist\myparse{#1}%
  \foreachitem\z\in\myparse[]{%
    \expandafter\theaction\expandafter{\z}\myparsesep[\zcnt]%
  }%
}
\setsepchar{\\}
\newcommand\theaction{\textit}
\begin{document}
\action{A\\B\\C plus more}

\renewcommand\theaction{\textsc} \setsepchar{(Hi Mom)\} \action{A (Hi Mom)\B (Hi Mom)\C plus more} \end{document}

enter image description here

1

Here's a fairly general method to achieve your needs, based on expl3.

The input is split at a specified delimiter (and spaces around items are trimmed off); each item is “adorned” as specified by a template, in which the current item is denoted by #1; finally, the “adorned items” are output with a specified separator between them (any valid code).

Note that the template argument should have ##1 if \actonlist is used in the definition of another command.

\documentclass{article}

\ExplSyntaxOn

\NewDocumentCommand{\actonlist}{ m m +m m} {% #1 = input separator % #2 = template % #3 = output separator % #4 = list \jinwen_actonlist:nnnn { #1 } { #2 } { #3 } { #4 } }

\seq_new:N \l__jinwen_actonlist_in_seq \seq_new:N \l__jinwen_actonlist_out_seq

\cs_new_protected:Nn \jinwen_actonlist:nnnn { \seq_set_split:Nnn \l__jinwen_actonlist_in_seq { #1 } { #4 } \seq_set_map:NNn \l__jinwen_actonlist_out_seq \l__jinwen_actonlist_in_seq { #2 } \seq_use:Nn \l__jinwen_actonlist_out_seq { #3 } }

\ExplSyntaxOff

\begin{document}

\actonlist{\}{\emph{#1}}{ (here a par)\par}{A\B\C}

\newcommand{\test}[1]{% \actonlist {\} {\emph{##1}} { (here a par)\par} {#1}% }

\test{A\B\C}

\newcommand{\testcomma}[1]{% \actonlist {,} {\textbf{##1}} { (here a par)\par} {#1}% }

\testcomma{A, B, C}

\end{document}

You can see from “here a par” that the output separator is only used between items. If you also need to use it at the end, just add to it in the definition of \test, say

\newcommand{\test}[1]{%
  \actonlist
    {\\}
    {\emph{##1}}
    { (here a par)\par}
    {#1} (here a par)\par
}

enter image description here

egreg
  • 1,121,712