UNPKG

rehype-citation

Version:

rehype plugin to add citation and bibliography from bibtex files

122 lines (121 loc) 5.82 kB
/** * @typedef {import('./types').CiteItem} CiteItem * @typedef {import('./types').Mode} Mode * @typedef {import('./types').Options} Options */ import { getSortedRelevantRegistryItems, split, isSameAuthor, getBibliographyEntryText, } from './utils.js'; import { htmlToHast } from './html-transform-node.js'; /** * Generate citation using citeproc * This accounts for prev citations and additional properties * * @param {*} citeproc * @param {Mode} mode * @param {CiteItem[]} entries * @param {string} citationIdRoot * @param {number} citationId * @param {any[]} citationPre * @param {Options} options * @param {boolean} isComposite * @param {import('./types').CitationFormat} citationFormat * @return {[string, string]} */ export const genCitation = (citeproc, mode, entries, citationIdRoot, citationId, citationPre, options, isComposite, citationFormat) => { const { inlineClass, linkCitations, showTooltips = false, tooltipAttribute = 'title' } = options; const key = `${citationIdRoot}-${citationId}`; const c = citeproc.processCitationCluster({ citationID: key, citationItems: entries, properties: mode === 'in-text' ? { noteIndex: 0, mode: isComposite ? 'composite' : '' } : { noteIndex: citationId, mode: isComposite ? 'composite' : '' }, }, citationPre.length > 0 ? citationPre : [], []); const citationText = c[1].find((x) => x[2] === key)[1]; const ids = `citation--${entries.map((x) => x.id.toLowerCase()).join('--')}--${citationId}`; // Generate tooltip map for each entry if enabled const tooltipMap = {}; if (showTooltips) { entries.forEach((entry) => { const entryText = getBibliographyEntryText(citeproc, entry.id); // Escape quotes and HTML entities for attribute value tooltipMap[entry.id.toLowerCase()] = entryText.replace(/"/g, '&quot;').replace(/&/g, '&amp;'); }); } // Wrapper tooltip for the span element (combined tooltip for all entries) const wrapperTooltipAttr = showTooltips ? ` ${tooltipAttribute}="${entries.map((e) => tooltipMap[e.id.toLowerCase()]).join('; ')}"` : ''; if (mode === 'note') { return [ citationText, htmlToHast(`<span class="${(inlineClass ?? []).join(' ')}" id=${ids}${wrapperTooltipAttr}><sup><a href="#cite-fn-${citationId}" id="cite-fnref-${citationId}" data-footnote-ref aria-describedby="footnote-label">${citationId}</a></sup></span>`), ]; } else if (linkCitations && citationFormat === 'numeric') { // e.g. [1, 2] let i = 0; const refIds = entries.map((e) => e.id); const output = citationText.replace(/\d+/g, function (d) { const refId = refIds[i].toLowerCase(); const tooltipAttr = showTooltips ? ` ${tooltipAttribute}="${tooltipMap[refId]}"` : ''; const url = `<a href="#bib-${refId}"${tooltipAttr}>${d}</a>`; i++; return url; }); return [ citationText, htmlToHast(`<span class="${(inlineClass ?? []).join(' ')}" id=${ids}>${output}</span>`), ]; } else if (linkCitations && citationFormat === 'author-date') { // E.g. (see Nash, 1950, pp. 12–13, 1951); (Nash, 1950; Xie, 2016) if (entries.length === 1) { // Do not link bracket const refId = entries[0].id.toLowerCase(); const tooltipAttr = showTooltips ? ` ${tooltipAttribute}="${tooltipMap[refId]}"` : ''; const output = isComposite ? `<a href="#bib-${refId}"${tooltipAttr}>${citationText}</a>` : `${citationText.slice(0, 1)}<a href="#bib-${refId}"${tooltipAttr}>${citationText.slice(1, -1)}</a>${citationText.slice(-1)}`; return [ citationText, htmlToHast(`<span class="${(inlineClass ?? []).join(' ')}" id=${ids}>${output}</span>`), ]; } else { // Retrieve the items in the correct order and attach link each of them const refIds = entries.map((e) => e.id); const results = getSortedRelevantRegistryItems(citeproc, refIds, citeproc.opt.sort_citations); const output = []; let str = citationText; for (const [i, item] of results.entries()) { // Need to compare author. If same just match on date. const id = item.id; let citeMatch = item.ambig; // If author is the same as the previous, some styles like apa collapse the author if (i > 0 && isSameAuthor(results[i - 1], item) && str.indexOf(citeMatch) === -1) { // Just match on year citeMatch = item.ref.issued.year.toString(); } const startPos = str.indexOf(citeMatch); const [start, rest] = split(str, startPos); output.push(start); // Irrelevant parts const refId = id.toLowerCase(); const tooltipAttr = showTooltips ? ` ${tooltipAttribute}="${tooltipMap[refId]}"` : ''; const url = `<a href="#bib-${refId}"${tooltipAttr}>${rest.substring(0, citeMatch.length)}</a>`; output.push(url); str = rest.substring(citeMatch.length); } output.push(str); return [ citationText, htmlToHast(`<span class="${(inlineClass ?? []).join(' ')}" id=${ids}>${output.join('')}</span>`), ]; } } else { return [ citationText, htmlToHast(`<span class="${(inlineClass ?? []).join(' ')}" id=${ids}${wrapperTooltipAttr}>${citationText}</span>`), ]; } };