0byt3m1n1
Path:
C:
/
Program Files
/
RStudio
/
resources
/
app
/
bin
/
quarto
/
share
/
filters
/
modules
/
[
Home
]
File: authors.lua
-- meta.lua -- Copyright (C) 2020-2022 Posit Software, PBC -- read and replace the authors field -- without reshaped data that has been -- restructured into the standard author -- format local kAuthorInput = 'authors' -- we ensure that if 'institute' is specified, we normalize it into -- and array in this value, which this can safely read and process local kInstituteInput = 'institutes' -- By default, simply replace the input structure with the -- normalized versions of the output local kAuthorOutput = kAuthorInput -- Where we'll write the normalized list of affiliations local kAffiliationOutput = "affiliations" -- Where we'll write the normalized list of funding local kFundingOutput = "funding" -- Where we'll write the 'by-author' list of authors which -- includes expanded affiliation information inline with the author local kByAuthor = "by-author" -- Where we'll write the 'by-affiliation' list of affiliations which -- includes expanded author information inline with each affiliation local kByAffiliation = "by-affiliation" local kAuthors = "authors" -- Funding placeholders -- Normalized into: -- funding: -- - statement: -- open-access: string (optional) -- awards: -- - id: string (optional) -- name: string (optional) -- description: string (optional) -- source: -- - string | { country, type } & text | { institution } -- investigator: -- - { text } | { name } -- recipient: -- - { text } | { name } | { institution } -- add support for instition DOI in addition to ringgold/isni, etc... local kFunding = "funding" local kAwards = "awards" local kAwardId = "id" local kAwardName = "name" local kAwardDesc = "description" local kSource = "source" local kSourceType = "type" local kStatement = "statement" local kOpenAccess = "open-access" local kRecipient = "recipient" local kInvestigator = "investigator" -- Properties that may appear on an individual author local kId = 'id' local kName = 'name' local kUrl = 'url' local kEmail = 'email' local kFax = 'fax' local kPhone = 'phone' local kOrcid = 'orcid' local kNote = 'note' local kAcknowledgements = 'acknowledgements' local kAffiliations = 'affiliations' local kAffiliation = 'affiliation' local kRef = 'ref' -- attributes hold a list of strings which -- represent true characteristics of the author -- (for example, that they are the corresponding author) -- the presence of a value means that it is true, the -- absence of a value means that it is false -- -- users can either write -- attributes: [correspoding, is-equal-contributor] -- or if attributes with these names are present (and truthy) -- on the author they will be collected into attributes. -- For example- -- author: -- name: John Hamm -- corresponding: true -- is-equal-contributor: true local kAttributes = 'attributes' -- flag values for attributes (attributes is a list of -- flag names) local kCorresponding = 'corresponding' local kEqualContributor = 'equal-contributor' local kDeceased = 'deceased' -- metadata holds options that appear in the author key -- that are not common to our author schema. we would like -- to generally discourage this type of data since -- it will be difficult to reliably share across templates and -- author representations, so we bucketize it here to -- suggest to users that this is 'other' data local kMetadata = 'metadata' -- a name which will be structured into a name object that -- look like: -- name: -- family: -- given: -- literal: -- We can accept a literal string (which we parse to get the family and given) -- or a structured object that declares all or some of the options directly local kGivenName = 'given' local kFamilyName = 'family' local kLiteralName = 'literal' local kDroppingParticle = 'dropping-particle' local kNonDroppingParticle = 'non-dropping-particle' local kNameFields = { kGivenName, kFamilyName, kLiteralName} -- Academic titles or professional certifications displayed following a personal name (for example, “MD”, “PhD”). local kDegrees = 'degrees' -- the roles that an author may place local kRoles = 'roles' local kRole = "role" local kDegreeContribution = "degree-of-contribution" local kAuthorRoleFields = { kRole, kRoles } -- CreDitT schema and definitions local kWritingOriginal = "writing – original draft" local kWritingEditing = "writing – review & editing" local kFormalAnalysis = "formal analysis" local kFundingAcquisition = "funding acquisition" local kConceptualization = "conceptualization" local kDataCuration = "data curation" local kInvestigation = "investigation" local kMethodology = "methodology" local kProjectAdmin = "project administration" local kResources = "resources" local kSoftware = "software" local kSupervision = "supervision" local kValidation = "validation" local kVisualization = "visualization" local kCreditRoles = {kConceptualization, kDataCuration, kFormalAnalysis, kFundingAcquisition, kInvestigation, kMethodology, kProjectAdmin, kResources, kSoftware, kSupervision, kValidation, kVisualization, kWritingOriginal, kWritingEditing} local kCreditAliases = { ["writing"] = kWritingOriginal, ["analysis"] = kFormalAnalysis, ["funding"] = kFundingAcquisition, ["editing"] = kWritingEditing } local kVocabIdentifier = "vocab-identifier" local kVocabTerm = "vocab-term" local kVocabTermIdentifier="vocab-term-identifier" local kCreditVocabIdentifier = "https://credit.niso.org" local kCreditVocabTermIdentifiers = { [kConceptualization] = "https://credit.niso.org/contributor-roles/conceptualization/", [kDataCuration] = "https://credit.niso.org/contributor-roles/data-curation/", [kFormalAnalysis] = "https://credit.niso.org/contributor-roles/formal-analysis/", [kFundingAcquisition] = "https://credit.niso.org/contributor-roles/funding-acquisition/", [kInvestigation] = "https://credit.niso.org/contributor-roles/investigation/", [kMethodology] = "https://credit.niso.org/contributor-roles/methodology/", [kProjectAdmin] = "https://credit.niso.org/contributor-roles/project-administration/", [kResources] = "https://credit.niso.org/contributor-roles/resources/", [kSoftware] = "https://credit.niso.org/contributor-roles/software/", [kSupervision] = "https://credit.niso.org/contributor-roles/supervision/", [kValidation] = "https://credit.niso.org/contributor-roles/validation/", [kVisualization] = "https://credit.niso.org/contributor-roles/visualization/", [kWritingOriginal] = "https://credit.niso.org/contributor-roles/writing-original-draft/", [kWritingEditing] = "https://credit.niso.org/contributor-roles/writing-review-editing/" } -- an affiliation which will be structured into a standalone local kAffilName = 'name' local kDepartment = 'department' local kGroup = 'group' local kAddress = 'address' local kCity = 'city' local kRegion = 'region' local kState = 'state' local kCountry = 'country' local kPostalCode = 'postal-code' local kISNI = "isni" local kRinggold = "ringgold" local kROR = "ror" -- labels contains the suggested labels for the various elements which -- are localized and should correctly deal with plurals, etc... local kLabels = 'labels' local kAuthorLbl = 'authors' local kAffiliationLbl = 'affiliations' local kPublishedLbl = 'published' local kModifiedLbl = 'modified' local kDoiLbl = 'doi' local kDescriptionLbl = 'description' local kAbstractLbl = 'abstract' local kKeywordsLbl = 'keywords' local kRelatedFormats = 'related_formats' -- affiliation fields that might be parsed into other fields -- (e.g. if we see affiliation-url with author, we make that affiliation/url) local kAffiliationUrl = 'affiliation-url' -- Titles are the values that we will accept in metadata to override the -- default value for the above labels (e.g. abstract-title will provide the label) -- for the abstract local kAuthorTitle = 'author-title' local kAffiliationTitle = 'affiliation-title' local kAbstractTitle = 'abstract-title' local kDescriptionTitle = 'description-title' local kPublishedTitle = 'published-title' local kModifiedTitle = 'modified-title' local kDoiTitle = 'doi-title' -- Deal with bibliography configuration as well local kBiblioConfig = 'biblio-config' -- The field types for an author (maps the field in an author table) -- to the way the field should be processed local kAuthorNameFields = { kName } local kAuthorNameLiteralFields = { kFamilyName, kGivenName, kDroppingParticle, kNonDroppingParticle } local kAuthorSimpleFields = { kId, kUrl, kEmail, kFax, kPhone, kOrcid, kAcknowledgements } local kAuthorAttributeFields = { kCorresponding, kEqualContributor, kDeceased } local kAuthorAffiliationFields = { kAffiliation, kAffiliations } -- Fields for affiliations (either inline in authors or -- separately in a affiliations key) local kAffiliationFields = { kId, kAffilName, kDepartment, kGroup, kAddress, kCity, kRegion, kCountry, kPostalCode, kUrl, kISNI, kRinggold, kROR } -- These affiliation fields will be mapped into 'region' -- (so users may also write 'state') local kAffiliationAliasedFields = { [kState]=kRegion, [kAffiliationUrl]=kUrl } -- This field will be included with 'by-author' and 'by-affiliation' and provides -- a simple incremental counter that can be used for things like note numbers local kNumber = "number" local kLetter = "letter" -- Remove Spaces from the ends of tables local function trimspace(tbl) if #tbl > 0 then if tbl[1].t == 'Space' then tbl = tslice(tbl, 2) end end if #tbl > 0 then if tbl[#tbl].t == 'Space' then tbl = tslice(tbl, #tbl -1) end end return tbl end -- Deep Copy a table local function deepCopy(original) local copy = {} for k, v in pairs(original) do if type(v) == "table" then v = deepCopy(v) end copy[k] = v end return copy end -- Finds a matching affiliation by looking through a list -- of affiliations (ignoring the id) local function findMatchingAffililation(affiliation, affiliations) for i, existingAffiliation in ipairs(affiliations) do -- an affiliation matches if the fields other than id -- are identical local matches = true for j, field in ipairs(kAffiliationFields) do if field ~= kId and matches then matches = affiliation[field] == existingAffiliation[field] end end -- This affiliation matches, return it if matches then return existingAffiliation end end return nil end -- Add an affiliation to the list of affiliations if needed -- and return either the exist affiliation, or the newly -- added affiliation with a proper id local function maybeAddAffiliation(affiliation, affiliations) local existingAff = findMatchingAffililation(affiliation, affiliations) if existingAff == nil then local affiliationNumber = #affiliations + 1 local affiliationId = 'aff-' .. affiliationNumber if affiliation[kId] == nil then affiliation[kId] = { pandoc.Str(affiliationId) } end affiliations[affiliationNumber] = affiliation return affiliation else return existingAff end end local function validateRefs(authors, affiliations) -- iterate through affiliations and ensure that anything -- referenced by an author has a peer affiliation -- get the list of affiliation ids local affilIds = {} if affiliations then for i,affiliation in ipairs(affiliations) do affilIds[#affilIds + 1] = affiliation[kId] end end -- go through each author and their affiliations and -- ensure that they are in the list for i,author in ipairs(authors) do if author[kAffiliations] then for i,affiliation in ipairs(author[kAffiliations]) do if not tcontains(affilIds, affiliation[kRef]) then fail("Undefined affiliation '" .. pandoc.utils.stringify(affiliation[kRef]) .. "' for author '" .. pandoc.utils.stringify(author[kName][kLiteralName]) .. "'.") end end end end end -- Finds a matching author by author id local function findAuthor(id, authors) for i, author in ipairs(authors) do if pandoc.utils.stringify(author[kId]) == id then return author end end return nil end -- Finds a matching affiliation by id local function findAffiliation(id, affiliations) for i, affiliation in ipairs(affiliations) do if affiliation[kId][1].text == id[1].text then return affiliation end end return nil end -- Normalizes an affilation object into the properly -- structured form local function processAffilationObj(affiliation) local affiliationNormalized = {} affiliationNormalized[kMetadata] = {} for affilKey, affilVal in pairs(affiliation) do if (tcontains(tkeys(kAffiliationAliasedFields), affilKey)) then affiliationNormalized[kAffiliationAliasedFields[affilKey]] = affilVal elseif tcontains(kAffiliationFields, affilKey) then affiliationNormalized[affilKey] = affilVal else affiliationNormalized[kMetadata][affilKey] = affilVal end end return affiliationNormalized; end local kBibtexNameTemplate = [[ @misc{x, author = {%s} } ]] --- Returns a CSLJSON-like name table. BibTeX knows how to parse names, --- so we leverage that. local function bibtexParseName(nameRaw) local bibtex = kBibtexNameTemplate:format(pandoc.utils.stringify(nameRaw)) local references = pandoc.read(bibtex, 'bibtex').meta.references if references then local reference = references[1] --[[@as table<string,any>]] if reference then local authors = reference.author if authors then local name = authors[1] if type(name) ~= 'table' then return nameRaw else -- most dropping particles are really non-dropping if name['dropping-particle'] and not name['non-dropping-particle'] then name['non-dropping-particle'] = name['dropping-particle'] name['dropping-particle'] = nil end return name end else return nameRaw end else return nameRaw end else return nameRaw end end local function createNameLiteral(name) local literalName = {} if name[kFamilyName] and name[kGivenName] then tappend(literalName, name[kGivenName]) tappend(literalName, {pandoc.Space()}) tappend(literalName, name[kFamilyName]) elseif name[kFamilyName] then tappend(literalName, name[kFamilyName]) elseif name[kGivenName] then tappend(literalName, name[kGivenName]) end return literalName end -- normalizes a name value by parsing it into -- family and given names local function normalizeName(name) -- no literal name, create one if name[kLiteralName] == nil then name[kLiteralName] = createNameLiteral(name) end -- no family or given name, parse the literal and create one if name[kFamilyName] == nil or name[kGivenName] == nil then if name[kLiteralName] then local parsedName = bibtexParseName(name) if type(parsedName) == 'table' then if parsedName.given ~= nil then name[kGivenName] = {pandoc.Str(parsedName.given)} end if parsedName.family ~= nil then name[kFamilyName] = {pandoc.Str(parsedName.family)} end if name[kDroppingParticle] ~= nil then name[kDroppingParticle] = parsedName[kDroppingParticle] end if name[kNonDroppingParticle] ~= nil then name[kNonDroppingParticle] = parsedName[kNonDroppingParticle] end else if #name[kLiteralName] > 1 then -- bibtex parsing failed, just split on space name[kGivenName] = name[kLiteralName][1] name[kFamilyName] = trimspace(tslice(name[kLiteralName], 2)) elseif name[kLiteralName] then -- what is this thing, just make it family name name[kFamilyName] = name[kLiteralName] end end end end return name end -- Converts name elements into a structured name local function toName(nameParts) if not tisarray(nameParts) then -- If the name is a table (e.g. already a complex object) -- just pick out the allowed fields and forward local name = {} for i,v in ipairs(kNameFields) do if nameParts[v] ~= nil then name[v] = nameParts[v] end end return normalizeName(name) else if #nameParts == 0 then return {} else return normalizeName({[kLiteralName] = nameParts}) end end end -- Normalizes a value that could be either a plain old markdown string, -- a name, or an affiliation local function processNameOrInstitutionObj(keyName, valueRaw, authors, affiliations, optional) if (pandoc.utils.type(valueRaw) == 'Inlines') then return { text = valueRaw } else if valueRaw.name ~= nil then return { name = toName(valueRaw.name) } elseif valueRaw.institution ~= nil then return { institution = processAffilationObj(valueRaw.institution) } elseif valueRaw.ref ~= nil then local refStr = pandoc.utils.stringify(valueRaw.ref) -- discover the reference (could be author or affiliation) local affiliation = findAffiliation({{ text = refStr }}, affiliations) if affiliation then return { institution = affiliation } else local author = findAuthor(refStr, authors) if author then return { [kName] = author[kName] } else fail("Invalid funding ref " .. refStr) end end else if not optional then fail("Invalid value for " .. keyName) end end end end local function processNameOrInstitution(keyName, values, authors, affiliations, optional) if values ~= nil then local pandocType = pandoc.utils.type(values) if pandocType == "List" then local results = pandoc.List() for i, value in ipairs(values) do local resolved = processNameOrInstitutionObj(keyName, value, authors, affiliations, optional) if resolved ~= nil then results:insert(resolved) end end return results else local resolved = processNameOrInstitutionObj(keyName, values, authors, affiliations, optional) if resolved ~= nil then return { resolved } end end else return {} end end local function processSources(sourceRaw, authors, affiliations) local pandocType = pandoc.utils.type(sourceRaw) if pandocType == 'Inlines' then return {{ text = sourceRaw }} else local result = processNameOrInstitution(kSource, sourceRaw, authors, affiliations, true) for i, key in ipairs({ kCountry, kSourceType }) do local valueRaw = sourceRaw[key] if valueRaw ~= nil then result[key] = valueRaw end end return result end end -- Normalizes an indivudal funding entry local function processFundingGroup(fundingGroup, authors, affiliations) if pandoc.utils.type(fundingGroup) == 'table' then -- awards -- this is a table of properties, process them local result = {} -- statement local statement = fundingGroup[kStatement]; if statement ~= nil then if pandoc.utils.type(statement) == "Block" then result[kStatement] = pandoc.utils.blocks_to_inlines({statement}) elseif pandoc.utils.type(statement) == "Blocks" then result[kStatement] = pandoc.utils.blocks_to_inlines(statement) else result[kStatement] = statement end end -- open-access (must be a block so it emits a p) local openAccess = fundingGroup[kOpenAccess]; if openAccess ~= nil then if pandoc.utils.type(openAccess) == "Inlines" then result[kOpenAccess] = pandoc.Para(openAccess) else result[kOpenAccess] = openAccess end end -- process any awards local awardsRaw = fundingGroup[kAwards]; if pandoc.utils.type(awardsRaw) ~= "List" then awardsRaw = pandoc.List({awardsRaw}) end local awards = pandoc.List({}) for i, awardRaw in ipairs(awardsRaw) do local award = {}; -- award-id, name, description for i, key in ipairs({ kAwardId, kAwardName, kAwardDesc }) do local valueRaw = awardRaw[key] if valueRaw ~= nil then award[key] = valueRaw end end -- Process the funding source local sourceRaw = awardRaw[kSource] if sourceRaw ~= nil then award[kSource] = processSources(sourceRaw, authors, affiliations) end -- Process recipients local recipientRaw = awardRaw[kRecipient] if recipientRaw ~= nil then award[kRecipient] = processNameOrInstitution(kRecipient, recipientRaw, authors, affiliations) end local investigatorRaw = awardRaw[kInvestigator] if investigatorRaw ~= nil then award[kInvestigator] = processNameOrInstitution(kInvestigator, investigatorRaw, authors, affiliations) end awards:insert(award) end if next(awards) ~= nil then result[kAwards] = awards end return result else -- this is a simple string / inlines, just -- use it as the source return { [kStatement] = fundingGroup } end end -- Replaces an affiliation reference with a different id -- (for example, if a reference to an affiliation is collapsed into a single -- entry with a single id) local function remapAuthorAffiliations(fromId, toId, authors) for i, author in ipairs(authors) do for j, affiliation in ipairs(author[kAffiliations]) do local existingRefId = affiliation[kRef] if existingRefId == fromId then affiliation[kRef] = toId end end end end -- Sets an attribute, initializeing the table if -- is not yet defined local function setAttribute(author, attribute) if not author[kAttributes] then author[kAttributes] = pandoc.MetaMap({}) end local attrStr = pandoc.utils.stringify(attribute) -- Don't duplicate attributes if not author[kAttributes][attrStr] then author[kAttributes][attrStr] = pandoc.Str('true') end end -- Process attributes onto an author -- attributes may be a simple string, a list of strings -- or a dictionary local function processAttributes(author, attributes) if tisarray(attributes) then -- process attributes as an array of values for i,v in ipairs(attributes) do if v then if v.t == "Str" then setAttribute(author, v) else for j, attr in ipairs(v) do setAttribute(author, attr) end end end end else -- process attributes as a dictionary for k,v in pairs(attributes) do if v then setAttribute(author, pandoc.Str(k)) end end end end -- Process an author note (including numbering it) local noteNumber = 1 local function processAuthorNote(author, note) author[kNote] = { number=noteNumber, text=note } noteNumber = noteNumber + 1 end local function setRole(author, role) if not author[kRoles] then author[kRoles] = {} end -- resolve shorthands local term = string.lower(role.role) local alias = kCreditAliases[term] if alias then term = alias end -- if this is a CreDiT Role then -- attach vocabulary decorations if tcontains(kCreditRoles, term) then role[kVocabIdentifier] = kCreditVocabIdentifier role[kVocabTerm] = term role[kVocabTermIdentifier] = kCreditVocabTermIdentifiers[term] end author[kRoles][#author[kRoles] + 1] = role end local function parseRole(role) if pandoc.utils.type(role) == 'Inlines' or pandoc.utils.type(role) == 'Inline' then -- this is just a simple string representing the role return pandoc.utils.stringify(role) else for k,v in pairs(role) do return pandoc.utils.stringify(k), pandoc.utils.stringify(v) end end end local function setDegree(author, degree) if not author[kDegrees] then author[kDegrees] = {} end author[kDegrees][#author[kDegrees] + 1] = degree end -- Processes author roles, which can be specified either using `role:` or `roles:` -- and can either be a single string: -- role: researcher -- or an array of strings: -- roles: [researcher, writer] -- or one or role objects with key role, value degree of contribution -- like: -- roles: -- - researcher: lead -- - writer: supporting -- general in support, but derived a bit from -- https://credit.niso.org local function processAuthorRoles(author, roles) if tisarray(roles) then for i,v in ipairs(roles) do local role, contribution = parseRole(v) setRole(author, { [kRole] = role, [kDegreeContribution] = contribution }) end else local role, contribution = parseRole(roles) setRole(author, { [kRole] = role, [kDegreeContribution] = contribution }) end end local function processAuthorDegrees(author, degrees) if tisarray(degrees) then for _i,v in ipairs(degrees) do setDegree(author, v) end else setDegree(author, degrees) end end -- Sets a metadata value, initializing the table if -- it not yet defined local function setMetadata(author, key, value) author[kMetadata][key] = value end local function setAffiliation(author, affiliation) if not author[kAffiliations] then author[kAffiliations] = {} end author[kAffiliations][#author[kAffiliations] + 1] = affiliation end local function byAuthors(authors, affiliations) local denormalizedAuthors = deepCopy(authors) if denormalizedAuthors then for i, author in ipairs(denormalizedAuthors) do denormalizedAuthors[kNumber] = i local authorAffiliations = author[kAffiliations] if authorAffiliations then for j, affilRef in ipairs(authorAffiliations) do local id = affilRef[kRef] author[kAffiliations][j] = findAffiliation(id, affiliations) end end end end return denormalizedAuthors end -- Finds a matching author by affiliation id local function findAffiliationAuthors(id, authors) local matchingAuthors = {} for i, author in ipairs(authors) do local authorAffils = author[kAffiliations] if authorAffils then for j, authorAffil in ipairs(authorAffils) do if authorAffil[kRef][1].text == id[1].text then matchingAuthors[#matchingAuthors + 1] = author end end end end return matchingAuthors end local function byAffiliations(authors, affiliations) local denormalizedAffiliations = deepCopy(affiliations) for i, affiliation in ipairs(denormalizedAffiliations) do local affilAuthor = findAffiliationAuthors(affiliation[kId], authors) if affilAuthor then affiliation[kAuthors] = affilAuthor end end return denormalizedAffiliations end -- Resolve labels for elements into metadata local function computeLabels(authors, affiliations, meta) local language = param("language", nil); if not _quarto.format.isAstOutput() then meta[kLabels] = { [kAuthorLbl] = {pandoc.Str("Authors")}, [kAffiliationLbl] = {pandoc.Str("Affiliations")} } if #authors == 1 then meta[kLabels][kAuthorLbl] = {pandoc.Str(language["title-block-author-single"])} else meta[kLabels][kAuthorLbl] = {pandoc.Str(language["title-block-author-plural"])} end if meta[kAuthorTitle] then meta[kLabels][kAuthors] = meta[kAuthorTitle] end if #affiliations == 1 then meta[kLabels][kAffiliationLbl] = {pandoc.Str(language["title-block-affiliation-single"])} else meta[kLabels][kAffiliationLbl] = {pandoc.Str(language["title-block-affiliation-plural"])} end if meta[kAffiliationTitle] then meta[kLabels][kAffiliationLbl] = meta[kAffiliationTitle] end meta[kLabels][kPublishedLbl] = {pandoc.Str(language["title-block-published"])} if meta[kPublishedTitle] then meta[kLabels][kPublishedLbl] = meta[kPublishedTitle] end meta[kLabels][kModifiedLbl] = {pandoc.Str(language["title-block-modified"])} if meta[kModifiedTitle] then meta[kLabels][kModifiedLbl] = meta[kModifiedTitle] end meta[kLabels][kDoiLbl] = {pandoc.Str("Doi")} if meta[kDoiTitle] then meta[kLabels][kDoiLbl] = meta[kDoiTitle] end meta[kLabels][kAbstractLbl] = {pandoc.Str(language["section-title-abstract"])} if meta[kAbstractTitle] then meta[kLabels][kAbstractLbl] = meta[kAbstractTitle] end meta[kLabels][kDescriptionLbl] = {pandoc.Str(language["listing-page-field-description"])} if meta[kDescriptionTitle] then meta[kLabels][kDescriptionLbl] = meta[kDescriptionTitle] end meta[kLabels][kKeywordsLbl] = {pandoc.Str(language["title-block-keywords"])} if meta[kKeywordsTitle] then meta[kLabels][kKeywordsLbl] = meta[kKeywordsTitle] end meta[kLabels][kRelatedFormats] = {pandoc.Str(language["related-formats-title"])} if meta["related-formats-title"] then meta[kLabels][kRelatedFormats] = meta["related-formats-title"] end end return meta end -- Get a letter for a number local function letter(number) number = number%26 return string.char(96 + number) end -- Processes an affiatiation into a normalized -- affilation local function processAffiliation(author, affiliation) local affiliations = {} local pandocType = pandoc.utils.type(affiliation) if pandocType == 'Inlines' then -- The affiliations is simple a set of inlines, use affiliations[#affiliations + 1] = processAffilationObj({ name=affiliation }) elseif pandocType == 'List' then for i, v in ipairs(affiliation) do if pandoc.utils.type(v) == 'Inlines' then -- This item is just a set inlines, use that as the name affiliations[#affiliations + 1] = processAffilationObj({ name=v }) else local keys = tkeys(v) if keys and #keys == 1 and keys[1] == kRef then -- See if this is just an item with a 'ref', and if it is, just pass -- it through on the author if author then setAffiliation(author, v) end else -- This is a more complex affilation, process it affiliations[#affiliations + 1] = processAffilationObj(v) end end end elseif pandocType == 'table' then -- This is a more complex affilation, process it affiliations[#affiliations + 1] = processAffilationObj(affiliation) end return affiliations end -- Processes an individual author into a normalized author -- and normalized set of affilations local function processAuthor(value) -- initialize the author local author = pandoc.MetaMap({}) author[kMetadata] = pandoc.MetaMap({}) -- initialize their affilations local authorAffiliations = {} local affiliationUrl = nil if pandoc.utils.type(value) == 'Inlines' then -- The value is simply an array, treat them as the author name author.name = toName(value); else -- Process the field into the proper place in the author -- structure local needsNameNormalization = false for authorKey, authorValue in pairs(value) do if tcontains(kAuthorNameFields, authorKey) then -- process any names author[authorKey] = toName(authorValue) elseif tcontains(kAuthorSimpleFields, authorKey) then -- process simple fields author[authorKey] = authorValue elseif tcontains(kAuthorAttributeFields, authorKey) then -- process a field into attributes (a field that appears) -- directly under the author if authorValue then setAttribute(author, pandoc.Str(authorKey)) end elseif authorKey == kAttributes then -- process an explicit attributes key processAttributes(author, authorValue) elseif authorKey == kNote then processAuthorNote(author, authorValue) elseif tcontains(kAuthorAffiliationFields, authorKey) then -- process affiliations that are specified in the author authorAffiliations = processAffiliation(author, authorValue) elseif authorKey == kAffiliationUrl then affiliationUrl = authorValue elseif tcontains(kAuthorRoleFields, authorKey) then processAuthorRoles(author, authorValue) elseif authorKey == kDegrees then processAuthorDegrees(author, authorValue) elseif tcontains(kAuthorNameLiteralFields, authorKey) then -- Normalize literal field names that appear under author into the name author.name = author.name or {} author.name[authorKey] = authorValue needsNameNormalization = true else -- since we don't recognize this value, place it under -- metadata to make it accessible to consumers of this -- data structure setMetadata(author, authorKey, authorValue) end end if needsNameNormalization then if author.name[kLiteralName] == nil then author.name[kLiteralName] = createNameLiteral(author.name) end end end -- If there is an affiliation url, forward that along if authorAffiliations and affiliationUrl then authorAffiliations[1][kUrl] = affiliationUrl end return { author=author, affiliations=authorAffiliations } end local function processAuthorMeta(meta) -- prevents the front matter for markdown from containing -- all the rendered author information that we generate if _quarto.format.isMarkdownOutput() then -- see whether the metadata block will be preserved -- if the YAML is being preserved, this should just no-op and leave -- things as they were local formatId = param("format-identifier", {}) if formatId['target-format'] == "markdown" then -- Don't normalize plain markdown as this preserves -- YAML front matter by default return nil else local parsed_format = _quarto.format.parse_format(formatId['target-format']) if parsed_format['extensions']['yaml_metadata_block'] then return nil end end end -- prefer to render 'authors' if it is available local authorsRaw = meta[kAuthorInput] if meta[kAuthors] then authorsRaw = meta[kAuthors] end -- authors should be a table of tables (e.g. it should be an array of inlines or tables) -- if it isn't, transform it into one if type(authorsRaw) == "table" then if (type(authorsRaw[1]) ~= "table") then authorsRaw = {authorsRaw} end end -- the normalized authors local authors = {} -- the normalized affilations local affiliations = {} if authorsRaw then for i,v in ipairs(authorsRaw) do local authorAndAffiliations = processAuthor(v) -- initialize the author local author = authorAndAffiliations.author local authorAffils = authorAndAffiliations.affiliations -- assign an id to this author if one isn't defined local authorNumber = #authors + 1 if author[kId] == nil then author[kId] = authorNumber end -- go through the affilations and add any to the list -- assigning an id if needed if authorAffils ~= nil then for i,v in ipairs(authorAffils) do local affiliation = maybeAddAffiliation(v, affiliations) setAffiliation(author, { ref=affiliation[kId] }) end end -- add this author to the list of authors authors[authorNumber] = author end end -- Add any affiliations that are explicitly specified local affiliationsRaw = meta[kAffiliations] if affiliationsRaw then local explicitAffils = processAffiliation(nil, affiliationsRaw) if explicitAffils then for i,affiliation in ipairs(explicitAffils) do local addedAffiliation = maybeAddAffiliation(affiliation, affiliations) -- for any authors that are using this affiliation, fix up their reference if affiliation[kId] ~= addedAffiliation[kId] then remapAuthorAffiliations(affiliation[kId], addedAffiliation[kId], authors) end end end end -- process 'institute', which is used by revealjs and beamer -- because they bear no direct relation to the authors -- we will just use their position to attach them local instituteRaw = meta[kInstituteInput] if instituteRaw then for i,institute in ipairs(instituteRaw) do -- add the affiliation local affiliation = processAffilationObj({ name=institute }) local addedAffiliation = maybeAddAffiliation(affiliation, affiliations) -- note the reference on the author -- if there aren't enough authors, attach the affiliations to the -- last author local author = authors[#authors] if i <= #authors then author = authors[i] end if author then setAffiliation(author, { ref=addedAffiliation[kId] }) end end end -- validate that every author affiliation has a corresponding -- affiliation defined in the affiliations key validateRefs(authors, affiliations) -- number the authors and affiliations for i,affil in ipairs(affiliations) do affil[kNumber] = i affil[kLetter] = letter(i) end for i,auth in ipairs(authors) do auth[kNumber] = i auth[kLetter] = letter(i) end -- Write the normalized data back to metadata if #authors ~= 0 then meta[kAuthorOutput] = authors end if #affiliations ~= 0 then meta[kAffiliationOutput] = affiliations end -- Write the de-normalized versions back to metadata if #authors ~= 0 then meta[kByAuthor] = byAuthors(authors, affiliations) end if #affiliations ~= 0 then meta[kByAffiliation] = byAffiliations(authors, affiliations) end -- the normalized funding local funding = {} -- process the 'funding' key local fundingRaw = meta[kFunding] if fundingRaw then -- ensure that this is table if pandoc.utils.type(fundingRaw) ~= "List" then fundingRaw = pandoc.List({fundingRaw}) end for i,fundingGroup in ipairs(fundingRaw) do local normalizedFundingGroup = processFundingGroup(fundingGroup, authors, affiliations) if normalizedFundingGroup then funding[i] = normalizedFundingGroup end end end -- write the normalized funding to output if #funding ~= 0 then meta[kFundingOutput] = funding end -- Provide localized or user specified strings for title block elements meta = computeLabels(authors, affiliations, meta) -- Provide biblio-config if it isn't specified if meta[kBiblioConfig] == nil and not _quarto.format.isAstOutput() then meta[kBiblioConfig] = true end return meta end return { processAuthorMeta = processAuthorMeta, constants = { author = { output_key = kAuthorOutput, note = kNote, attributes = kAttributes } } }