4

I have a comma-separated list of information provided by the author as a preamble to my document. I then want to use this list and I'd like to put one item from my list per line. My idea was simply to replace commas with line breaks. But when I put formatting on my elements, it doesn't work.

I'd like the author to be able to put in as much formatting as he likes (or references in the glossary) without there being any problems.

MWE :

\documentclass{article}

\usepackage{xstring}

\begin{document}
    % In my case, the first argument is a command in the reality
    \StrSubstitute{Test, Test2, Test3}{,}{\newline}             % OK
    \StrSubstitute{Test, \textit{Test2}, Test3}{,}{\newline}    % NOK
\end{document}
0

5 Answers 5

4

To format a list of items separeted by commas or whatever as you want, you can use listofitems:

mwe

\documentclass{article}
\usepackage{listofitems}
\setsepchar{,}
\readlist\foo{Test1,\textit{Test2},{\bfseries Test3},\LaTeX,Test4}
\renewcommand\showitemsmacro[1]{\qquad\textbullet\quad #1\par}
\begin{document}

\foo[1] made with \foo[4]:

\showitems\foo

End of the example.

\end{document}

or with the same result:

\documentclass{article}
\usepackage{listofitems}
\setsepchar{,}
\readlist\foo{Test1,\textit{Test2},{\bfseries Test3},\LaTeX,Test4}
\begin{document}

\foo[1] made with \foo[4]:

\foreachitem\test\in\foo{\qquad\textbullet\quad \test\par}

End of the example.

\end{document}
6

Here is with old LaTeX:

\documentclass{article}

\makeatletter
\newcommand\commatonewline[1]{%
   \@for\user@tmp:=#1\do{\user@tmp\newline}%
}
\makeatother

\begin{document}
indentation is normal at start of paragraph

    % In my case, the first argument is a command in the reality
    \commatonewline{Test, Test2, Test3}            % OK
    \commatonewline{Test, \textit{Test2}, Test3}   % OK, too
\end{document}

Other answers will tell you with modern LaTeX (or with homemade loops which may have advantages compared to the old LaTeX2e ones).

output of macro

4

The main problem is that xstring tries full expansion of the arguments to its commands.

Here's how to do it with expl3.

\documentclass{article}

\ExplSyntaxOn

\NewDocumentCommand{\typesetlist}{m +O{} +m +O{}}
 {
  #2 % before
  \exp_args:NV \clist_use:nn { #1 } { #3 }
  #4
 }

\ExplSyntaxOff

\newcommand{\listprovidedbytheauthor}{Test1,\textit{Test2},{\bfseries Test3},\LaTeX,Test4}

\begin{document}

\typesetlist{\listprovidedbytheauthor}[\begin{flushleft}]{\\}[\end{flushleft}]

\typesetlist{\listprovidedbytheauthor}[\noindent Keywords: ]{, }

\end{document}

enter image description here

As you see, braces around the items are removed, which can have bad consequences in case wrong input such as \bfseries Test3 is used (with or without braces). To be safe from such input errors, you can use a two-step approach: we split the list at commas and then surround each item by braces.

\documentclass{article}

\ExplSyntaxOn

\NewDocumentCommand{\typesetlist}{m +O{} +m +O{}}
 {
  #2 % before
  \clem_list_typeset:Vn #1 { #3 }
  #4
 }

\seq_new:N \l__clem_typeset_list_in_seq
\seq_new:N \l__clem_typeset_list_out_seq

\cs_new_protected:Nn \clem_list_typeset:nn
 {
  \seq_set_from_clist:Nn \l__clem_typeset_list_in_seq { #1 }
  \seq_set_map:NNn \l__clem_typeset_list_out_seq \l__clem_typeset_list_in_seq { {##1} }
  \seq_use:Nn \l__clem_typeset_list_out_seq { #2 }
 }
\cs_generate_variant:Nn \clem_list_typeset:nn { V }


\ExplSyntaxOff

\newcommand{\listprovidedbytheauthor}{Test1,\textit{Test2},{\bfseries Test3},\LaTeX,Test4}

\begin{document}

\typesetlist{\listprovidedbytheauthor}[\begin{flushleft}]{\\}[\end{flushleft}]

\typesetlist{\listprovidedbytheauthor}[\noindent Keywords: ]{, }

\end{document}

enter image description here

4
  • just a detail, it needs a double brace I think (I don't know why) {{\bfseries Test3}}, otherwise everything that follows is in \bfseries
    – pascal974
    Jun 6, 2023 at 12:42
  • @pascal974 That's what happens when you input \bfseries Test instead of the correct \textbf{Test}.
    – egreg
    Jun 6, 2023 at 13:16
  • euh, I dare not correct your answer...
    – pascal974
    Jun 6, 2023 at 13:38
  • 1
    @pascal974 I added a workaround.
    – egreg
    Jun 6, 2023 at 19:46
3

You don't want to expand formatting commands before printing. For example \edef\test{\textit{Test2}} will crash, but \edef\test{\noexpand\textit{Test2}} will run. But with xstring, you need to delay the expansion until it is through.

\documentclass{article}

\usepackage{xstring}

\begin{document}
    \let\foo=\relax% not expandable
    \StrSubstitute{Test, \foo{Test2}, Test3}{,}{\newline}[\test]%
    \let\foo=\textit% now expandable
    \test
\end{document}
3

If you're just interested in processing the list for printing, there's no need to use xstring; etoolbox provides list processing capabilities:

enter image description here

\documentclass{article}

\usepackage{etoolbox}

\newcommand{\processlist}[2][,]{%
  \def\itemdelim{\def\itemdelim{#1}}% Item delimiter delayed by one cycle (https://tex.stackexchange.com/a/89187/5764)
  \renewcommand*{\do}[1]{\itemdelim##1}% How each item is processed
  \docsvlist{#2}}% Process CSV list

\begin{document}

\setlength{\parindent}{0pt}% Just for this example

First list:

\processlist[\newline]{Test, Test2, Test3}

\hrulefill

Second list:

\processlist[\newline]{Test, \textit{Test2}, Test3}

\end{document}

There are also list parsers/processors available in LaTeX3. The following is taken partially from the xparse documentation and produces the same result as above:

\documentclass{article}

\NewDocumentCommand{\nextitem}{ m }{ #1 \par}
\NewDocumentCommand{\processlist}{ > { \SplitList { , } } m }{%
  \ProcessList{#1}{ \nextitem } }

\begin{document}

\setlength{\parindent}{0pt}% Just for this example

First list:

\processlist{Test, Test2, Test3}

\hrulefill

Second list:

\processlist{Test, \textit{Test2}, Test3}

\end{document}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .