--[[ affiliation-blocks – generate title components Copyright © 2017–2021 Albert Krewinkel Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ]] local from_utils = require("utils") local has_key = from_utils.has_key local List = require("pandoc.List") local utils = require("pandoc.utils") local stringify = utils.stringify local M = {} local default_marks = { corresponding_author = FORMAT == "latex" and { pandoc.RawInline("latex", "*") } or { pandoc.Str("*") }, equal_contributor = FORMAT == "latex" and { pandoc.RawInline("latex", "\\#") } or { pandoc.Str("#") }, } local function get_orcid_mark(orcid_value) if not orcid_value then return {} end local orcid_str if type(orcid_value) == "string" then orcid_str = orcid_value elseif type(orcid_value) == "table" then if orcid_value.text then orcid_str = orcid_value.text elseif orcid_value[1] and orcid_value[1].text then orcid_str = orcid_value[1].text else return {} end else return {} end orcid_str = orcid_str:gsub("[%-%s]", "") if FORMAT == "latex" then return { pandoc.RawInline("latex", "\\orcidlink{" .. orcid_str .. "}") } elseif FORMAT:match("docx") then local orcid_url = "https://orcid.org/" .. orcid_str return { pandoc.Str(" "), pandoc.Link("ID", orcid_url), } else local orcid_url = "https://orcid.org/" .. orcid_str return { pandoc.Link(pandoc.Str(""), orcid_url, "", { class = "orcid" }) } end end M.default_marks = default_marks local function is_equal_contributor(author) if has_key(author, "attributes") then return author.attributes["equal-contributor"] end return nil end local function create_equal_contributors_block(authors, mark) local has_equal_contribs = List:new(authors):find_if(is_equal_contributor) if not has_equal_contribs then return nil end local contributors = { pandoc.Superscript(mark("equal_contributor")), pandoc.Space(), pandoc.Str("These authors contributed equally to this work."), } return List:new({ pandoc.Para(contributors) }) end M.create_equal_contributors_block = create_equal_contributors_block local function intercalate(lists, elem) local result = List:new({}) for i = 1, (#lists - 1) do result:extend(lists[i]) result:extend(elem) end if #lists > 0 then result:extend(lists[#lists]) end return result end local function is_corresponding_author(author) if has_key(author, "attributes") then if author.attributes["corresponding"] then return author.email end end return nil end M.is_corresponding_author = is_corresponding_author local function create_correspondence_blocks(authors, mark) local corresponding_authors = List:new({}) for _, author in ipairs(authors) do if is_corresponding_author(author) then local mailto = "mailto:" .. stringify(author.email) local author_with_mail = List:new( author.name.literal .. List:new({ pandoc.Space(), pandoc.Str("(") }) .. author.email .. List:new({ pandoc.Str(")") }) ) local link = pandoc.Link(author_with_mail, mailto) table.insert(corresponding_authors, { link }) end end if #corresponding_authors == 0 then return nil end local correspondence = List:new({ pandoc.Superscript(mark("corresponding_author")), pandoc.Space(), pandoc.Str("Corresponding to:"), pandoc.Space(), }) local sep = List:new({ pandoc.Str(","), pandoc.Space() }) return { pandoc.Para(correspondence .. intercalate(corresponding_authors, sep)) } end M.create_correspondence_blocks = create_correspondence_blocks local function author_inline_generator(get_mark, meta) return function(author) local author_marks = List:new({}) if has_key(author, "orcid") then author_marks[#author_marks + 1] = get_orcid_mark(author.orcid) end local affilstyle = meta and meta.affilstyle and stringify(meta.affilstyle) or "alphabeta" for _, idx in ipairs(author.affiliations) do local idx_num = tonumber(stringify(idx)) -- Convert MetaString/MetaInlines to number if not idx_num then error("Invalid affiliation index: " .. tostring(idx)) end local idx_str if affilstyle == "number" then idx_str = tostring(idx_num) else if idx_num > 26 then error("Too many affiliations: only up to 26 (a-z) are supported") end idx_str = string.char(96 + idx_num) end author_marks[#author_marks + 1] = { pandoc.Str(idx_str) } end if has_key(author, "attributes") then if author.attributes["equal-contributor"] then author_marks[#author_marks + 1] = get_mark("equal_contributor") end end if is_corresponding_author(author) then author_marks[#author_marks + 1] = get_mark("corresponding_author") end if FORMAT:match("latex") then author.name.literal[#author.name.literal + 1] = pandoc.Superscript(intercalate(author_marks, { pandoc.Str(",") })) return author else local res = List.clone(author.name.literal) res[#res + 1] = pandoc.Superscript(intercalate(author_marks, { pandoc.Str(",") })) return res end end end M.author_inline_generator = author_inline_generator local function create_authors_inlines(authors, mark, meta) local inlines_generator = author_inline_generator(mark, meta) local inlines = List:new(authors):map(inlines_generator) local and_str = List:new({ pandoc.Space(), pandoc.Str("and"), pandoc.Space() }) local last_author = inlines[#inlines] inlines[#inlines] = nil local result = intercalate(inlines, { pandoc.Str(","), pandoc.Space() }) if #authors > 1 then if #authors == 2 then result:extend(and_str) else result:extend(List:new({ pandoc.Str(",") }) .. and_str) end end result:extend(last_author) return result end M.create_authors_inlines = create_authors_inlines local function create_affiliations_blocks_alphabeta(affiliations, meta) local affilstyle = meta and meta.affilstyle and stringify(meta.affilstyle) or "alphabeta" local affil_lines = List:new(affiliations):map(function(affil, i) if affilstyle == "number" then num_inlines = pandoc.List:new({ pandoc.Superscript(pandoc.Str(tostring(i))), pandoc.Space(), }) else num_inlines = pandoc.List:new({ pandoc.Superscript(pandoc.Str(string.char(96 + i))), pandoc.Space(), }) end local name_inlines = type(affil.name) == "table" and affil.name or { pandoc.Str(tostring(affil.name)) } local city_inlines = type(affil.city) == "table" and affil.city or { pandoc.Str(tostring(affil.city)) } local postcode_inlines = type(affil["postal-code"]) == "table" and affil["postal-code"] or { pandoc.Str(tostring(affil["postal-code"])) } local country_inlines = type(affil.country) == "table" and affil.country or { pandoc.Str(tostring(affil.country or affil["postal-code"])) } return num_inlines :extend(name_inlines) :extend({ pandoc.Str(", ") }) :extend(city_inlines) :extend({ pandoc.Space() }) :extend(postcode_inlines) :extend({ pandoc.Str(", ") }) :extend(country_inlines) :extend({ pandoc.Str(".") }) end) local combined_inlines = pandoc.List:new() for i, line in ipairs(affil_lines) do combined_inlines:extend(line) if i < #affil_lines then combined_inlines:extend({ pandoc.LineBreak() }) end end return { pandoc.Para(combined_inlines) } end local function create_affiliations_blocks_number(affiliations, meta) local affilstyle = meta and meta.affilstyle and stringify(meta.affilstyle) or "alphabeta" local affil_lines = List:new(affiliations):map(function(affil, i) local num_inlines = pandoc.List:new({ pandoc.Superscript(pandoc.Str(tostring(i))), pandoc.Space(), }) local name_inlines = type(affil.name) == "table" and affil.name or { pandoc.Str(tostring(affil.name)) } local city_inlines = type(affil.city) == "table" and affil.city or { pandoc.Str(tostring(affil.city)) } local postcode_inlines = type(affil["postal-code"]) == "table" and affil["postal-code"] or { pandoc.Str(tostring(affil["postal-code"])) } local country_inlines = type(affil.country) == "table" and affil.country or { pandoc.Str(tostring(affil.country or affil["postal-code"])) } return num_inlines :extend(name_inlines) :extend({ pandoc.Str(", ") }) :extend(city_inlines) :extend({ pandoc.Space() }) :extend(postcode_inlines) :extend({ pandoc.Str(", ") }) :extend(country_inlines) :extend({ pandoc.Str(".") }) end) local combined_inlines = pandoc.List:new() for i, line in ipairs(affil_lines) do combined_inlines:extend(line) if i < #affil_lines then combined_inlines:extend({ pandoc.LineBreak() }) end end return { pandoc.Para(combined_inlines) } end M.create_affiliations_blocks = create_affiliations_blocks_alphabeta function Meta(meta) local affilstyle = meta and meta.affilstyle and stringify(meta.affilstyle) or "alphabeta" M.create_affiliations_blocks = affilstyle == "number" and create_affiliations_blocks_number or create_affiliations_blocks_alphabeta if meta.authors then meta.author = create_authors_inlines(meta.authors, M.default_marks, meta) end if meta.affiliations then meta.institute = M.create_affiliations_blocks(meta.affiliations, meta) end if meta.authors then local equal_contributors = create_equal_contributors_block(meta.authors, M.default_marks) if equal_contributors then meta["equal-contributors"] = equal_contributors end local correspondence = create_correspondence_blocks(meta.authors, M.default_marks) if correspondence then meta.correspondence = correspondence end end return meta end return M