-- Handle frontmatter stuff for .docx, html, and typst formats if FORMAT:match 'latex' then return end local andreplacement = "and" local List = require 'pandoc.List' local utilsapa = require("utilsapa") local stringify = utilsapa.stringify local function get_and(m) if m.language and m.language["citation-last-author-separator"] then andreplacement = stringify(m.language["citation-last-author-separator"]) end end ---http://lua-users.org/wiki/StringRecipes local function ends_with(str, ending) return string.sub(str.text, -1) == ending end local function file_exists(name) local f = io.open(name, 'r') if f ~= nil then io.close(f) return true else return false end end -- Check if meta is present or if it has length of 0 local function chkmeta(meta_item) ispresent = false if meta_item then if #meta_item > 0 then ispresent = true end end return ispresent end -- Convert list to string with oxford comma local function oxfordcommalister(lists) local lastsep = pandoc.Str(", " .. andreplacement .. " ") local sep = pandoc.Str(", ") if #lists == 2 then lastsep = pandoc.Str(" " .. andreplacement .. " ") end local result = List:new {} for i, v in ipairs(lists) do if i > 1 then if i == #lists then result:extend(List:new { lastsep }) else result:extend(List:new { sep }) end end result:extend(List:new { v }) end return result end local get_author_paragraph = function(authors, different) local authordisplay = List:new {} local superii = "" local sep = ", " for i, a in ipairs(authors) do if i == 1 then sep = "" elseif i == #authors then if i == 2 then sep = " " .. andreplacement .. " " else sep = ", " .. andreplacement .. " " end else sep = ", " end authordisplay:extend({ pandoc.Str(sep .. stringify(a.apaauthordisplay)) }) superii = "" if a.affiliations then for j, aff in ipairs(a.affiliations) do if j > 1 then superii = superii .. "," end superii = superii .. aff.number end end if different then authordisplay:extend({ pandoc.Superscript(superii) }) end end return pandoc.Para(authordisplay) end local extend_paragraph = function(para, meta_item, sep) sep = sep or pandoc.Space() if meta_item and #meta_item > 0 then if #para.content > 1 then para.content:extend({ sep }) end para.content:extend(meta_item) end return para end return { { Meta = get_and }, { Pandoc = function(doc) local body = List:new {} local meta = doc.meta local documenttitle = "" local intabovetitle = 2 local newline = pandoc.LineBreak() if FORMAT:match 'docx' then newline = pandoc.SoftBreak() end if meta.apatitledisplay then if meta["blank-lines-above-title"] and #meta["blank-lines-above-title"] > 0 then local possiblenumber = stringify(meta["blank-lines-above-title"]) if type(possiblenumber) == "number" then local intnumber = tonumber(possiblenumber) * 1 intabovetitle = math.floor(intnumber) or 2 else intabovetitle = 2 end end for i = 1, intabovetitle do body:extend({ newline }) end documenttitle = pandoc.Header(1, meta.apatitledisplay) documenttitle.classes = { "title", "unnumbered", "unlisted" } documenttitle.identifier = "title" if not meta["suppress-title"] then body:extend({ documenttitle }) end end local byauthor = meta["by-author"] local affiliations = meta["affiliations"] local authornote = false if meta["author-note"] and not meta["suppress-author-note"] then authornote = meta["author-note"] end local mask = false if meta["mask"] and stringify(meta["mask"]) == "true" then mask = true end local affilations_different = meta.affiliationsdifferent if meta["suppress-affiliation"] then affilations_different = false end local affiliations_str = List() local authordiv = pandoc.Div({}) if not meta["suppress-author"] and byauthor then authordiv = pandoc.Div({ newline, get_author_paragraph(byauthor, affilations_different) }) end authordiv.classes:insert("Author") if affiliations then if byauthor then for i, a in ipairs(affiliations) do affiliations_str = List() mysep = pandoc.Str("") if affilations_different and not meta["suppress-author"] then affiliations_str:extend({ pandoc.Superscript(stringify(a.number)) }) end if chkmeta(a.group) then affiliations_str:extend(a.group) mysep = pandoc.Str(", ") end if chkmeta(a.department) then affiliations_str:extend({ mysep }) affiliations_str:extend(a.department) mysep = pandoc.Str(", ") end if chkmeta(a.name) then affiliations_str:extend({ mysep }) affiliations_str:extend(a.name) mysep = pandoc.Str(", ") end if not (chkmeta(a.group) or chkmeta(a.department) or chkmeta(a.name)) then mysep = pandoc.Str("") if chkmeta(a.city) then affiliations_str:extend(a.city) mysep = pandoc.Str(", ") end if chkmeta(a.region) then affiliations_str:extend({ mysep }) affiliations_str:extend(a.region) end end if not meta["suppress-affiliation"] then authordiv.content:extend({ pandoc.Para(pandoc.Inlines(affiliations_str)) }) end end end end if not mask then body:extend({ authordiv }) end if meta["draft-date"] then draftdate = os.date("%B %d, %Y") if type(meta["draft-date"]) == "table" then draftdate = meta["draft-date"] end draftdatediv = pandoc.Div({ pandoc.Para(draftdate) }) draftdatediv.classes:insert("Author") body:extend({ draftdatediv }) end local authornoteheadertext = "Author Note" if meta.language and meta.language["title-block-author-note"] then authornoteheadertext = meta.language["title-block-author-note"] end local emailword = "Email" if meta.language and meta.language.email then emailword = stringify(meta.language.email) end local authornoteheader = pandoc.Header(1, authornoteheadertext) authornoteheader.classes = { "unnumbered", "unlisted", "AuthorNote" } authornoteheader.identifier = "author-note" local intabovenote = 2 local possiblenumber = 2 if not mask and not meta["suppress-author-note"] and (byauthor or authornote) then if authornote then if authornote["blank-lines-above-author-note"] and #authornote["blank-lines-above-author-note"] > 0 then possiblenumber = stringify(authornote["blank-lines-above-author-note"]) intabovenote = math.floor(tonumber(possiblenumber)) or 2 end end if meta["blank-lines-above-author-note"] and #meta["blank-lines-above-author-note"] > 0 then possiblenumber = stringify(meta["blank-lines-above-author-note"]) intabovenote = math.floor(tonumber(possiblenumber)) or 2 end for i = 1, intabovenote do body:extend({ newline }) end body:extend({ authornoteheader }) end local img if byauthor then for i, a in ipairs(byauthor) do if a.orcid then local orcidfile = "_extensions/wjschne/apaquarto/ORCID-iD_icon-vector.svg" if not file_exists(orcidfile) then orcidfile = "_extensions/apaquarto/ORCID-iD_icon-vector.svg" end img = pandoc.Image("Orcid ID Logo: A green circle with white letters ID", orcidfile) img.attr = pandoc.Attr('orcid', { 'img-fluid' }, { width = '4.23mm' }) pp = pandoc.Para(pandoc.Str("")) pp.content:extend(a.apaauthordisplay) pp.content:extend({ pandoc.Space(), img }) pp.content:extend({ pandoc.Space(), pandoc.Link("https://orcid.org/" .. stringify(a.orcid), "https://orcid.org/" .. stringify(a.orcid)) }) if not mask and not meta["suppress-orcid"] then body:extend({ pp }) end end end end if authornote then if authornote["status-changes"] then local second_paragraph = pandoc.Para(pandoc.Str("")) second_paragraph = extend_paragraph(second_paragraph, authornote["status-changes"]["affiliation-change"] or authornote["affiliation-change"] or meta["affiliation-change"]) second_paragraph = extend_paragraph(second_paragraph, authornote["status-changes"].deceased or authornote.deceased or meta.deceased) if #second_paragraph.content > 1 then if not mask and not meta["suppress-status-change-paragraph"] then body:extend({ second_paragraph }) end end end if authornote.disclosures then local third_paragraph = pandoc.Para(pandoc.Str("")) third_paragraph = extend_paragraph(third_paragraph, authornote.disclosures["study-registration"] or authornote["study-registration"] or meta["study-registration"]) third_paragraph = extend_paragraph(third_paragraph, authornote.disclosures["data-sharing"] or authornote["data-sharing"] or meta["data-sharing"]) third_paragraph = extend_paragraph(third_paragraph, authornote.disclosures["related-report"] or authornote["related-report"] or meta["related-report"]) third_paragraph = extend_paragraph(third_paragraph, authornote.disclosures["conflict-of-interest"] or authornote["conflict-of-interest"] or meta["conflict-of-interest"]) third_paragraph = extend_paragraph(third_paragraph, authornote.disclosures["financial-support"] or authornote["financial-support"] or meta["financial-support"]) third_paragraph = extend_paragraph(third_paragraph, authornote.disclosures.gratitude or authornote.gratitude or meta.gratitude) third_paragraph = extend_paragraph(third_paragraph, authornote.disclosures["authorship-agreements"] or authornote["authorship-agreements"] or meta["authorship-agreements"]) if #third_paragraph.content > 1 then if not mask and not meta["suppress-disclosures-paragraph"] then body:extend({ third_paragraph }) end end end end local credit_paragraph = pandoc.Para(pandoc.Str("")) if byauthor then for i, a in ipairs(byauthor) do if a.roles then credit_paragraph = extend_paragraph(credit_paragraph, { pandoc.Emph(a.apaauthordisplay) }, pandoc.Str(". ")) credit_paragraph.content:extend({ pandoc.Strong(pandoc.Str(": ")) }) local rolelist = {} for j, role in ipairs(a.roles) do if role.role == "Writing - original draft" or role.role == "writing - original draft" then role["vocab-term"] = "writing – original draft" end if role.role == "Writing - reviewing & editing" or role.role == "writing - reviewing & editing" then role["vocab-term"] = "Writing – reviewing & editing" end if role["vocab-term"] then role.display = role["vocab-term"] else role.display = role.role end if role["degree-of-contribution"] then role.display = role.display .. " (" .. role["degree-of-contribution"] .. ")" end table.insert(rolelist, pandoc.Str(role.display)) end credit_paragraph.content:extend(oxfordcommalister(rolelist)) end end end if #credit_paragraph.content > 1 then local authorroleintroduction = pandoc.Str( "Author roles were classified using the Contributor Role Taxonomy (CRediT; https://credit.niso.org/) as follows:") if meta.language and meta.language["title-block-role-introduction"] then authorroleintroduction = meta.language["title-block-role-introduction"] if type(authorroleintroduction) == "string" then authorroleintroduction = pandoc.Inlines(authorroleintroduction) end end credit_paragraph.content:insert(1, pandoc.Space()) for i, j in pairs(authorroleintroduction) do credit_paragraph.content:insert(i, j) end if not mask and not meta["suppress-credit-statement"] then body:extend({ credit_paragraph }) end end local corresponding_paragraph = pandoc.Para(pandoc.Str("")) local check_corresponding = false if meta["author-note"] and meta["author-note"]["correspondence-note"] then corresponding_paragraph.content:extend(meta["author-note"]["correspondence-note"]) else if byauthor then for i, a in ipairs(byauthor) do if a.attributes then if a.attributes.corresponding and stringify(a.attributes.corresponding) == "true" then if check_corresponding then error("There can only be one author marked as the corresponding author. " .. stringify(a.apaauthordisplay) .. " is the second author you have marked as the corresponding author.") end check_corresponding = true corresponding_paragraph.content:extend(a.apaauthordisplay) if a.affiliations then local address = a.affiliations[1] if not meta["suppress-corresponding-group"] then corresponding_paragraph = extend_paragraph(corresponding_paragraph, address.group, pandoc.Str(", ")) end if not meta["suppress-corresponding-department"] then corresponding_paragraph = extend_paragraph(corresponding_paragraph, address.department, pandoc.Str(", ")) end if not meta["suppress-corresponding-affiliation-name"] then corresponding_paragraph = extend_paragraph(corresponding_paragraph, address.name, pandoc.Str(", ")) end if not meta["suppress-corresponding-address"] then corresponding_paragraph = extend_paragraph(corresponding_paragraph, address.address, pandoc.Str(", ")) end if not meta["suppress-corresponding-city"] then corresponding_paragraph = extend_paragraph(corresponding_paragraph, address.city, pandoc.Str(", ")) end if not meta["suppress-corresponding-region"] then corresponding_paragraph = extend_paragraph(corresponding_paragraph, address.region, pandoc.Str(", ")) end if not meta["suppress-corresponding-postal-code"] then corresponding_paragraph = extend_paragraph(corresponding_paragraph, address["postal-code"]) end if not meta["suppress-corresponding-country"] then corresponding_paragraph = extend_paragraph(corresponding_paragraph, address.country, pandoc.Str(", ")) end if not meta["suppress-corresponding-email"] then if a.email then corresponding_paragraph.content:extend({ pandoc.Str(", " .. emailword .. ":") }) corresponding_paragraph = extend_paragraph(corresponding_paragraph, { pandoc.Link(stringify(a.email), "mailto:" .. stringify(a.email)) }) end end end end end end end if #corresponding_paragraph.content > 1 then local correspondencenote = pandoc.Str("Correspondence concerning this article should be addressed to ") if meta.language and meta.language["title-block-correspondence-note"] then correspondencenote = meta.language["title-block-correspondence-note"] if type(correspondencenote) == "string" then correspondencenote = pandoc.Inlines(correspondencenote) end end corresponding_paragraph.content:insert(1, pandoc.Space()) for i, j in pairs(correspondencenote) do corresponding_paragraph.content:insert(i, j) end end end if (not mask) and (not meta["suppress-corresponding-paragraph"]) then body:extend({ corresponding_paragraph }) end if meta.apaabstract and #meta.apaabstract > 0 and not meta["suppress-abstract"] then local abstractheadertext = pandoc.Str("Abstract") if meta.language and meta.language["section-title-abstract"] then abstractheadertext = meta.language["section-title-abstract"] end local abstractheader = pandoc.Header(1, abstractheadertext) abstractheader.classes = { "unnumbered", "unlisted", "AuthorNote" } abstractheader.identifier = "abstract" if FORMAT:match 'docx' then body:extend({ pandoc.RawBlock('openxml', '') }) end if FORMAT:match 'typst' then body:extend({ pandoc.RawBlock('typst', '#pagebreak()\n\n') }) end if FORMAT:match 'html' then body:extend({ pandoc.RawBlock('html', '
') }) end body:extend({ abstractheader }) local abstract_paragraph = pandoc.Para(pandoc.Str("")) if pandoc.utils.type(meta.apaabstract) == "Inlines" then abstract_paragraph.content:extend(meta.apaabstract or meta.abstract) local abstractdiv = pandoc.Div(abstract_paragraph) abstractdiv.classes:insert("AbstractFirstParagraph") body:extend({ abstractdiv }) end if pandoc.utils.type(meta.apaabstract) == "Blocks" then local abstractdiv = pandoc.Div({}) local abstractfirstparagraphdiv = pandoc.Div({}) local abstractlinecounter = 1 if FORMAT == "typst" then abstractlinecounter = 2 end meta.apaabstract:walk { LineBlock = function(lb) lb:walk { traverse = "topdown", Inlines = function(el) local lbpara = pandoc.Para(el) if abstractlinecounter == 1 then abstractfirstparagraphdiv.content:extend({ lbpara }) abstractfirstparagraphdiv.classes:insert("AbstractFirstParagraph") else abstractdiv.content:extend({ lbpara }) if abstractlinecounter == 2 then abstractdiv.classes:insert("Abstract") end end abstractlinecounter = abstractlinecounter + 1 return el, false end } end } if abstractlinecounter > 1 then body:extend({ abstractfirstparagraphdiv }) end if abstractlinecounter > 2 then body:extend({ abstractdiv }) end end end if meta["impact-statement"] and #meta["impact-statement"] > 0 and not meta["suppress-impact-statement"] then local impactheadertext = pandoc.Str("Impact Statement") if meta.language and meta.language["title-impact-statement"] then impactheadertext = meta.language["title-impact-statement"] end local impactheader = pandoc.Header(1, impactheadertext) impactheader.classes = { "unnumbered", "unlisted", "AuthorNote" } impactheader.identifier = "impact" body:extend({ impactheader }) local impact_paragraph = pandoc.Para(pandoc.Str("")) if pandoc.utils.type(meta["impact-statement"]) == "Inlines" then impact_paragraph.content:extend(meta["impact-statement"]) local impactdiv = pandoc.Div(impact_paragraph) impactdiv.classes:insert("AbstractFirstParagraph") body:extend({ impactdiv }) end end if meta.keywords and not meta["suppress-keywords"] then local keywordsword = pandoc.Str("Keywords") if meta.language and meta.language["title-block-keywords"] then keywordsword = stringify(meta.language["title-block-keywords"]) end local keywords_paragraph = pandoc.Para({ pandoc.Emph(keywordsword), pandoc.Str(":") }) if pandoc.utils.type(meta.keywords) == "Inlines" then keywords_paragraph.content:extend(meta.keywords) else for i, k in ipairs(meta.keywords) do if i == 1 then keywords_paragraph = extend_paragraph(keywords_paragraph, k) else keywords_paragraph = extend_paragraph(keywords_paragraph, k, pandoc.Str(", ")) end end end body:extend({ keywords_paragraph }) end if meta["word-count"] then local word_count_word = "Word Count" if meta.language and meta.language["title-block-word-count"] then word_count_word = stringify(meta.language["title-block-word-count"]) end local word_count_paragraph = pandoc.Para({ pandoc.Emph(word_count_word), pandoc.Str(": " .. meta.wordn) }) body:extend({ word_count_paragraph }) end if FORMAT:match 'docx' then body:extend({ pandoc.RawBlock('openxml', '') }) end if FORMAT:match 'typst' then local pg = '#pagebreak()\n\n' if meta['first-page'] then pg = '#counter(page).update(' .. stringify(meta['first-page']) .. ' - 1)\n #pagebreak()\n\n' end body:extend({ pandoc.RawBlock('typst', pg) }) end if FORMAT:match 'html' then body:extend({ pandoc.RawBlock('html', '
') }) end local myshorttitle = "" if meta["apatitle"] then myshorttitle = meta["apatitle"] end if meta["shorttitle"] and #meta["shorttitle"] > 0 then myshorttitle = meta["shorttitle"] end for i, v in ipairs(myshorttitle) do if v.t == "Str" then v.text = pandoc.text.upper(v.text) end end if not meta["suppress-short-title"] then meta.description = myshorttitle else meta.description = " " end if meta["suppress-title-page"] then body = List:new {} end if FORMAT:match 'typst' and PANDOC_WRITER_OPTIONS["table_of_contents"] then body:extend({ pandoc.RawBlock('typst', '\n\n#show outline.entry: it => {show link: set text(fill: black)\nlink(it.element.location(),it.indented(none, it.inner(), ))}\n\n#outline(title: [Table of Contents], indent: 1.5em)\n\n') }) body:extend({ pandoc.RawBlock('typst', '#pagebreak()\n\n') }) end if FORMAT:match 'typst' and meta["list-of-figures"] then body:extend({ pandoc.RawBlock('typst', '\n\n#outline(title: [List of Figures], target: figure.where(kind: "quarto-float-fig"),)\n\n') }) body:extend({ pandoc.RawBlock('typst', '#pagebreak()\n\n') }) end if FORMAT:match 'typst' and meta["list-of-tables"] then body:extend({ pandoc.RawBlock('typst', '\n\n#outline(title: [List of Tables], target: figure.where(kind: "quarto-float-tbl"),)\n\n') }) body:extend({ pandoc.RawBlock('typst', '#pagebreak()\n\n') }) end if meta.apatitledisplay and not meta["suppress-title-introduction"] then local firstpageheader = documenttitle:clone() firstpageheader.identifier = "firstheader" firstpageheader.classes = { "title", "unnumbered" } body:extend({ firstpageheader }) end body:extend(doc.blocks) return pandoc.Pandoc(body, meta) end } }