我创建了一个名为的宏\fromlist
来从列表中获取值。该值可以排版,但不能直接调用,为什么以及如何处理?
代码:
\documentclass[a4paper]{article}
\usepackage{pgffor}
\usepackage{geometry}
\geometry{showframe}
\geometry{left=1cm,right=1cm,top=1cm,bottom=1cm}
\parindent0pt
\begin{document}
\newcommand\fromlist[2]{%
\foreach \x [count=\i] in #1{%
\ifnum\i=#2 \x\breakforeach\fi%
}%
}
% Test:
\def\bbb{2in}\rule{\bbb}{5pt} % This ok.
\def\bbb{\fromlist{{1in,2in,3in}}{2}}\bbb %This is ok.
\rule{\fromlist{{1in,2in,3in}}{2}}{5pt} % This causes error, why?
答案1
您需要通过扩展来提取项目,您不能在 的参数内进行赋值\rule
。幸运的是,LaTeX 在clist
(comma list) 模块中提供了这样一个现成的功能。更确切地说,nn
变体是可用的,它适用于显式列表,如果您需要扩展包含列表的命令,您可以像ee
这里第二个示例那样声明一个变体。
\documentclass[a4paper]{article}
\usepackage{geometry}
\geometry{showframe}
\geometry{left=1cm,right=1cm,top=1cm,bottom=1cm}
\parindent0pt
\ExplSyntaxOn
\cs_generate_variant:Nn\clist_item:nn{ee}
\newcommand\fromlist{\clist_item:ee}
\ExplSyntaxOff
\newcommand\mylist{1in,2in,3in}
\begin{document}
\rule{\fromlist{1in,2in,3in}{2}}{5pt}
\rule{\fromlist{\mylist}{3}}{5pt}
\end{document}
答案2
当我们说“一个命令是(完全)可扩展的”时,我们的意思是它通过不执行任何赋值或排版指令(其中包括\relax
)来提供预期的结果。
所以你的\bbb
作品,因为它的扩张是长度。TeX 知道 的第一个参数\rule
应该包含一个(固定的)长度,它会执行宏扩展,直到找到符合要求的内容。
当 TeX 看到你的 时\fromlist
,它会意识到它不是一个长度,所以它会进行扩展并看到\foreach
。如果你这样做
latexdef -p pgffor -s foreach
从终端,你会得到
% pgffor.code.tex, line 60:
\let\foreach=\pgffor@foreach
% pgffor.code.tex, line 45:
\def\pgffor@foreach{%
\pgffor@atbeginforeach%
\let\pgffor@assign@before@code=\pgfutil@empty%
\let\pgffor@assign@after@code=\pgfutil@empty%
\let\pgffor@assign@once@code=\pgfutil@empty%
\let\pgffor@remember@code=\pgfutil@empty%
\let\pgffor@remember@once@code=\pgfutil@empty%
\pgffor@alphabeticsequencefalse%
\pgffor@contextfalse%
%
\let\pgffor@var=\pgfutil@empty
%
\pgffor@vars%
}
你知道你的计划注定要失败,因为即使\pgffor@atbeginforeach
它是可扩展的(实际上并非如此),接下来还会有一堆\let
指令,而一个已经太多了。
是否需要运行latexdef
才能查看命令是否可扩展?其实不需要。一般来说,除非有人告诉您命令是可扩展的,否则您不应该期望命令是可扩展的。
在这些expl3
函数中,您可以发现那些(完全)可扩展的函数,因为它们的名称后面有一个星号(实心或空心)。描述位于interface3.pdf
。
您的输入是一个逗号分隔的项目列表,因此它符合 的语法clist
。是的,该函数\clist_item:nn
后面有一个星号!因此您可以这样做
\ExplSyntaxOn
\cs_set_eq:NN \fromlist \clist_item:nn
\ExplSyntaxOff
但你会问一个新问题,因为
\def\ccc{1pt,2pt,3pt}
\rule{\fromlist\ccc{2}}{3cm}
不起作用。如何同时容纳隐式和显式逗号列表?
我的建议是\fromlist
使用接受隐式列表的 *-variant 来定义:
\ExplSyntaxOn
\NewExpandableDocumentCommand{\fromlist}{smm}
{
\IfBooleanTF{#1}
{% there is *
\clist_item:Nn #2 { #3 }
}
{% no *
\clist_item:nn { #2 } { #3 }
}
}
\ExplSyntaxOff
一个完整的例子。
\documentclass{article}
\ExplSyntaxOn
\NewExpandableDocumentCommand{\fromlist}{smm}
{
\IfBooleanTF{#1}
{% there is *
\clist_item:Nn #2 { #3 }
}
{% no *
\clist_item:nn { #2 } { #3 }
}
}
\ExplSyntaxOff
\newcommand{\mylist}{1in,2in,3in}
\begin{document}
\rule{\fromlist{1in,2in,3in}{2}}{5pt}
\rule{\fromlist*{\mylist}{3}}{5pt}
\end{document}
答案3
正如我所评论的,\fromlist
是不可扩展的(例如,尝试\edef\tmp{\fromlist{{1in,2in,3in}}{2}}
)。因此,\rule
看不到 的最终结果\fromlist
,无法用作其输入。因此它会引发错误。
下面提出了一种不同的方法,使用listofitems
来读取和解析列表。然后,可以可扩展地调用列表中的特定条目。关键是在将列表元素传递给 之前读取列表\rule
。
我首先将其展示为一个两步过程,然后\readlist
是\rule
。或者,也可以将这两个步骤放在宏中\Zrule
,就像我在第二种方法中所做的那样。
\documentclass[a4paper]{article}
\usepackage{listofitems}
\usepackage{geometry}
\geometry{showframe}
\geometry{left=1cm,right=1cm,top=1cm,bottom=1cm}
\parindent0pt
\newcommand\Zrule[3]{%
\readlist\rulearg{#1}%
\rule{\rulearg[#2]}{#3}
}
\begin{document}
\readlist\rulelen{1cm,2.4cm,3cm}
\rule{\rulelen[2]}{5pt}
\Zrule{1in,2in,3in}{2}{5pt}
\end{document}
答案4
这是一个变体
\fromlist{⟨tokens to prepend to i-th comma-list-item⟩}%
{⟨tokens to append to i-th comma-list-item⟩}%
{⟨TeX-⟨number⟩-quantity of value i⟩}%
{⟨comma-list⟩}
在当地范围内我第 1 次\foreach
迭代—
⟨tokens to prepend to i-th comma-list-item⟩{⟨i-th comma-list-item⟩}⟨tokens to append to i-th comma-list-item⟩
:
\documentclass[a4paper]{article}
\usepackage{pgffor}
\usepackage{geometry}
\geometry{showframe}
\geometry{left=1cm,right=1cm,top=1cm,bottom=1cm}
\parindent0pt
\makeatletter
\newcommand\PassFirstToSecond[2]{#2{#1}}
\newcommand\fromlist[4]{%
\foreach \x [count=\i] in #4{%
\ifnum\i=\expandafter\@firstofone\expandafter{\number#3} %
\expandafter\@firstofone\else\expandafter\@gobble\fi
{%
\breakforeach
\expandafter\PassFirstToSecond\expandafter{\x}{#1}#2%
}%
}%
}%
\makeatother
\begin{document}
List provided explicitly:
\fromlist{\rule}{{5pt}}{1}{{1in,2in,3in}}
\fromlist{\rule}{{5pt}}{2}{{1in,2in,3in}}
\fromlist{\rule}{{5pt}}{3}{{1in,2in,3in}}
\newcommand\commalist{1in,2in,3in}
List provided via macro:
\fromlist{\rule}{{5pt}}{1}{\commalist}
\fromlist{\rule}{{5pt}}{2}{\commalist}
\fromlist{\rule}{{5pt}}{3}{\commalist}
\end{document}