我想创建一个命令来打印一些使用相同 3 个命令插入的联系人:\name{},\work{},\email{}
。
每次我们使用这 3 个命令时,都会收集一个新联系人,然后将它们全部打印出来,如下所示。
我怀疑我需要某种列表数组来保存联系人,但我不知道如何搜索它。
平均能量损失
\documentclass{article}
\makeatletter
\def\listcontacts{%
\noindent\textbf{\@name}\par
\noindent\textit{\@work}\par
\noindent\texttt{\@email}\par
\vskip 1in % to start a new block of contact
}
\newcommand{\name}[1]{\def\@name{#1}}
\newcommand{\work}[1]{\def\@work{#1}}
\newcommand{\email}[1]{\def\@email{#1}}
\newcommand{\information}[3]{%
\name{#1}
\work{#2}
\email{#3}
}
\makeatother
\information{Faa Foo}{Univ.\ Blah}{[email protected]}
%% I'd like to insert a new one with same commands
%\name{Faa Foo}
%\work{Univ.\ Blah}
%\email{[email protected]}
% and repeat the process with different number of blocks,
%that is, could be any finite number of contacts.
\begin{document}
\listcontacts
\end{document}
答案1
这与 Steven Seglets 的解决方案类似,但包装略有不同。您可以显示单个联系人\ShowContact{}
或列出所有联系人\listcontacts
笔记:
- 这些值是根据
#1
访问联系人的密钥来存储的。尝试重复使用同一密钥现在会被标记为错误。 - 现已更新,添加
\medskip
之间打印整个列表时,条目数\medskip
。不是在第一个条目之前添加,而不是在最后一个条目之后添加,如图所示。
代码:
\documentclass{article}
\usepackage{etoolbox}
\makeatletter
\newtoggle{@IsFirstEntryInList}
\newcommand*{\@ShowContact}[1]{%
\iftoggle{@IsFirstEntryInList}{%
\global\togglefalse{@IsFirstEntryInList}%
}{%
\medskip% <-- separator between entries
}%
\ShowContact{#1}%
}
\newcommand*{\ShowContact}[1]{%
\par\noindent\textbf{\csuse{name #1}}%
\par\noindent\textit{\csuse{work #1}}%
\par\noindent\texttt{\csuse{email #1}}%
}%
%% https://tex.stackexchange.com/a/14394/4301
\newcommand*{\listcontacts}{% Initialize
\global\toggletrue{@IsFirstEntryInList}%
}
\newcommand{\AddToListOfContacts}[1]{%
\g@addto@macro\listcontacts{{#1}}%
}
\newcommand{\information}[3]{%
\ifcsdef{name #1}{%
\PackageError{\jobname}{Multiple uses of the key: '#1'}{}%
}{%
\csdef{name #1}{#1}%
\csdef{work #1}{#2}%
\csdef{email #1}{#3}%
\AddToListOfContacts{\unexpanded{\@ShowContact{#1}}}%
}%
}
\makeatother
\information{Faa Foo}{Univ.\ Blah}{[email protected]}
\information{Harvard}{Harvard University}{[email protected]}
%\information{Harvard}{xxx University}{[email protected]}% <-- Triggers an error
\begin{document}
\noindent
To show one contact:
\ShowContact{Harvard}
\medskip\noindent
To show all contacts:
\listcontacts
\par\noindent
Text following to check that there is no extra space at end...
\end{document}
答案2
我已使得\@name[<index>]
\@work[<index>]
和\@email[<index>]
是数组,其中每次调用 时索引都会递增\information{}{}{}
。
\listcontact{<index>}
将提供指定索引的联系信息。在 MWE 中,我循环遍历所有索引。
\documentclass{article}
\usepackage{pgffor}
\makeatletter
\newcounter{infocnt}
\newcommand{\name}[1]{%
\expandafter\def\csname @name[\theinfocnt]\endcsname{#1}}
\newcommand{\work}[1]{%
\expandafter\def\csname @work[\theinfocnt]\endcsname{#1}}
\newcommand{\email}[1]{%
\expandafter\def\csname @email[\theinfocnt]\endcsname{#1}}
\newcommand{\information}[3]{%
\stepcounter{infocnt}%
\name{#1}%
\work{#2}%
\email{#3}%
}
\newcommand\listcontact[1]{\noindent%
NAME: \textbf{\csname @name[#1]\endcsname}\\
WORK: \textbf{\csname @work[#1]\endcsname}\\
EMAIL: \textbf{\csname @email[#1]\endcsname}\par
\vskip 1in
}
\makeatother
\information{Faa Foo}{Univ.\ Blah}{[email protected]}
\information{XXX}{YYY}{[email protected]}
\begin{document}
\foreach\x in {1,2,...,\theinfocnt}{\listcontact{\x}}
\end{document}
不过,如果我有选择权,我会一次性输入整个列表,非常简单,输出也一样。该listofitems
包会立即将列表存储为可访问数组:
\documentclass{article}
\usepackage{listofitems}
\newcommand\listcontact[1]{\noindent%
NAME: \textbf{\contacts[#1,1]}\\
WORK: \textbf{\contacts[#1,2]}\\
EMAIL: \textbf{\contacts[#1,3]}\par
\vskip 1in
}
\setsepchar{\\/&}
\begin{document}
\readlist*\contacts{
Faa Foo & Univ.\ Blah & [email protected]\\
XXX & YYY & [email protected]}
\foreachitem\x\in\contacts[]{\listcontact{\xcnt}}
Here is the email of contact 2: \contacts[2,3].
\end{document}
通过软件包的少量补充readarray
,联系人列表甚至可以存储在外部文件中。
答案3
这是“属性列表”的游乐场。在这里我定义了\newcontact
一个具有键值接口的联系人。
其中\newcontactscheme
一个定义了打印数据的不同方法;如果没有提供可选参数,则default
使用。在定义中,使用 打印当前联系人的对应的值,用 表示。\listcontact
\getcontact{<key>}{#1}
<key>
#1
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\newcontact}{mm}
{% #1 = contact label, #2 = data
\prop_new:c { g_sigur_contact_#1_prop }
\prop_gset_from_keyval:cn { g_sigur_contact_#1_prop } { #2 }
}
\NewDocumentCommand{\listcontact}{O{default}m}
{
\cs_set_eq:Nc \__sigur_contact_list:n { sigur_contact_list_#1:n }
\__sigur_contact_list:n { #2 }
}
\NewDocumentCommand{\getcontact}{mm}
{
\sigur_contact_print:nn { #1 } { #2 }
}
\NewDocumentCommand{\newcontactscheme}{m+m}
{% #1 = scheme name, #2 = definition
\cs_new_protected:cn { sigur_contact_list_#1:n } { #2 }
}
\cs_new_protected:Nn \sigur_contact_print:nn
{
\prop_item:cn { g_sigur_contact_#2_prop } { #1 }
}
\ExplSyntaxOff
\newcontactscheme{default}{%
\par\noindent
\getcontact{name}{#1}\\
\getcontact{work}{#1}\\
\getcontact{email}{#1}\par
}
\newcontactscheme{short}{%
\getcontact{name}{#1}, \getcontact{email}{#1}%
}
\newcontactscheme{alternate}{%
\begin{tabular}[t]{@{}l@{ }l@{}}
Name: & \getcontact{name}{#1} \\
Work: & \getcontact{work}{#1} \\
Email: & \texttt{\getcontact{email}{#1}}
\end{tabular}%
}
\newcontact{foo}{
name=Faa Foo,
work=Univ.\ Blah,
[email protected]
}
\newcontact{xxx}{
name=XXX,
work=YYY,
[email protected]
}
\begin{document}
\listcontact{foo}
\medskip
\listcontact{xxx}
\medskip
Short: \listcontact[short]{xxx}
\medskip
Alternate: \listcontact[alternate]{foo}
\end{document}
还定义了一个版本\listallcontacts
(可选参数是定义的方案之一)。
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\newcontact}{mm}
{% #1 = contact label, #2 = data
\prop_new:c { g_sigur_contact_#1_prop }
\prop_gset_from_keyval:cn { g_sigur_contact_#1_prop } { #2 }
\seq_gput_right:Nn \g_sigur_contact_seq { #1 }
}
\NewDocumentCommand{\listcontact}{O{default}m}
{
\cs_set_eq:Nc \__sigur_contact_list:n { sigur_contact_list_#1:n }
\__sigur_contact_list:n { #2 }
}
\NewDocumentCommand{\listallcontacts}{O{default}}
{
\cs_set_eq:Nc \__sigur_contact_list:n { sigur_contact_list_#1:n }
\seq_map_inline:Nn \g_sigur_contact_seq
{
\__sigur_contact_list:n { ##1 } \par
}
}
\NewDocumentCommand{\getcontact}{mm}
{
\sigur_contact_print:nn { #1 } { #2 }
}
\NewDocumentCommand{\newcontactscheme}{m+m}
{% #1 = scheme name, #2 = definition
\cs_new_protected:cn { sigur_contact_list_#1:n } { #2 }
}
\seq_new:N \g_sigur_contact_seq
\cs_new_protected:Nn \sigur_contact_print:nn
{
\prop_item:cn { g_sigur_contact_#2_prop } { #1 }
}
\ExplSyntaxOff
\newcontactscheme{default}{%
\par\noindent
\getcontact{name}{#1}\\
\getcontact{work}{#1}\\
\getcontact{email}{#1}\par
}
\newcontactscheme{short}{%
\getcontact{name}{#1}, \getcontact{email}{#1}%
}
\newcontactscheme{alternate}{%
\begin{tabular}[t]{@{}l@{ }l@{}}
Name: & \getcontact{name}{#1} \\
Work: & \getcontact{work}{#1} \\
Email: & \texttt{\getcontact{email}{#1}}
\end{tabular}%
}
\newcontact{foo}{
name=Faa Foo,
work=Univ.\ Blah,
[email protected]
}
\newcontact{xxx}{
name=XXX,
work=YYY,
[email protected]
}
\begin{document}
\listcontact{foo}
\medskip
\listcontact{xxx}
\medskip
Short: \listcontact[short]{xxx}
\medskip
Alternate: \listcontact[alternate]{foo}
\medskip
\listallcontacts
\medskip
\listallcontacts[short]
\end{document}
答案4
这个“答案”并没有回答你的问题。
关于 LaTeX 数据库管理缺陷的几点评论
关于数据库管理的几点总体评论:
通常数据被组织在所谓的数据库中。
例如,数据库可以是联系方式的列表。
您希望联系的每个人或组织都可能与数据库中的一条数据记录相连,该数据记录包含与该个人/组织相关的所有联系方式。
因此,数据记录本身可以通过数据字段来组织,每个字段用于存储/提供属于特定类别的信息。
因此,数据库的逻辑结构可能是这样的:
Data-base: contact-details
==========================
Data-record 001:
----------------
Value of data-field "Name":=John Doe
Value of data-field "Work":=whistle blower
Value of data-field "E-Mail":[email protected]
Data-record 002:
----------------
Value of data-field "Name":=Jane Smith
Value of data-field "Work":=employée at department of alternative facts
Value of data-field "E-Mail":[email protected]
...
你需要手段
- 都用于识别属于数据库的每条记录
- 并保持属于数据库的单个记录彼此可区分。
数据字段“姓名”、“工作”或“电子邮件”不适合此目的。
例如,有一天数据库中可能会出现两个不同人同名的记录。
因此,在数据库管理中,通常的做法是在数据记录结构中引入一个数据字段,每个数据记录的数据字段都保存着一条信息,而该信息不会出现在任何其他数据记录的相应数据字段中。
通常这个数据字段被称为“主键”。
通常这个主键只是通过对数据记录进行连续编号而得到的一些连续编号的项。
引入主键时,数据库的“联系方式”可能如下所示:
Data-base: contact-details
==========================
Data-record 001:
----------------
Value of data-field "Primary Key":=001
Value of data-field "Name":=John Doe
Value of data-field "Work":=whistle blower
Value of data-field "E-Mail":[email protected]
Data-record 002:
----------------
Value of data-field "Primary Key":=002
Value of data-field "Name":=Jane Smith
Value of data-field "Work":=employée at department of alternative facts
Value of data-field "E-Mail":[email protected]
执行此操作时,除了单个数据记录之外,您还可以维护
- 属于数据库的主键列表
- 表示属于数据库的记录数量的计数器:
Data-base: contact-details
==========================
Amount of data-records: 2
List of primary-keys: {001}, {002}
Data-record 001:
----------------
Value of data-field "Primary Key":=001
Value of data-field "Name":=John Doe
Value of data-field "Work":=whistle blower
Value of data-field "E-Mail":[email protected]
Data-record 002:
----------------
Value of data-field "Primary Key":=002
Value of data-field "Name":=Jane Smith
Value of data-field "Work":=employée at department of alternative facts
Value of data-field "E-Mail":[email protected]
主键列表对于整理特定的数据记录特别有用:
假设您希望检索 Jane Smith 的电子邮件地址:
就伪代码而言,您可以通过类似这样的指令来执行此操作:
Iterate on that list of primary-keys that belongs to
the data-base "contact-details" as follows:
In each iteration "look" at the value of the data-field "Name"
of that data-record whose data-field "Primary Key" equals the
current list element.
If that value equals "Jane Smith", print out the value of the
data-field "E-Mail" of that data-record.
与 (La)TeX 相关的陷阱:
当使用 (La)TeX 实现此类功能时,您会遇到一些问题,这些问题与 (La)TeX 是一种宏语言有关,它在读取和标记输入方面对输入进行一些预处理:
例如,花括号({
和}
)在 LaTeX 中具有特定含义,并且通常每个左括号必须有一个匹配的右括号,反之亦然。
例如,百分比字符 ( %
) 在 LaTeX 中具有特定含义。
但根据 RFC 2822,电子邮件地址可能包含不平衡的花括号和百分比字符。
因此,通过 (La)TeX 输入提供的数据库维护此类条目可能与不太有经验的用户认为是问题的情况有关。
例如,这将是一个很好的电子邮件地址。Alt}Fa%c#[email protected]
但是,如果相关数据库是用 (La)TeX 维护的,那么如何将值放入数据记录的数据字段“电子邮件”中呢?Alt}Fa%c#[email protected]
乍一看,您可以通过提供所需符号的宏来实现此目的:
就像是:
Value of data-field "E-Mail":=Alt\leftbrace Fa\textpercent c\[email protected]
此外,人物/组织的名称可能包含写入 (La)TeX 文件的输入编码不包含的字符/符号。
假设需要维护其联系方式的人员之一的名字为Alaïa Maëlys Gambolpütty而数据库则写在用 ASCII 编码的 (La)TeX 输入文件中。
在这种情况下,您必须使用控制序列标记来执行此操作,控制序列标记的名称由输入编码范围内的字符组成。
但是这样的控制序列使得整理特定的数据记录变得困难。
因此,对每条数据进行两次维护可能是一个好主意:
一旦仅通过相关输入编码的字符编写。
一旦以某种方式编写,也可以出现提供所需字符/符号的 LaTeX 控制序列:
Data-base: contact-details
==========================
Amount of data-records: 3
List of primary-keys: {001}, {002}, {003}
Data-record 001:
----------------
Value of data-field "Primary Key":=001
Value of data-field "Name" in input-encoding-representation:=John Doe
Value of data-field "Name" in LaTeX-representation:=John Doe
Value of data-field "Work" in input-encoding-representation:=whistle blower
Value of data-field "Work" in LaTeX-representation:=whistle blower
Value of data-field "E-Mail" in input-encoding-representation:[email protected]
Value of data-field "E-Mail" in LaTeX-representation:[email protected]
Data-record 002:
----------------
Value of data-field "Primary Key":=002
Value of data-field "Name" in input-encoding-representation:=Jane Smith
Value of data-field "Name" in LaTeX-representation:=Jane Smith
Value of data-field "Work" in input-encoding-representation:=employee at department of alternative facts
Value of data-field "Work" in LaTeX-representation:=employ\'ee at department of alternative facts
Value of data-field "E-Mail" in input-encoding-representation:[email protected]
Value of data-field "E-Mail" in LaTeX-representation:[email protected]
Data-record 003:
----------------
Value of data-field "Primary Key":=003
Value of data-field "Name" in input-encoding-representation:=Alaia Maelys Gambolpuetty
Value of data-field "Name" in LaTeX-representation:=Ala\"{\i}a Ma\"{e}lys Gambolp\"{u}tty
Value of data-field "Work" in input-encoding-representation:=employee at department of alternative facts
Value of data-field "Work" in LaTeX-representation:=employée at department of alternative facts
Value of data-field "E-Mail" in input-encoding-representation:=Alt}Fa%c#[email protected]
Value of data-field "E-Mail" in LaTeX-representation:=Alt\leftbrace Fa\textpercent c\[email protected]
...
通过这种方法,您可以实现一个界面,其中根据输入编码表示进行搜索和整理,并根据 LaTeX 表示提供数据。
通过这样的界面,一个阶段将始终包括在 verbatim-catcode-régime 下读取和标记输入编码表示。
这是可行的,但是使用构成接口的宏
- 在纯扩展环境中是不可能的。
- 在其他宏的定义文本中很麻烦。
有了这样的接口,将表示数据库名称、数据字段名称或输入编码表示中的值的所有内容评估为输入编码的字符也是一个好主意。
因此,为了进行正确的错误检查,需要一个例程来完全评估/扩展参数,然后检查扩展结果是否确实包含仅遵守 verbatim-catcode-régime 的字符标记。
对可能包含任意标记的参数进行全面评估/扩展已经是一个很好的问题。
一种简单的方法是使用\edef
。
一方面,这也会破坏接口的可扩展性。
另一方面,\edef
使用任意令牌并不完全安全。
例如,看看
\edef\test{%
Where does the definition text
stop?\iffalse{\fi Here?} Or Here?{\iffalse}\fi
}
我认为在 LaTeX 中实现某种数据库管理的简约界面是可行的,例如:
\NewInitializedDataRecord{<Primary Key>}%
{<Name>}%
{<Work>}%
{<EMail>}%
\NewEmptyDataRecord{<Primary Key>}
\AddNewDataFieldToDataRecord{<Primary Key>}{<Name of data-field>}{<Value>}%
\VerbatimizedAddNewDataFieldToDataRecord{<Primary Key>}{<Name of data-field>}|<Value>|% <- verbatim-arg
(extraction of verbatimized values of data-fields via \scantokens...)
\PrintFieldValueFromDataRecord{<Primary Key>}{<Name of data-field>}
\PrintDataRecord{<Primary Key>}
\PrintAllDataRecords
\PrintAllDataRecordsWithSpecificDatFieldValue{<Name of data-field>}{<Specific Value>}
但在详细说明之前,需要指定可能形成数据字段值的标记以及比较数据字段值时对它们的处理。