在 LaTeX 中,人们可以用来datatool
创建简单的格式信函。例如,数据存储在电子表格或 CSV 文件中,如下所示:
Name,Amount,Date
"Mr. White","$300","Dec. 2, 1911"
"Mr. Brown","$300","Dec. 3, 1911"
然后,提供一个简单的模板:
Dear \insertName,
You owe \insertAmount. Please send it before \insertDate.
编译后,它会为电子表格中的每一行生成一个字母。
ConTeXt 中是否有类似的用于创建格式信函的工具?M-数据库模块似乎共享了许多功能datatool
,但我看不出有任何方法可以使用它来创建格式信函。
答案1
据我所知,没有内置功能。但通常你可以通过组合一些现有功能来实现。你只需要一个CSV 解析器然后你就可以使用缓冲区来完成剩下的工作了。(我稍微修改了一下界面,这样你就可以简单地\insert[Field Name]
代替\insertFieldName
。)用法如下:
定义一个模板在修改后的形式中,您的示例代码将如下所示:
\startcsvtemplate [tpl] 亲爱的 \insert[Name],
您欠款 \insert[金额]。请在 \insert[日期] 之前发送。 \par \stopcsvtemplate
尾随的结束线会被删除,因此您必须明确请求段落。
定义一个输入缓冲区(可选):输入可以从文件或缓冲区读取。在后一种情况下,需要定义缓冲区,就像任何其他缓冲区一样:
\startbuffer[csdata] 姓名、金额、日期 “怀特先生”、“\letterdollar 300”、“1911 年 12 月 2 日” “布朗先生”、“\letterdollar 300”、“1911 年 12 月 3 日” “前提女士”、“\letterdollar 42”、“1911 年 12 月 4 日” “结论女士”、“\letterdollar 23”、“1911 年 12 月 5 日” \stopbuffer
要求输入解析:根据您选择从缓冲区还是文件读取数据,您必须使用适当的命令来处理它:
\processcsvbuffer[一][cs数据] \processcsvfile[二][test.csv]
这两个命令的第一个参数是ID通过它以后可以引用数据集(类似于
\useexternalfigure[a_cow][cow.pdf]
)。现在数据集和模板已经就绪,您可以在工作定义:
\definecsvjob [测试] [ 数据=two, 模板=tpl, ]
这将生成一个宏
\testing
,您可以在文档中使用它来生成输出。\启动文本 \测试 \停止文本
注意::下面的答案可以(如果经常使用,可能应该)通过定义一些模板语言并将字符串处理完全移至 Lua 来改进。事实上,由于从 TeX 重复调用 Lua,性能会很差。
% macros=mkvi
\unprotect
\startluacode
local datasets = { }
local buffersraw = buffers.raw
local context = context
local ioloaddata = io.loaddata
local lpegmatch = lpeg.match
local stringformat = string.format
local stringmatch = string.match
local stringsub = string.sub
local tableconcat = table.concat
local tableswapped = table.swapped
local die = function (msg) print(msg or "ERROR") os.exit(1) end
local csv_parser
do
--- This is (more than) an RFC 4180 parser.
--- https://www.rfc-editor.org/rfc/rfc4180
local C, Cg, Cs, Ct, P, S, V
= lpeg.C, lpeg.Cg, lpeg.Cs, lpeg.Ct, lpeg.P, lpeg.S, lpeg.V
local backslash = P[[\letterbackslash]]
local comma = ","
local dquote = P[["]]
local eol = S"\n\r"^1
local noquote = 1 - dquote
local unescape = function (s) return stringsub(s, 2) end
csv_parser = P{
"file",
file = Ct((V"header" * eol)^-1 * V"records"),
header = Cg(Ct(V"name" * (comma * V"name")^0), "header"),
records = V"record" * (eol * V"record")^0 * eol^0,
record = Ct(V"field" * (comma * V"field")^0),
name = V"field",
field = V"escaped" + V"non_escaped",
--- Deviate from rfc: the “textdata” terminal was defined only
--- for 7bit ASCII. Also, any character may occur in a quoted
--- field as long as it is escaped with a backslash. (\TEX --- macros start with two backslashes.)
escaped = dquote
* Cs(((backslash * 1 / unescape) + noquote)^0)
* dquote
,
non_escaped = C((1 - dquote - eol - comma)^0),
}
end
local process = function (id, raw)
--- buffers may have trailing EOLs
raw = stringmatch(raw, "^[\n\r]*(.-)[\n\r]*$")
local data = lpegmatch(csv_parser, raw)
--- map column name -> column nr
data.header = tableswapped(data.header)
datasets[id] = data
end
--- escaping hell ahead, please ignore.
local s_item = [[
\bgroup
\string\def\string\insert{\string\getvalue{csv_insert_field}{%s}{%s}}%%
%s%% template
\egroup
]]
local typeset = function (id, template)
local data = datasets[id] or die("ERROR unknown dataset: " .. id)
template = stringmatch(buffersraw(template), "^[\n\r]*(.-)[\n\r]*$")
local result = { }
local last = \letterhash data
for i=1, last do
result[i] = stringformat(s_item, id, i, template)
end
context(tableconcat(result))
end
local insert = function (id, n, field)
local this = datasets[id]
context(this[n][this.header[field]])
end
commands.process_csv = process
commands.process_csv_file = function (id, fname)
process(id, ioloaddata(fname, true))
end
commands.typeset_csv_job = typeset
commands.insert_csv_field = insert
\stopluacode
\startinterface all
\setinterfaceconstant{template}{template}
\setinterfaceconstant {data}{data}
\stopinterface
\def\processcsvbuffer[#id][#buf]{%
\ctxcommand{process_csv([[#id]], buffers.raw(\!!bs#buf\!!es))}%
}
\def\processcsvfile[#id][#filename]{%
\ctxcommand{process_csv_file([[#id]], \!!bs\detokenize{#filename}\!!es)}%
}
%% modeled after \startbuffer
\setuvalue{\e!start csvtemplate}{%
\begingroup
\obeylines
\dosingleempty\csv_template_start%
}
\def\csv_template_start[#id]{%
\buff_start_indeed{}{#id}{\e!start csvtemplate}{\e!stop csvtemplate}%
}
\installnamespace {csvjob}
\installcommandhandler \????csvjob {csvjob} \????csvjob
\appendtoks
\setuevalue{\currentcsvjob}{\csv_job_direct[\currentcsvjob]}
\to \everydefinecsvjob
\unexpanded\def\csv_job_direct[#id]{%
\edef\currentcsvjob{#id}%
\dosingleempty\csv_job_indeed%
}
\def\csv_job_indeed[#setups]{%
\iffirstargument\setupcurrentcsvjob[#setups]\fi
\ctxcommand{typeset_csv_job(
[[\csvjobparameter\c!data]],
[[\csvjobparameter\c!template]])}%
}
\def\csv_insert_field#id#n[#field]{%
\ctxcommand{insert_csv_field([[#id]], #n, [[#field]])}%
}
\protect
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% demo
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Stepwise instructions.
%% step 1: Define template.
\startcsvtemplate [tpl]
Dear \insert[Name],
You owe \insert[Amount]. Please send it before \insert[Date].
\par
\stopcsvtemplate
%% step 2: Define an input (CSV).
\startbuffer[csdata]
Name,Amount,Date
"Mr. White","\\letterdollar 300","Dec. 2, 1911"
"Mr. Brown","\\letterdollar 300","Dec. 3, 1911"
"Ms. Premise","\\letterdollar 42","Dec. 4, 1911"
"Ms. Conclusion","\\letterdollar 23","Dec. 5, 1911"
\stopbuffer
%% step 3: Parse and store the input.
\processcsvbuffer[one][csdata]
%\processcsvfile[two][test.csv]
%% step 4: Declare a job, joining dataset and template.
\definecsvjob [testing] [
data=two,
template=tpl,
]
%% step 5: Enjoy!
\starttext
\testing
\stoptext
答案2
我不知道如何使用ConTeXt
,但我知道你可以LuaTex
在中使用ConTeXt
,所以我给你一个LuaLaTeX
易于移植的示例,ConTeXt
因为主要工作是在中完成的Lua
。原理是你在-world 中定义字母的模板,LaTeX/ConTeXt
包括一些LaTeX/ConTeXt
宏(变量),并从中重复调用模板,LuaTeX
每次为给定的 csv 文件中的每个条目重新定义变量。在执行此操作之前,你必须读取 csv 文件并将数据存储在表中Lua
。
也许在信函模板中使用参数(#1、#2、#3、...)而不是每次都重新定义变量会是一个更简洁的解决方案,但这样用户就不容易阅读模板,因为他不知道参数的含义,而且您还被限制为九个参数。这是您的选择。
\documentclass{article}
\usepackage{filecontents}
\begin{filecontents*}{datafile.csv}
Mr.;Homer;Simpson;Evergreen Terrace, Springfield
Ms.;Marge;Simpson;Evergreen Terrace, Springfield
Mr.;Bart;Simpson;Evergreen Terrace, Springfield
Ms.;Lisa;Simpson;Evergreen Terrace, Springfield
Ms.;Maggie;Simpson;Evergreen Terrace, Springfield
\end{filecontents*}
\begin{filecontents*}{luaFunctions.lua}
function ReadData()
local input = io.open('datafile.csv', 'r')
dataTable = {}
for line in input:lines() do
-- split the line
local split = string.explode(line, ";")
-- store the arguments in variables
tableItem = {}
tableItem.Sex = split[1]
tableItem.FirstName = split[2]
tableItem.SurName = split[3]
tableItem.Address = split[4]
-- insert the arguments of one line in the table
table.insert(dataTable, tableItem)
end
end
function Letter(sex, firstName, surName, address)
-- redefine the latex commands and execute the \letterTemplate macro
tex.print(string.format("\\def\\sex{%s}",sex))
tex.print(string.format("\\def\\firstName{%s}",firstName))
tex.print(string.format("\\def\\surName{%s}",surName))
tex.print(string.format("\\def\\address{%s}",address))
tex.print("\\letterTemplate")
end
function PrintLetter()
-- read the external file and store the data in a table
ReadData()
-- loop through the table and print a letter for each table item
for i,p in ipairs(dataTable) do
Letter(p.Sex, p.FirstName, p.SurName, p.Address)
-- pagebreak
if i ~= #dataTable then
tex.print("\\newpage")
end
end
end
\end{filecontents*}
\directlua{dofile("luaFunctions.lua")}
\def\letterTemplate{%
Hallo \sex\ \firstName,\\
your surname is \surName\ and you live at \address.\\
Regards
}
\begin{document}
\directlua{PrintLetter()}
\end{document}