UNPKG

myst-to-jats

Version:
663 lines (662 loc) 23.7 kB
import * as credit from 'credit-roles'; import { doi } from 'doi-utils'; import { orcid } from 'orcid'; export function getJournalIds() { // [{ type: 'element', name: 'journal-id', attributes: {'journal-id-type': ...}, text: ...}] return []; } export function getJournalTitleGroup() { const elements = []; // { type: 'element', name: 'journal-title', text: ...} // { type: 'element', name: 'journal-subtitle', text: ...} // translated title group? // abbreviated journal title? return elements.length ? [{ type: 'element', name: 'journal-title-group', elements }] : []; } export function getJournalAffiliations() { // contrib-group, aff, aff-alternatives return []; } export function getJournalISNs() { // issn, issn-l, isbn... return []; } export function getJournalPublisher() { // publisher name, publisher loc return []; } export function getJournalMeta() { const elements = [ ...getJournalIds(), ...getJournalTitleGroup(), ...getJournalAffiliations(), ...getJournalISNs(), ...getJournalPublisher(), // notes // self-url ]; return elements.length ? { type: 'element', name: 'journal-meta', elements } : null; } /** * Add a article `title-group`, for example: * * ```xml * <title-group> * <article-title> * Systematic review of day hospital care for elderly people * </article-title> * </title-group> * ``` * * See: https://jats.nlm.nih.gov/archiving/tag-library/1.3/element/article-title.html */ export function getArticleTitle(frontmatter) { const title = frontmatter?.title; const subtitle = frontmatter?.subtitle; const short_title = frontmatter?.short_title; if (!title && !subtitle && !short_title) return []; const articleTitle = [ { type: 'element', name: 'article-title', elements: title ? [{ type: 'text', text: title }] : [], }, ]; const articleSubtitle = subtitle ? [ { type: 'element', name: 'subtitle', elements: [{ type: 'text', text: subtitle }], }, ] : []; const articleShortTitle = short_title ? [ { type: 'element', name: 'alt-title', attributes: { 'alt-title-type': 'running-head' }, elements: [{ type: 'text', text: short_title }], }, ] : []; return [ { type: 'element', name: 'title-group', elements: [...articleTitle, ...articleSubtitle, ...articleShortTitle], }, ]; } function nameElementFromContributor(contrib) { if (contrib.nameParsed && (contrib.nameParsed?.given || contrib.nameParsed?.family)) { const { given, family, dropping_particle, non_dropping_particle, suffix } = contrib.nameParsed; const nameElements = []; if (family) { nameElements.push({ type: 'element', name: 'surname', elements: [ { type: 'text', text: non_dropping_particle ? `${non_dropping_particle} ${family}` : family, }, ], }); } if (given) { nameElements.push({ type: 'element', name: 'given-names', elements: [ { type: 'text', text: dropping_particle ? `${given} ${dropping_particle}` : given, }, ], }); } // Prefix not yet supported by name parsing if (suffix) { nameElements.push({ type: 'element', name: 'suffix', elements: [{ type: 'text', text: suffix }], }); } return { type: 'element', name: 'name', attributes: { 'name-style': 'western' }, elements: nameElements, }; } else if (contrib.name) { return { type: 'element', name: 'string-name', attributes: { 'name-style': 'western' }, elements: [{ type: 'text', text: contrib.name }], }; } } /** * Add authors and contributors to contrib-group * * Authors are tagged as contrib-type="author" */ export function getArticleAuthors(frontmatter) { const generateContrib = (author, type) => { const attributes = {}; const elements = []; if (type) attributes['contrib-type'] = type; if (author.corresponding) attributes.corresp = 'yes'; if (author.deceased) attributes['deceased'] = 'yes'; if (author.equal_contributor != null) { attributes['equal-contrib'] = author.equal_contributor ? 'yes' : 'no'; } if (orcid.validate(author.orcid)) { elements.push({ type: 'element', name: 'contrib-id', attributes: { 'contrib-id-type': 'orcid' }, elements: [{ type: 'text', text: orcid.buildUrl(author.orcid) }], }); } const name = nameElementFromContributor(author); if (name) elements.push(name); if (author.roles) { // See https://jats4r.org/credit-taxonomy/ elements.push(...author.roles.map((role) => { const attrs = {}; if (credit.validate(role)) { attrs.vocab = 'credit'; attrs['vocab-identifier'] = credit.CREDIT_URL; attrs['vocab-term'] = credit.normalize(role); attrs['vocab-term-identifier'] = credit.buildUrl(role); } return { type: 'element', name: 'role', attributes: attrs, elements: [{ type: 'text', text: role }], }; })); } if (author.affiliations) { elements.push(...author.affiliations.map((aff) => { return { type: 'element', name: 'xref', attributes: { 'ref-type': 'aff', rid: aff }, }; })); } if (author.email) { elements.push({ type: 'element', name: 'email', elements: [{ type: 'text', text: author.email }], }); } if (author.url) { elements.push({ type: 'element', name: 'ext-link', attributes: { 'ext-link-type': 'uri', 'xlink:href': author.url }, elements: [{ type: 'text', text: author.url }], }); } return { type: 'element', name: 'contrib', attributes, elements }; }; const authorContribs = (frontmatter.authors ?? []).map((author) => { return generateContrib(author, 'author'); }); const contribGroups = []; if (authorContribs.length) { contribGroups.push({ type: 'element', name: 'contrib-group', elements: authorContribs }); } return contribGroups; } function instWrapElementsFromAffiliation(affiliation, includeDept = true) { const elements = []; const instWrapElements = []; if (affiliation.name) { instWrapElements.push({ type: 'element', name: 'institution', elements: [{ type: 'text', text: affiliation.name }], }); } if (affiliation.isni) { instWrapElements.push({ type: 'element', name: 'institution-id', attributes: { 'institution-id-type': 'isni' }, elements: [{ type: 'text', text: affiliation.isni }], }); } if (affiliation.ringgold) { instWrapElements.push({ type: 'element', name: 'institution-id', attributes: { 'institution-id-type': 'ringgold' }, elements: [{ type: 'text', text: `${affiliation.ringgold}` }], }); } if (affiliation.ror) { instWrapElements.push({ type: 'element', name: 'institution-id', attributes: { 'institution-id-type': 'ror' }, elements: [{ type: 'text', text: affiliation.ror }], }); } if (affiliation.doi) { const doiAttrs = { 'institution-id-type': 'doi' }; const funder = doi.isOpenFunderRegistry(affiliation.doi); if (funder) { doiAttrs.vocab = 'open-funder-registry'; } instWrapElements.push({ type: 'element', name: 'institution-id', attributes: doiAttrs, elements: [{ type: 'text', text: doi.normalize(affiliation.doi) }], }); } if (instWrapElements.length) { elements.push({ type: 'element', name: 'institution-wrap', elements: instWrapElements }); } if (includeDept && affiliation.department) { elements.push({ type: 'element', name: 'institution-wrap', elements: [ { type: 'element', name: 'institution', attributes: { 'content-type': 'dept' }, elements: [{ type: 'text', text: affiliation.department }], }, ], }); } return elements; } export function getArticleAffiliations(frontmatter) { if (!frontmatter.affiliations?.length) return []; // Only add affiliations from authors, not contributors const affIds = [ ...new Set(frontmatter.authors?.map((auth) => auth.affiliations ?? []).flat() ?? []), ]; if (!affIds?.length) return []; const affs = affIds .map((id) => frontmatter.affiliations?.find((aff) => aff.id === id)) .filter((aff) => !!aff) .map((affiliation) => { const elements = []; const attributes = {}; if (affiliation.id) { attributes.id = affiliation.id; } elements.push(...instWrapElementsFromAffiliation(affiliation)); if (affiliation.address) { elements.push({ type: 'element', name: 'addr-line', elements: [{ type: 'text', text: affiliation.address }], }); } if (affiliation.city) { elements.push({ type: 'element', name: 'city', elements: [{ type: 'text', text: affiliation.city }], }); } if (affiliation.state) { elements.push({ type: 'element', name: 'state', elements: [{ type: 'text', text: affiliation.state }], }); } if (affiliation.postal_code) { elements.push({ type: 'element', name: 'postal-code', elements: [{ type: 'text', text: affiliation.postal_code }], }); } if (affiliation.country) { elements.push({ type: 'element', name: 'country', elements: [{ type: 'text', text: affiliation.country }], }); } if (affiliation.phone) { elements.push({ type: 'element', name: 'phone', elements: [{ type: 'text', text: affiliation.phone }], }); } if (affiliation.fax) { elements.push({ type: 'element', name: 'fax', elements: [{ type: 'text', text: affiliation.fax }], }); } if (affiliation.email) { elements.push({ type: 'element', name: 'email', elements: [{ type: 'text', text: affiliation.email }], }); } if (affiliation.url) { elements.push({ type: 'element', name: 'ext-link', attributes: { 'ext-link-type': 'uri', 'xlink:href': affiliation.url }, elements: [{ type: 'text', text: affiliation.url }], }); } return { type: 'element', name: 'aff', attributes, elements, }; }); return affs ? affs : []; } export function getArticlePermissions(frontmatter) { // copyright-statement: '© 2023, Authors et al' // copyright-year: '2023' // copyright-holder: 'Authors et al' const isCCBY = frontmatter.license?.content?.id === 'CC-BY-4.0'; const licenseUrl = frontmatter.license?.content?.url ?? frontmatter.license?.code?.url; // Add `<ali:free_to_read />` to the permissions const freeToRead = frontmatter.open_access ? [{ type: 'element', name: 'ali:free_to_read' }] : []; // TODO: This can be generalized to use `frontmatter.license?.content?.CC` and update the copy in future const licenseP = isCCBY ? [ { type: 'element', name: 'license-p', elements: [ { type: 'text', text: `This ${frontmatter.open_access ? 'is an open access article' : 'article is'} distributed under the terms of the `, }, { type: 'element', name: 'ext-link', attributes: { 'ext-link-type': 'uri', 'xlink:href': 'http://creativecommons.org/licenses/by/4.0/', }, elements: [{ type: 'text', text: 'Creative Commons Attribution License' }], }, { type: 'text', text: ', which permits unrestricted use, distribution, and reproduction in any medium, provided the original author and source are credited.', }, ], }, ] : []; return licenseUrl ? [ { type: 'element', name: 'permissions', elements: [ ...freeToRead, { type: 'element', name: 'license', attributes: { 'xlink:href': licenseUrl }, elements: [ { type: 'element', name: 'ali:license_ref', elements: [{ type: 'text', text: licenseUrl }], }, ...licenseP, ], }, ], }, ] : []; } export function getKwdGroup(frontmatter) { const kwds = frontmatter.keywords?.map((keyword) => { return { type: 'element', name: 'kwd', elements: [{ type: 'text', text: keyword }] }; }); return kwds?.length ? [{ type: 'element', name: 'kwd-group', elements: kwds }] : []; } export function getFundingGroup(frontmatter) { const fundingGroups = frontmatter.funding?.map((fund) => { const elements = []; if (fund.awards?.length) { elements.push(...fund.awards.map((award) => { const awardElements = []; const resolvedSources = award.sources ?.map((source) => { return frontmatter.affiliations?.find((aff) => aff.id === source); }) .filter((source) => !!source); if (resolvedSources?.length) { awardElements.push(...resolvedSources.map((source) => { return { type: 'element', name: 'funding-source', elements: instWrapElementsFromAffiliation(source, false), }; })); } if (award.id) { awardElements.push({ type: 'element', name: 'award-id', elements: [{ type: 'text', text: award.id }], }); } if (award.name) { awardElements.push({ type: 'element', name: 'award-name', elements: [{ type: 'text', text: award.name }], }); } if (award.description) { awardElements.push({ type: 'element', name: 'award-desc', elements: [{ type: 'text', text: award.description }], }); } if (award.recipients?.length) { awardElements.push(...award.recipients.map((recipient) => { const recipientElements = []; const author = [ ...(frontmatter.authors ?? []), ...(frontmatter.contributors ?? []), ].find((auth) => auth.id === recipient) ?? { name: recipient }; if (orcid.validate(author.orcid)) { recipientElements.push({ type: 'element', name: 'contrib-id', attributes: { 'contrib-id-type': 'orcid' }, elements: [{ type: 'text', text: orcid.buildUrl(author.orcid) }], }); } const name = nameElementFromContributor(author); if (name) recipientElements.push(name); return { type: 'element', name: 'principal-award-recipient', elements: recipientElements, }; })); } if (award.investigators?.length) { awardElements.push(...award.investigators.map((investigator) => { const investigatorElements = []; const author = [ ...(frontmatter.authors ?? []), ...(frontmatter.contributors ?? []), ].find((auth) => auth.id === investigator) ?? { name: investigator }; if (orcid.validate(author.orcid)) { investigatorElements.push({ type: 'element', name: 'contrib-id', attributes: { 'contrib-id-type': 'orcid' }, elements: [{ type: 'text', text: orcid.buildUrl(author.orcid) }], }); } const name = nameElementFromContributor(author); if (name) investigatorElements.push(name); return { type: 'element', name: 'principal-investigator', elements: investigatorElements, }; })); } return { type: 'element', name: 'award-group', elements: awardElements }; })); } if (fund.statement) { elements.push({ type: 'element', name: 'funding-statement', elements: [{ type: 'text', text: fund.statement }], }); } if (fund.open_access) { elements.push({ type: 'element', name: 'open-access', elements: [ { type: 'element', name: 'p', elements: [{ type: 'text', text: fund.open_access }] }, ], }); } return { type: 'element', name: 'funding-group', elements }; }); return fundingGroups ? fundingGroups : []; } export function getArticleVolume(frontmatter) { const text = frontmatter.volume?.number; return text ? [{ type: 'element', name: 'volume', elements: [{ type: 'text', text: `${text}` }] }] : []; } export function getArticleIssue(frontmatter) { const text = frontmatter.issue?.number; return text ? [{ type: 'element', name: 'issue', elements: [{ type: 'text', text: `${text}` }] }] : []; } export function getArticlePages(frontmatter) { // fpage/lpage, page-range, or elocation-id const { first_page, last_page } = frontmatter ?? {}; const pages = []; if (first_page) { pages.push({ type: 'element', name: 'fpage', elements: [{ type: 'text', text: `${first_page}` }], }); } if (last_page) { pages.push({ type: 'element', name: 'lpage', elements: [{ type: 'text', text: `${last_page}` }], }); } return pages; } export function getArticleIds(frontmatter) { const ids = []; if (doi.validate(frontmatter.doi)) { ids.push({ type: 'element', name: 'article-id', attributes: { 'pub-id-type': 'doi' }, elements: [{ type: 'text', text: doi.normalize(frontmatter.doi) }], }); } return ids; } export function getArticleMeta(frontmatter, state) { const elements = []; if (frontmatter) { elements.push(...getArticleIds(frontmatter), // article-version, article-version-alternatives // article-categories ...getArticleTitle(frontmatter), ...getArticleAuthors(frontmatter), ...getArticleAffiliations(frontmatter), // author-notes // pub-date or pub-date-not-available ...getArticleVolume(frontmatter), // volume-id // volume-series ...getArticleIssue(frontmatter), // issue-id // issue-title // issue-title-group // issue-sponsor // issue-part // volume-issue-group // isbn // supplement ...getArticlePages(frontmatter), // email, ext-link, uri, product, supplementary-material // history // pub-history ...getArticlePermissions(frontmatter)); } if (state?.data.abstracts && state.data.abstracts.length > 0) { elements.push(...state.data.abstracts); } if (frontmatter) { elements.push( // trans-abstract ...getKwdGroup(frontmatter), ...getFundingGroup(frontmatter)); } return { type: 'element', name: 'article-meta', elements }; } /** * Get <front> JATS element * * This element must be defined in a JATS article and must include <article-meta> */ export function getFront(frontmatter, state) { const elements = []; const journalMeta = getJournalMeta(); if (journalMeta) elements.push(journalMeta); const articleMeta = getArticleMeta(frontmatter, state); elements.push(articleMeta); return [{ type: 'element', name: 'front', elements }]; }