我试图理解 在命令中存储字符串数组 但找不到以下问题的(明显的)解决方案:
我有关于作曲家、标题、副标题和其他一些由数字字符串 id 索引的元数据的信息,例如 00034、00035、02354、12335(它们都是以零开头的 5 位数字),我想将其存储在关联数组(或者可能是矩阵?)中,并能够在文档的各个位置调用该信息,例如
\data{00034}{composer}
以及各种“文本操作”,例如
\data{00034}{composer}: \data{00034}{title}, \emph{\data{00034}{subtitle}}
使用 LaTeX 可以实现吗?TIA!
编辑 1:我忘了提到任何“定义”或“填充”命令块都是由数据库导出生成的,因此如下面的许多优秀解决方案中所示的大量“设置”命令块不是问题。
编辑 2:@Marijn 建议看另一篇文章,但尽管它可能对某些 TeX-Wizard 有用,但对我来说却没用,因为水平比较低。这里的回复很棒,很有帮助。
答案1
\documentclass{article}
\newcommand\savedata[3]{\expandafter\def\csname data-#1-#2\endcsname{#3}}
\newcommand\data[2]{\csname data-#1-#2\endcsname}
\savedata{00034}{composer}{Beethoven}
\savedata{00034}{title}{Bagatelle No. 25}
\savedata{00034}{subtitle}{Für Elise}
\begin{document}
\data{00034}{composer}: \data{00034}{title}, \emph{\data{00034}{subtitle}}
\end{document}
答案2
这显然是财产清单的工作。
查看\uselists
命令以了解原因:我们可以映射已知列表;中间参数是一个模板,#1
代表当前 ID。
\documentclass{article}
\usepackage{booktabs}
\ExplSyntaxOn
\NewDocumentCommand{\definelist}{mm}
{
\mflxvii_list_define:nn { #1 } { #2 }
}
\NewDocumentCommand{\addtolist}{mmm}
{
\mflxvii_list_add:nnn { #1 } { #2 } { #3 }
}
\NewExpandableDocumentCommand{\getitem}{mm}
{
\mflxvii_list_get:nn { #1 } { #2 }
}
\NewDocumentCommand{\uselists}{mmm}
{% #1 = pre code, #2 = body, #3 = post code
\mflxvii_list_use:nnn { #1 } { #2 } { #3 }
}
\seq_new:N \g_mflxvii_list_all_seq
% check whether the list already exists
\cs_new_protected:Nn \mflxvii_list_define:nn
{
\prop_if_exist:cTF { g_mflxvii_list_#1_prop }
{% already defined
\msg_error:nnn { mflxvii } { list-exist } { #1 }
}
{% define a new one
% add to the global list of lists
\seq_gput_right:Nn \g_mflxvii_list_all_seq { #1 }
% build the list from the given data
\prop_new:c { g_mflxvii_list_#1_prop }
\prop_gset_from_keyval:cn { g_mflxvii_list_#1_prop } { #2 }
}
}
\cs_new_protected:Nn \mflxvii_list_add:nnn
{
\prop_if_exist:cTF { g_mflxvii_list_#1_prop }
{% add if existing
\prop_gput:cnn { g_mflxvii_list_#1_prop } { #2 } { #3 }
}
{% list doesn't exist
\msg_error:nnn { mflxvii } { list-exist } { #1 }
}
}
\cs_new:Nn \mflxvii_list_get:nn
{
\prop_item:cn { g_mflxvii_list_#1_prop } { #2 }
}
\cs_new_protected:Nn \mflxvii_list_use:nnn
{
\group_begin:
\cs_set:Nn \__mflxvii_list_use_body:n { #2 }
% pre code
#1
% body
\seq_map_function:NN \g_mflxvii_list_all_seq \__mflxvii_list_use_body:n
% post code
#3
\group_end:
}
\ExplSyntaxOff
\definelist{00034}{
composer=Beethoven,
title=Bagatelle No.\@ 25,
subtitle=Für Elise
}
\definelist{00035}{
composer=Bob Geldof,
title=The Great Song of Indifference
}
\addtolist{00035}{subtitle}{I don't mind}
\begin{document}
\getitem{00034}{composer}: \getitem{00034}{title}, \getitem{00034}{subtitle}
\bigskip
\uselists{%
\begin{tabular}{@{}llll@{}}
\toprule
ID & composer & title & subtitle \\
\midrule
}{ #1 & \getitem{#1}{composer} & \getitem{#1}{title} & \getitem{#1}{subtitle} \\
}{%
\bottomrule\end{tabular}
}
\end{document}
答案3
似乎您有一个记录数组,因此每个数组元素本身都是一个具有数据字段的记录。
您可以为每个数据项定义一个控制序列:
\documentclass{article}
\ExplSyntaxOn
\cs_new:Npn \StoreData #1#2#3 {
\cs_new:cpn {ArrayItem:{#1}_RecordField:{#2}} {#3}
}
\cs_new:Npn \Data #1#2 {
\cs_if_exist_use:cF {ArrayItem:{#1}_RecordField:{#2}} {--data~not~available--}
}
\ExplSyntaxOff
%-------------------------------------------------------------------
\StoreData {00034}{composer}{Beethoven}
\StoreData {00034}{title}{Bagatelle No.\@ 25}
\StoreData {00034}{subtitle}{Für Elise}
%-------------------------------------------------------------------
\StoreData {00035}{composer}{Bob Geldof}
\StoreData {00035}{title}{The Great Song of Indifference}
\StoreData {00035}{subtitle}{I don't mind}
%-------------------------------------------------------------------
\begin{document}
\Data{00034}{composer}\par
\Data{00034}{title}\par
\Data{00034}{subtitle}\par
\Data{00035}{composer}\par
\Data{00035}{title}\par
\Data{00035}{subtitle}\par
\Data{00035}{Unknown}\par
\Data{00036}{subtitle}
\end{document}
您可以使用 expl3 的包 l3prop 并维护一个属性列表,其中属性名称由数组元素名称和数组元素字段名称组合而成:
\documentclass{article}
\ExplSyntaxOn
\prop_new:N \g__MyModule_Array_prop
\cs_new:Npn \AddArrayProperty #1#2#3 {
\prop_gput:Nnn \g__MyModule_Array_prop {ArrayItem:{#1}_RecordField:{#2}} {#3}
}
\cs_new:Npn \Data #1#2 {
\prop_if_in:NnTF
\g__MyModule_Array_prop
{ArrayItem:{#1}_RecordField:{#2}}
{ \prop_item:Nn \g__MyModule_Array_prop {ArrayItem:{#1}_RecordField:{#2}} }
{--data~not~available--}
}
\ExplSyntaxOff
%-------------------------------------------------------------------
\AddArrayProperty {00034}{composer}{Beethoven}
\AddArrayProperty {00034}{title}{Bagatelle No.\@ 25}
\AddArrayProperty {00034}{subtitle}{Für Elise}
%-------------------------------------------------------------------
\AddArrayProperty {00035}{composer}{Bob Geldof}
\AddArrayProperty {00035}{title}{The Great Song of Indifference}
\AddArrayProperty {00035}{subtitle}{I don't mind}
%-------------------------------------------------------------------
\begin{document}
\Data{00034}{composer}\par
\Data{00034}{title}\par
\Data{00034}{subtitle}\par
\Data{00035}{composer}\par
\Data{00035}{title}\par
\Data{00035}{subtitle}\par
\Data{00035}{Unknown}\par
\Data{00036}{subtitle}
\end{document}
您可以尝试嵌套\str_case:nnF
:
\documentclass{article}
\newcommand\MyArrayOfRecords{%
{00034}{%
{composer}{Beethoven}%
{title}{Bagatelle No.\@ 25}%
{subtitle}{Für Elise}%
}%
{00035}{%
{composer}{Bob Geldof}%
{title}{The Great Song of Indifference}%
{subtitle}{I don't mind}%
}%
}%
\ExplSyntaxOn
\cs_new:Nn \__MyStuff_Check_Item:nnn {
\quark_if_no_value:nTF
{#1}
{--#2~not~available--}
{ \str_case:nnF {#3}{#1}{--#3~not~available~in~#2--} }
}
\cs_new:Npn \Data #1#2 {
\exp_args:Nf
\__MyStuff_Check_Item:nnn
{ \exp_args:Nno \str_case:nnF {#1} { \MyArrayOfRecords } {\exp_not:N \q_no_value} }
{#1}
{#2}
}
\ExplSyntaxOff
\begin{document}
\Data{00034}{composer}\par
\Data{00034}{title}\par
\Data{00034}{subtitle}\par
\Data{00035}{composer}\par
\Data{00035}{title}\par
\Data{00035}{subtitle}\par
\Data{00035}{Unknown}\par
\Data{00036}{subtitle}
\end{document}
如果数据以 .csv 列表形式提供,则包数据工具可能感兴趣:
% Have LaTeX create some dummy .csv-files.
% In a real life scenario the .csv-files could come from the software you use
% for maintaining and exporting your data.
\begin{filecontents*}{MyMusic1.csv}
num,comp,ttl,sbttl
00034,Beethoven,Bagatelle No.\@ 25,Für Elise
00035,Bob Geldof,The Great Song of Indifference,I don't mind
\end{filecontents*}
\begin{filecontents*}{MyMusic2.csv}
sbttl,ttl,comp,num
Der für die Sünde der Welt gemarterte und sterbende Jesus,Brockes-Passion,Georg Friedrich Händel,00047
\end{filecontents*}
\begin{filecontents*}{MyMusic3.csv}
Jean Sibelius,Finlandia,Opus 26,01769
Monty Python,,Worried,00036
\end{filecontents*}
\documentclass{article}
\usepackage{datatool}
% To create new database from a .csv-file with \DTLloaddb set the
% switch \DTLnewdbonloadtrue ; this is the default on the start:
% \DTLnewdbonloadtrue
\DTLloaddb[%
% MyMusic1.csv has a header row, thus noheader=false:
noheader=false,
% Let's override the field-names given in the .csv-file's header row by new names
% given in keys={...} in the order in which the old names occur in the header-row:
keys={ID,composer,title,subtitle}
]{MusicDatabase}{MyMusic1.csv}
% To apennd with \DTLloaddb to an existing database data stemming
% from a .csv-file set the switch \DTLnewdbonloadfalse:
\DTLnewdbonloadfalse
\DTLloaddb[%
noheader=false,
keys={subtitle,title,composer,ID}
]{MusicDatabase}{MyMusic2.csv}
\DTLloaddb[%
noheader=true,%
keys={composer,subtitle,title,ID},
% With the last call to \DTLloaddb set the column-headings in case of
% using facilities of the package datatool for creating tables
%(tabular/longtable) from data of the database. You can change this
% setting laterwards via \DTLsetheader.
headers={%
Numerical String ID,Composer,Title,Subtitle%
}
]{MusicDatabase}{MyMusic3.csv}
%----------------------------------------------------------------------------------------
% \GetDataFieldEntryByID{<ID>}%
% {<datafield>}%
% {<tokens in case only ID does not exist>}%
% {<tokens in case only datafield does not exist>}%
% {<tokens in case both ID and datafield don't exist>}%
% {<tokens in case datafield does exist but does not hold data>}
\newcommand\MyScratchMacro{}%
\newcommand\GetDataFieldEntryByID[6]{%
\begingroup
\DTLifhaskey{MusicDatabase}{#2}{%
\DTLgetvalueforkey{\MyScratchMacro}{ID}{MusicDatabase}{ID}{#1}%
\DTLifnullorempty{\MyScratchMacro}{%
\endgroup#3%
}{%
\DTLgetvalueforkey{\MyScratchMacro}{#2}{MusicDatabase}{ID}{#1}%
\DTLifnullorempty{\MyScratchMacro}%
{\endgroup#6}%
{\expandafter\endgroup\MyScratchMacro}%
}%
}{%
\DTLgetvalueforkey{\MyScratchMacro}{ID}{MusicDatabase}{ID}{#1}%
\DTLifnullorempty{\MyScratchMacro}{%
\endgroup#5%
}{%
\endgroup#4%
}%
}%
}%
%----------------------------------------------------------------------------------------
\begin{document}
% Sort the database. This affects the order in which rows of the database are processed when
% iterating the database from the first to the last row, e.g. in the course if crearing a table
% where all database rows are displayed.
\dtlsort{ID=ascending}{MusicDatabase}{\dtlcompare}%
%
% Have datatool create a table with all database-rows, don't display the subtitle-column:
\begingroup
\scriptsize
\DTLdisplaydb[subtitle]{MusicDatabase}%
\par
\endgroup
\newpage
\GetDataFieldEntryByID{00034}%
{composer}%
{Error: Database does not have a row with ID 00034}%
{Error: Database does not have a key/datafield/column ``composer''}%
{Error: Database neither does have row with ID 00034 nor does have a key/datafield/column ``composer''}%
{Error: The row with the ID 00034 does not have data in key/datafield/column ``composer''}
\GetDataFieldEntryByID{00034}%
{title}%
{Error: Database does not have a row with ID 00034}%
{Error: Database does not have a key/datafield/column ``title''}%
{Error: Database neither does have row with ID 00034 nor does have a key/datafield/column ``title''}%
{Error: The row with the ID 00034 does not have data in key/datafield/column ``title''}
\GetDataFieldEntryByID{00034}%
{subtitle}%
{Error: Database does not have a row with ID 00034}%
{Error: Database does not have a key/datafield/column ``subtitle''}%
{Error: Database neither does have row with ID 00034 nor does have a key/datafield/column ``subtitle''}%
{Error: The row with the ID 00034 does not have data in key/datafield/column ``subtitle''}
\GetDataFieldEntryByID{00034}%
{ID}%
{Error: Database does not have a row with ID 00034}%
{Error: Database does not have a key/datafield/column ``ID''}%
{Error: Database neither does have row with ID 00034 nor does have a key/datafield/column ``ID''}%
{Error: The row with the ID 00034 does not have data in key/datafield/column ``ID''}
\medskip\hrule\medskip
\GetDataFieldEntryByID{00035}%
{composer}%
{Error: Database does not have a row with ID 00035}%
{Error: Database does not have a key/datafield/column ``composer''}%
{Error: Database neither does have row with ID 00035 nor does have a key/datafield/column ``composer''}%
{Error: The row with the ID 00035 does not have data in key/datafield/column ``composer''}
\GetDataFieldEntryByID{00035}%
{title}%
{Error: Database does not have a row with ID 00035}%
{Error: Database does not have a key/datafield/column ``title''}%
{Error: Database neither does have row with ID 00035 nor does have a key/datafield/column ``title''}%
{Error: The row with the ID 00035 does not have data in key/datafield/column ``title''}
\GetDataFieldEntryByID{00035}%
{subtitle}%
{Error: Database does not have a row with ID 00035}%
{Error: Database does not have a key/datafield/column ``subtitle''}%
{Error: Database neither does have row with ID 00035 nor does have a key/datafield/column ``subtitle''}%
{Error: The row with the ID 00035 does not have data in key/datafield/column ``subtitle''}
\GetDataFieldEntryByID{00035}%
{ID}%
{Error: Database does not have a row with ID 00035}%
{Error: Database does not have a key/datafield/column ``ID''}%
{Error: Database neither does have row with ID 00035 nor does have a key/datafield/column ``ID''}%
{Error: The row with the ID 00035 does not have data in key/datafield/column ``ID''}
\medskip\hrule\medskip
\GetDataFieldEntryByID{00036}%
{composer}%
{Error: Database does not have a row with ID 00036}%
{Error: Database does not have a key/datafield/column ``composer''}%
{Error: Database neither does have row with ID 00036 nor does have a key/datafield/column ``composer''}%
{Error: The row with the ID 00036 does not have data in key/datafield/column ``composer''}
\GetDataFieldEntryByID{00036}%
{title}%
{Error: Database does not have a row with ID 00036}%
{Error: Database does not have a key/datafield/column ``title''}%
{Error: Database neither does have row with ID 00036 nor does have a key/datafield/column ``title''}%
{Error: The row with the ID 00036 does not have data in key/datafield/column ``title''}
\GetDataFieldEntryByID{00036}%
{ID}%
{Error: Database does not have a row with ID 00036}%
{Error: Database does not have a key/datafield/column ``ID''}%
{Error: Database neither does have row with ID 00036 nor does have a key/datafield/column ``ID''}%
{Error: The row with the ID 00036 does not have data in key/datafield/column ``ID''}
\medskip\hrule\medskip
\GetDataFieldEntryByID{00047}%
{composer}%
{Error: Database does not have a row with ID 00047}%
{Error: Database does not have a key/datafield/column ``composer''}%
{Error: Database neither does have row with ID 00047 nor does have a key/datafield/column ``composer''}%
{Error: The row with the ID 00047 does not have data in key/datafield/column ``composer''}
\GetDataFieldEntryByID{00047}%
{title}%
{Error: Database does not have a row with ID 00047}%
{Error: Database does not have a key/datafield/column ``title''}%
{Error: Database neither does have row with ID 00047 nor does have a key/datafield/column ``title''}%
{Error: The row with the ID 00047 does not have data in key/datafield/column ``title''}
\GetDataFieldEntryByID{00047}%
{subtitle}%
{Error: Database does not have a row with ID 00047}%
{Error: Database does not have a key/datafield/column ``subtitle''}%
{Error: Database neither does have row with ID 00047 nor does have a key/datafield/column ``subtitle''}%
{Error: The row with the ID 00047 does not have data in key/datafield/column ``subtitle''}
\GetDataFieldEntryByID{00047}%
{ID}%
{Error: Database does not have a row with ID 00047}%
{Error: Database does not have a key/datafield/column ``ID''}%
{Error: Database neither does have row with ID 00047 nor does have a key/datafield/column ``ID''}%
{Error: The row with the ID 00047 does not have data in key/datafield/column ``ID''}
\medskip\hrule\medskip
\GetDataFieldEntryByID{01769}%
{composer}%
{Error: Database does not have a row with ID 01769}%
{Error: Database does not have a key/datafield/column ``composer''}%
{Error: Database neither does have row with ID 01769 nor does have a key/datafield/column ``composer''}%
{Error: The row with the ID 01769 does not have data in key/datafield/column ``composer''}
\GetDataFieldEntryByID{01769}%
{title}%
{Error: Database does not have a row with ID 01769}%
{Error: Database does not have a key/datafield/column ``title''}%
{Error: Database neither does have row with ID 01769 nor does have a key/datafield/column ``title''}%
{Error: The row with the ID 01769 does not have data in key/datafield/column ``title''}
\GetDataFieldEntryByID{01769}%
{subtitle}%
{Error: Database does not have a row with ID 01769}%
{Error: Database does not have a key/datafield/column ``subtitle''}%
{Error: Database neither does have row with ID 01769 nor does have a key/datafield/column ``subtitle''}%
{Error: The row with the ID 01769 does not have data in key/datafield/column ``subtitle''}
\GetDataFieldEntryByID{01769}%
{ID}%
{Error: Database does not have a row with ID 01769}%
{Error: Database does not have a key/datafield/column ``ID''}%
{Error: Database neither does have row with ID 01769 nor does have a key/datafield/column ``ID''}%
{Error: The row with the ID 01769 does not have data in key/datafield/column ``ID''}
\medskip\hrule\medskip
Error-triggering:
\GetDataFieldEntryByID{00036}%
{subtitle}%
{Error: Database does not have a row with ID 00036}%
{Error: Database does not have a key/datafield/column ``subtitle''}%
{Error: Database neither does have row with ID 00036 nor does have a key/datafield/column ``subtitle''}%
{Error: The row with the ID 00036 does not have data in key/datafield/column ``subtitle''}
\GetDataFieldEntryByID{00050}%
{subtitle}%
{Error: Database does not have a row with ID 00050}%
{Error: Database does not have a key/datafield/column ``subtitle''}%
{Error: Database neither does have row with ID 00050 nor does have a key/datafield/column ``subtitle''}%
{Error: The row with the ID 00050 does not have data in key/datafield/column ``subtitle''}
\GetDataFieldEntryByID{00036}%
{year}%
{Error: Database does not have a row with ID 00036}%
{Error: Database does not have a key/datafield/column ``year''}%
{Error: Database neither does have row with ID 00036 nor does have a key/datafield/column ``year''}%
{Error: The row with the ID 00036 does not have data in key/datafield/column ``year''}
\GetDataFieldEntryByID{00050}%
{year}%
{Error: Database does not have a row with ID 00050}%
{Error: Database does not have a key/datafield/column ``year''}%
{Error: Database neither does have row with ID 00050 nor does have a key/datafield/column ``year''}%
{Error: The row with the ID 00050 does not have data in key/datafield/column ``year''}
\end{document}