From 16e7a59b8b147680dd837ae1ee8f94255133f978 Mon Sep 17 00:00:00 2001 From: Timothy Lin Date: Fri, 13 Oct 2023 10:23:47 +0800 Subject: [PATCH] feat: add support for multiple bibliography files --- README.md | 4 ++-- src/citation-js/core/Cite/index.js | 7 ++++++- src/citation-js/plugin-cff/index.js | 2 +- src/gen-citation.js | 12 +++++------ src/generator.js | 31 +++++++++++++++-------------- src/types.js | 4 ++-- src/utils.js | 21 +++++++++---------- test/index.js | 20 ++++++++++++++----- 8 files changed, 59 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 204f0bb..ba4e77f 100644 --- a/README.md +++ b/README.md @@ -97,11 +97,11 @@ If no `bibliography` file is passed, the plugin will be skipped. #### options.bibliography -Type: `string`. +Type: `string|string[]`. By default, if no `bibliography` file is passed, the plugin will be skipped. -Name of bibtex or CSL-JSON file. +Name or path to Bibtex, CSL-JSON or CFF file. If multiple files are provided, they will be merged. #### options.path diff --git a/src/citation-js/core/Cite/index.js b/src/citation-js/core/Cite/index.js index b069752..6188902 100644 --- a/src/citation-js/core/Cite/index.js +++ b/src/citation-js/core/Cite/index.js @@ -52,7 +52,12 @@ function Cite(data, options = {}) { */ this.data = [] - this.set(data, options) + // Modified citation-js to accept an array of objects + // Use add instead of set to retain previous data + data.forEach((d) => { + this.add(d, options) + }) + // this.set(data, options) this.options(options) return this diff --git a/src/citation-js/plugin-cff/index.js b/src/citation-js/plugin-cff/index.js index 562622c..cd9a797 100644 --- a/src/citation-js/plugin-cff/index.js +++ b/src/citation-js/plugin-cff/index.js @@ -647,7 +647,7 @@ function addId(entry) { if ('DOI' in entry) { entry.id = entry.DOI } else if ('URL' in entry) { - entry.id = entry.URL.replace("http://", "").replace("https://", "") + entry.id = entry.URL.replace('http://', '').replace('https://', '') } } diff --git a/src/gen-citation.js b/src/gen-citation.js index d6c307d..9ae8010 100644 --- a/src/gen-citation.js +++ b/src/gen-citation.js @@ -82,12 +82,12 @@ export const genCitation = ( const output = isComposite ? `${citationText}` : `${citationText.slice( - 0, - 1 - )}${citationText.slice( - 1, - -1 - )}${citationText.slice(-1)}` + 0, + 1 + )}${citationText.slice( + 1, + -1 + )}${citationText.slice(-1)}` return [ citationText, htmlToHast(`${output}`), diff --git a/src/generator.js b/src/generator.js index 0b6c49f..5b69532 100644 --- a/src/generator.js +++ b/src/generator.js @@ -43,13 +43,9 @@ const idRoot = 'CITATION' const rehypeCitationGenerator = (Cite) => { return (options = {}) => { return async (tree, file) => { - let bibliography = await getBibliography(options, file) - if (!bibliography) { - return - } - /** @type {string} */ - let bibtexFile + /** @type {string[]} */ + let bibtexFile = [] /** @type {string} */ // @ts-ignore const inputCiteformat = options.csl || file?.data?.frontmatter?.csl || defaultCiteFormat const inputLang = options.lang || 'en-US' @@ -57,18 +53,23 @@ const rehypeCitationGenerator = (Cite) => { const citeFormat = await loadCSL(Cite, inputCiteformat, options.path) const lang = await loadLocale(Cite, inputLang, options.path) - if (isValidHttpUrl(bibliography)) { - isNode - const response = await fetch(bibliography) - bibtexFile = await response.text() - } else { - if (isNode) { - bibtexFile = await readFile(bibliography) + let bibliography = await getBibliography(options, file) + if (bibliography.length === 0) { + return + } + + for (let i = 0; i < bibliography.length; i++) { + if (isValidHttpUrl(bibliography[i])) { + const response = await fetch(bibliography[i]) + bibtexFile.push(await response.text()) } else { - throw new Error(`Cannot read non valid bibliography URL in node env.`) + if (isNode) { + bibtexFile.push(await readFile(bibliography[i])) + } else { + throw new Error(`Cannot read non valid bibliography URL in node env.`) + } } } - const citations = new Cite(bibtexFile) const citationIds = citations.data.map((x) => x.id) const citationPre = [] diff --git a/src/types.js b/src/types.js index 7a5430a..cfde000 100644 --- a/src/types.js +++ b/src/types.js @@ -1,8 +1,8 @@ /** * @typedef Options * Configuration. - * @property {string} [bibliography] - * Name of bibtex or CSL-JSON file + * @property {string | string[]} [bibliography] + * Name or path to Bibtex, CSL-JSON or CFF file. If multiple files are provided, they will be merged. * @property {string} [path] * Optional path to file (node). Will be joined with `options.bibliography` and used in place of cwd of file if provided. * @property {'apa'|'vancouver'|'harvard1'|'chicago'|'mla'|string} [csl] diff --git a/src/utils.js b/src/utils.js index b4e6812..c544587 100644 --- a/src/utils.js +++ b/src/utils.js @@ -42,21 +42,22 @@ export const isValidHttpUrl = (str) => { * @param {import('vfile').VFile} file */ export const getBibliography = async (options, file) => { - let bibliography = '' + /** @type {string[]} */ + let bibliography = [] if (options.bibliography) { - bibliography = options.bibliography + bibliography = typeof options.bibliography === 'string' ? [options.bibliography] : options.bibliography // @ts-ignore } else if (file?.data?.frontmatter?.bibliography) { // @ts-ignore - bibliography = file.data.frontmatter.bibliography + bibliography = typeof file.data.frontmatter.bibliography === 'string' ? [file.data.frontmatter.bibliography] : file.data.frontmatter.bibliography // If local path, get absolute path - if (!isValidHttpUrl(bibliography)) { - if (isNode) { - bibliography = await import('path').then((path) => - path.join(options.path || file.cwd, bibliography) - ) - } else { - throw new Error(`Cannot read non valid bibliography URL in node env.`) + for (let i = 0; i < bibliography.length; i++) { + if (!isValidHttpUrl(bibliography[i])) { + if (isNode) { + bibliography[i] = await import('path').then((path) => path.join(options.path || file.cwd, bibliography[i])) + } else { + throw new Error(`Cannot read non valid bibliography URL in node env.`) + } } } } diff --git a/test/index.js b/test/index.js index 96c1047..40223ab 100644 --- a/test/index.js +++ b/test/index.js @@ -7,7 +7,7 @@ import rehypeCitation from '../index.js' const bibliography = './test/references-data.bib' const cslJSON = './test/csl-json-data.json' const cff = './test/CITATION.cff' -const urlCff = "./test/pytorch-CITATION.cff" +const urlCff = './test/pytorch-CITATION.cff' const processHtml = (html, options, input = bibliography) => { return rehype() @@ -280,21 +280,31 @@ rehypeCitationTest('generates multiple inline bibs', async () => { rehypeCitationTest('works with cff file and add doi as id', async () => { const result = await processHtml( - dedent`
[@10.5281/zenodo.1234]<
`, + dedent`
[@10.5281/zenodo.1234]
`, { suppressBibliography: true }, cff ) - const expected = dedent`
(Lisa & Bot, 2017)<
` + const expected = dedent`
(Lisa & Bot, 2017)
` assert.is(result, expected) }) rehypeCitationTest('adds url as id for preferred citation', async () => { const result = await processHtml( - dedent`
[@papers.neurips.cc/paper/9015-pytorch-an-imperative-style-high-performance-deep-learning-library.pdf]<
`, + dedent`
[@papers.neurips.cc/paper/9015-pytorch-an-imperative-style-high-performance-deep-learning-library.pdf]
`, { suppressBibliography: true }, urlCff ) - const expected = dedent`
(Paszke et al., 2019)<
` + const expected = dedent`
(Paszke et al., 2019)
` + assert.is(result, expected) +}) + +rehypeCitationTest('parses multiple bibliography files', async () => { + const result = await processHtml( + dedent`
[@10.5281/zenodo.1234] and [@Nash1950]
`, + { suppressBibliography: true }, + [bibliography, cff] + ) + const expected = dedent`
(Lisa & Bot, 2017) and (Nash, 1950)
` assert.is(result, expected) })