在 \foreach 中添加 CSV 列表时出现扩展问题

在 \foreach 中添加 CSV 列表时出现扩展问题

另一个扩展问题是我无法猜测\edef/的正确组合\expandafters来开始工作。

背景:

下面我定义了\AddToCommaSeparateList哪些用途\g@addto@macro按照如何保存字符串的运行列表,然后逐个处理它们。当在 之外使用时,它可以按我的意愿工作\foreach,并能正确处理包含逗号的列表成员:

在此处输入图片描述

问题:

但是当我尝试从 内部向此列表添加内容时\foreach,我遇到了扩展问题。答案似乎很明显,我只需要扩展添加到列表中的内容。嗯,这应该不太难,至少我是这么认为的。所以我定义了\ExpandedAddToCommaSeparateList,其中而不是

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

我只是使用:

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

好吧,就像扩展问题通常出现的情况一样(至少对我来说),事情并没有像预期的那样顺利进行。在下面的 MWE 中,我不得不求助于 来\xdef编译,但我只需要\edef

在此处输入图片描述

问题:

  • 我需要向宏添加什么神奇的\edefand/or组合才能在 a 外部和内部使用它?\expandafter\ExpandedAddToCommaSeparateList\foreach

笔记:

  • 我认为这段代码似乎很长,但我认为这是由于注释,并且第一阶段只是为了测试代码在基本使用中是否按预期工作。
  • 问题输出以红色突出显示,它应该与蓝色输出相对应。
  • 我真的只想要一个可以在两种情况下(内部和外部)工作的宏版本\foreach。我真的不知道我会在哪里不是想要扩展(至少在我的用例中)。因此,我尝试在添加到列表的宏中处理扩展问题,而不是在 中处理\foreach

代码:

\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}

答案1

\ExpandedMember没有和 没有 的替代版本\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
}

分组\edef\x只是为了确保\x在其自身扩展后不可用。它在一个组中定义,并且其扩展关闭了该组,因此定义就消失了。


LaTeX3 实现。有一个很大的变化:不再使用控制序列,而是为列表指定名称(为了统一,我还更改了几个宏名称)

\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}

在此处输入图片描述


实际上,序列更适合此应用程序(例如,不需要额外的括号,并且处理速度可能更快)。

\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}

您可能愿意添加

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

与之一起使用\foreach,以便在

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

辅助宏\x只会扩展一次,并且列表将包含\Gx\Gy\Gz不是它们的扩展。

答案2

您想要添加的扩展\ExpandedMember,而不是\ExpandedMember其本身:

\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}

答案3

由于您的评论表明您的列表可能包含宏元素,因此如果是,\AddToCommaList则将扩展累积。现在,将外括号添加到(并扩展)累积元素。\mywrapper\@firstofone\mywrapper

\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}

在此处输入图片描述

另外,您可能考虑合并列表,而不是添加。在这种情况下,您可以调用方法

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

或者

\skvfiltermergelist{<listcmd>}{<newitems>}{<filter>}

相关内容