4

I am trying to use \dolistloop from etoolbox with the \do-command redefined in the preamble. However, the redefinition of the \do-command is ignored. If I put it within the document, it works as expected. Here is the example that does not work as intended:

\documentclass{article}
\usepackage{etoolbox}

\newcommand{\alist}{}
\listadd{\alist}{Parrot}
\listadd{\alist}{Canary}

\def\birdlist{}

\renewcommand*{\do}[1]{
\appto\birdlist{Bird: #1. }
}

\begin{document}

\dolistloop{\alist}
\birdlist

\end{document}

The output of that code is: ParrotCanary, indicating that \do has not been redefined. If I move the redefinition of \do into the document,

\documentclass{article}
\usepackage{etoolbox}

\newcommand{\alist}{}
\listadd{\alist}{Parrot}
\listadd{\alist}{Canary}

\def\birdlist{}

\begin{document}

\renewcommand*{\do}[1]{
\appto\birdlist{Bird: #1. }
}
\dolistloop{\alist}
\birdlist

\end{document}

the result is the expected: Bird: Parrot. Bird: Canary. How can I use the \dolistloop function with a \do-function that is defined in the preamble? (I am working on a project with documents in several subfolders and I want to avoid appending the \renewcommand*{\do}-code in each folder's .tex-files.)

1
  • 2
    Welcome to TeX.SE. You can't, at least not this way. \do is redefined in \begin{document} to be \noexpand
    – user31729
    Mar 2, 2018 at 22:43

2 Answers 2

3

I don't recommend using \dolistloop in a global context: \do is a macro that is often redefined, especially in \begin{document} (or in \AtBeginDocument{...} in which it gets the meaning of \noexpand. I also would not use it 'globally' inside \begin{document}.

I suggest to use \forlistloop which requires a handler, but even this can be simplified (see the code)

Rename the personal definition of \do into, say, \dothis and call \forlistloop{\dothis}{\alist} instead

\documentclass{article}
\usepackage{etoolbox}

\newcommand{\alist}{}
\listadd{\alist}{Parrot}
\listadd{\alist}{Canary}

\newcommand{\birdlist}{}

\newcommand*{\dothis}[1]{%
\appto\birdlist{Bird: #1. }
}

%Define a wrapper
\newcommand{\fakedolistloop}[1]{%
  \forlistloop{\dothis}{#1}%
}

\fakedolistloop{\alist}

% Or call it directly

%\forlistloop{\dothis}{\alist}
\birdlist

\end{document}

enter image description here

The inevitable expl3 solution

\documentclass{article}
\usepackage{etoolbox}

\usepackage{xparse}

\ExplSyntaxOn
\seq_new:N \g_rasmus_bird_seq

\NewDocumentCommand{\addbird}{+m}{
  \clist_map_inline:nn {#1} {
    \seq_gput_right:Nn \g_rasmus_bird_seq {##1}
  }
}

\NewDocumentCommand{\listloop}{}{
  \seq_map_inline:Nn \g_rasmus_bird_seq {\dothis{##1}}
}
\ExplSyntaxOff

\addbird{Parrot,Canary}
\addbird{Dead Parrot,Pantomime Goose}

\newcommand{\birdlist}{}

\newcommand*{\dothis}[1]{%
\appto\birdlist{Bird: #1. }
}

\begin{document}
\listloop


\birdlist
\end{document}

Next installment

\documentclass{article}
\usepackage{etoolbox}

\newcommand{\alist}{}
\listadd{\alist}{Parrot}
\listadd{\alist}{Canary}

\newcommand{\birdlist}{}

\newcommand*{\dothis}[2]{%
  \appto{#1}{Bird: #2. }
}

\newcommand{\fakedolistloop}[2]{%
  \forlistloop{\dothis{#2}}{#1}%
}

\begin{document}
\newcommand{\otherbirdlist}{}
\fakedolistloop{\alist}{\birdlist}
\listadd{\alist}{Sparrow}

\fakedolistloop{\alist}{\otherbirdlist}

Showing:

\birdlist

or

\otherbirdlist
\end{document}
5
  • That works. Much appreciated. Would it be a possible to add the list to which we are appending, i.e \birdlist, as an argument to the loop-function? That would add flexibility to the usage of the command.
    – Rasmus
    Mar 2, 2018 at 22:59
  • @Rasmus: Do you mean \fakelistloop{\alist}{\birdlist}?
    – user31729
    Mar 2, 2018 at 23:01
  • @Rasmus: See the 3rd. version at the end -- did you mean that?
    – user31729
    Mar 2, 2018 at 23:06
  • Ah :-) Great. But I am not sure I understand why that works. It seems that \fakelistloop only takes one argument?
    – Rasmus
    Mar 2, 2018 at 23:06
  • It seemed to work also in the case where \fakelistloop was defined with only one argument. Which confused me a bit.
    – Rasmus
    Mar 2, 2018 at 23:08
2

You can't use \do that way. It's a scratch macro and you should redefine it at point of usage, as its meaning cannot be relied upon.

A different implementation:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\listadd}{mm}
 {
  \rasmus_list_add:nn { #1 } { #2 }
 }
\NewDocumentCommand{\listmap}{m+m}
 {
  % define the scratch macro based on the second argument
  \cs_set:Nn \__rasmus_list_do:n { #2 }
  % map the list using the newly defined macro
  \seq_map_function:cN { g_rasmus_list_#1_seq } \__rasmus_list_do:n
 }

\cs_new_protected:Nn \rasmus_list_add:nn
 {
  % make sure the list exists
  \seq_if_exist:cF { g_rasmus_list_#1_seq }
   {
    \seq_new:c  { g_rasmus_list_#1_seq }
   }
  % add the items
  \clist_map_inline:nn { #2 }
   {
    \seq_put_right:cn  { g_rasmus_list_#1_seq } { ##1 }
   }
 }
\ExplSyntaxOff

\begin{document}

\listadd{alist}{Parrot,Canary}

\listmap{alist}{%
  \listadd{birdlist}{Bird: #1.}%
}

\listmap{birdlist}{#1\par}

\end{document}

You can add items sequentially or also as a comma separated list, so

\listadd{alist}{Parrot,Canary}

and

\listadd{alist}{Parrot}
\listadd{alist}{Canary}

are equivalent.

As you see, you can use \listmap for different purposes, here for populating a new list and for printing the new list. The current item in the list is simply denoted by #1.

The trick is to redefine a scratch macro ourselves at point of use.

enter image description here

2
  • That works, although I must confess that the syntax is a little cumbersome for a novice.
    – Rasmus
    Mar 2, 2018 at 23:14
  • @Rasmus It's a whole new world. :-)
    – egreg
    Mar 2, 2018 at 23:17

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.