Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

Another expansion issue that I am unable to guess at the correct combination of \edef/\expandafters to get to work.

Background:

Below I have defined \AddToCommaSeparateList which uses \g@addto@macro as per How keep a running list of strings and then process them one at a time. This works as I desire when used outside of a \foreach and properly handles list members containing a comma:

enter image description here

Problem:

But when I attempt to add to this list from within a \foreach, I run into an expansion issue. Well the answer seems pretty obvious, I just need to expand the content that is added to the list. Well, that should not be too difficult, or so I think. So I defined \ExpandedAddToCommaSeparateList, where instead of

\g@addto@macro#1{{#2}}%      used in `\AddToCommaSeparateList`

I simply use:

\edef\ExpandedMember{#2}% 
\g@addto@macro#1{{\ExpandedMember}}%

Well, as is usual with expansion issues (at least for me), things did not quite work as expected. In the MWE below I had to resort to an \xdef to get things to compile, but I should only need an \edef.

enter image description here

Question:

  • What magic combination of \edef and/or \expandafters do I need to add to the \ExpandedAddToCommaSeparateList macro so that I can use it outside and inside a \foreach?

Notes:

  • I think this code seems rather long, but I think that is due to the comments, and the first phase which is just to test that the code works as expected in basic usage.
  • The problem output is highlighted in red, it should correspond to the output in blue.
  • I really just want one version of this macro that works in both cases (inside and outside of a \foreach). Don't really see where I would not want expansion (at least in my use case). Hence, am tryinig to handle the expansion issue from within the macro that adds to the list, as opposed to within the \foreach.

Code:

\documentclass{article}
\usepackage{xcolor}
\usepackage{xstring}
\usepackage{pgffor}

\makeatletter
\newcommand*{\ResetCommaSeparatedList}[1]{\gdef#1{}}%
\newcommand*{\IfEmptyCommaSeparatedList}[3]{%
    % #1 = listname
    % #2 = code if list is empty
    % #3 = code if list is non-empty
    \IfStrEq{#1}{}{#2}{#3}%
}

\newcommand*{\AddToCommaSeparateList}[2]{%
    % #1 = listname
    % #2 = content to add to CSV list
    %
    % Don't add a leading comma in list
    \IfEmptyCommaSeparatedList{#1}{}{\g@addto@macro#1{,}}%
    %
    %  Extra brace group in case #2 contains a comma
    \g@addto@macro#1{{#2}}%
}%
\newcommand*{\ExpandedAddToCommaSeparateList}[2]{% with expansion?
    % #1 = listname
    % #2 = content to add to CSV list
    %
    % Don't add a leading comma in list
    \IfEmptyCommaSeparatedList{#1}{}{\g@addto@macro#1{,}}%
    %
    % Only need a \edef here, but usign \xdef to get this to compile
    \xdef\ExpandedMember{#2}% 
    %  Extra brace group in case #2 contains a comma
    \g@addto@macro#1{{\ExpandedMember}}%
}%
\newcommand*{\DumpList}[1][black]{%
    \par
    MyList = \par
    \foreach \x in \MyList {\hspace*{1cm}\textcolor{#1}{\x}\par}
}%
\makeatother

\newcommand*{\MyList}{}%

\begin{document}
\noindent\textbf{Show that this works outside of foreach}\par
\ResetCommaSeparatedList{\MyList}
\AddToCommaSeparateList{\MyList}{1}
\AddToCommaSeparateList{\MyList}{2}
\AddToCommaSeparateList{\MyList}{3}
\AddToCommaSeparateList{\MyList}{4, 5, 6}
\DumpList[blue]

% Repeat just to ensure that that \ResetCommaSeparatedList works
\ResetCommaSeparatedList{\MyList}
\AddToCommaSeparateList{\MyList}{x}
\AddToCommaSeparateList{\MyList}{y}
\AddToCommaSeparateList{\MyList}{z}
\AddToCommaSeparateList{\MyList}{a, b, c}
\DumpList

\bigskip\hrule
\bigskip\noindent
But, when the content is added via a \verb|\foreach| there are 
expansion issues.
I want this to yield \textcolor{blue}{1, 2, 3}:

% --------------------------------- Problems start here
\ResetCommaSeparatedList{\MyList}
\foreach \x in {1,...,3} {%
    % A check is made here so that only certain ones get added
    % to the CSV list (omitted here as it is not relevant to issue)
    \ExpandedAddToCommaSeparateList{\MyList}{\x}%
}
\DumpList[red]

\bigskip\noindent
Also, would prefer just one version of this macro, so this should 
produce results identical to the first output in blue above:
\ResetCommaSeparatedList{\MyList}
\ExpandedAddToCommaSeparateList{\MyList}{1}
\ExpandedAddToCommaSeparateList{\MyList}{2}
\ExpandedAddToCommaSeparateList{\MyList}{3}
\ExpandedAddToCommaSeparateList{\MyList}{4, 5, 6}
\DumpList[red]
\end{document}
share|improve this question
add comment

3 Answers

up vote 6 down vote accepted

An alternative version without \ExpandedMember and with no \expandafter:

\newcommand*{\ExpandedAddToCommaSeparateList}[2]{% with expansion?
    % #1 = listname
    % #2 = content to add to CSV list
    %
    % Don't add a leading comma in list
    \IfEmptyCommaSeparatedList{#1}{}{\g@addto@macro#1{,}}%
    %  Extra brace group in case #2 contains a comma
    \begingroup\edef\x{\endgroup
      \noexpand\g@addto@macro\noexpand#1{{#2}}}\x
}

The grouping around \edef\x is just to ensure that \x won't be available after its own expansion. It's defined in a group and its expansion closes the group, so the definition vanishes.


A LaTeX3 implementation. There is one big change: instead of a control sequence a name is given to the list (I also changed a couple of macro names for uniformity)

\documentclass{article}
\usepackage{xcolor}
\usepackage{xparse}
\usepackage{pgffor}

\ExplSyntaxOn
\NewDocumentCommand{\ResetCommaSeparatedList} { m }
 {
  \clist_gclear_new:c { g_grill_list_ #1 _clist }
 }
\NewDocumentCommand{\AddToCommaSeparatedList} { m m }
 {
  % #1 = listname
  % #2 = content to add to CSV list
  %
  \clist_gput_right:cn { g_grill_list_ #1 _clist } { {#2} } 
 }
\NewDocumentCommand{\ExpandedAddToCommaSeparatedList} { m m }
 {
  % #1 = listname
  % #2 = content to add to CSV list
  \clist_gput_right:cx { g_grill_list_ #1 _clist } { {#2} }
 }
\NewDocumentCommand{\DumpList} { O{black} m }
 {
  \par
  #2 = \par
  \clist_map_inline:cn { g_grill_list_ #2 _clist }
   {
    \hspace*{1cm}\textcolor{#1}{##1}\par
   }
 }
\ExplSyntaxOff

\newcommand{\Gx}{1}
\newcommand{\Gy}{2}
\newcommand{\Gz}{3}

\begin{document}
\noindent\textbf{Show that this works}\par
\ResetCommaSeparatedList{MyList}
\AddToCommaSeparatedList{MyList}{1}
\AddToCommaSeparatedList{MyList}{2}
\AddToCommaSeparatedList{MyList}{3}
\AddToCommaSeparatedList{MyList}{4, 5, 6}
\DumpList[blue]{MyList}

% Repeat just to ensure that that \ResetCommaSeparatedList works
\ResetCommaSeparatedList{MyList}
\AddToCommaSeparatedList{MyList}{x}
\AddToCommaSeparatedList{MyList}{y}
\AddToCommaSeparatedList{MyList}{z}
\AddToCommaSeparatedList{MyList}{a, b, c}
\DumpList{MyList}

\ResetCommaSeparatedList{MyList}
\foreach \x in {1,...,3} {%
    \ExpandedAddToCommaSeparatedList{MyList}{\x}%
}
\DumpList[red]{MyList}

\bigskip
\ResetCommaSeparatedList{MyList}
\ExpandedAddToCommaSeparatedList{MyList}{\Gx}
\ExpandedAddToCommaSeparatedList{MyList}{\Gy}
\ExpandedAddToCommaSeparatedList{MyList}{\Gz}
\ExpandedAddToCommaSeparatedList{MyList}{4, 5, 6}
\DumpList[red]{MyList}
\end{document}

enter image description here


Actually, sequences would be better suited for this application (no need of additional braces, for instance, and probably faster processing).

\documentclass{article}
\usepackage{xcolor}
\usepackage{xparse}
\usepackage{pgffor}

\ExplSyntaxOn
\NewDocumentCommand{\ResetList} { m }
 {
  \seq_gclear_new:c { g_grill_list_ #1 _seq }
 }
\NewDocumentCommand{\AddToList} { m m }
 {
  % #1 = listname
  % #2 = content to add to list
  %
  \seq_gput_right:cn { g_grill_list_ #1 _seq } { #2 } 
 }
\NewDocumentCommand{\ExpandedAddToList} { m m }
 {
  % #1 = listname
  % #2 = content to add to list
  \seq_gput_right:cx { g_grill_list_ #1 _seq } { #2 }
 }
\NewDocumentCommand{\DumpList} { O{black} m }
 {
  \par
  #2 = \par
  \seq_map_inline:cn { g_grill_list_ #2 _seq }
   {
    \hspace*{1cm}\textcolor{#1}{##1}\par
   }
 }
\ExplSyntaxOff

\newcommand{\Gx}{1}
\newcommand{\Gy}{2}
\newcommand{\Gz}{3}

\begin{document}
\noindent\textbf{Show that this works}\par
\ResetList{MyList}
\AddToList{MyList}{1}
\AddToList{MyList}{2}
\AddToList{MyList}{3}
\AddToList{MyList}{4, 5, 6}
\DumpList[blue]{MyList}

% Repeat just to ensure that that \ResetList works
\ResetList{MyList}
\AddToList{MyList}{x}
\AddToList{MyList}{y}
\AddToList{MyList}{z}
\AddToList{MyList}{a, b, c}
\DumpList{MyList}

\ResetList{MyList}
\foreach \x in {1,...,3} {%
    \ExpandedAddToList{MyList}{\x}%
}
\DumpList[red]{MyList}

\bigskip
\ResetList{MyList}
\ExpandedAddToList{MyList}{\Gx}
\ExpandedAddToList{MyList}{\Gy}
\ExpandedAddToList{MyList}{\Gz}
\ExpandedAddToList{MyList}{4, 5, 6}
\DumpList[red]{MyList}
\end{document}

You may be willing to add

\NewDocumentCommand{\ExpandedOnceAddToList} { m m }
 {
  % #1 = listname
  % #2 = content to add to list
  \seq_gput_right:co { g_grill_list_ #1 _seq } { #2 }
 }

for use with \foreach, so that in

\ResetList{MyList}
\foreach \x in {\Gx,\Gy,\Gz} {%
    \ExpandedOnceAddToList{MyList}{\x}%
}

the auxiliary macro \x would be expanded just once and the list would contain \Gx, \Gy and \Gz rather than their expansions.

share|improve this answer
add comment

You want to add the expansion of \ExpandedMember, not \ExpandedMember itself:

\documentclass{article}
\usepackage{xcolor}
\usepackage{xstring}
\usepackage{pgffor}

\makeatletter
\newcommand*{\ResetCommaSeparatedList}[1]{\gdef#1{}}%
\newcommand*{\IfEmptyCommaSeparatedList}[3]{%
    % #1 = listname
    % #2 = code if list is empty
    % #3 = code if list is non-empty
    \IfStrEq{#1}{}{#2}{#3}%
}

\newcommand*{\AddToCommaSeparateList}[2]{%
    % #1 = listname
    % #2 = content to add to CSV list
    %
    % Don't add a leading comma in list
    \IfEmptyCommaSeparatedList{#1}{}{\g@addto@macro#1{,}}%
    %
    %  Extra brace group in case #2 contains a comma
    \g@addto@macro#1{{#2}}%
}%
\newcommand*{\ExpandedAddToCommaSeparateList}[2]{% with expansion?
    % #1 = listname
    % #2 = content to add to CSV list
    %
    % Don't add a leading comma in list
    \IfEmptyCommaSeparatedList{#1}{}{\g@addto@macro#1{,}}%
    %
    % Only need a \edef here, but usign \xdef to get this to compile
    \edef\ExpandedMember{#2}% 
    %  Extra brace group in case #2 contains a comma
    \expandafter\g@addto@macro\expandafter#1\expandafter{\expandafter{\ExpandedMember}}%
}%
\newcommand*{\DumpList}[1][black]{%
    \par
    MyList = \par
    \foreach \x in \MyList {\hspace*{1cm}\textcolor{#1}{\x}\par}
}%
\makeatother

\newcommand*{\MyList}{}%

\begin{document}
\noindent\textbf{Show that this works outside of foreach}\par
\ResetCommaSeparatedList{\MyList}
\AddToCommaSeparateList{\MyList}{1}
\AddToCommaSeparateList{\MyList}{2}
\AddToCommaSeparateList{\MyList}{3}
\AddToCommaSeparateList{\MyList}{4, 5, 6}
\DumpList[blue]

% Repeat just to ensure that that \ResetCommaSeparatedList works
\ResetCommaSeparatedList{\MyList}
\AddToCommaSeparateList{\MyList}{x}
\AddToCommaSeparateList{\MyList}{y}
\AddToCommaSeparateList{\MyList}{z}
\AddToCommaSeparateList{\MyList}{a, b, c}
\DumpList

\bigskip\hrule
\bigskip\noindent
But, when the content is added via a \verb|\foreach| there are 
expansion issues.
I want this to yield \textcolor{blue}{1, 2, 3}:

% --------------------------------- Problems start here
\ResetCommaSeparatedList{\MyList}
\foreach \x in {1,...,3} {%
    % A check is made here so that only certain ones get added
    % to the CSV list (omitted here as it is not relevant to issue)
    \ExpandedAddToCommaSeparateList{\MyList}{\x}%
}
\DumpList[red]

\bigskip\noindent
Also, would prefer just one version of this macro, so this should 
produce results identical to the first output in blue above:
\ResetCommaSeparatedList{\MyList}
\ExpandedAddToCommaSeparateList{\MyList}{1}
\ExpandedAddToCommaSeparateList{\MyList}{2}
\ExpandedAddToCommaSeparateList{\MyList}{3}
\ExpandedAddToCommaSeparateList{\MyList}{4, 5, 6}
\DumpList[red]
\end{document}
share|improve this answer
    
One does not need to define \ExpandedMember; just say \begingroup\edef\x{\endgroup \unexpanded{\g@addto@macro#1}{{#2}}}\x –  egreg Nov 20 '12 at 23:46
    
@egreg: Right, but I thought it might be helpful to explain why the current solution wasn't working. –  cyberSingularity Nov 20 '12 at 23:49
    
Both work. I can almost follow this solution. @egreg's almost makes sense too (now that I have looked at it for a few minutes), except for the grouping issue. –  Peter Grill Nov 20 '12 at 23:51
    
@PeterGrill: and to see how easy things are with expl3 in case you are interested, if you \usepackage{expl3} then the \ExpandedMember stuff can be replaced with \csname exp_args:NNx\endcsname\g@addto@macro#1{{#2}} (the \csname...\endcsname is to avoid having to switch to expl3 syntax). Much clearer I think. –  cyberSingularity Nov 20 '12 at 23:59
    
To explain the \expandafters, I think of it as meaning "skip the next token, expand the following token" (and the \expandafter itself gets eaten). In this case the following token in all but the last case is another \expandafter, to skip enough tokens (\g@addto@macro then #1 which I assume is one token as another macro is expected there, then a brace and another brace) until we eventually expand \ExpandedMember, which is what we wanted. –  cyberSingularity Nov 21 '12 at 0:06
add comment

Since your comment suggests that your list might contain macro elements, \AddToCommaList will expand the accretion if \mywrapper is, say, \@firstofone. For now, \mywrapper adds outer braces to (and expands) accreted elements.

\documentclass{article}
\usepackage{xcolor}
\usepackage{loops}[2012/10/16]
\newcommand*\ResetCommaList[1]{\gdef#1{}}
% You can change \mywrapper to format added items:
\newcommand*\mywrapper[1]{{#1}}
\newcommand*\MyList{}
% Expand new item while adding:
\newcommand*\AddToCommaList[2]{\edef#1{\skvaddlist,#1\mywrapper{#2}}}
\newcommand*\DumpList[1][black]{%
  \par MyList = \par
  % PGF's \foreach will normally assume \MyList to contain the elements of 
  % the list. Since a list may be made up of \macro items, \foreachfox 
  % doesn't make that assumption. So you would need to tell it to expand 
  % the list. This can be done using the keys 'list is a macro', 
  % 'expand list once', 'expand list twice', etc.
  \foreachfox [list is a macro] \MyList {%
    \hspace*{1cm}\textcolor{#1}{##1}\endgraf
  }%
}
\def\MyListOfValues{Rancheria, Paiute, Pascua, Paskenta}

\begin{document}
\noindent\textbf{Show that this works outside of foreach}\par
\ResetCommaList\MyList
\AddToCommaList\MyList\MyListOfValues
\AddToCommaList\MyList{1}
\AddToCommaList\MyList{2}
\AddToCommaList\MyList{3}
\AddToCommaList\MyList{4, 5, 6}
\DumpList[blue]

% Repeat printing:
\ResetCommaList\MyList
\AddToCommaList\MyList{x}
\AddToCommaList\MyList{y}
\AddToCommaList\MyList{z}
\AddToCommaList\MyList{a, b, c}
\DumpList

\bigskip\hrule\bigskip\noindent

\ResetCommaList\MyList
\def\ignoredlist{3,B}
\foreachfox {1,...,4,A,...,D} {%
  % Decide if new item should be added:
  \skvxiffound{,#1,}\in{,\ignoredlist,}\then\else
    \AddToCommaList\MyList{#1}%
  \fi
}
\DumpList[red]

\bigskip\noindent
\ResetCommaList\MyList
\AddToCommaList\MyList{1}
\AddToCommaList\MyList{2}
\AddToCommaList\MyList{3}
\AddToCommaList\MyList{4, 5, 6}
\DumpList[red]
\end{document}

enter image description here

Also, you might consider merging lists, instead of adding. In that case, you can call the method

\skvmergelist{<listcmd>}{<newitems>}

or

\skvfiltermergelist{<listcmd>}{<newitems>}{<filter>}
share|improve this answer
    
Cool... Did not know about the loops package. But, yes the list may be defined as macro (I reverted my other answer back for the same reason). –  Peter Grill Nov 22 '12 at 7:04
    
@PeterGrill: So you might have something like \AddToCommaList\MyList{\x,\y,3,4}, where, e.g., \def\x{1} \def\y{2}? –  Ahmed Musa Nov 22 '12 at 7:33
    
Sure that is more than I had considered. But,for me it is either all fixed constants passed as a parameter (as in the MWE in the question), or defined something like \newcommand{\MyListOfValues}{1,2,3,4}, and then \AddToCommaSeparateList{\MyList}{MyListOfValues}. To be able to handle both cases, using an \edef\Foo followed by giving \Foo without a curly brace group to \foreach works (for both cases). –  Peter Grill Nov 22 '12 at 7:56
    
I have updated my answer accordingly. –  Ahmed Musa Nov 22 '12 at 19:38
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.