diff options
Diffstat (limited to 'Master/texmf-dist/tex/context/base/mkiv/publ-aut.lua')
-rw-r--r-- | Master/texmf-dist/tex/context/base/mkiv/publ-aut.lua | 876 |
1 files changed, 876 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/context/base/mkiv/publ-aut.lua b/Master/texmf-dist/tex/context/base/mkiv/publ-aut.lua new file mode 100644 index 00000000000..5ed25f288c3 --- /dev/null +++ b/Master/texmf-dist/tex/context/base/mkiv/publ-aut.lua @@ -0,0 +1,876 @@ +if not modules then modules = { } end modules ['publ-aut'] = { + version = 1.001, + comment = "this module part of publication support", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +if not characters then + dofile(resolvers.findfile("char-def.lua")) + dofile(resolvers.findfile("char-ini.lua")) +end + +local lpeg = lpeg + +local type, next, tostring = type, next, tostring +local concat = table.concat +local utfchar = utf.char +local utfsub = utf.sub +local formatters = string.formatters + +local P, S, C, V, Cs, Ct, Cg, Cf, Cc = lpeg.P, lpeg.S, lpeg.C, lpeg.V, lpeg.Cs, lpeg.Ct, lpeg.Cg, lpeg.Cf, lpeg.Cc +local lpegmatch, lpegpatterns = lpeg.match, lpeg.patterns +local settings_to_hash = utilities.parsers.settings_to_hash + +local context = context +----- commands = commands + +local implement = interfaces.implement +local ctx_setmacro = interfaces.setmacro + +local publications = publications + +local datasets = publications.datasets +local getcasted = publications.getcasted + +local allocate = utilities.storage.allocate + +local chardata = characters.data + +local trace_hashing = false trackers.register("publications.authorhash", function(v) trace_hashing = v end) + +local report = logs.reporter("publications","authors") +local report_cite = logs.reporter("publications","cite") + +local v_last = interfaces.variables.last + +-- local function makesplitter(separator) +-- return Ct { "start", +-- start = (Cs((V("outer") + (1-separator))^1) + separator^1)^1, +-- start = Cs(V("outer")) + (Cs((V("inner") + (1-separator))^1) + separator^1)^1, +-- outer = (P("{")/"") * ((V("inner") + P(1-P("}")))^0) * (P("}")/""), +-- inner = P("{") * ((V("inner") + P(1-P("}")))^0) * P("}"), +-- } +-- end + +-- authorlist = { authorspec and authorspec and authorspec } +-- authorspec = composedname +-- authorspec = surnames, firstnames +-- authorspec = von, surnames, firstnames +-- authorspec = von, surnames, jr, firstnames +-- authorspec = von, surnames, jr, firstnames, initials + +local space = lpegpatterns.whitespace +local comma = P(",") +local period = P(".") +local dash = P("-") +local firstcharacter = lpegpatterns.utf8byte +local utf8character = lpegpatterns.utf8character +local p_and = space^1 * (P("and") + P("&&") + P("++")) * space^1 +local p_comma = space^0 * comma * space^0 +local p_space = space^1 +local p_shortone = C((utf8character -dash-period)^1) +local p_longone = C( utf8character) * (1-dash-period)^0 + +local p_empty = P("{}")/"" * #(p_space^0 * (P(-1) + P(","))) + +local andsplitter = Ct { "start", + start = (Cs((V("inner") + (1-p_and))^1) + p_and)^1, + inner = P("{") * ((V("inner") + P(1-P("}")))^1) * P("}"), +} + +local commasplitter = Ct { "start", + start = Cs(V("outer")) + (p_empty + Cs((V("inner") + (1-p_comma))^1) + p_comma)^1, + outer = (P("{")/"") * ((V("inner") + P(1-P("}")))^1) * (P("}")/""), + inner = P("{") * ((V("inner") + P(1-P("}")))^1) * P("}"), +} + +local spacesplitter = Ct { "start", + start = Cs(V("outer")) + (Cs((V("inner") + (1-p_space))^1) + p_space)^1, + outer = (P("{")/"") * ((V("inner") + P(1-P("}")))^1) * (P("}")/""), + inner = P("{") * ((V("inner") + P(1-P("}")))^1) * P("}"), +} + +local p_initial = p_shortone * period * dash^0 + + p_longone * (period + dash + P(-1)) +local initialsplitter = p_initial * P(-1) + Ct((p_initial)^1) + +local optionsplitter = Cf(Ct("") * Cg(C((1-space)^1) * space^0 * Cc(true))^1,rawset) + +local function is_upper(str) + local first = lpegmatch(firstcharacter,str) + local okay = chardata[first] + return okay and okay.category == "lu" +end + +-- local cleaner = Cs( ( P("{}")/"" + P(1) )^1 ) + +local cache = allocate() -- 33% reuse on tugboat.bib +local nofhits = 0 +local nofused = 0 + +publications.authorcache = cache + +local function makeinitials(firstnames) + if firstnames and #firstnames > 0 then + local initials = { } + for i=1,#firstnames do + initials[i] = lpegmatch(initialsplitter,firstnames[i]) + end + return initials + end +end + +local authormap = allocate() +publications.authormap = authormap + +local function splitauthor(author) + local detail = cache[author] + if detail then + return detail + end + local remapped = authormap[author] + if remapped then + report("remapping %a to %a",author,remapped) + local detail = cache[remapped] + if detail then + cache[author] = detail + return detail + end + end + local author = remapped or author + local firstnames, vons, surnames, initials, juniors, options + local split = lpegmatch(commasplitter,author) + local n = #split + detail = { + original = author, + snippets = n, + } + if n == 1 then + -- {First Middle von Last} + local words = lpegmatch(spacesplitter,author) + firstnames, vons, surnames = { }, { }, { } + local i, n = 1, #words + while i <= n do + local w = words[i] + if is_upper(w) then + firstnames[#firstnames+1], i = w, i + 1 + else + break + end + end + while i <= n do + local w = words[i] + if is_upper(w) then + break + else + vons[#vons+1], i = w, i + 1 + end + end + if i <= n then + while i <= n do + surnames[#surnames+1], i = words[i], i + 1 + end + elseif #vons == 0 then + surnames[1] = firstnames[#firstnames] + firstnames[#firstnames] = nil + else + -- mess + end + if #surnames == 0 then + -- safeguard + firstnames = { } + vons = { } + surnames = { author } + else + initials = makeinitials(firstnames) + end + elseif n == 2 then + -- {Last, First} + -- {von Last, First} + firstnames, vons, surnames = { }, { }, { } + local words = lpegmatch(spacesplitter,split[1]) + local i, n = 1, #words + while i <= n do + local w = words[i] + if is_upper(w) then + break + else + vons[#vons+1], i = w, i + 1 + end + end + while i <= n do + surnames[#surnames+1], i = words[i], i + 1 + end + -- + local words = lpegmatch(spacesplitter,split[2]) + local i, n = 1, #words + while i <= n do + local w = words[i] + if is_upper(w) then + firstnames[#firstnames+1], i = w, i + 1 + else + break + end + end + while i <= n do + vons[#vons+1], i = words[i], i + 1 + end + if surnames and firstnames and #surnames == 0 then + -- safeguard + surnames[1] = firstnames[#firstnames] + firstnames[#firstnames] = nil + end + initials = makeinitials(firstnames) + elseif n == 3 then + -- {von Last, First, Jr} + surnames = lpegmatch(spacesplitter,split[1]) + juniors = lpegmatch(spacesplitter,split[2]) + firstnames = lpegmatch(spacesplitter,split[3]) + initials = makeinitials(firstnames) + elseif n == 4 then + -- {Von, Last, First, Jr} + vons = lpegmatch(spacesplitter,split[1]) + surnames = lpegmatch(spacesplitter,split[2]) + juniors = lpegmatch(spacesplitter,split[3]) + firstnames = lpegmatch(spacesplitter,split[4]) + initials = makeinitials(firstnames) + elseif n >= 5 then + -- {Von, Last, First, Jr, F.} + -- {Von, Last, First, Jr, Fr., options} + vons = lpegmatch(spacesplitter,split[1]) + surnames = lpegmatch(spacesplitter,split[2]) + juniors = lpegmatch(spacesplitter,split[3]) + firstnames = lpegmatch(spacesplitter,split[4]) + initials = lpegmatch(spacesplitter,split[5]) + options = split[6] + if options then + options = lpegmatch(optionsplitter,options) + end + end + if firstnames and #firstnames > 0 then detail.firstnames = firstnames end + if vons and #vons > 0 then detail.vons = vons end + if surnames and #surnames > 0 then detail.surnames = surnames end + if initials and #initials > 0 then detail.initials = initials end + if juniors and #juniors > 0 then detail.juniors = juniors end + if options and next(options) then detail.options = options end + cache[author] = detail + nofhits = nofhits + 1 + return detail +end + +local function splitauthorstring(str) + if not str or str == "" then + return + end + nofused = nofused + 1 + + local remapped = authormap[str] + if remapped then + local detail = cache[remapped] + if detail then + cache[str] = detail + return { detail } + end + end + + local authors = cache[str] + if authors then + return { authors } -- we assume one author + end + + -- we could cache these too but it can become messy .. leave that for later + + local authors = lpegmatch(andsplitter,str) or { } -- maybe fake an author + local nofauthors = #authors + for i=1,nofauthors do + authors[i] = splitauthor(authors[i]) + end + if nofauthors > 1 and authors[nofauthors].original == "others" then + -- only the last one is looked at + authors[nofauthors] = nil + authors.others = true + end + return authors +end + +publications.splitoneauthor = splitauthor +publications.splitauthor = splitauthorstring + +local function the_initials(initials,symbol,connector) + if not symbol then + symbol = "." + end + if not connector then + connector = "-" + end + local result, r = { }, 0 + for i=1,#initials do + local initial = initials[i] + if type(initial) == "table" then + local set, s = { }, 0 + for i=1,#initial do + if i > 1 then + s = s + 1 ; set[s] = connector + end + s = s + 1 ; set[s] = initial[i] + s = s + 1 ; set[s] = symbol + end + r = r + 1 ; result[r] = concat(set) + else + r = r + 1 ; result[r] = initial .. symbol + end + end + return result +end + +local ctx_btxsetconcat = context.btxsetconcat +local ctx_btxsetoverflow = context.btxsetoverflow +local ctx_btxsetinitials = context.btxsetinitials +local ctx_btxsetfirstnames = context.btxsetfirstnames +local ctx_btxsetvons = context.btxsetvons +local ctx_btxsetsurnames = context.btxsetsurnames +local ctx_btxsetjuniors = context.btxsetjuniors +local ctx_btxciteauthorsetup = context.btxciteauthorsetup +local ctx_btxlistauthorsetup = context.btxlistauthorsetup +local ctx_btxsetauthorvariant = context.btxsetauthorvariant +local ctx_btxstartauthor = context.btxstartauthor +local ctx_btxstopauthor = context.btxstopauthor + +local concatstate = publications.concatstate +local f_invalid = formatters["<invalid %s: %s>"] + +local currentauthordata = nil +local currentauthorsymbol = nil + +local manipulators = typesetters.manipulators +local splitmanipulation = manipulators.splitspecification +local applymanipulation = manipulators.applyspecification +local manipulatormethods = manipulators.methods + +local function value(i,field) + if currentauthordata then + local entry = currentauthordata[i] + if entry then + local value = entry[field] + if value and #value > 0 then + return value + end + end + end +end + +implement { name = "btxcurrentfirstnames", arguments = "integer", actions = function(i) local v = value(i,"firstnames") if v then context(concat(v," ")) end end } +implement { name = "btxcurrentinitials", arguments = "integer", actions = function(i) local v = value(i,"initials") if v then context(concat(the_initials(v,currentauthorsymbol))) end end } +implement { name = "btxcurrentjuniors", arguments = "integer", actions = function(i) local v = value(i,"juniors") if v then context(concat(v," ")) end end } +implement { name = "btxcurrentsurnames", arguments = "integer", actions = function(i) local v = value(i,"surnames") if v then context(concat(v," ")) end end } +implement { name = "btxcurrentvons", arguments = "integer", actions = function(i) local v = value(i,"vons") if v then context(concat(v," ")) end end } + +local function btxauthorfield(i,field) + if currentauthordata then + local entry = currentauthordata[i] + if entry then + local manipulator, field = splitmanipulation(field) + local value = entry[field] + if not value or #value == 0 then + -- value, no need for message + elseif manipulator then + for i=1,#value do + if i > 1 then + context(" ") + end + context(applymanipulation(manipulator,value) or value) + end + elseif field == "initials" then + context(concat(the_initials(value,currentauthorsymbol))) + else + context(concat(value," ")) + end + end + end +end + +-- This is somewhat tricky: an author is not always an author but +-- can also be a title or key, depending on the (optional) set it's +-- in. Also, authors can be combined with years and so and they +-- might be called upon mixed with other calls. + +local function btxauthor(dataset,tag,field,settings) + local split, usedfield, kind = getcasted(dataset,tag,field) + if kind == "author" then + local max = split and #split or 0 + if max == 0 then + return + -- error + end + local absmax = max + local etallimit = tonumber(settings.etallimit) or 1000 + local etaldisplay = tonumber(settings.etaldisplay) or etallimit + local etaloption = settings_to_hash(settings.etaloption or "") + local etallast = etaloption[v_last] + local combiner = settings.combiner + local symbol = settings.symbol + local index = settings.index + if not combiner or combiner == "" then + combiner = "normal" + end + if not symbol then + symbol = "." + end + local ctx_btxsetup = settings.kind == "cite" and ctx_btxciteauthorsetup or ctx_btxlistauthorsetup + if max > etallimit and (etaldisplay+(etallast and 1 or 0)) < max then + max = etaldisplay + else + etallast = false + end + currentauthordata = split + currentauthorsymbol = symbol + + local function oneauthor(i,last,justone) + local author = split[i] + if index then + ctx_btxstartauthor(i,1,0) + elseif last then + ctx_btxstartauthor(i,1,0) + ctx_btxsetconcat(0) + ctx_btxsetauthorvariant(combiner) + else + local state = author.state or 0 + ctx_btxstartauthor(i,max,state) + ctx_btxsetconcat(concatstate(i,max)) + ctx_btxsetauthorvariant(combiner) + end + local initials = author.initials + if initials and #initials > 0 then + ctx_btxsetinitials() -- (concat(the_initials(initials,symbol)," ")) + end + local firstnames = author.firstnames + if firstnames and #firstnames > 0 then + ctx_btxsetfirstnames() -- (concat(firstnames," ")) + end + local vons = author.vons + if vons and #vons > 0 then + ctx_btxsetvons() -- (concat(vons," ")) + end + local surnames = author.surnames + if surnames and #surnames > 0 then + ctx_btxsetsurnames() -- (concat(surnames," ")) + end + local juniors = author.juniors + if juniors and #juniors > 0 then + ctx_btxsetjuniors() -- (concat(juniors," ")) + end + if not index and i == max then + if split.others then + ctx_btxsetoverflow(1) + else + local overflow = #split - max + if overflow > 0 then + ctx_btxsetoverflow(overflow) + end + end + end + ctx_btxsetup(combiner) + ctx_btxstopauthor() + end + if index then + oneauthor(index) + elseif max == 1 then + oneauthor(1,false,true) + else + for i=1,max do + oneauthor(i) + end + if etallast then + oneauthor(absmax,true) + end + end + else + report("ignored field %a of tag %a, used field %a is no author",field,tag,usedfield) + end +end + +implement { + name = "btxauthorfield", + actions = btxauthorfield, + arguments = { "integer", "string" } +} + +implement { + name = "btxauthor", + actions = btxauthor, + arguments = { + "string", + "string", + "string", + { + { "combiner" }, + { "kind" }, + { "etallimit" }, + { "etaldisplay" }, + { "etaloption" }, + { "symbol" }, + } + } +} + +local function components(snippet,short) + local vons = snippet.vons + local surnames = snippet.surnames + local initials = snippet.initials + local firstnames = not short and snippet.firstnames + local juniors = snippet.juniors + return + vons and #vons > 0 and concat(vons," ") or "", + surnames and #surnames > 0 and concat(surnames," ") or "", + initials and #initials > 0 and concat(the_initials(initials)," ") or "", + firstnames and #firstnames > 0 and concat(firstnames," ") or "", + juniors and #juniors > 0 and concat(juniors, " ") or "" +end + +local collapsers = allocate { } + +publications.authorcollapsers = collapsers + +local function default(author) -- one author + local hash = author.hash + if hash then + return hash + end + local original = author.original + local vons = author.vons + local surnames = author.surnames + local initials = author.initials + local firstnames = author.firstnames + local juniors = author.juniors + local result = { } + local nofresult = 0 + if vons and #vons > 0 then + for j=1,#vons do + nofresult = nofresult + 1 + result[nofresult] = vons[j] + end + end + if surnames and #surnames > 0 then + for j=1,#surnames do + nofresult = nofresult + 1 + result[nofresult] = surnames[j] + end + end + if initials and #initials > 0 then + initials = the_initials(initials) + for j=1,#initials do + nofresult = nofresult + 1 + result[nofresult] = initials[j] + end + end + if firstnames and #firstnames > 0 then + for j=1,#firstnames do + nofresult = nofresult + 1 + result[nofresult] = firstnames[j] + end + end + if juniors and #juniors > 0 then + for j=1,#juniors do + nofresult = nofresult + 1 + result[nofresult] = juniors[j] + end + end + local hash = concat(result," ") + if trace_hashing then + report("hash: %s -> %s",original,hash) + end + author.hash = hash + return hash +end + +local authorhashers = { } +publications.authorhashers = authorhashers + +-- todo: some hashing + +local function name(authors) + if type(authors) == "table" then + local n = #authors + if n == 0 then + return "" + end + local result = { } + local nofresult = 0 + for i=1,n do + local author = authors[i] + local surnames = author.surnames + if surnames and #surnames > 0 then + for j=1,#surnames do + nofresult = nofresult + 1 + result[nofresult] = surnames[j] + end + end + end + return concat(result," ") + else + return authors + end +end + +table.setmetatableindex(authorhashers,function(t,k) + t[k] = name + return name +end) + +authorhashers.normal = function(authors) + if type(authors) == "table" then + local n = #authors + if n == 0 then + return "" + end + local result = { } + local nofresult = 0 + for i=1,n do + local author = authors[i] + local vons = author.vons + local surnames = author.surnames + local firstnames = author.firstnames + local juniors = author.juniors + if vons and #vons > 0 then + for j=1,#vons do + nofresult = nofresult + 1 + result[nofresult] = vons[j] + end + end + if surnames and #surnames > 0 then + for j=1,#surnames do + nofresult = nofresult + 1 + result[nofresult] = surnames[j] + end + end + if firstnames and #firstnames > 0 then + for j=1,#firstnames do + nofresult = nofresult + 1 + result[nofresult] = firstnames[j] + end + end + if juniors and #juniors > 0 then + for j=1,#juniors do + nofresult = nofresult + 1 + result[nofresult] = juniors[j] + end + end + end + return concat(result," ") + else + return authors + end +end + +authorhashers.normalshort = function(authors) + if type(authors) == "table" then + local n = #authors + if n == 0 then + return "" + end + local result = { } + local nofresult = 0 + for i=1,n do + local author = authors[i] + local vons = author.vons + local surnames = author.surnames + local initials = author.initials + local juniors = author.juniors + if vons and #vons > 0 then + for j=1,#vons do + nofresult = nofresult + 1 + result[nofresult] = vons[j] + end + end + if surnames and #surnames > 0 then + for j=1,#surnames do + nofresult = nofresult + 1 + result[nofresult] = surnames[j] + end + end + if initials and #initials > 0 then + initials = the_initials(initials) + for j=1,#initials do + nofresult = nofresult + 1 + result[nofresult] = initials[j] + end + end + if juniors and #juniors > 0 then + for j=1,#juniors do + nofresult = nofresult + 1 + result[nofresult] = juniors[j] + end + end + end + return concat(result," ") + else + return authors + end +end + +authorhashers.normalinverted = authorhashers.normal +authorhashers.invertedshort = authorhashers.normalshort + +local p_clean = Cs ( ( + P("\\btxcmd") / "" -- better keep the argument + + S("`~!@#$%^&*()_-+={}[]:;\"\'<>,.?/|\\") / "" + + lpeg.patterns.utf8character + )^1) + +authorhashers.short = function(authors) + -- a short is a real dumb hardcodes kind of tag and we only support + -- this one because some users might expect it, not because it makes + -- sense + if type(authors) == "table" then + local n = #authors + if n == 0 then + return "unk" + elseif n == 1 then + local surnames = authors[1].surnames + if not surnames or #surnames == 0 then + return "err" + else + local s = surnames[1] + local c = lpegmatch(p_clean,s) + if trace_hashing and s ~= c then + report_cite("name %a cleaned to %a for short construction",s,c) + end + return utfsub(c,1,3) + end + else + local t = { } + for i=1,n do + if i > 3 then + t[#t+1] = "+" -- indeed + break + end + local surnames = authors[i].surnames + if not surnames or #surnames == 0 then + t[#t+1] = "?" + else + local s = surnames[1] + local c = lpegmatch(p_clean,s) + if s ~= c then + report_cite("name %a cleaned to %a for short construction",s,c) + end + t[#t+1] = utfsub(c,1,1) + end + end + return concat(t) + end + else + return utfsub(authors,1,3) + end +end + +collapsers.default = default + +local function authorwriter(key,index) + if not key then + return "" + end + if type(key) == "string" then + return key + end + local n = #key + if n == 0 then + return "" + end + if index then + if not key[index] then + return "" + end + elseif n == 1 then + index = 1 + end + if index then + local author = key[index] + local options = author.options + if options then + for option in next, options do + local collapse = collapsers[option] + if collapse then + return collapse(author) + end + end + end + local hash = default(author) + -- if trace_hashing then + -- report("hash: %s",hash) + -- end + return hash + end + local t = { } + local s = 0 + for i=1,n do + local author = key[i] + local options = author.options + s = s + 1 + if options then + local done = false + for option in next, options do + local collapse = collapsers[option] + if collapse then + t[s] = collapse(author) + done = true + end + end + if not done then + t[s] = default(author) + end + else + t[s] = default(author) + end + end + local hash = concat(t," & ") + -- if trace_hashing then + -- report("hash: %s",hash) + -- end + return hash +end + +local function writer(key) + return authorwriter(key) -- discard extra arguments in the caller +end + +publications.writers .author = writer +publications.casters .author = splitauthorstring +publications.components.author = components + +-- sharedmethods.author = { +-- { field = "key", default = "", unknown = "" }, +-- { field = "author", default = "", unknown = "" }, +-- { field = "title", default = "", unknown = "" }, +-- } + +-- Analysis of the APA by Alan: +-- +-- first : key author editor publisher title journal volume number pages +-- second: year suffix title month day journal volume number + +publications.sortmethods.authoryear = { + sequence = { + -- { field = "key", default = "ZZZZ", unknown = "ZZZZ" }, + { field = "author", default = "", unknown = "" }, + { field = "year", default = "9998", unknown = "9999" }, + -- { field = "suffix", default = " ", unknown = " " }, + { field = "month", default = "13", unknown = "14" }, + { field = "day", default = "32", unknown = "33" }, + { field = "journal", default = "", unknown = "" }, + { field = "volume", default = "", unknown = "" }, + -- { field = "number", default = "", unknown = "" }, + { field = "pages", default = "", unknown = "" }, + { field = "title", default = "", unknown = "" }, + { field = "index", default = "", unknown = "" }, + }, +} + +implement { + name = "btxremapauthor", + arguments = { "string", "string" }, + actions = function(k,v) + publications.authormap[k] = v + end +} |