194 lines
6 KiB
JavaScript
194 lines
6 KiB
JavaScript
|
import fetch from 'cross-fetch';
|
||
|
export const isNode = typeof window === 'undefined';
|
||
|
export const readFile = async (path) => {
|
||
|
if (isValidHttpUrl(path)) {
|
||
|
return fetch(path)
|
||
|
.then((response) => response.text())
|
||
|
.then((data) => data);
|
||
|
}
|
||
|
else {
|
||
|
if (isNode) {
|
||
|
return import('fs').then((fs) => fs.readFileSync(path, 'utf8'));
|
||
|
}
|
||
|
else {
|
||
|
throw new Error(`Cannot read non valid URL in node env.`);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
/**
|
||
|
* Check if valid URL
|
||
|
* https://stackoverflow.com/questions/5717093/check-if-a-javascript-string-is-a-url
|
||
|
*
|
||
|
* @param {string} str
|
||
|
* @return {boolean}
|
||
|
*/
|
||
|
export const isValidHttpUrl = (str) => {
|
||
|
let url;
|
||
|
try {
|
||
|
url = new URL(str);
|
||
|
}
|
||
|
catch (_) {
|
||
|
return false;
|
||
|
}
|
||
|
return url.protocol === 'http:' || url.protocol === 'https:';
|
||
|
};
|
||
|
/**
|
||
|
* Get bibliography by merging options and vfile data
|
||
|
*
|
||
|
* @param {import('./generator.js').Options} options
|
||
|
* @param {import('vfile').VFile} file
|
||
|
*/
|
||
|
export const getBibliography = async (options, file) => {
|
||
|
let bibliography = '';
|
||
|
if (options.bibliography) {
|
||
|
bibliography = options.bibliography;
|
||
|
// @ts-ignore
|
||
|
}
|
||
|
else if (file?.data?.frontmatter?.bibliography) {
|
||
|
// @ts-ignore
|
||
|
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.`);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return bibliography;
|
||
|
};
|
||
|
/**
|
||
|
* Load CSL - supports predefined name from config.templates.data or http, file path (nodejs)
|
||
|
*
|
||
|
* @param {*} Cite cite object from citation-js
|
||
|
* @param {string} format CSL name e.g. apa or file path to CSL file
|
||
|
* @param {string} root optional root path
|
||
|
*/
|
||
|
export const loadCSL = async (Cite, format, root = '') => {
|
||
|
const config = Cite.plugins.config.get('@csl');
|
||
|
if (!Object.keys(config.templates.data).includes(format)) {
|
||
|
const cslName = `customCSL-${Math.random().toString(36).slice(2, 7)}`;
|
||
|
let cslPath = '';
|
||
|
if (isValidHttpUrl(format))
|
||
|
cslPath = format;
|
||
|
else {
|
||
|
if (isNode)
|
||
|
cslPath = await import('path').then((path) => path.join(root, format));
|
||
|
}
|
||
|
try {
|
||
|
config.templates.add(cslName, await readFile(cslPath));
|
||
|
}
|
||
|
catch (err) {
|
||
|
throw new Error(`Input CSL option, ${format}, is invalid or is an unknown file.`);
|
||
|
}
|
||
|
return cslName;
|
||
|
}
|
||
|
else {
|
||
|
return format;
|
||
|
}
|
||
|
};
|
||
|
/**
|
||
|
* Load locale - supports predefined name from config.locales.data or http, file path (nodejs)
|
||
|
*
|
||
|
* @param {*} Cite cite object from citation-js
|
||
|
* @param {string} format locale name
|
||
|
* @param {string} root optional root path
|
||
|
*/
|
||
|
export const loadLocale = async (Cite, format, root = '') => {
|
||
|
const config = Cite.plugins.config.get('@csl');
|
||
|
if (!Object.keys(config.locales.data).includes(format)) {
|
||
|
let localePath = '';
|
||
|
if (isValidHttpUrl(format))
|
||
|
localePath = format;
|
||
|
else {
|
||
|
if (isNode)
|
||
|
localePath = await import('path').then((path) => path.join(root, format));
|
||
|
}
|
||
|
try {
|
||
|
const file = await readFile(localePath);
|
||
|
const xmlLangRe = /xml:lang="(.+)"/;
|
||
|
const localeName = file.match(xmlLangRe)[1];
|
||
|
config.locales.add(localeName, file);
|
||
|
return localeName;
|
||
|
}
|
||
|
catch (err) {
|
||
|
throw new Error(`Input locale option, ${format}, is invalid or is an unknown file.`);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
return format;
|
||
|
}
|
||
|
};
|
||
|
/**
|
||
|
* Get citation format
|
||
|
*
|
||
|
* @param {*} citeproc citeproc
|
||
|
* @returns string
|
||
|
*/
|
||
|
export const getCitationFormat = (citeproc) => {
|
||
|
const info = citeproc.cslXml.dataObj.children[0];
|
||
|
const node = info.children.find((x) => x['attrs'] && x['attrs']['citation-format']);
|
||
|
// citation-format takes 5 possible values
|
||
|
// https://docs.citationstyles.org/en/stable/specification.html#toc-entry-14
|
||
|
/** @type {'author-date' | 'author' | 'numeric' | 'note' | 'label'} */
|
||
|
const citationFormat = node['attrs']['citation-format'];
|
||
|
return citationFormat;
|
||
|
};
|
||
|
/**
|
||
|
* Get registry objects that matches a list of relevantIds
|
||
|
* If sorted is false, retrieve registry item in the order of the given relevantIds
|
||
|
*
|
||
|
* @param {*} citeproc citeproc
|
||
|
* @param {string[]} relevantIds
|
||
|
* @param {boolean} sorted
|
||
|
* @return {*} registry objects that matches Ids, in the correct order
|
||
|
*/
|
||
|
export const getSortedRelevantRegistryItems = (citeproc, relevantIds, sorted) => {
|
||
|
const res = [];
|
||
|
if (sorted) {
|
||
|
// If sorted follow registry order
|
||
|
for (const item of citeproc.registry.reflist) {
|
||
|
if (relevantIds.includes(item.id))
|
||
|
res.push(item);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
// Otherwise follow the relevantIds
|
||
|
for (const id of relevantIds) {
|
||
|
res.push(citeproc.registry.reflist.find((x) => x.id === id));
|
||
|
}
|
||
|
}
|
||
|
return res;
|
||
|
};
|
||
|
/**
|
||
|
* Split a string into two parts based on a given index position
|
||
|
*
|
||
|
* @param {string} str
|
||
|
* @param {number} index
|
||
|
* @return {string[]}
|
||
|
*/
|
||
|
export const split = (str, index) => {
|
||
|
return [str.slice(0, index), str.slice(index)];
|
||
|
};
|
||
|
/**
|
||
|
* Check if two registry objects belong to the same author
|
||
|
* Currently only checks on family name
|
||
|
*
|
||
|
* @param {*} item registry object
|
||
|
* @param {*} item2 registry object
|
||
|
* @return {boolean}
|
||
|
*/
|
||
|
export const isSameAuthor = (item, item2) => {
|
||
|
const authorList = item.ref.author;
|
||
|
const authorList2 = item2.ref.author;
|
||
|
if (authorList.length !== authorList2.length)
|
||
|
return false;
|
||
|
for (let i = 0; i < authorList.length; i++) {
|
||
|
if (authorList[i].family !== authorList2[i].family)
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
};
|