diff --git a/README.md b/README.md index f020707..1b48d6b 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ Required, path to file. Will be joined with `options.bibliography` and `options. Type: `'apa'|'vancouver'|'harvard1'|'chicago'|'mla'|string`. Default: `apa`. -For the main `rehypeCitation` plugin, one of 'apa', 'vancouver', 'harvard1', 'chicago', 'mla'. A local file path or URL to a valid CSL file is also accepted. +For the main `rehypeCitation` plugin, one of 'apa', 'vancouver', 'harvard1', 'chicago', 'mla'. A local file path or URL to a valid CSL file is also accepted. Can also be specified as a frontmatter option in the markdown file to override the default. #### options.lang @@ -141,7 +141,7 @@ Suppress bibliography? By default, biliography is inserted after the entire mark Type: `string[]`. -Citation IDs (@item1) to include in the bibliography even if they are not cited in the document. +Citation IDs (@item1) to include in the bibliography even if they are not cited in the document. Can also be specified as a frontmatter option in the markdown file. #### options.inlineClass diff --git a/package-lock.json b/package-lock.json index eb630dd..5bbaf69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,12 @@ "version": "2.0.0", "license": "MIT", "dependencies": { - "@citation-js/core": "^0.7.1", + "@citation-js/core": "^0.7.14", "@citation-js/date": "^0.5.1", "@citation-js/name": "^0.4.2", - "@citation-js/plugin-bibjson": "^0.7.2", - "@citation-js/plugin-bibtex": "^0.7.2", - "@citation-js/plugin-csl": "^0.7.2", + "@citation-js/plugin-bibjson": "^0.7.14", + "@citation-js/plugin-bibtex": "^0.7.14", + "@citation-js/plugin-csl": "^0.7.14", "citeproc": "^2.4.63", "cross-fetch": "^4.0.0", "hast-util-from-dom": "^5.0.0", @@ -1922,9 +1922,9 @@ } }, "node_modules/@citation-js/core": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@citation-js/core/-/core-0.7.1.tgz", - "integrity": "sha512-vt7O/KbWNj6v0/fTKRJfjLE0VU4bV13LR69bG4V4lvWEq8TqXSNR4TVEINJuAwBLCofTk6LRyF82oGpb9AgaPQ==", + "version": "0.7.14", + "resolved": "https://registry.npmjs.org/@citation-js/core/-/core-0.7.14.tgz", + "integrity": "sha512-dgeGqYDSQmn2MtnWZkwPGpJQPh43yr1lAAr9jl1NJ9pIY1RXUQxtlAUZVur0V9PHdbfQC+kkvB1KC3VpgVV3MA==", "dependencies": { "@citation-js/date": "^0.5.0", "@citation-js/name": "^0.4.2", @@ -1952,9 +1952,9 @@ } }, "node_modules/@citation-js/plugin-bibjson": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@citation-js/plugin-bibjson/-/plugin-bibjson-0.7.2.tgz", - "integrity": "sha512-RW2kVcdPX2XeslRcWT6sGTQZgo9JdN3BqvH8cjrIkmaH8ws21C83Oxl7J0B/7cqxT1CL836bg4TXq7gRbMGIzg==", + "version": "0.7.14", + "resolved": "https://registry.npmjs.org/@citation-js/plugin-bibjson/-/plugin-bibjson-0.7.14.tgz", + "integrity": "sha512-Hcmk01KrpHwcl5uVoLE6TRaJRFg7/qUvpJDcKqx3LLLCsNbaBlISfRDeFETrjjipTetkX70RvtS7FfGUN58gCQ==", "dependencies": { "@citation-js/date": "^0.5.0", "@citation-js/name": "^0.4.2" @@ -1967,9 +1967,9 @@ } }, "node_modules/@citation-js/plugin-bibtex": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@citation-js/plugin-bibtex/-/plugin-bibtex-0.7.2.tgz", - "integrity": "sha512-QkgPfYbs1dierenxKWzIlPV7450Jci8bvZbcpjO3HQTfTaj15SV2C2AaeKhUotXZmTjm49AJQWHZtrNh+7YWjg==", + "version": "0.7.14", + "resolved": "https://registry.npmjs.org/@citation-js/plugin-bibtex/-/plugin-bibtex-0.7.14.tgz", + "integrity": "sha512-xHOHqhF6dthLRv46N9U+mQgYLiiWQHLvQWK9+mcBKz+/3NWge62Xb1oBouNWwLEPd5FV/8gp9fp7SOp93T0dUg==", "dependencies": { "@citation-js/date": "^0.5.0", "@citation-js/name": "^0.4.2", @@ -1983,9 +1983,9 @@ } }, "node_modules/@citation-js/plugin-csl": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@citation-js/plugin-csl/-/plugin-csl-0.7.2.tgz", - "integrity": "sha512-ogNb/gdcZ/EXZq+We+/aw6RkiYxjF+h7myJypyR0PApGJq9GzwcfGHW9ded2JdQIxyz4fO46pCTR4nv6FPUvMA==", + "version": "0.7.14", + "resolved": "https://registry.npmjs.org/@citation-js/plugin-csl/-/plugin-csl-0.7.14.tgz", + "integrity": "sha512-7AKB8lMz1IqdtoE33NnWIpteLYMuSl3xqT+Cax7sQKwAIJEoq2HBmb43Ja8xQQ36nREAupQJv1V6XksIAmYnCg==", "dependencies": { "@citation-js/date": "^0.5.0", "citeproc": "^2.4.6" diff --git a/package.json b/package.json index e5f8b34..d624e92 100644 --- a/package.json +++ b/package.json @@ -71,12 +71,12 @@ }, "homepage": "https://github.com/timlrx/rehype-citation#readme", "dependencies": { - "@citation-js/core": "^0.7.1", + "@citation-js/core": "^0.7.14", "@citation-js/date": "^0.5.1", "@citation-js/name": "^0.4.2", - "@citation-js/plugin-bibjson": "^0.7.2", - "@citation-js/plugin-bibtex": "^0.7.2", - "@citation-js/plugin-csl": "^0.7.2", + "@citation-js/plugin-bibjson": "^0.7.14", + "@citation-js/plugin-bibtex": "^0.7.14", + "@citation-js/plugin-csl": "^0.7.14", "citeproc": "^2.4.63", "cross-fetch": "^4.0.0", "hast-util-from-dom": "^5.0.0", diff --git a/src/citation-js/core/Cite/async.js b/src/citation-js/core/Cite/async.js index 7309a3b..ced32ac 100644 --- a/src/citation-js/core/Cite/async.js +++ b/src/citation-js/core/Cite/async.js @@ -1,27 +1,9 @@ -// @ts-nocheck -/** - * @callback module:@citation-js/core.Cite~asyncCallback - * @param {Cite} data - Cite object - */ - -/** - * @access public - * @memberof module:@citation-js/core.Cite - * - * @param {module:@citation-js/core~InputData} data - input data - * @param {module:@citation-js/core~InputOptions} [options={}] - cite options - * @param {module:@citation-js/core.Cite~asyncCallback} [callback] - if not given, function returns promise. - * - * @return {Promise} if callback is omitted, returns a promise - */ function async(data, options, callback) { if (typeof options === 'function' && !callback) { callback = options options = undefined } - const promise = new this().setAsync(data, options) - if (typeof callback === 'function') { promise.then(callback) return undefined @@ -29,5 +11,4 @@ function async(data, options, callback) { return promise } } - export default async diff --git a/src/citation-js/core/Cite/get.js b/src/citation-js/core/Cite/get.js index 531a759..cfe30a8 100644 --- a/src/citation-js/core/Cite/get.js +++ b/src/citation-js/core/Cite/get.js @@ -1,85 +1,46 @@ -// @ts-nocheck import { validateOutputOptions as validate } from './validate.js' import { format as formatData } from '../plugins/output.js' import { clean as parseCsl } from '../plugins/input/csl.js' - -/** - * Get a list of the data entry IDs, in the order of that list - * - * @access public - * @method getIds - * @memberof module:@citation-js/core.Cite# - * - * @return {Array} List of IDs - */ export function getIds() { return this.data.map((entry) => entry.id) } - -/** - * Get formatted data from your object. - * - * @access public - * @method format - * @memberof module:@citation-js/core.Cite# - * - * @param {String} format - format module name - * @param {...*} options - module options (see relevant documentation) - * - * @return {String|Array} formatted data - */ export function format(format, ...options) { return formatData(format, parseCsl(this.data), ...options) } - -/** - * Get formatted data from your object. - * - * @access public - * @method get - * @memberof module:@citation-js/core.Cite# - * @tutorial output - * @deprecated use {@link module:@citation-js/core.Cite#format} - * - * @param {module:@citation-js/core~OutputOptions} [options={}] - Output options - * - * @return {String|Array} The formatted data - */ -/* istanbul ignore next: deprecated */ export function get(options = {}) { validate(options) - const parsedOptions = Object.assign({}, this.defaultOptions, this._options.output, options) - const { type, style } = parsedOptions const [styleType, styleFormat] = style.split('-') const newStyle = styleType === 'citation' ? 'bibliography' : styleType === 'csl' ? 'data' : styleType const newType = type === 'string' ? 'text' : type === 'json' ? 'object' : type - let formatOptions - switch (newStyle) { case 'bibliography': { const { lang, append, prepend } = parsedOptions - formatOptions = { template: styleFormat, lang, format: newType, append, prepend } + formatOptions = { + template: styleFormat, + lang, + format: newType, + append, + prepend, + } break } - case 'data': case 'bibtex': case 'bibtxt': case 'ndjson': case 'ris': - formatOptions = { type: newType } + formatOptions = { + type: newType, + } break - default: throw new Error(`Invalid style "${newStyle}"`) } - const result = this.format(newStyle, Object.assign(formatOptions, options._newOptions)) - const { format } = parsedOptions if ( format === 'real' && diff --git a/src/citation-js/core/Cite/index.js b/src/citation-js/core/Cite/index.js index 6188902..e4d8546 100644 --- a/src/citation-js/core/Cite/index.js +++ b/src/citation-js/core/Cite/index.js @@ -1,74 +1,24 @@ -//@ts-nocheck +// @ts-nocheck import * as log from './log.js' import * as options from './options.js' import * as set from './set.js' import * as sort from './sort.js' import * as get from './get.js' import * as staticMethods from './static.js' - -/** - * Create a `Cite` object with almost any kind of data, and manipulate it with its default methods. - * - * @access public - * @constructor Cite - * @memberof module:@citation-js/core - * - * @param {module:@citation-js/core~InputData} data - Input data - * @param {module:@citation-js/core~InputOptions} [options={}] - Input options - */ function Cite(data, options = {}) { - // Making it Scope-Safe if (!(this instanceof Cite)) { return new Cite(data, options) } - - /** - * The default options for the output. See [input options](../#cite.in.options) - * - * @access protected - * @memberof module:@citation-js/core.Cite# - * - * @var {module:@citation-js/core~InputOptions} _options - */ this._options = options - - /** - * The saved-images-log - * - * @access protected - * @memberof module:@citation-js/core.Cite# - * - * @var {Array>} log - */ this.log = [] - - /** - * The parsed data - * - * @access protected - * @memberof module:@citation-js/core.Cite# - * - * @var {Array} data - */ this.data = [] - - // 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.set(data, options) this.options(options) - return this } - Object.assign(Cite.prototype, log, options, set, sort, get) - Cite.prototype[Symbol.iterator] = function* () { yield* this.data } - Object.assign(Cite, staticMethods) - export default Cite diff --git a/src/citation-js/core/Cite/log.js b/src/citation-js/core/Cite/log.js index b8af3d2..aa6e1f6 100644 --- a/src/citation-js/core/Cite/log.js +++ b/src/citation-js/core/Cite/log.js @@ -1,22 +1,6 @@ -// @ts-nocheck -/** - * @memberof module: @citation-js / core.Cite# - * - * @return {Number} The latest version of the object - */ function currentVersion() { return this.log.length } - -/** - * Returns an image of the object in the version specified. - * - * @memberof module:@citation-js/core.Cite# - * - * @param {Number} [versnum=1] - The number of the version you want to retrieve. Illegal numbers: numbers under or equal to zero, floats, numbers above the current version of the object. - * - * @return {module:@citation-js/core.Cite} The version of the object with the version number passed. `undefined` if an illegal number is passed. - */ function retrieveVersion(versnum = 1) { if (versnum <= 0 || versnum > this.currentVersion()) { return null @@ -27,42 +11,14 @@ function retrieveVersion(versnum = 1) { return image } } - -/** - * Returns the second to last saved image of the object. - * - * @memberof module:@citation-js/core.Cite# - * - * @param {Number} [number=1] - number of versions to go back. - * - * @return {module:@citation-js/core.Cite} The second to last version of the object. `undefined` if used on first version. - */ function undo(number = 1) { return this.retrieveVersion(this.currentVersion() - number) } - -/** - * Returns the last saved image of the object. - * - * @memberof module:@citation-js/core.Cite# - * - * @return {module:@citation-js/core.Cite} The last version of the object. `undefined` if used on first version. - */ function retrieveLastVersion() { return this.retrieveVersion(this.currentVersion()) } - -/** - * Save an image of the current version of the object. - * - * @memberof module:@citation-js/core.Cite# - * - * @return {module:@citation-js/core.Cite} The current version of the object. - */ function save() { this.log.push([JSON.stringify(this.data), JSON.stringify(this._options)]) - return this } - export { currentVersion, retrieveVersion, retrieveLastVersion, undo, save } diff --git a/src/citation-js/core/Cite/options.js b/src/citation-js/core/Cite/options.js index 2afde07..073c5ef 100644 --- a/src/citation-js/core/Cite/options.js +++ b/src/citation-js/core/Cite/options.js @@ -1,32 +1,16 @@ import { validateOutputOptions as validate } from './validate.js' - -/** - * @memberof module:@citation-js/core.Cite# - * - * @constant {module:@citation-js/core~OutputOptions} defaultOptions - default output options - */ -const defaultOptions = { format: 'real', type: 'json', style: 'csl', lang: 'en-US' } - -/** - * Change the default options of a `Cite` object. - * - * @memberof Cite# - * - * @param {module:@citation-js/core~OutputOptions} options - The options for the output - * @param {Boolean} [log=false] - Show this call in the log - * - * @return {module:@citation-js/core.Cite} The updated parent object - */ +const defaultOptions = { + format: 'real', + type: 'json', + style: 'csl', + lang: 'en-US', +} function options(options, log) { validate(options) - if (log) { this.save() } - Object.assign(this._options, options) - return this } - export { options, defaultOptions } diff --git a/src/citation-js/core/Cite/set.js b/src/citation-js/core/Cite/set.js index 1b3c8ed..14b736e 100644 --- a/src/citation-js/core/Cite/set.js +++ b/src/citation-js/core/Cite/set.js @@ -1,119 +1,50 @@ // @ts-nocheck import { chain as parseInput, chainAsync as parseInputAsync } from '../plugins/input/index.js' import fetchId from '../util/fetchId.js' - -/** - * Add an object to the array of objects - * - * @memberof module:@citation-js/core.Cite# - * - * @param {module:@citation-js/core~InputData} data - The data to add to your object - * @param {module:@citation-js/core~InputOptions} [options] - Options - * @param {Boolean} [log=false] - Show this call in the log - * - * @return {module:@citation-js/core.Cite} The updated parent object - */ function add(data, options = {}, log = false) { if (options === true || log === true) { this.save() } - this.data.push(...parseInput(data, options)) - this.data .filter((entry) => !Object.prototype.hasOwnProperty.call(entry, 'id')) .forEach((entry) => { entry.id = fetchId(this.getIds(), 'temp_id_') }) - return this } - -/** - * Add an object to the array of objects - * - * @memberof module:@citation-js/core.Cite# - * - * @param {module:@citation-js/core~InputData} data - The data to add to your object - * @param {module:@citation-js/core~InputOptions} [options] - Options - * @param {Boolean} [log=false] - Show this call in the log - * - * @return {Promise} The updated parent object - */ async function addAsync(data, options = {}, log = false) { if (options === true || log === true) { this.save() } - this.data.push(...(await parseInputAsync(data, options))) - this.data .filter((entry) => !Object.prototype.hasOwnProperty.call(entry, 'id')) .forEach((entry) => { entry.id = fetchId(this.getIds(), 'temp_id_') }) - return this } - -/** - * Recreate a `Cite` object with almost any kind of data, and manipulate it with its default methods. - * - * @memberof module:@citation-js/core.Cite# - * - * @param {module:@citation-js/core~InputData} data - Replacement data - * @param {module:@citation-js/core~InputOptions} [options] - Options - * @param {Boolean} [log=false] - Show this call in the log - * - * @return {module:@citation-js/core.Cite} The updated parent object - */ function set(data, options = {}, log = false) { if (options === true || log === true) { this.save() } - this.data = [] return typeof options !== 'boolean' ? this.add(data, options) : this.add(data) } - -/** - * Recreate a `Cite` object with almost any kind of data, and manipulate it with its default methods. - * - * @memberof module:@citation-js/core.Cite# - * - * @param {module:@citation-js/core~InputData} data - Replacement data - * @param {module:@citation-js/core~InputOptions} [options] - Options - * @param {Boolean} [log=false] - Show this call in the log - * - * @return {Promise} The updated parent object - */ async function setAsync(data, options = {}, log = false) { if (options === true || log === true) { this.save() } - this.data = [] return typeof options !== 'boolean' ? this.addAsync(data, options) : this.addAsync(data) } - -/** - * Reset a `Cite` object. - * - * @memberof module:@citation-js/core.Cite# - * - * @param {Boolean} [log=false] - Show this call in the log - * - * @return {module:@citation-js/core.Cite} The updated, empty parent object (except the log, the log lives) - */ function reset(log) { if (log) { this.save() } - this.data = [] this._options = {} - return this } - export { add, addAsync, set, setAsync, reset } diff --git a/src/citation-js/core/Cite/sort.js b/src/citation-js/core/Cite/sort.js index ebf292c..9865a75 100644 --- a/src/citation-js/core/Cite/sort.js +++ b/src/citation-js/core/Cite/sort.js @@ -1,113 +1,46 @@ import { getLabel } from '../plugin-common/output/label.js' import { format as getName } from '@citation-js/name' - -/** - * @callback module:@citation-js/core.Cite#sort~sort - * @param {module:@citation-js/core~CSL} a - element a - * @param {module:@citation-js/core~CSL} b - element b - * @return {Number} positive for a > b, negative for b > a, zero for a = b - */ - -/** - * Get value for comparing - * - * @access private - * @method getComparisonValue - * - * @param {module:@citation-js/core~CSL} obj - obj - * @param {String} prop - The prop in question - * @param {Boolean} label - Prop is label - * - * @return {String|Number} something to compare - */ function getComparisonValue(obj, prop, label = prop === 'label') { let value = label ? getLabel(obj) : obj[prop] - switch (prop) { case 'author': case 'editor': return value.map((name) => name.literal || name.family || getName(name)) - case 'accessed': case 'issued': return value['date-parts'][0] - case 'page': return value.split('-').map((num) => parseInt(num)) - case 'edition': case 'issue': case 'volume': value = parseInt(value) return !isNaN(value) ? value : -Infinity - default: return value || -Infinity } } - -/** - * Compares props - * - * @access private - * @method compareProp - * - * @param {module:@citation-js/core~CSL} entryA - * @param {module:@citation-js/core~CSL} entryB - * @param {String} prop - The prop in question. Prepend ! to sort the other way around. - * @param {Boolean} flip - Override flip - * - * @return {Number} positive for a > b, negative for b > a, zero for a = b (flips if prop has !) - */ function compareProp(entryA, entryB, prop, flip = /^!/.test(prop)) { prop = prop.replace(/^!/, '') const a = getComparisonValue(entryA, prop) const b = getComparisonValue(entryB, prop) - return (flip ? -1 : 1) * (a > b ? 1 : a < b ? -1 : 0) } - -/** - * Generates a sorting callback based on props. - * - * @access private - * @method getSortCallback - * - * @param {...String} props - How to sort - * - * @return {module:@citation-js/core.Cite#sort~sort} sorting callback - */ function getSortCallback(...props) { return (a, b) => { const keys = props.slice() let output = 0 - while (!output && keys.length) { output = compareProp(a, b, keys.shift()) } - return output } } - -/** - * Sort the dataset - * - * @memberof module:@citation-js/core.Cite# - * - * @param {module:@citation-js/core.Cite#sort~sort|Array} [method=[]] - How to sort - * @param {Boolean} [log=false] - Show this call in the log - * - * @return {module:@citation-js/core.Cite} The updated parent object - */ function sort(method = [], log) { if (log) { this.save() } - this.data.sort(typeof method === 'function' ? method : getSortCallback(...method, 'label')) - return this } - export { sort } diff --git a/src/citation-js/core/Cite/validate.js b/src/citation-js/core/Cite/validate.js index 9b07a72..7f4e974 100644 --- a/src/citation-js/core/Cite/validate.js +++ b/src/citation-js/core/Cite/validate.js @@ -1,32 +1,12 @@ -// @ts-nocheck const formats = ['real', 'string'] const types = ['json', 'html', 'string', 'rtf'] const styles = ['csl', 'bibtex', 'bibtxt', 'citation-*', 'ris', 'ndjson'] const wrapperTypes = ['string', 'function'] - -/** - * @access public - * @method validateOutputOptions - * @memberof module:@citation-js/core.Cite - * - * @deprecated - * @param {module:@citation-js/core~OutputOptions} - options - * - * @return {Boolean} true (if valid) - * @throws {TypeError} Options not an object - * @throws {TypeError} Invalid options - * @throws {Error} Invalid options combination - * - * @todo check registers if styles and langs are present - */ -/* istanbul ignore next: deprecated */ export function validateOutputOptions(options) { if (typeof options !== 'object') { throw new TypeError('Options not an object!') } - const { format, type, style, lang, append, prepend } = options - if (format && !formats.includes(format)) { throw new TypeError(`Option format ("${format}") should be one of: ${formats}`) } else if (type && !types.includes(type)) { @@ -42,33 +22,15 @@ export function validateOutputOptions(options) { } else if (append && !wrapperTypes.includes(typeof append)) { throw new TypeError(`Option append should be a string or a function, but is a ${typeof append}`) } - if (/^citation/.test(style) && type === 'json') { throw new Error(`Combination type/style of json/citation-* is not valid: ${type}/${style}`) } - return true } - -/** - * @access public - * @method valdiateOptions - * @memberof module:@citation-js/core.Cite - * - * @param {module:@citation-js/core~InputOptions} - options - * - * @return {Boolean} true (if valid) - * @throws {TypeError} Options not an object - * @throws {TypeError} Invalid options - * - * @todo check registers if type is present - */ export function validateOptions(options) { if (typeof options !== 'object') { throw new TypeError('Options should be an object') } - - /* istanbul ignore if: deprecated */ if (options.output) { validateOutputOptions(options.output) } else if (options.maxChainLength && typeof options.maxChainLength !== 'number') { @@ -82,6 +44,5 @@ export function validateOptions(options) { } else if (options.target != null && typeof options.target !== 'string') { throw new TypeError('Option target should be a boolean') } - return true } diff --git a/src/citation-js/core/index.js b/src/citation-js/core/index.js index 9dce52e..759e2b7 100644 --- a/src/citation-js/core/index.js +++ b/src/citation-js/core/index.js @@ -2,7 +2,6 @@ import Cite from './Cite/index.js' import * as plugins from './plugins/index.js' import * as util from './util/index.js' import logger from './logger.js' -// import pkg from '../package.json'; import './plugin-common/index.js' -export const version = 0.65 +export const version = 0.7 export { Cite, plugins, util, logger } diff --git a/src/citation-js/core/logger.js b/src/citation-js/core/logger.js index f318580..83b927b 100644 --- a/src/citation-js/core/logger.js +++ b/src/citation-js/core/logger.js @@ -1,41 +1,22 @@ -/** - * @memberof module:@citation-js/core - * @var {Object} logger - * @property _output - * @property {Console} _console - * @property {Array} _log - * @property {Array} _levels - * @property {module:@citation-js/core~logLevel} level - */ const logger = { _output(level, scope, msg) { this._log.push(scope, msg) - if (this._levels.indexOf(level) < this._levels.indexOf(this.level)) { return } - this._console.log(scope, ...msg) }, _console: null, _log: [], - - /** - * @typedef {String} module:@citation-js/core~logLevel - */ _levels: ['http', 'debug', 'unmapped', 'info', 'warn', 'error', 'silent'], - level: 'silent', } - for (const level of logger._levels) { logger[level] = (scope, ...msg) => logger._output(level, scope, msg) } - if (typeof console.Console === 'function') { logger._console = new console.Console(process.stderr) } else { logger._console = console } - export default logger diff --git a/src/citation-js/core/plugin-common/index.js b/src/citation-js/core/plugin-common/index.js index 5d94f43..33f86a0 100644 --- a/src/citation-js/core/plugin-common/index.js +++ b/src/citation-js/core/plugin-common/index.js @@ -1,6 +1,6 @@ -import * as plugins from '../plugins/index.js' -import { ref, formats as input } from './input/index.js' -import output from './output/index.js' +import * as plugins from '../plugins' +import { ref, formats as input } from './input/' +import output from './output/' plugins.add(ref, { input, output, diff --git a/src/citation-js/core/plugin-common/input/index.js b/src/citation-js/core/plugin-common/input/index.js index 4ef0cf4..a928f0d 100644 --- a/src/citation-js/core/plugin-common/input/index.js +++ b/src/citation-js/core/plugin-common/input/index.js @@ -1,17 +1,15 @@ // @ts-nocheck -/* global jQuery, HTMLElement */ - -/** - * @module input/other - */ - import * as empty from './empty.js' import * as json from './json.js' import * as jquery from './jquery.js' import * as html from './html.js' - export const ref = '@else' -export const parsers = { empty, json, jquery, html } +export const parsers = { + empty, + json, + jquery, + html, +} export const formats = { '@empty/text': { parse: empty.parse, @@ -51,7 +49,6 @@ export const formats = { parse: jquery.parse, parseType: { dataType: 'ComplexObject', - /* istanbul ignore next: not testable in Node */ predicate(input) { return typeof jQuery !== 'undefined' && input instanceof jQuery }, @@ -61,7 +58,6 @@ export const formats = { parse: html.parse, parseType: { dataType: 'ComplexObject', - /* istanbul ignore next: not testable in Node */ predicate(input) { return typeof HTMLElement !== 'undefined' && input instanceof HTMLElement }, diff --git a/src/citation-js/core/plugin-common/input/jquery.js b/src/citation-js/core/plugin-common/input/jquery.js index 929f1c4..67dbcea 100644 --- a/src/citation-js/core/plugin-common/input/jquery.js +++ b/src/citation-js/core/plugin-common/input/jquery.js @@ -1,3 +1,3 @@ -export /* istanbul ignore next: not testable in Node */ function parse(input) { +export function parse(input) { return input.val() || input.text() || input.html() } diff --git a/src/citation-js/core/plugin-common/input/json.js b/src/citation-js/core/plugin-common/input/json.js index c81dc93..6dd93f6 100644 --- a/src/citation-js/core/plugin-common/input/json.js +++ b/src/citation-js/core/plugin-common/input/json.js @@ -1,16 +1,4 @@ -// @ts-nocheck -/** - * @module input/other - */ - import logger from '../../logger.js' - -/** - * - * @access private - * @constant substituters - * @default - */ const substituters = [ [/((?:\[|:|,)\s*)'((?:\\'|[^'])*?[^\\])?'(?=\s*(?:\]|}|,))/g, '$1"$2"'], [ @@ -18,22 +6,10 @@ const substituters = [ '$1"$2$3$4"$5:', ], ] - -/** - * Parse (in)valid JSON - * - * @access protected - * @method parseJSON - * - * @param {String} str - The input string - * - * @return {Object|Array|Array} The parsed object - */ function parseJSON(str) { if (typeof str !== 'string') { return JSON.parse(str) } - try { return JSON.parse(str) } catch (e) { @@ -44,5 +20,4 @@ function parseJSON(str) { return JSON.parse(str) } } - export { parseJSON as parse, parseJSON as default } diff --git a/src/citation-js/core/plugin-common/input/url.js b/src/citation-js/core/plugin-common/input/url.js new file mode 100644 index 0000000..a077a04 --- /dev/null +++ b/src/citation-js/core/plugin-common/input/url.js @@ -0,0 +1 @@ +export { fetchFile as parse, fetchFileAsync as parseAsync } from '../../util/index.js' diff --git a/src/citation-js/core/plugin-common/output/index.js b/src/citation-js/core/plugin-common/output/index.js index b4ea684..9c617b9 100644 --- a/src/citation-js/core/plugin-common/output/index.js +++ b/src/citation-js/core/plugin-common/output/index.js @@ -1,4 +1,3 @@ import json from './json.js' import label from './label.js' - export default Object.assign({}, json, label) diff --git a/src/citation-js/core/plugin-common/output/json.js b/src/citation-js/core/plugin-common/output/json.js index 1dfe011..421dc15 100644 --- a/src/citation-js/core/plugin-common/output/json.js +++ b/src/citation-js/core/plugin-common/output/json.js @@ -1,66 +1,24 @@ // @ts-nocheck -/** - * @module output/json - */ - import * as plugins from '../../plugins/index.js' import * as util from '../../util/index.js' import logger from '../../logger.js' - -/** - * Append commas to every item but the last. Should unfortunately, probably be a utility. - * - * @access private - * - * @param {String} item - * @param {Number} index - * @param {Array} array - * - * @return {String} modified item - */ function appendCommas(string, index, array) { return string + (index < array.length - 1 ? ',' : '') } - -/** - * Convert a JSON array or object to HTML. - * - * @access private - * - * @param {Object|Array} src - The data - * @param {Cite.get.dict~dict} dict - Dictionary - * - * @return {String} string form - */ function getJsonObject(src, dict) { const isArray = Array.isArray(src) let entries - if (isArray) { entries = src.map((entry) => getJsonValue(entry, dict)) } else { entries = Object.keys(src) - // remove values that cannot be stringified, as is custom .filter((prop) => JSON.stringify(src[prop])) .map((prop) => `"${prop}": ${getJsonValue(src[prop], dict)}`) } - entries = entries.map(appendCommas).map((entry) => dict.listItem.join(entry)) entries = dict.list.join(entries.join('')) - return isArray ? `[${entries}]` : `{${entries}}` } - -/** - * Convert JSON to HTML. - * - * @access private - * - * @param {*} src - The data - * @param {Cite.get.dict~dict} dict - Dictionary - * - * @return {String} string form - */ function getJsonValue(src, dict) { if (typeof src === 'object' && src !== null) { if (src.length === 0) { @@ -74,47 +32,20 @@ function getJsonValue(src, dict) { return JSON.stringify(src) } } - -/** - * Get a JSON string from CSL - * - * @access protected - * @method getJson - * - * @param {Array} src - Input CSL - * @param {Cite.get.dict~dict} dict - Dictionary - * - * @return {String} JSON string - */ function getJson(src, dict) { let entries = src.map((entry) => getJsonObject(entry, dict)) entries = entries.map(appendCommas).map((entry) => dict.entry.join(entry)) entries = entries.join('') - return dict.bibliographyContainer.join(`[${entries}]`) } - -/** - * Get a JSON HTML string from CSL - * - * @access protected - * @method getJsonWrapper - * @deprecated use the generalised method: {@link module:output/json~getJson} - * - * @param {Array} src - Input CSL - * - * @return {String} JSON HTML string - */ -export /* istanbul ignore next: deprecated */ function getJsonWrapper(src) { +export function getJsonWrapper(src) { return getJson(src, plugins.dict.get('html')) } - export default { data(data, { type, format = type || 'text', version = '1.0.2' } = {}) { if (version < '1.0.2') { data = util.downgradeCsl(data) } - if (format === 'object') { return util.deepCopy(data) } else if (format === 'text') { @@ -131,7 +62,6 @@ export default { if (version < '1.0.2') { data = util.downgradeCsl(data) } - return data.map((entry) => JSON.stringify(entry)).join('\n') }, } diff --git a/src/citation-js/core/plugin-common/output/label.js b/src/citation-js/core/plugin-common/output/label.js index a13a6f6..ca589ed 100644 --- a/src/citation-js/core/plugin-common/output/label.js +++ b/src/citation-js/core/plugin-common/output/label.js @@ -1,26 +1,8 @@ -// @ts-nocheck -/** - * @module output/label - */ - -/** - * Get a label from CSL data - * - * @access protected - * @method getLabel - * @todo flavors/formats - * - * @param {CSL} entry - Input CSL - * - * @return {String} The label - */ function getLabel(entry) { if ('citation-label' in entry) { return entry['citation-label'] } - let res = '' - if (entry.author) { res += entry.author[0].family || entry.author[0].literal } @@ -32,10 +14,8 @@ function getLabel(entry) { } else if (entry.title) { res += entry.title.replace(/<\/?.*?>/g, '').match(/^(?:(?:the|a|an)\s+)?(\S+)/i)[1] } - return res } - export { getLabel } export default { label(data) { diff --git a/src/citation-js/core/plugins/config.js b/src/citation-js/core/plugins/config.js index e6b6fd3..7643192 100644 --- a/src/citation-js/core/plugins/config.js +++ b/src/citation-js/core/plugins/config.js @@ -1,60 +1,16 @@ -// @ts-nocheck -/** - * @namespace config - * @memberof module:@citation-js/core.plugins - */ - const configs = {} - -/** - * @access public - * @method add - * @memberof module:@citation-js/core.plugins.config - * @param {module:@citation-js/core.plugins~pluginRef} ref - plugin reference/name - * @param {Object} config - */ export function add(ref, config) { configs[ref] = config } - -/** - * @access public - * @method get - * @memberof module:@citation-js/core.plugins.config - * @param {module:@citation-js/core.plugins~pluginRef} ref - plugin reference/name - * @return {Object} config - */ export function get(ref) { return configs[ref] } - -/** - * @access public - * @method has - * @memberof module:@citation-js/core.plugins.config - * @param {module:@citation-js/core.plugins~pluginRef} ref - plugin reference/name - * @return {Boolean} - */ export function has(ref) { return Object.prototype.hasOwnProperty.call(configs, ref) } - -/** - * @access public - * @method remove - * @memberof module:@citation-js/core.plugins.config - * @param {module:@citation-js/core.plugins~pluginRef} ref - plugin reference/name - */ export function remove(ref) { delete configs[ref] } - -/** - * @access public - * @method list - * @memberof module:@citation-js/core.plugins.config - * @return {Array} list of available plugin configs - */ export function list() { return Object.keys(configs) } diff --git a/src/citation-js/core/plugins/dict.js b/src/citation-js/core/plugins/dict.js index 744219c..c23222c 100644 --- a/src/citation-js/core/plugins/dict.js +++ b/src/citation-js/core/plugins/dict.js @@ -1,49 +1,10 @@ -// @ts-nocheck -/** - * @namespace dict - * @memberof module:@citation-js/core.plugins - */ - import Register from '../util/register.js' - -/** - * @typedef module:@citation-js/core.plugins.dict~dictName - * @type String - */ - -/** - * @typedef module:@citation-js/core.plugins.dict~dict - * @type Object - */ - -/** - * @typedef module:@citation-js/core.plugins.dict~entryName - * @type String - */ - -/** - * @typedef module:@citation-js/core.plugins.dict~dictEntry - * @type Array - */ - -/** - * Validate input arguments - * - * @access private - * @memberof module:@citation-js/core.plugins.dict - * - * @param {module:@citation-js/core.plugins.dict~dictName} name - output format name - * @param {module:@citation-js/core.plugins.dict~dict} formatter - outputting function - * @throws {TypeError} Invalid output format name - * @throws {TypeError} Invalid formatter - */ function validate(name, dict) { if (typeof name !== 'string') { throw new TypeError(`Invalid dict name, expected string, got ${typeof name}`) } else if (typeof dict !== 'object') { throw new TypeError(`Invalid dict, expected object, got ${typeof dict}`) } - for (const entryName in dict) { const entry = dict[entryName] if (!Array.isArray(entry) || entry.some((part) => typeof part !== 'string')) { @@ -51,14 +12,6 @@ function validate(name, dict) { } } } - -/** - * @access public - * @memberof module:@citation-js/core.plugins.dict - * @constant register - * - * @type module:@citation-js/core.util.Register - */ export const register = new Register({ html: { bibliographyContainer: ['
', '
'], @@ -73,89 +26,25 @@ export const register = new Register({ listItem: ['\t', '\n'], }, }) - -/** - * Add dictionary to register. Can be used by output plugins. - * - * @todo docs - * - * @access public - * @memberof module:@citation-js/core.plugins.dict - * @method add - * - * @param {module:@citation-js/core.plugins.dict~dictName} name - dictionary name - * @param {module:@citation-js/core.plugins.dict~dict} dict - dictionary data - * @throws {TypeError} argument validation error - */ export function add(name, dict) { validate(name, dict) register.set(name, dict) } - -/** - * Remove dictionary. - * - * @access public - * @memberof module:@citation-js/core.plugins.dict - * @method remove - * - * @param {module:@citation-js/core.plugins.dict~dictName} name - output format name - */ export function remove(name) { register.remove(name) } - -/** - * Check if dictionary plugin exists. - * - * @access public - * @memberof module:@citation-js/core.plugins.dict - * @method has - * - * @param {module:@citation-js/core.plugins.dict~dictName} name - output format name - * @return {Boolean} register has plugin - */ export function has(name) { return register.has(name) } - -/** - * List dictionary plugins. - * - * @access public - * @memberof module:@citation-js/core.plugins.dict - * @method list - * - * @return {Array} list of plugins - */ export function list() { return register.list() } - -/** - * Get dictionary data. - * - * @access public - * @memberof module:@citation-js/core.plugins.dict - * @method get - * - * @param {module:@citation-js/core.plugins.dict~dictName} name - output format name - * @return {module:@citation-js/core.plugins.dict~dict} dictionary data - */ export function get(name) { if (!register.has(name)) { throw new Error(`Dict "${name}" unavailable`) } return register.get(name) } - -/** - * Object containing HTML strings for building JSON and BibTeX. Made to match citeproc, for compatibility. - * - * @access protected - * @memberof module:@citation-js/core.plugins.dict - * @deprecated use the new formatting dicts: {@link module:@citation-js/core.plugins.dict} - */ export const htmlDict = { wr_start: '
', wr_end: '
', @@ -166,14 +55,6 @@ export const htmlDict = { li_start: '
  • ', li_end: '
  • ', } - -/** - * Object containing text strings for building JSON and BibTeX. Made to match citeproc, for compatibility. - * - * @access protected - * @memberof module:@citation-js/core.plugins.dict - * @deprecated use the new formatting dicts: {@link module:@citation-js/core.plugins.dict} - */ export const textDict = { wr_start: '', wr_end: '\n', diff --git a/src/citation-js/core/plugins/index.js b/src/citation-js/core/plugins/index.js index cdb7036..494e2ad 100644 --- a/src/citation-js/core/plugins/index.js +++ b/src/citation-js/core/plugins/index.js @@ -1,103 +1,48 @@ // @ts-nocheck -/** - * @namespace plugins - * @memberof module:@citation-js/core - */ - import * as input from './input/index.js' import * as output from './output.js' import * as dict from './dict.js' import * as config from './config.js' - const registers = { input, output, dict, config, } - const indices = {} - -/** - * @access public - * @method add - * @memberof module:@citation-js/core.plugins - * @param {module:@citation-js/core.plugins~pluginRef} ref - plugin reference/name - * @param {module:@citation-js/core.plugins~plugins} [plugins={}] - */ export function add(ref, plugins = {}) { const mainIndex = (indices[ref] = {}) - for (const type in plugins) { if (type === 'config') { - mainIndex.config = { [ref]: plugins.config } + mainIndex.config = { + [ref]: plugins.config, + } registers.config.add(ref, plugins.config) continue } - const typeIndex = (mainIndex[type] = {}) const typePlugins = plugins[type] - for (const name in typePlugins) { const typePlugin = typePlugins[name] - typeIndex[name] = true registers[type].add(name, typePlugin) } } } - -/** - * @access public - * @method remove - * @memberof module:@citation-js/core.plugins - * @param {module:@citation-js/core.plugins~pluginRef} ref - plugin reference/name - */ export function remove(ref) { const mainIndex = indices[ref] - for (const type in mainIndex) { const typeIndex = mainIndex[type] - for (const name in typeIndex) { registers[type].remove(name) } } - delete indices[ref] } - -/** - * @access public - * @method has - * @memberof module:@citation-js/core.plugins - * @param {module:@citation-js/core.plugins~pluginRef} ref - plugin reference/name - * @returns {Boolean} plugin is registered - */ export function has(ref) { return ref in indices } - -/** - * @access public - * @method list - * @memberof module:@citation-js/core.plugins - * @returns {Array} list of registered plugins - */ export function list() { return Object.keys(indices) } - -/** - * @typedef {String} module:@citation-js/core.plugins~pluginRef - */ - -/** - * @typedef {Object} module:@citation-js/core.plugins~plugins - * @property {Object} input - * @property {Object} output - * @property {Object} dict - * @property {Object} config - */ - export { input, output, dict, config } diff --git a/src/citation-js/core/plugins/input/chain.js b/src/citation-js/core/plugins/input/chain.js index 3c5b14b..cef1219 100644 --- a/src/citation-js/core/plugins/input/chain.js +++ b/src/citation-js/core/plugins/input/chain.js @@ -1,44 +1,24 @@ // @ts-nocheck import { deepCopy, upgradeCsl } from '../../util/index.js' import logger from '../../logger.js' - import { get as getTypeInfo } from './register.js' import { type as parseType } from './type.js' import { data as parseData, dataAsync as parseDataAsync } from './data.js' import { applyGraph, removeGraph } from './graph.js' - -/** - * @access private - * @param {Array} graph - * @return {String} - */ function prepareParseGraph(graph) { - return ( - graph - // collapse continuous iterations of the same type - .reduce((array, next) => { - const last = array[array.length - 1] - if (last && last.type === next.type) { - last.count = last.count + 1 || 2 - } else { - array.push(next) - } - return array - }, []) - // presentation - .map((element) => (element.count > 1 ? element.count + 'x ' : '') + element.type) - .join(' -> ') - ) + return graph + .reduce((array, next) => { + const last = array[array.length - 1] + if (last && last.type === next.type) { + last.count = last.count + 1 || 2 + } else { + array.push(next) + } + return array + }, []) + .map((element) => (element.count > 1 ? element.count + 'x ' : '') + element.type) + .join(' -> ') } - -/** - * @access private - * @memberof module:@citation-js/core.plugins.input - * @constructor ChainParser - * - * @param {module:@citation-js/core~InputData} input - * @param {module:@citation-js/core~InputOptions} options - */ class ChainParser { constructor(input, options = {}) { this.options = Object.assign( @@ -51,32 +31,28 @@ class ChainParser { }, options ) - this.type = this.options.forceType this.data = typeof input === 'object' ? deepCopy(input) : input - this.graph = [{ type: this.type, data: input }] + this.graph = [ + { + type: this.type, + data: input, + }, + ] this.iteration = 0 } - - /** - * After a round of data parsing, update type information and check targets. - * - * @access public - * @return {Boolean} Whether this is the last iteration or not - */ iterate() { if (this.iteration !== 0) { const typeInfo = getTypeInfo(this.type) - if (typeInfo && typeInfo.outputs) { this.type = typeInfo.outputs } else { this.type = parseType(this.data) } - - this.graph.push({ type: this.type }) + this.graph.push({ + type: this.type, + }) } - if (this.error || this.type === this.options.target) { return false } else if (this.iteration >= this.options.maxChainLength) { @@ -89,13 +65,6 @@ class ChainParser { return true } } - - /** - * Finish the iteration and return parsed data. - * - * @access public - * @return Array - */ end() { if (this.error) { logger.error('[core]', this.error.message) @@ -113,22 +82,8 @@ class ChainParser { } } } - -/** - * Parse input until success. - * - * @access protected - * @method chain - * @memberof module:@citation-js/core.plugins.input - * - * @param {module:@citation-js/core~InputData} input - input data - * @param {module:@citation-js/core~InputOptions} [options] - options - * - * @return {Array} The parsed input - */ export const chain = (...args) => { const chain = new ChainParser(...args) - while (chain.iterate()) { try { chain.data = parseData(chain.data, chain.type) @@ -136,66 +91,24 @@ export const chain = (...args) => { chain.error = e } } - return chain.end() } - -/** - * Parse input once. - * - * @access protected - * @method chainLink - * @memberof module:@citation-js/core.plugins.input - * - * @param {module:@citation-js/core~InputData} input - input data - * - * @return {module:@citation-js/core~InputData} The parsed input - */ export const chainLink = (input) => { const type = parseType(input) const output = type.match(/array|object/) ? deepCopy(input) : input - return parseData(output, type) } - -/** - * Parse input until success. (async) - * - * @access protected - * @method chainAsync - * @memberof module:@citation-js/core.plugins.input - * - * @param {module:@citation-js/core~InputData} input - input data - * @param {module:@citation-js/core~InputOptions} [options] - options - * - * @return {Promise>} The parsed input - */ export const chainAsync = async (...args) => { const chain = new ChainParser(...args) - while (chain.iterate()) { chain.data = await parseDataAsync(chain.data, chain.type).catch((e) => { chain.error = e }) } - return chain.end() } - -/** - * Parse input once. (async) - * - * @access protected - * @method chainLinkAsync - * @memberof module:@citation-js/core.plugins.input - * - * @param {module:@citation-js/core~InputData} input - The input data - * - * @return {Promise} The parsed input - */ export const chainLinkAsync = async (input) => { const type = parseType(input) const output = type.match(/array|object/) ? deepCopy(input) : input - return parseDataAsync(output, type) } diff --git a/src/citation-js/core/plugins/input/csl.js b/src/citation-js/core/plugins/input/csl.js index 5d6380c..a51eec6 100644 --- a/src/citation-js/core/plugins/input/csl.js +++ b/src/citation-js/core/plugins/input/csl.js @@ -1,21 +1,63 @@ -// @ts-nocheck +function ownKeys(e, r) { + var t = Object.keys(e) + if (Object.getOwnPropertySymbols) { + var o = Object.getOwnPropertySymbols(e) + r && + (o = o.filter(function (r) { + return Object.getOwnPropertyDescriptor(e, r).enumerable + })), + t.push.apply(t, o) + } + return t +} +function _objectSpread(e) { + for (var r = 1; r < arguments.length; r++) { + var t = null != arguments[r] ? arguments[r] : {} + r % 2 + ? ownKeys(Object(t), !0).forEach(function (r) { + _defineProperty(e, r, t[r]) + }) + : Object.getOwnPropertyDescriptors + ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) + : ownKeys(Object(t)).forEach(function (r) { + Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)) + }) + } + return e +} +function _defineProperty(obj, key, value) { + key = _toPropertyKey(key) + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true, + }) + } else { + obj[key] = value + } + return obj +} +function _toPropertyKey(t) { + var i = _toPrimitive(t, 'string') + return 'symbol' == typeof i ? i : i + '' +} +function _toPrimitive(t, r) { + if ('object' != typeof t || !t) return t + var e = t[Symbol.toPrimitive] + if (void 0 !== e) { + var i = e.call(t, r || 'default') + if ('object' != typeof i) return i + throw new TypeError('@@toPrimitive must return a primitive value.') + } + return ('string' === r ? String : Number)(t) +} import { parse as parseName } from '@citation-js/name' - const NAME = 1 const NAME_LIST = 2 const DATE = 3 const TYPE = 4 - -/** - * Data from https://github.com/citation-style-language/schema/blob/master/schemas/input/csl-data.json - * - * - true if a valid type - * - string if another type should be used - * - * @access private - * @constant entryTypes - * @memberof module:@citation-js/core.plugins.input - */ const entryTypes = { article: true, 'article-journal': true, @@ -62,27 +104,12 @@ const entryTypes = { thesis: true, treaty: true, webpage: true, - - // From https://github.com/CrossRef/rest-api-doc/issues/187 'journal-article': 'article-journal', 'book-chapter': 'chapter', 'posted-content': 'manuscript', 'proceedings-article': 'paper-conference', + dissertation: 'thesis', } - -/** - * Object containing type info on CSL-JSON fields. - * - * * string: primitive value type - * * array: list of primitive value types - * * number: special type - * - * Data from https://github.com/citation-style-language/schema/blob/master/csl-data.json - * - * @access private - * @constant fieldTypes - * @memberof module:@citation-js/core.plugins.input - */ const fieldTypes = { author: NAME_LIST, chair: NAME_LIST, @@ -110,7 +137,6 @@ const fieldTypes = { 'script-writer': NAME_LIST, 'series-creator': NAME_LIST, translator: NAME_LIST, - accessed: DATE, 'available-date': DATE, container: DATE, @@ -118,12 +144,9 @@ const fieldTypes = { issued: DATE, 'original-date': DATE, submitted: DATE, - type: TYPE, - - categories: 'object', // TODO Array + categories: 'object', custom: 'object', - id: ['string', 'number'], language: 'string', journalAbbreviation: 'string', @@ -148,7 +171,7 @@ const fieldTypes = { division: 'string', DOI: 'string', edition: ['string', 'number'], - event: 'string', // deprecated + event: 'string', 'event-title': 'string', 'event-place': 'string', 'first-reference-note-number': 'string', @@ -193,20 +216,19 @@ const fieldTypes = { 'volume-title-short': 'string', 'year-suffix': 'string', } - -/** - * Correct a name. - * - * @access private - * @memberof module:@citation-js/core.plugins.input - * - * @param {*} name - name - * @param {Boolean} bestGuessConversions - make some best guess conversions on type mismatch - * - * @return {Object} returns the (corrected) value if possible, otherwise undefined - */ function correctName(name, bestGuessConversions) { if (typeof name === 'object' && name !== null && (name.literal || name.given || name.family)) { + if (name.ORCID || name.orcid || name._ORCID) { + name = _objectSpread( + { + _orcid: name.ORCID || name.orcid || name._ORCID, + }, + name + ) + delete name.ORCID + delete name.orcid + delete name._ORCID + } return name } else if (!bestGuessConversions) { return undefined @@ -214,36 +236,12 @@ function correctName(name, bestGuessConversions) { return parseName(name) } } - -/** - * Correct a name field. - * - * @access private - * @memberof module:@citation-js/core.plugins.input - * - * @param {*} nameList - name list - * @param {Boolean} bestGuessConversions - make some best guess conversions on type mismatch - * - * @return {Array|undefined} returns the (corrected) value if possible, otherwise undefined - */ function correctNameList(nameList, bestGuessConversions) { if (nameList instanceof Array) { const names = nameList.map((name) => correctName(name, bestGuessConversions)).filter(Boolean) return names.length ? names : undefined } } - -/** - * Correct date parts - * - * @access private - * @memberof module:@citation-js/core.plugins.input.util - * - * @param {Array} dateParts - * @param {Boolean} bestGuessConversions - make some best guess conversions on type mismatch - * - * @return {Array|undefined} - */ function correctDateParts(dateParts, bestGuessConversions) { if (dateParts.every((part) => typeof part === 'number')) { return dateParts @@ -253,94 +251,60 @@ function correctDateParts(dateParts, bestGuessConversions) { return dateParts.map((part) => parseInt(part)) } } - -/** - * Correct a date field. - * - * @access private - * @memberof module:@citation-js/core.plugins.input.util - * - * @param {*} date - date - * @param {Boolean} bestGuessConversions - make some best guess conversions on type mismatch - * - * @return {Array|undefined} returns the (corrected) value if possible, otherwise undefined - */ function correctDate(date, bestGuessConversions) { const dp = 'date-parts' - if (typeof date !== 'object' || date === null) { return undefined - - // "{'date-parts': [[2000, 1, 1], ...]}" } else if (date[dp] instanceof Array && date[dp].every((part) => part instanceof Array)) { const range = date[dp] .map((dateParts) => correctDateParts(dateParts, bestGuessConversions)) .filter(Boolean) - return range.length ? { ...date, 'date-parts': range } : undefined - - // LEGACY support - // "[{'date-parts': [2000, 1, 1]}, ...]" + return range.length + ? _objectSpread( + _objectSpread({}, date), + {}, + { + 'date-parts': range, + } + ) + : undefined } else if (date instanceof Array && date.every((part) => part[dp] instanceof Array)) { const range = date .map((dateParts) => correctDateParts(dateParts[dp], bestGuessConversions)) .filter(Boolean) - return range.length ? { 'date-parts': range } : undefined - - // LEGACY support - // "{'date-parts': [2000, 1, 1]}" + return range.length + ? { + 'date-parts': range, + } + : undefined } else if (date[dp] instanceof Array) { const dateParts = correctDateParts(date[dp], bestGuessConversions) - return dateParts && { 'date-parts': [dateParts] } - - // No separate date-parts + return ( + dateParts && { + 'date-parts': [dateParts], + } + ) } else if ('literal' in date || 'raw' in date) { return date } } - -/** - * Correct a type field. - * - * @access private - * @memberof module:@citation-js/core.plugins.input.util - * - * @param {String|*} type - type - * @param {Boolean} bestGuessConversions - make some best guess conversions on type mismatch - * - * @return {String|undefined} returns the (corrected) value if possible, otherwise undefined - */ function correctType(type, bestGuessConversions) { - // Also anything that can be converted to a string. Taking `language` as a field - // with similar string constraints, as fields like `title` might take HTML into - // account in the future. type = correctField('language', type, bestGuessConversions) - if (entryTypes[type] === true) { return type - } else if (bestGuessConversions && type in entryTypes) { - return entryTypes[type] - } else { - return undefined } + if (bestGuessConversions) { + if (type in entryTypes) { + return entryTypes[type] + } else if (type.toLowerCase() !== type) { + return correctType(type.toLowerCase(), bestGuessConversions) + } + } + return undefined } - -/** - * Correct a field. - * - * @access private - * @memberof module:@citation-js/core.plugins.input.util - * - * @param {String} fieldName - field name - * @param {*} value - value - * @param {Boolean} bestGuessConversions - make some best guess conversions on type mismatch - * - * @return {*|undefined} returns the (corrected) value if possible, otherwise undefined - */ function correctField(fieldName, value, bestGuessConversions) { const fieldType = [].concat(fieldTypes[fieldName]) - switch (fieldTypes[fieldName]) { - /* istanbul ignore next: no field has this */ case NAME: return correctName(value, bestGuessConversions) case NAME_LIST: @@ -350,7 +314,6 @@ function correctField(fieldName, value, bestGuessConversions) { case TYPE: return correctType(value, bestGuessConversions) } - if (bestGuessConversions) { if ( typeof value === 'string' && @@ -369,37 +332,20 @@ function correctField(fieldName, value, bestGuessConversions) { return correctField(fieldName, value[0], bestGuessConversions) } } - if (fieldType.includes(typeof value)) { return value } } - -/** - * Make CSL JSON conform to standards so that plugins don't have to typecheck all the time. - * - * @access protected - * @method clean - * @memberof module:@citation-js/core.plugins.input.util - * - * @param {Array} data - Array of CSL - * @param {Boolean} [bestGuessConversions=true] - make some best guess conversions on type mismatch - * - * @return {Array} Array of clean CSL - */ function parseCsl(data, bestGuessConversions = true) { return data.map(function (entry) { const clean = {} - for (const field in entry) { const correction = correctField(field, entry[field], bestGuessConversions) if (correction !== undefined) { clean[field] = correction } } - return clean }) } - export { parseCsl as clean } diff --git a/src/citation-js/core/plugins/input/data.js b/src/citation-js/core/plugins/input/data.js index 367111c..0f1a105 100644 --- a/src/citation-js/core/plugins/input/data.js +++ b/src/citation-js/core/plugins/input/data.js @@ -1,28 +1,6 @@ -// @ts-nocheck import { chain, chainAsync } from './chain.js' - -/** - * @access private - * @memberof module:@citation-js/core.plugins.input - * - * @typedef {Object} parsers - */ const parsers = {} - -/** - * @access private - * @memberof module:@citation-js/core.plugins.input - * - * @typedef {Object} asyncParsers - */ const asyncParsers = {} - -/** - * @access private - * @memberof module:@citation-js/core.plugins.input - * - * @typedef {Object} nativeParsers - */ const nativeParsers = { '@csl/object': (input) => [input], '@csl/list+object': (input) => input, @@ -31,48 +9,18 @@ const nativeParsers = { throw new Error('This format is not supported or recognized') }, } - -/** - * @access private - * @memberof module:@citation-js/core.plugins.input - * - * @typedef {Object} nativeAsyncParsers - */ const nativeAsyncParsers = { '@else/list+object': async (input) => (await Promise.all(input.map(chainAsync))).flat(), } - -/** - * @access public - * @method data - * @memberof module:@citation-js/core.plugins.input - * - * @param {module:@citation-js/core~InputData} input - input data - * @param {module:@citation-js/core.plugins.input~format} type - input type - * - * @return {*} parsed data - * @return {Null} if no parser available - */ export function data(input, type) { if (typeof parsers[type] === 'function') { return parsers[type](input) + } else if (typeof nativeParsers[type] === 'function') { + return nativeParsers[type](input) } else { - throw new Error('This format is not supported or recognized') - // throw new TypeError(`No synchronous parser found for ${type}`) + throw new TypeError(`No synchronous parser found for ${type}`) } } - -/** - * @access public - * @method dataAsync - * @memberof module:@citation-js/core.plugins.input - * - * @param {module:@citation-js/core~InputData} input - input data - * @param {module:@citation-js/core.plugins.input~format} type - input type - * - * @return {Promise} parsed data - * @return {Promise} if no parser available - */ export async function dataAsync(input, type) { if (typeof asyncParsers[type] === 'function') { return asyncParsers[type](input) @@ -84,17 +32,6 @@ export async function dataAsync(input, type) { throw new TypeError(`No parser found for ${type}`) } } - -/** - * @access protected - * @method addDataParser - * @memberof module:@citation-js/core.plugins.input - * - * @param {module:@citation-js/core.plugins.input~format} format - * @param {module:@citation-js/core.plugins.input~parse|module:@citation-js/core.plugins.input~parseAsync} parser - * @param {Object} [options={}] - * @param {Boolean} [options.async=false] - */ export function addDataParser(format, { parser, async }) { if (async) { asyncParsers[format] = parser @@ -102,42 +39,14 @@ export function addDataParser(format, { parser, async }) { parsers[format] = parser } } - -/** - * @access public - * @method hasDataParser - * @memberof module:@citation-js/core.plugins.input - * - * @param {module:@citation-js/core.plugins.input~format} type - * @param {Boolean} [async=false] - check only for async, or only sync - * - * @return {Boolean} parser exists - */ export function hasDataParser(type, async) { return async ? asyncParsers[type] || nativeAsyncParsers[type] : parsers[type] || nativeParsers[type] } - -/** - * @access public - * @method removeDataParser - * @memberof module:@citation-js/core.plugins.input - * - * @param {module:@citation-js/core.plugins.input~format} type - * @param {Boolean} [async=false] - */ export function removeDataParser(type, async) { delete (async ? asyncParsers : parsers)[type] } - -/** - * @access public - * @method listDataParser - * @memberof module:@citation-js/core.plugins.input - * - * @param {Boolean} [async=false] - */ export function listDataParser(async) { return Object.keys(async ? asyncParsers : parsers) } diff --git a/src/citation-js/core/plugins/input/dataType.js b/src/citation-js/core/plugins/input/dataType.js index 053cbe5..2d0c9d6 100644 --- a/src/citation-js/core/plugins/input/dataType.js +++ b/src/citation-js/core/plugins/input/dataType.js @@ -1,14 +1,3 @@ -/** - * Gets the constructor name, with a special case for `null` and `undefined` - * - * @access public - * @method typeOf - * @memberof module:@citation-js/core.plugins.input.util - * - * @param {*} thing - input data or anything else - * - * @return {String} type - */ export function typeOf(thing) { switch (thing) { case undefined: @@ -19,21 +8,10 @@ export function typeOf(thing) { return thing.constructor.name } } - -/** - * @access public - * @method dataTypeOf - * @memberof module:@citation-js/core.plugins.input.util - * - * @param {*} thing - input data or anything else - * - * @return {module:@citation-js/core.plugins.input~dataType} dataType - */ export function dataTypeOf(thing) { switch (typeof thing) { case 'string': return 'String' - case 'object': if (Array.isArray(thing)) { return 'Array' @@ -42,8 +20,6 @@ export function dataTypeOf(thing) { } else if (typeOf(thing) !== 'Null') { return 'ComplexObject' } - // fall through when thing === null, return default value - default: return 'Primitive' } diff --git a/src/citation-js/core/plugins/input/graph.js b/src/citation-js/core/plugins/input/graph.js index a422cee..8f1a880 100644 --- a/src/citation-js/core/plugins/input/graph.js +++ b/src/citation-js/core/plugins/input/graph.js @@ -1,15 +1,3 @@ -/** - * Apply a parse chain graph to an element - * - * @access protected - * @method applyGraph - * @memberof module:@citation-js/core.plugins.input.util - * - * @param {module:@citation-js/core~CSL} entry - * @param {Array} graph - * - * @return {module:@citation-js/core~CSL} entry - */ export function applyGraph(entry, graph) { if (entry._graph) { const index = graph.findIndex(({ type }) => type === '@else/list+object') @@ -17,22 +5,9 @@ export function applyGraph(entry, graph) { graph.splice(index + 1, 0, ...entry._graph.slice(0, -1)) } } - entry._graph = graph return entry } - -/** - * Remove the parse chain graph from an element - * - * @access protected - * @method removeGraph - * @memberof module:@citation-js/core.plugins.input.util - * - * @param {module:@citation-js/core~CSL} entry - * - * @return {module:@citation-js/core~CSL} entry - */ export function removeGraph(entry) { delete entry._graph return entry diff --git a/src/citation-js/core/plugins/input/index.js b/src/citation-js/core/plugins/input/index.js index 35dfd0b..3677ba3 100644 --- a/src/citation-js/core/plugins/input/index.js +++ b/src/citation-js/core/plugins/input/index.js @@ -1,21 +1,9 @@ -/** - * @namespace input - * @memberof module:@citation-js/core.plugins - */ - import * as dataType from './dataType.js' import * as graph from './graph.js' import * as parser from './parser.js' import * as csl from './csl.js' - -/** - * @namespace util - * @memberof module:@citation-js/core.plugins.input - */ export const util = Object.assign({}, dataType, graph, parser, csl) - export * from './register' - export * from './chain' export * from './type' export * from './data' diff --git a/src/citation-js/core/plugins/input/parser.js b/src/citation-js/core/plugins/input/parser.js index 5493457..255def2 100644 --- a/src/citation-js/core/plugins/input/parser.js +++ b/src/citation-js/core/plugins/input/parser.js @@ -1,62 +1,62 @@ // @ts-nocheck +function _defineProperty(obj, key, value) { + key = _toPropertyKey(key) + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true, + }) + } else { + obj[key] = value + } + return obj +} +function _toPropertyKey(t) { + var i = _toPrimitive(t, 'string') + return 'symbol' == typeof i ? i : i + '' +} +function _toPrimitive(t, r) { + if ('object' != typeof t || !t) return t + var e = t[Symbol.toPrimitive] + if (void 0 !== e) { + var i = e.call(t, r || 'default') + if ('object' != typeof i) return i + throw new TypeError('@@toPrimitive must return a primitive value.') + } + return ('string' === r ? String : Number)(t) +} import { type, typeMatcher } from './type.js' - -/** - * @memberof module:@citation-js/core.plugins.input.util - * @param {module:@citation-js/core.plugins.input~typeParser} data - */ class TypeParser { - /** - * @access protected - * @type {Array} - */ - validDataTypes = ['String', 'Array', 'SimpleObject', 'ComplexObject', 'Primitive'] - constructor(data) { + _defineProperty(this, 'validDataTypes', [ + 'String', + 'Array', + 'SimpleObject', + 'ComplexObject', + 'Primitive', + ]) this.data = data } - - // ========================================================================== - // Validation - // ========================================================================== - - /** - * @access protected - * @throws {RangeError} if dataType is not valid - */ validateDataType() { const dataType = this.data.dataType if (dataType && !this.validDataTypes.includes(dataType)) { throw new RangeError(`dataType was ${dataType}; expected one of ${this.validDataTypes}`) } } - - /** - * @access protected - * @throws {TypeError} if predicate is not valid - */ validateParseType() { const predicate = this.data.predicate if (predicate && !(predicate instanceof RegExp || typeof predicate === 'function')) { throw new TypeError(`predicate was ${typeof predicate}; expected RegExp or function`) } } - - /** - * @access protected - * @throws {TypeError} if predicate is not valid - */ validateTokenList() { const tokenList = this.data.tokenList if (tokenList && typeof tokenList !== 'object') { throw new TypeError(`tokenList was ${typeof tokenList}; expected object or RegExp`) } } - - /** - * @access protected - * @throws {TypeError} if propertyConstraint is not valid - */ validatePropertyConstraint() { const propertyConstraint = this.data.propertyConstraint if (propertyConstraint && typeof propertyConstraint !== 'object') { @@ -65,33 +65,18 @@ class TypeParser { ) } } - - /** - * @access protected - * @throws {TypeError} if elementConstraint is not valid - */ validateElementConstraint() { const elementConstraint = this.data.elementConstraint if (elementConstraint && typeof elementConstraint !== 'string') { throw new TypeError(`elementConstraint was ${typeof elementConstraint}; expected string`) } } - - /** - * @access protected - * @throws {TypeError} if extends is not valid - */ validateExtends() { const extend = this.data.extends if (extend && typeof extend !== 'string') { throw new TypeError(`extends was ${typeof extend}; expected string`) } } - - /** - * @access public - * @throws {TypeError|RangeError} if typeParser is not valid - */ validate() { if (this.data === null || typeof this.data !== 'object') { throw new TypeError(`typeParser was ${typeof this.data}; expected object`) @@ -103,73 +88,44 @@ class TypeParser { this.validateElementConstraint() this.validateExtends() } - - // ========================================================================== - // Simplification helpers - // ========================================================================== - - /** - * @access protected - * @return {Array} - */ parseTokenList() { let tokenList = this.data.tokenList - if (!tokenList) { return [] } else if (tokenList instanceof RegExp) { - tokenList = { token: tokenList } + tokenList = { + token: tokenList, + } } - const { token, split = /\s+/, trim = true, every = true } = tokenList - const trimInput = (input) => (trim ? input.trim() : input) const testTokens = every ? 'every' : 'some' - const predicate = (input) => trimInput(input) .split(split) [testTokens]((part) => token.test(part)) - return [predicate] } - - /** - * @access protected - * @return {Array} - */ parsePropertyConstraint() { const constraints = [].concat(this.data.propertyConstraint || []) - return constraints.map(({ props, match, value }) => { props = [].concat(props) - switch (match) { - case 'any': // fall-through + case 'any': case 'some': return (input) => props.some((prop) => prop in input && (!value || value(input[prop]))) case 'none': return (input) => !props.some((prop) => prop in input && (!value || value(input[prop]))) - case 'every': // fall-through + case 'every': default: return (input) => props.every((prop) => prop in input && (!value || value(input[prop]))) } }) } - - /** - * @access protected - * @return {Array} - */ parseElementConstraint() { const constraint = this.data.elementConstraint return !constraint ? [] : [(input) => input.every((entry) => type(entry) === constraint)] } - - /** - * @access protected - * @return {Array} - */ parsePredicate() { if (this.data.predicate instanceof RegExp) { return [this.data.predicate.test.bind(this.data.predicate)] @@ -179,11 +135,6 @@ class TypeParser { return [] } } - - /** - * @access protected - * @return {module:@citation-js/core.plugins.input~predicate} - */ getCombinedPredicate() { const predicates = [ ...this.parsePredicate(), @@ -191,7 +142,6 @@ class TypeParser { ...this.parsePropertyConstraint(), ...this.parseElementConstraint(), ] - if (predicates.length === 0) { return () => true } else if (predicates.length === 1) { @@ -200,11 +150,6 @@ class TypeParser { return (input) => predicates.every((predicate) => predicate(input)) } } - - /** - * @access protected - * @return {module:@citation-js/core.plugins.input~dataType} - */ getDataType() { if (this.data.dataType) { return this.data.dataType @@ -218,52 +163,21 @@ class TypeParser { return 'Primitive' } } - - // ========================================================================== - // Data simplification - // ========================================================================== - - /** - * @type {module:@citation-js/core.plugins.input~dataType} - */ get dataType() { return this.getDataType() } - - /** - * @type {module:@citation-js/core.plugins.input~predicate} - */ get predicate() { return this.getCombinedPredicate() } - - /** - * @type {module:@citation-js/core.plugins.input~format} - */ get extends() { return this.data.extends } } - -/** - * @memberof module:@citation-js/core.plugins.input.util - * @param {module:@citation-js/core.plugins.input~dataParser|module:@citation-js/core.plugins.input~asyncDataParser} parser - * @param {Object} options - * @param {Boolean} [options.async=false] - */ class DataParser { constructor(parser, { async } = {}) { this.parser = parser this.async = async } - - // ========================================================================== - // Validation - // ========================================================================== - - /** - * @throws {TypeError} if dataParser is not valid - */ validate() { const parser = this.parser if (typeof parser !== 'function') { @@ -271,45 +185,29 @@ class DataParser { } } } - -/** - * @memberof module:@citation-js/core.plugins.input.util - * @param {module:@citation-js/core.plugins.input~format} format - * @param {module:@citation-js/core.plugins.input~parsers} parsers - */ class FormatParser { constructor(format, parsers = {}) { this.format = format - if (parsers.parseType) { this.typeParser = new TypeParser(parsers.parseType) } if (parsers.parse) { - this.dataParser = new DataParser(parsers.parse, { async: false }) + this.dataParser = new DataParser(parsers.parse, { + async: false, + }) } if (parsers.parseAsync) { - this.asyncDataParser = new DataParser(parsers.parseAsync, { async: true }) + this.asyncDataParser = new DataParser(parsers.parseAsync, { + async: true, + }) } } - - // ========================================================================== - // Validation - // ========================================================================== - - /** - * @access protected - * @throws {TypeError} if format is not valid - */ validateFormat() { const format = this.format if (!typeMatcher.test(format)) { throw new TypeError(`format name was "${format}"; didn't match expected pattern`) } } - - /** - * @throws {TypeError} if formatParser is not valid - */ validate() { this.validateFormat() if (this.typeParser) { @@ -323,5 +221,4 @@ class FormatParser { } } } - export { TypeParser, DataParser, FormatParser } diff --git a/src/citation-js/core/plugins/input/register.js b/src/citation-js/core/plugins/input/register.js index e6e9a2d..6ac39f3 100644 --- a/src/citation-js/core/plugins/input/register.js +++ b/src/citation-js/core/plugins/input/register.js @@ -1,32 +1,11 @@ -// @ts-nocheck import { FormatParser } from './parser.js' import { addTypeParser, removeTypeParser } from './type.js' import { addDataParser, removeDataParser } from './data.js' - -/** - * @access private - * @type {Object} - */ const formats = {} - -/** - * See the relevant tutorial: {@tutorial input_plugins} - * - * @access public - * @method add - * @memberof module:@citation-js/core.plugins.input - * - * @param {module:@citation-js/core.plugins.input~format} format - input format name - * @param {module:@citation-js/core.plugins.input~parsers} parsers - parsers - * - * @tutorial input_plugins - */ export function add(format, parsers) { const formatParser = new FormatParser(format, parsers) formatParser.validate() - const index = formats[format] || (formats[format] = {}) - if (formatParser.typeParser) { addTypeParser(format, formatParser.typeParser) index.type = true @@ -39,38 +18,18 @@ export function add(format, parsers) { addDataParser(format, formatParser.asyncDataParser) index.asyncData = true } - if (parsers.outputs) { index.outputs = parsers.outputs } } - -/** - * @access public - * @method get - * @memberof module:@citation-js/core.plugins.input - * - * @param {module:@citation-js/core.plugins.input~format} format - input format name - * @returns {Object} index - */ export function get(format) { return formats[format] } - -/** - * @access public - * @method remove - * @memberof module:@citation-js/core.plugins.input - * - * @param {module:@citation-js/core.plugins.input~format} format - input format name - */ export function remove(format) { const index = formats[format] - if (!index) { return } - if (index.type) { removeTypeParser(format) } @@ -80,27 +39,11 @@ export function remove(format) { if (index.asyncData) { removeDataParser(format, true) } - delete formats[format] } - -/** - * @access public - * @method has - * @memberof module:@citation-js/core.plugins.input - * @param {module:@citation-js/core.plugins.input~format} format - input format name - * @returns {Boolean} input format is registered - */ export function has(format) { return format in formats } - -/** - * @access public - * @method list - * @memberof module:@citation-js/core.plugins.input - * @returns {Array} input format is registered - */ export function list() { return Object.keys(formats) } diff --git a/src/citation-js/core/plugins/input/type.js b/src/citation-js/core/plugins/input/type.js index 64760e9..fb50019 100644 --- a/src/citation-js/core/plugins/input/type.js +++ b/src/citation-js/core/plugins/input/type.js @@ -1,24 +1,8 @@ -// @ts-nocheck import logger from '../../logger.js' import { dataTypeOf } from './dataType.js' - -// register const types = {} const dataTypes = {} - -// extensions not registered as such const unregExts = {} - -/** - * Hard-coded, for reasons - * - * @access private - * @memberof module:@citation-js/core.plugins.input - * - * @param {module:@citation-js/core~InputData} input - * @param {module:@citation-js/core.plugins.input~dataType} dataType - * @return {module:@citation-js/core.plugins.input~format} native format - */ function parseNativeTypes(input, dataType) { switch (dataType) { case 'Array': @@ -27,26 +11,13 @@ function parseNativeTypes(input, dataType) { } else { return '@else/list+object' } - case 'SimpleObject': case 'ComplexObject': - // might, of course, be something completely else, but this is how the parser works return '@csl/object' - default: return '@invalid' } } - -/** - * @access private - * @memberof module:@citation-js/core.plugins.input - * - * @param {Array} [typeList=[]] - * @param {module:@citation-js/core~InputData} data - * - * @return {module:@citation-js/core.plugins.input~format} native format - */ function matchType(typeList = [], data) { for (const type of typeList) { if (types[type].predicate(data)) { @@ -54,43 +25,15 @@ function matchType(typeList = [], data) { } } } - -/** - * @access public - * @method type - * @memberof module:@citation-js/core.plugins.input - * - * @param {module:@citation-js/core~InputData} input - * - * @return {module:@citation-js/core.plugins.input~format} type - */ export function type(input) { const dataType = dataTypeOf(input) - - // Empty array should be @csl/list+object too if (dataType === 'Array' && input.length === 0) { - // Off-load to parseNativeTypes() to not repeat the name - // '@csl/list+object' here as well, as it might change return parseNativeTypes(input, dataType) } - const match = matchType(dataTypes[dataType], input) - - // If no matching formats found, test if native format, - // else invalid input. return match || parseNativeTypes(input, dataType) } - -/** - * @access public - * @method addTypeParser - * @memberof module:@citation-js/core.plugins.input - * - * @param {module:@citation-js/core.plugins.input~format} format - * @param {module:@citation-js/core.plugins.input.util.TypeParser} typeParser - */ export function addTypeParser(format, { dataType, predicate, extends: extend }) { - // 1. check if any subclass formats are waiting for this format let extensions = [] if (format in unregExts) { extensions = unregExts[format] @@ -100,21 +43,16 @@ export function addTypeParser(format, { dataType, predicate, extends: extend }) `Subclasses "${extensions}" finally registered to parent type "${format}"` ) } - - // 2. create object with parser info - const object = { predicate, extensions } + const object = { + predicate, + extensions, + } types[format] = object - - // 3. determine which type lists the type should be added to if (extend) { - // 3.1. if format is subclass, check if parent type is registered const parentTypeParser = types[extend] - if (parentTypeParser) { - // 3.1.1. if it is, add the type parser parentTypeParser.extensions.push(format) } else { - // 3.1.2. if it isn't, register type as waiting if (!unregExts[extend]) { unregExts[extend] = [] } @@ -122,36 +60,15 @@ export function addTypeParser(format, { dataType, predicate, extends: extend }) logger.debug('[core]', `Subclass "${format}" is waiting on parent type "${extend}"`) } } else { - // 3.2. else, add const typeList = dataTypes[dataType] || (dataTypes[dataType] = []) typeList.push(format) } } - -/** - * @access public - * @method hasTypeParser - * @memberof module:@citation-js/core.plugins.input - * - * @param {module:@citation-js/core.plugins.input~format} type - * - * @return {Boolean} type parser is registered - */ export function hasTypeParser(type) { return Object.prototype.hasOwnProperty.call(types, type) } - -/** - * @access public - * @method removeTypeParser - * @memberof module:@citation-js/core.plugins.input - * - * @param {module:@citation-js/core.plugins.input~format} type - */ export function removeTypeParser(type) { delete types[type] - - // Removing orphaned type refs const typeLists = [ ...Object.keys(dataTypes).map((key) => dataTypes[key]), ...Object.keys(types) @@ -165,28 +82,14 @@ export function removeTypeParser(type) { } }) } - -/** - * @access public - * @method listTypeParser - * @memberof module:@citation-js/core.plugins.input - * - * @return {Array} list of registered type parsers - */ export function listTypeParser() { return Object.keys(types) } - -/** - * @access public - * @method treeTypeParser - * @memberof module:@citation-js/core.plugins.input - * - * @return {Object} tree structure - */ -/* istanbul ignore next: debugging */ export function treeTypeParser() { - const attachNode = (name) => ({ name, children: types[name].extensions.map(attachNode) }) + const attachNode = (name) => ({ + name, + children: types[name].extensions.map(attachNode), + }) return { name: 'Type tree', children: Object.keys(dataTypes).map((name) => ({ @@ -195,13 +98,4 @@ export function treeTypeParser() { })), } } - -/** - * Validate and parse the format name - * - * @access public - * @method typeMatcher - * @memberof module:@citation-js/core.plugins.input - * @type {RegExp} - */ export const typeMatcher = /^(?:@(.+?))(?:\/(?:(.+?)\+)?(?:(.+)))?$/ diff --git a/src/citation-js/core/plugins/input/types.jsdoc b/src/citation-js/core/plugins/input/types.jsdoc index dfc15ae..d79843a 100644 --- a/src/citation-js/core/plugins/input/types.jsdoc +++ b/src/citation-js/core/plugins/input/types.jsdoc @@ -74,4 +74,4 @@ /** * @typedef module:@citation-js/core.plugins.input~elementConstraint * @type module:@citation-js/core.plugins.input~format - */ \ No newline at end of file + */ diff --git a/src/citation-js/core/plugins/output.js b/src/citation-js/core/plugins/output.js index c59572c..03e1957 100644 --- a/src/citation-js/core/plugins/output.js +++ b/src/citation-js/core/plugins/output.js @@ -1,33 +1,4 @@ -// @ts-nocheck -/** - * @namespace output - * @memberof module:@citation-js/core.plugins - */ - import Register from '../util/register.js' - -/** - * @callback module:@citation-js/core.plugins.output~formatter - * @param {Array} data - * @return {String} output - */ - -/** - * @typedef module:@citation-js/core.plugins.output~formatterName - * @type String - */ - -/** - * Validate input arguments - * - * @access private - * @memberof module:@citation-js/core.plugins.output - * - * @param {String} name - output format name - * @param {module:@citation-js/core.plugins.output~formatter} formatter - outputting function - * @throws {TypeError} Invalid output format name - * @throws {TypeError} Invalid formatter - */ function validate(name, formatter) { if (typeof name !== 'string') { throw new TypeError(`Invalid output format name, expected string, got ${typeof name}`) @@ -35,84 +6,20 @@ function validate(name, formatter) { throw new TypeError(`Invalid formatter, expected function, got ${typeof formatter}`) } } - -/** - * @access public - * @memberof module:@citation-js/core.plugins.output - * @constant register - * - * @type module:@citation-js/core.util.Register - */ export const register = new Register() - -/** - * Add output plugin. - * - * @access public - * @memberof module:@citation-js/core.plugins.output - * @method add - * - * @param {module:@citation-js/core.plugins.output~formatterName} name - output format name - * @param {module:@citation-js/core.plugins.output~formatter} formatter - outputting function - * @throws {TypeError} validation errors - */ export function add(name, formatter) { validate(name, formatter) - register.set(name, formatter) } - -/** - * Remove output plugin. - * - * @access public - * @memberof module:@citation-js/core.plugins.output - * @method remove - * - * @param {module:@citation-js/core.plugins.output~formatterName} name - output format name - */ export function remove(name) { register.remove(name) } - -/** - * Check if output plugin exists. - * - * @access public - * @memberof module:@citation-js/core.plugins.output - * @method has - * - * @param {module:@citation-js/core.plugins.output~formatterName} name - output format name - * @return {Boolean} register has plugin - */ export function has(name) { return register.has(name) } - -/** - * List output plugins. - * - * @access public - * @memberof module:@citation-js/core.plugins.output - * @method list - * - * @return {Array} list of plugins - */ export function list() { return register.list() } - -/** - * Call output plugin - * - * @access public - * @memberof module:@citation-js/core.plugins.output - * @method format - * - * @param {module:@citation-js/core.plugins.output~formatterName} name - output format name - * @param {Array} data - all entries - * @param {...*} options - output options - */ export function format(name, data, ...options) { if (!register.has(name)) { throw new Error(`Output format "${name}" unavailable`) diff --git a/src/citation-js/core/util/csl.js b/src/citation-js/core/util/csl.js index 549eaa7..e085b1f 100644 --- a/src/citation-js/core/util/csl.js +++ b/src/citation-js/core/util/csl.js @@ -1,19 +1,63 @@ -/** - * Upgrade CSL item from 1.0.1 to 1.0.2 - * - * @method upgradeCsl - * @memberof module:@citation-js/core.util - * - * @param {Object} item - Input object - * - * @return {Object} upgraded item - */ +function ownKeys(e, r) { + var t = Object.keys(e) + if (Object.getOwnPropertySymbols) { + var o = Object.getOwnPropertySymbols(e) + r && + (o = o.filter(function (r) { + return Object.getOwnPropertyDescriptor(e, r).enumerable + })), + t.push.apply(t, o) + } + return t +} +function _objectSpread(e) { + for (var r = 1; r < arguments.length; r++) { + var t = null != arguments[r] ? arguments[r] : {} + r % 2 + ? ownKeys(Object(t), !0).forEach(function (r) { + _defineProperty(e, r, t[r]) + }) + : Object.getOwnPropertyDescriptors + ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) + : ownKeys(Object(t)).forEach(function (r) { + Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)) + }) + } + return e +} +function _defineProperty(obj, key, value) { + key = _toPropertyKey(key) + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true, + }) + } else { + obj[key] = value + } + return obj +} +function _toPropertyKey(t) { + var i = _toPrimitive(t, 'string') + return 'symbol' == typeof i ? i : i + '' +} +function _toPrimitive(t, r) { + if ('object' != typeof t || !t) return t + var e = t[Symbol.toPrimitive] + if (void 0 !== e) { + var i = e.call(t, r || 'default') + if ('object' != typeof i) return i + throw new TypeError('@@toPrimitive must return a primitive value.') + } + return ('string' === r ? String : Number)(t) +} export function upgradeCsl(item) { if (Array.isArray(item)) { return item.map(upgradeCsl) } - - item = { ...item } + item = _objectSpread({}, item) if ('event' in item) { item['event-title'] = item.event delete item.event @@ -23,23 +67,11 @@ export function upgradeCsl(item) { } return item } - -/** - * Downgrade CSL item from 1.0.2 to 1.0.1 - * - * @method downgradeCsl - * @memberof module:@citation-js/core.util - * - * @param {Object} item - Input object - * - * @return {Object} downgraded item - */ export function downgradeCsl(item) { if (Array.isArray(item)) { return item.map(downgradeCsl) } - - item = { ...item } + item = _objectSpread({}, item) if ('event-title' in item) { item.event = item['event-title'] delete item['event-title'] diff --git a/src/citation-js/core/util/deepCopy.js b/src/citation-js/core/util/deepCopy.js index 6c64429..131705c 100644 --- a/src/citation-js/core/util/deepCopy.js +++ b/src/citation-js/core/util/deepCopy.js @@ -1,17 +1,3 @@ -// @ts-nocheck -/** - * Duplicate objects to prevent Cite changing values outside of own scope - * - * @access protected - * @method deepCopy - * @memberof module:@citation-js/core.util - * - * @param {Object} obj - Input object - * @param {Set} [seen] - * - * @return {Object} Duplicated object - * @throws {TypeError} - */ export function deepCopy(value, seen = new Set()) { if ( typeof value !== 'object' || @@ -20,14 +6,11 @@ export function deepCopy(value, seen = new Set()) { ) { return value } - if (seen.has(value)) { throw new TypeError('Recursively copying circular structure') } - seen.add(value) let copy - if (value.constructor === Array) { copy = value.map((value) => deepCopy(value, seen)) } else { @@ -37,19 +20,7 @@ export function deepCopy(value, seen = new Set()) { } copy = object } - seen.delete(value) return copy } - -// // Unfortunately too costly performance-wise, but the -// // proper way to clone any object (apart from arrays perhaps): -// const object = Object.create(Object.getPrototypeOf(value)) -// const descriptors = Object.getOwnPropertyDescriptors(value) -// for (let key in descriptors) { -// const descriptor = descriptors[key] -// if (descriptor.value) { descriptor.value = deepCopy(descriptor.value) } -// Object.defineProperty(object, key, descriptor) -// } - export default deepCopy diff --git a/src/citation-js/core/util/fetchFile.js b/src/citation-js/core/util/fetchFile.js index cbde458..c879221 100644 --- a/src/citation-js/core/util/fetchFile.js +++ b/src/citation-js/core/util/fetchFile.js @@ -1,26 +1,38 @@ -//@ts-nocheck +// @ts-nocheck import syncFetch from 'sync-fetch' import fetchPolyfill from 'fetch-ponyfill' import logger from '../logger.js' -// import pkg from '../../package.json'; -const { fetch, Headers } = fetchPolyfill() -const corsEnabled = typeof location !== 'undefined' && typeof document !== 'undefined' -let userAgent = `Citation.js/0.65` +const isBrowser = typeof location !== 'undefined' && typeof navigator !== 'undefined' +const { fetch: asyncFetch, Headers: asyncHeaders } = + typeof fetch === 'function' && isBrowser + ? { + fetch, + Headers, + } + : fetchPolyfill() + +let userAgent = `Citation.js/0.7` +if ( + typeof process !== 'undefined' && + process && + process.release && + process.release.name === 'node' && + process.version +) { + userAgent += ` Node.js/${process.version}` +} function normaliseHeaders(headers) { const result = {} const entries = - headers instanceof Headers || headers instanceof syncFetch.Headers + headers instanceof asyncHeaders || headers instanceof syncFetch.Headers ? Array.from(headers) : Object.entries(headers) - for (const [name, header] of entries) { result[name.toLowerCase()] = header.toString() } - return result } - function parseOpts(opts = {}) { const reqOpts = { headers: { @@ -29,57 +41,46 @@ function parseOpts(opts = {}) { method: 'GET', checkContentType: opts.checkContentType, } - - if (userAgent && !corsEnabled) { + if (userAgent && !isBrowser) { reqOpts.headers['user-agent'] = userAgent } - if (opts.body) { reqOpts.method = 'POST' const isJson = typeof opts.body !== 'string' reqOpts.body = isJson ? JSON.stringify(opts.body) : opts.body reqOpts.headers['content-type'] = isJson ? 'application/json' : 'text/plain' } - if (opts.headers) { Object.assign(reqOpts.headers, normaliseHeaders(opts.headers)) } - return reqOpts } - function sameType(request, response) { if (!request.accept || request.accept === '*/*' || !response['content-type']) { return true } - const [a, b] = response['content-type'].split(';')[0].trim().split('/') return request.accept .split(',') .map((type) => type.split(';')[0].trim().split('/')) .some(([c, d]) => (c === a || c === '*') && (d === b || d === '*')) } - function checkResponse(response, opts) { const { status, headers } = response let error - if (status >= 400) { error = new Error(`Server responded with status code ${status}`) } else if (opts.checkContentType === true && !sameType(opts.headers, normaliseHeaders(headers))) { error = new Error(`Server responded with content-type ${headers.get('content-type')}`) } - if (error) { error.status = status error.headers = headers error.body = response.body throw error } - return response } - export function fetchFile(url, opts) { const reqOpts = parseOpts(opts) logger.http('[core]', reqOpts.method, url, reqOpts) @@ -89,7 +90,7 @@ export function fetchFile(url, opts) { export async function fetchFileAsync(url, opts) { const reqOpts = parseOpts(opts) logger.http('[core]', reqOpts.method, url, reqOpts) - return fetch(url, reqOpts) + return asyncFetch(url, reqOpts) .then((response) => checkResponse(response, reqOpts)) .then((response) => response.text()) } diff --git a/src/citation-js/core/util/fetchFileAsync.js b/src/citation-js/core/util/fetchFileAsync.js index e3fe1c6..a997df8 100644 --- a/src/citation-js/core/util/fetchFileAsync.js +++ b/src/citation-js/core/util/fetchFileAsync.js @@ -1,3 +1,2 @@ import { fetchFileAsync } from './fetchFile.js' - export default fetchFileAsync diff --git a/src/citation-js/core/util/fetchId.js b/src/citation-js/core/util/fetchId.js index 7b8b2b5..8552539 100644 --- a/src/citation-js/core/util/fetchId.js +++ b/src/citation-js/core/util/fetchId.js @@ -1,22 +1,8 @@ -/** - * Generate ID - * - * @access protected - * @memberof module:@citation-js/core.util - * - * @param {Array} list - old ID list - * @param {String} prefix - ID prefix - * - * @return {String} CSL ID - */ function fetchId(list, prefix) { let id - while (id === undefined || list.includes(id)) { id = `${prefix}${Math.random().toString().slice(2)}` } - return id } - export default fetchId diff --git a/src/citation-js/core/util/grammar.js b/src/citation-js/core/util/grammar.js index e4b7214..67c6540 100644 --- a/src/citation-js/core/util/grammar.js +++ b/src/citation-js/core/util/grammar.js @@ -1,22 +1,4 @@ -// @ts-nocheck import { deepCopy } from './deepCopy.js' - -/** - * @typedef module:@citation-js/core.util.Grammar~ruleName - * @type {String} - */ - -/** - * @callback module:@citation-js/core.util.Grammar~rule - * @this module:@citation-js/core.util.Grammar - */ - -/** - * @memberof module:@citation-js/core.util - * - * @param {Object} rules - * @param {Object} state - */ class Grammar { constructor(rules, state) { this.rules = rules @@ -24,12 +6,6 @@ class Grammar { this.mainRule = Object.keys(rules)[0] this.log = [] } - - /** - * @param iterator - lexer supporting formatError() and next() - * @param [mainRule] - defaults to the first rule - * @return result of the main rule - */ parse(iterator, mainRule) { this.lexer = iterator this.token = this.lexer.next() @@ -37,31 +13,14 @@ class Grammar { this.log = [] return this.consumeRule(mainRule || this.mainRule) } - - /** - * @return {Boolean} true if there are no more tokens - */ matchEndOfFile() { return !this.token } - - /** - * @param {String} type - a token type - * @return {Boolean} true if the current token has the given type - */ matchToken(type) { return this.token && type === this.token.type } - - /** - * @param {String} [type] - a token type - * @param {Boolean} [optional=false] - false if it should throw an error if the type does not match - * @return {Object} token information - * @throws {SyntaxError} detailed syntax error if the current token is not the expected type or if there are no tokens left - */ consumeToken(type, optional) { const token = this.token - if (!type || (token && token.type === type)) { this.token = this.lexer.next() return token @@ -74,11 +33,6 @@ class Grammar { throw error } } - - /** - * @param {String} rule - a rule name - * @return whatever the rule function returns - */ consumeRule(rule) { this.log.push(rule) const result = this.rules[rule].call(this) @@ -86,5 +40,4 @@ class Grammar { return result } } - export { Grammar } diff --git a/src/citation-js/core/util/index.js b/src/citation-js/core/util/index.js index 6356e5a..4faa4e6 100644 --- a/src/citation-js/core/util/index.js +++ b/src/citation-js/core/util/index.js @@ -1,8 +1,3 @@ -/** - * @namespace util - * @memberof module:@citation-js/core - */ - import { upgradeCsl, downgradeCsl } from './csl.js' import deepCopy from './deepCopy.js' import { fetchFile, fetchFileAsync, setUserAgent } from './fetchFile.js' @@ -11,7 +6,6 @@ import TokenStack from './stack.js' import Register from './register.js' import { Grammar } from './grammar.js' import { Translator } from './translator.js' - export { upgradeCsl, downgradeCsl, diff --git a/src/citation-js/core/util/register.js b/src/citation-js/core/util/register.js index d1d0aa6..33300c5 100644 --- a/src/citation-js/core/util/register.js +++ b/src/citation-js/core/util/register.js @@ -1,71 +1,29 @@ -// @ts-nocheck -/** - * @memberof module:@citation-js/core.util - * @param {Object} [data={}] - initial values - */ class Register { constructor(data = {}) { this.data = data } - - /** - * @param {String} key - * @param {*} value - * @return {Register} this - */ set(key, value) { this.data[key] = value return this } - - /** - * @param {String} key - * @param {*} value - * @return {Register} this - */ add(...args) { return this.set(...args) } - - /** - * @param {String} key - * @return {Register} this - */ delete(key) { delete this.data[key] return this } - - /** - * @param {String} key - * @return {Register} this - */ remove(...args) { return this.delete(...args) } - - /** - * @param {String} key - * @return {*} value - */ get(key) { return this.data[key] } - - /** - * @param {String} key - * @return {Boolean} register has key - */ has(key) { return Object.prototype.hasOwnProperty.call(this.data, key) } - - /** - * @return {Array} list of keys - */ list() { return Object.keys(this.data) } } - export default Register diff --git a/src/citation-js/core/util/stack.js b/src/citation-js/core/util/stack.js index d2e73f5..648e7f9 100644 --- a/src/citation-js/core/util/stack.js +++ b/src/citation-js/core/util/stack.js @@ -1,74 +1,13 @@ // @ts-nocheck -/** - * TokenStack pattern - * - * @typedef module:@citation-js/core.util.TokenStack~pattern - * @type {String|RegExp|module:@citation-js/core.util.TokenStack~match|Array} - */ - -/** - * TokenStack pattern sequence - * - * @typedef module:@citation-js/core.util.TokenStack~sequence - * @type {String|Array} - */ - -/** - * @callback module:@citation-js/core.util.TokenStack~match - * @param {String} token - token - * @param {Number} index - token index - * @param {Array} stack - token stack - * @return {Boolean} match or not - */ - -/** - * @callback module:@citation-js/core.util.TokenStack~tokenMap - * @param {String} token - token - * @return {String} new token - */ - -/** - * @callback module:@citation-js/core.util.TokenStack~tokenFilter - * @param {String} token - token - * @return {Boolean} keep or not - */ - -/** - * Create a TokenStack for parsing strings with complex escape sequences. - * - * @memberof module:@citation-js/core.util - * - * @param {Array} array - list of tokens - */ class TokenStack { constructor(array) { this.stack = array this.index = 0 this.current = this.stack[this.index] } - - /** - * Get string representation of pattern. - * - * @access protected - * - * @param {String|RegExp} pattern - pattern - * - * @return {String} string representation - */ static getPatternText(pattern) { return `"${pattern instanceof RegExp ? pattern.source : pattern}"` } - - /** - * Get a single callback to match a token against one or several patterns. - * - * @access protected - * - * @param {module:@citation-js/core.util.TokenStack~pattern} pattern - pattern - * - * @return {module:@citation-js/core.util.TokenStack~match} Match callback - */ static getMatchCallback(pattern) { if (Array.isArray(pattern)) { const matches = pattern.map(TokenStack.getMatchCallback) @@ -81,65 +20,22 @@ class TokenStack { return (token) => pattern === token } } - - /** - * Get a number representing the number of tokens that are left. - * - * @access protected - * - * @return {Number} tokens left - */ tokensLeft() { return this.stack.length - this.index } - - /** - * Match current token against pattern. - * - * @access protected - * - * @param {module:@citation-js/core.util.TokenStack~pattern} pattern - pattern - * - * @return {Boolean} match - */ matches(pattern) { return TokenStack.getMatchCallback(pattern)(this.current, this.index, this.stack) } - - /** - * Match current token against pattern. - * - * @access protected - * - * @param {module:@citation-js/core.util.TokenStack~sequence} pattern - pattern - * - * @return {Boolean} match - */ matchesSequence(sequence) { const part = this.stack.slice(this.index, this.index + sequence.length).join('') return typeof sequence === 'string' ? part === sequence : sequence.every((pattern, index) => TokenStack.getMatchCallback(pattern)(part[index])) } - - /** - * Consume a single token if possible, and throw if not. - * - * @access protected - * - * @param {module:@citation-js/core.util.TokenStack~pattern} [pattern=/^[\s\S]$/] - pattern - * @param {Object} options - * @param {Boolean} [options.inverse=false] - invert pattern - * @param {Boolean} [options.spaced=true] - allow leading and trailing whitespace - * - * @return {String} token - * @throws {SyntaxError} Unexpected token at index: Expected pattern, got token - */ consumeToken(pattern = /^[\s\S]$/, { inverse = false, spaced = true } = {}) { if (spaced) { this.consumeWhitespace() } - const token = this.current const match = TokenStack.getMatchCallback(pattern)(token, this.index, this.stack) if (match) { @@ -151,40 +47,16 @@ class TokenStack { )}, got "${token}"` ) } - if (spaced) { this.consumeWhitespace() } - return token } - - /** - * Consume a single token if possible, and throw if not. - * - * @access protected - * - * @param {module:@citation-js/core.util.TokenStack~pattern} [pattern=/^\s$/] - whitespace pattern - * @param {Object} options - * @param {Boolean} [options.optional=true] - allow having no whitespace - * - * @return {String} matched whitespace - * @throws {SyntaxError} Unexpected token at index: Expected whitespace, got token - */ consumeWhitespace(pattern = /^\s$/, { optional = true } = {}) { - return this.consume(pattern, { min: +!optional }) + return this.consume(pattern, { + min: +!optional, + }) } - - /** - * Consume n tokens. Throws if not enough tokens left - * - * @access protected - * - * @param {Number} length - number of tokens - * - * @return {String} consumed tokens - * @throws {SyntaxError} Not enough tokens left - */ consumeN(length) { if (this.tokensLeft() < length) { throw new SyntaxError('Not enough tokens left') @@ -195,17 +67,6 @@ class TokenStack { } return this.stack.slice(start, this.index).join('') } - - /** - * Consume a pattern spanning multiple tokens ('sequence'). - * - * @access protected - * - * @param {module:@citation-js/core.util.TokenStack~sequence} sequence - sequence - * - * @return {String} consumed tokens - * @throws {SyntaxError} Expected sequence, got tokens - */ consumeSequence(sequence) { if (this.matchesSequence(sequence)) { return this.consumeN(sequence.length) @@ -213,52 +74,28 @@ class TokenStack { throw new SyntaxError(`Expected "${sequence}", got "${this.consumeN(sequence.length)}"`) } } - - /** - * Consumes all consecutive tokens matching pattern. Throws if number of matched tokens not within range min-max. - * - * @access protected - * - * @param {module:@citation-js/core.util.TokenStack~pattern} [pattern=/^[\s\S]$/] - pattern - * @param {Object} options - * @param {Boolean} [options.inverse=false] - invert pattern - * @param {Number} [options.min=0] - mininum number of consumed tokens - * @param {Number} [options.max=Infinity] - maximum number of matched tokens - * @param {module:@citation-js/core.util.TokenStack~tokenMap} [options.tokenMap] - map tokens before returning - * @param {module:@citation-js/core.util.TokenStack~tokenFilter} [options.tokenFilter] - filter tokens before returning - * - * @return {String} consumed tokens - * @throws {SyntaxError} Not enough tokens - * @throws {SyntaxError} Too many tokens - */ consume( pattern = /^[\s\S]$/, { min = 0, max = Infinity, inverse = false, tokenMap, tokenFilter } = {} ) { const start = this.index const match = TokenStack.getMatchCallback(pattern) - while (match(this.current, this.index, this.stack) !== inverse) { this.current = this.stack[++this.index] } - let consumed = this.stack.slice(start, this.index) - if (consumed.length < min) { throw new SyntaxError(`Not enough ${TokenStack.getPatternText(pattern)}`) } else if (consumed.length > max) { throw new SyntaxError(`Too many ${TokenStack.getPatternText(pattern)}`) } - if (tokenMap) { consumed = consumed.map(tokenMap) } if (tokenFilter) { consumed = consumed.filter(tokenFilter) } - return consumed.join('') } } - export default TokenStack diff --git a/src/citation-js/core/util/translator.js b/src/citation-js/core/util/translator.js index 7461bd8..8b4d33d 100644 --- a/src/citation-js/core/util/translator.js +++ b/src/citation-js/core/util/translator.js @@ -1,71 +1,9 @@ // @ts-nocheck -/** - * Mapping unit. - * - * @typedef {Object} module:@citation-js/core.util.Translator~statement - * @property {String|Array} [source] - properties to source value from - * @property {String|Array} [target] - properties the value should go to - * @property {Object} [convert] - convert serialized or nested values - * @property {module:@citation-js/core.util.Translator~convertProp} [convert.toTarget] - function to convert source prop to target - * @property {module:@citation-js/core.util.Translator~convertProp} [convert.toSource] - function to convert target prop to source - * @property {Object} [when] - conditions as to when this statement should apply - * @property {module:@citation-js/core.util.Translator~condition} [when.source] - * @property {module:@citation-js/core.util.Translator~condition} [when.target] - */ - -/** - * In the case of toTarget, source is input and target is output. In the case of - * toSource, source is output and target is input. - * - * @callback module:@citation-js/core.util.Translator~convertProp - * @param {...*} input - input values - * @return {Array|*} If output is an array and multiple output properties are - * specified, the output is divided over those properties. - */ - -/** - * A top-level Boolean enables or disables a mapping unit in a given direction. - * Otherwise, individual properties are checked with an object specifying the - * property name and one of four things: - * - * - A boolean, checking for presence of the property - * - An array of values for checking whether the property value is in them - * - A value that should match with the property value - * - A predicate function taking in the value and returning a boolean - * - * All conditions have to be fulfilled for the mapping unit to be enabled. - * - * @typedef {Boolean|Object|module:@citation-js/core.util.Translator~conditionPropPredicate|*>} module:@citation-js/core.util.Translator~condition - */ - -/** - * Return, based on a property, whether a mapping should apply. - * - * @callback module:@citation-js/core.util.Translator~conditionPropPredicate - * @param {*} input - input value - * @return {Boolean} - */ - -/** - * Return, whether a mapping should apply. - * - * @callback module:@citation-js/core.util.Translator~conditionPredicate - * @param {Object} input - input - * @return {Boolean} - */ - -/** - * @access private - * @memberof module:@citation-js/core.util.Translator - * @param {module:@citation-js/core.util.Translator~condition} condition - * @return {module:@citation-js/core.util.Translator~conditionPredicate} - */ function createConditionEval(condition) { return function conditionEval(input) { if (typeof condition === 'boolean') { return condition } - return Object.keys(condition).every((prop) => { const value = condition[prop] if (value === true) { @@ -82,39 +20,19 @@ function createConditionEval(condition) { }) } } - -/** - * @access private - * @typedef {Object} module:@citation-js/core.util.Translator~normalizedStatement - * @property {Array} inputProp - * @property {Array} outputProp - * @property {module:@citation-js/core.util.Translator~convertProp} convert - * @property {module:@citation-js/core.util.Translator~conditionPredicate} condition - */ - -/** - * @access private - * @memberof module:@citation-js/core.util.Translator - * @param {module:@citation-js/core.util.Translator~statement} prop - * @param {Boolean} toSource - * @return {module:@citation-js/core.util.Translator~normalizedStatement} normalized one-directional object - */ function parsePropStatement(prop, toSource) { let inputProp let outputProp let convert let condition - if (typeof prop === 'string') { inputProp = outputProp = prop } else if (prop) { inputProp = toSource ? prop.target : prop.source outputProp = toSource ? prop.source : prop.target - if (prop.convert) { convert = toSource ? prop.convert.toSource : prop.convert.toTarget } - if (prop.when) { condition = toSource ? prop.when.target : prop.when.source if (condition != null) { @@ -124,58 +42,39 @@ function parsePropStatement(prop, toSource) { } else { return null } - inputProp = [].concat(inputProp).filter(Boolean) outputProp = [].concat(outputProp).filter(Boolean) - - return { inputProp, outputProp, convert, condition } + return { + inputProp, + outputProp, + convert, + condition, + } } - -/** - * Return, whether a mapping should apply. - * - * @callback module:@citation-js/core.util.Translator~convert - * @param {Object} input - input - * @return {Object} output - */ - -/** - * @access private - * @memberof module:@citation-js/core.util.Translator - * @param {Array} props - * @param {Boolean} toSource - * @return {module:@citation-js/core.util.Translator~convert} converter - */ function createConverter(props, toSource) { toSource = toSource === Translator.CONVERT_TO_SOURCE props = props.map((prop) => parsePropStatement(prop, toSource)).filter(Boolean) - return function converter(input) { const output = {} - for (const { inputProp, outputProp, convert, condition } of props) { - // Skip when no output will be assigned if (outputProp.length === 0) { continue - // Skip when requested by the requirements of the prop converter } else if (condition && !condition(input)) { continue - // Skip when none of the required props are in the input data - // NOTE: if no input is required, do not skip } else if (inputProp.length !== 0 && inputProp.every((prop) => !(prop in input))) { continue } - let outputData = inputProp.map((prop) => input[prop]) if (convert) { try { const converted = convert.apply(input, outputData) outputData = outputProp.length === 1 ? [converted] : converted } catch (cause) { - throw new Error(`Failed to convert ${inputProp} to ${outputProp}`, { cause }) + throw new Error(`Failed to convert ${inputProp} to ${outputProp}`, { + cause, + }) } } - outputProp.forEach((prop, index) => { const value = outputData[index] if (value !== undefined) { @@ -183,43 +82,15 @@ function createConverter(props, toSource) { } }) } - return output } } - -/** - * @memberof module:@citation-js/core.util - * - * @param {Array} props - * - * @todo proper merging (?) - * @todo 'else' conditions - */ class Translator { constructor(props) { - /** - * @type {module:@citation-js/core.util.Translator~convert} - */ this.convertToSource = createConverter(props, Translator.CONVERT_TO_SOURCE) - - /** - * @type {module:@citation-js/core.util.Translator~convert} - */ this.convertToTarget = createConverter(props, Translator.CONVERT_TO_TARGET) } } - -/** - * @memberof module:@citation-js/core.util.Translator - * @property {Symbol} CONVERT_TO_SOURCE - */ Translator.CONVERT_TO_SOURCE = Symbol('convert to source') - -/** - * @memberof module:@citation-js/core.util.Translator - * @property {Symbol} CONVERT_TO_TARGET - */ Translator.CONVERT_TO_TARGET = Symbol('convert to target') - export { Translator } diff --git a/src/citation-js/plugin-bibjson/index.js b/src/citation-js/plugin-bibjson/index.js index f38af3b..b4d035c 100644 --- a/src/citation-js/plugin-bibjson/index.js +++ b/src/citation-js/plugin-bibjson/index.js @@ -1,58 +1,12 @@ -/** - * ## Formats - * - * ### BibJSON - * - * This plugin adds input support for the [BibJSON format](http://okfnlabs.org/bibjson/), with three variants: - * - * * collections, where the records are extracted and parsed - * * records, which are parsed - * * records of the [quickscrape](https://github.com/ContentMine/quickscrape) variant, which are parsed - * - * @module module:@citation-js/plugin-bibjson - */ - import * as json from './json.js' import { plugins } from '../core/index.js' - const scraperLinks = ['fulltext_html', 'fulltext_xml', 'fulltext_pdf'] const authorNameFields = ['name', 'lastname', 'lastName', 'firstname', 'firstName'] - -/** - * @constant {module:@citation-js/core.plugins~pluginRef} ref - * @memberof module:@citation-js/plugin-bibjson - * @default '@bibjson' - */ const ref = '@bibjson' - -/** - * @access protected - * @namespace parsers - * @memberof module:@citation-js/plugin-bibjson - */ const parsers = { - /** - * @access protected - * @namespace json - * @memberof module:@citation-js/plugin-bibjson.parsers - */ json, } - -/** - * @namespace formats - * @type module:@citation-js/core.plugins.input~format,module:@citation-js/core.plugins.input~parsers - * @memberof module:@citation-js/plugin-bibjson - */ const formats = { - /** - * Object with quickscrape-style BibJSON. - * - * @type module:@citation-js/core.plugins.input~parsers - * @memberof module:@citation-js/plugin-bibjson.formats - * @property {module:@citation-js/core.plugins.input~dataParser} parse - * @property {module:@citation-js/core.plugins.input~typeParser} parseType - */ '@bibjson/quickscrape+record+object': { parse: json.quickscrapeRecord, parseType: { @@ -65,14 +19,6 @@ const formats = { extends: '@bibjson/record+object', }, }, - /** - * Object with BibJSON. - * - * @type module:@citation-js/core.plugins.input~parsers - * @memberof module:@citation-js/plugin-bibjson.formats - * @property {module:@citation-js/core.plugins.input~dataParser} parse - * @property {module:@citation-js/core.plugins.input~typeParser} parseType - */ '@bibjson/record+object': { parse: json.record, parseType: { @@ -95,14 +41,6 @@ const formats = { ], }, }, - /** - * Array of {@link module:@citation-js/plugin-bibjson.formats."@bibjson/record+object"|BibJSON objects}. - * - * @type module:@citation-js/core.plugins.input~parsers - * @memberof module:@citation-js/plugin-bibjson.formats - * @property {module:@citation-js/core.plugins.input~dataParser} parse - * @property {module:@citation-js/core.plugins.input~typeParser} parseType - */ '@bibjson/collection+object': { parse(collection) { return collection.records @@ -126,9 +64,7 @@ const formats = { }, }, } - plugins.add(ref, { input: formats, }) - export { ref, parsers, formats } diff --git a/src/citation-js/plugin-bibjson/json.js b/src/citation-js/plugin-bibjson/json.js index ae4fac3..4f26ef3 100644 --- a/src/citation-js/plugin-bibjson/json.js +++ b/src/citation-js/plugin-bibjson/json.js @@ -1,39 +1,27 @@ // @ts-nocheck import { parse as parseDate } from '@citation-js/date' import { parse as parseName } from '@citation-js/name' - function nameProps(person) { const { firstname, lastname, firstName: given = firstname, lastName: family = lastname } = person - if (given && family) { - return { given, family } + return { + given, + family, + } } else if (person.name) { return parseName(person.name) } } - -const identifiers = [ - 'PMID', - 'PMCID', - 'DOI', - 'ISBN', - // 'URL' is actually the URL of the record collection, if I understand it correctly, - // and not of the record. Otherwise, it should be included. -] - +const identifiers = ['PMID', 'PMCID', 'DOI', 'ISBN'] const journalIdentifiers = ['ISSN'] - function idProps(input, identifiers) { const output = {} - for (const prop in input) { const upperCaseProp = prop.toUpperCase() - if (identifiers.includes(upperCaseProp)) { output[upperCaseProp] = input[prop] } } - if (input.identifier) { for (let { id, type = '' } of input.identifier) { type = type.toUpperCase() @@ -42,11 +30,8 @@ function idProps(input, identifiers) { } } } - return output } - -// copied from BibTeX, as BibJSON is based on BibTeX const typeMap = { article: 'article', book: 'book', @@ -65,17 +50,16 @@ const typeMap = { techreport: 'report', unpublished: 'manuscript', } - function quickscrapeSpecificProps() { - return { type: 'article-journal' } + return { + type: 'article-journal', + } } - function generalProps(input) { const output = { type: typeMap[input.type] || 'document', title: input.title, } - if (input.author) { output.author = input.author.map(nameProps).filter(Boolean) } @@ -88,27 +72,24 @@ function generalProps(input) { } output.author = input.reviewer.map(nameProps).filter(Boolean) } - - /* istanbul ignore next: no examples found */ if (Array.isArray(input.keywords)) { output.keyword = input.keywords.join() } else if (input.keywords) { output.keyword = input.keywords } - if (input.publisher) { output.publisher = input.publisher.name || input.publisher } - if (input.date && input.date.published) { output.issued = parseDate(input.date.published) } else if (input.year) { - output.issued = { 'date-parts': [[+input.year]] } + output.issued = { + 'date-parts': [[+input.year]], + } } if (input.date && input.date.submitted) { output.submitted = parseDate(input.date.submitted) } - if (input.journal) { const journal = input.journal if (journal.name) { @@ -120,57 +101,31 @@ function generalProps(input) { if (journal.issue) { output.issue = journal.issue } - Object.assign(output, idProps(journal, journalIdentifiers)) - if (journal.firstpage) { output['page-first'] = journal.firstpage } - /* istanbul ignore else: no examples found */ if (journal.pages) { output.page = journal.pages.replace('--', '-') } else if (journal.firstpage && journal.lastpage) { output.page = journal.firstpage + '-' + journal.lastpage } } - if (input.link && typeof input.link[0] === 'object') { output.URL = input.link[0].url } - Object.assign(output, idProps(input, identifiers)) - if (input.cid) { output.id = input.cid } else if (output.DOI) { output.id = output.DOI } - return output } - -/** - * Parse ContentMine quickscrape data - * - * @access protected - * @memberof module:@citation-js/plugin-bibjson.parsers.json - * @param {Object} data - The input data - * @return {Array} The formatted input data - */ const parseContentMine = function (data) { return Object.assign(generalProps(data), quickscrapeSpecificProps(data)) } - -/** - * Parse BibJSON data - * - * @access protected - * @memberof module:@citation-js/plugin-bibjson.parsers.json - * @param {Object} data - The input data - * @return {Array} The formatted input data - */ const parseBibJson = function (data) { return generalProps(data) } - export { parseContentMine as quickscrapeRecord, parseBibJson as record } diff --git a/src/citation-js/plugin-bibtex/config.js b/src/citation-js/plugin-bibtex/config.js index 859ee62..a21b02c 100644 --- a/src/citation-js/plugin-bibtex/config.js +++ b/src/citation-js/plugin-bibtex/config.js @@ -14,5 +14,11 @@ export default { }, format: { useIdAsLabel: false, + checkLabel: true, + asciiOnly: true, + }, + biber: { + annotationMarker: '+an', + namedAnnotationMarker: ':', }, } diff --git a/src/citation-js/plugin-bibtex/input/bibtxt.js b/src/citation-js/plugin-bibtex/input/bibtxt.js index 42e063d..d536657 100644 --- a/src/citation-js/plugin-bibtex/input/bibtxt.js +++ b/src/citation-js/plugin-bibtex/input/bibtxt.js @@ -1,28 +1,11 @@ -/** - * @access private - * @constant bibTxtRegex - * @default - */ const bibTxtRegex = { splitEntries: /\n\s*(?=\[)/g, parseEntry: /^\[(.+?)\]\s*(?:\n([\s\S]+))?$/, splitPairs: /((?=.)\s)*\n\s*/g, splitPair: /:(.*)/, } - -/** - * Parse single Bib.TXT entry - * - * @access private - * @method parseBibTxtEntry - * - * @param {String} entry - The input data - * - * @return {Object} Array of BibTeX-JSON - */ const parseBibTxtEntry = (entry) => { const [, label, pairs] = entry.match(bibTxtRegex.parseEntry) || [] - if (!label || !pairs) { return {} } else { @@ -31,19 +14,15 @@ const parseBibTxtEntry = (entry) => { label, properties: {}, } - pairs .trim() .split(bibTxtRegex.splitPairs) .filter((v) => v) .forEach((pair) => { let [key, value] = pair.split(bibTxtRegex.splitPair) - - /* istanbul ignore else */ if (value) { key = key.trim() value = value.trim() - if (key === 'type') { out.type = value } else { @@ -51,21 +30,8 @@ const parseBibTxtEntry = (entry) => { } } }) - return out } } - -/** - * Parse Bib.TXT data - * - * @access private - * @method parseBibTxt - * - * @param {String} src - The input data - * - * @return {Array} Array of BibTeX-JSON - */ const parseBibTxt = (src) => src.trim().split(bibTxtRegex.splitEntries).map(parseBibTxtEntry) - export { parseBibTxt as parse, parseBibTxt as text, parseBibTxtEntry as textEntry } diff --git a/src/citation-js/plugin-bibtex/input/constants.js b/src/citation-js/plugin-bibtex/input/constants.js index 170c666..7526267 100644 --- a/src/citation-js/plugin-bibtex/input/constants.js +++ b/src/citation-js/plugin-bibtex/input/constants.js @@ -1,3 +1,6 @@ +// import _required from './required.json' assert { type: "json" }; +// import _fieldTypes from './fieldTypes.json' assert { type: "json" }; +// import unicode from './unicode.json' assert { type: "json" }; import _required from './required.js' import _fieldTypes from './fieldTypes.js' import unicode from './unicode.js' @@ -5,9 +8,7 @@ export const required = _required export const fieldTypes = _fieldTypes export const diacritics = unicode.diacritics export const commands = unicode.commands - -// Adapted from astrocite-bibtex (accessed 2018-02-22) -// https://github.com/dsifford/astrocite/blob/668a9e4/packages/astrocite-bibtex/src/constants.ts#L112-L148 +export const mathCommands = unicode.mathCommands export const defaultStrings = { jan: '01', feb: '02', @@ -48,46 +49,34 @@ export const formattingEnvs = { sl: 'italics', slshape: 'italics', em: 'italics', - bf: 'bold', bfseries: 'bold', - sc: 'smallcaps', scshape: 'smallcaps', - - // Font selection rm: undefined, sf: undefined, tt: undefined, } - export const formattingCommands = { textit: 'italics', textsl: 'italics', emph: 'italics', mkbibitalic: 'italics', mkbibemph: 'italics', - textbf: 'bold', strong: 'bold', mkbibbold: 'bold', - textsc: 'smallcaps', - textsuperscript: 'superscript', textsubscript: 'subscript', - enquote: 'quotes', mkbibquote: 'quotes', - - // Font selection textmd: undefined, textrm: undefined, textsf: undefined, texttt: undefined, textup: undefined, } - export const formatting = { italics: ['', ''], bold: ['', ''], @@ -97,9 +86,6 @@ export const formatting = { nocase: ['', ''], quotes: ['\u201C', '\u201D'], } - -// Partly adapted from retorquere/bibtex-parser (2020-11-16) -// https://github.com/retorquere/bibtex-parser/blob/7ad73df/index.ts export const argumentCommands = { ElsevierGlyph(glyph) { return String.fromCharCode(parseInt(glyph, 16)) @@ -111,9 +97,7 @@ export const argumentCommands = { return url }, } - export const ligaturePattern = /---?|''|``|~/g - export const ligatures = { '--': '\u2013', '---': '\u2014', @@ -121,18 +105,13 @@ export const ligatures = { "''": '\u201D', '~': '\u00A0', } - export const mathScriptFormatting = { '^': 'superscript', sp: 'superscript', - _: 'subscript', sb: 'subscript', - mathrm: undefined, } - -/* eslint-disable quote-props */ export const mathScripts = { '^': { 0: '\u2070', @@ -184,10 +163,6 @@ export const mathScripts = { t: '\u209C', }, } -/* eslint-enable quote-props */ - -// Adapted from retorquere/bibtex-parser (2020-10-15) -// https://github.com/retorquere/bibtex-parser/blob/0c8bd92/index.ts#L416-L441 export const sentenceCaseLanguages = [ 'american', 'british', diff --git a/src/citation-js/plugin-bibtex/input/entries.js b/src/citation-js/plugin-bibtex/input/entries.js index 893ae55..6106960 100644 --- a/src/citation-js/plugin-bibtex/input/entries.js +++ b/src/citation-js/plugin-bibtex/input/entries.js @@ -1,11 +1,64 @@ +function ownKeys(e, r) { + var t = Object.keys(e) + if (Object.getOwnPropertySymbols) { + var o = Object.getOwnPropertySymbols(e) + r && + (o = o.filter(function (r) { + return Object.getOwnPropertyDescriptor(e, r).enumerable + })), + t.push.apply(t, o) + } + return t +} +function _objectSpread(e) { + for (var r = 1; r < arguments.length; r++) { + var t = null != arguments[r] ? arguments[r] : {} + r % 2 + ? ownKeys(Object(t), !0).forEach(function (r) { + _defineProperty(e, r, t[r]) + }) + : Object.getOwnPropertyDescriptors + ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) + : ownKeys(Object(t)).forEach(function (r) { + Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)) + }) + } + return e +} +function _defineProperty(obj, key, value) { + key = _toPropertyKey(key) + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true, + }) + } else { + obj[key] = value + } + return obj +} +function _toPropertyKey(t) { + var i = _toPrimitive(t, 'string') + return 'symbol' == typeof i ? i : i + '' +} +function _toPrimitive(t, r) { + if ('object' != typeof t || !t) return t + var e = t[Symbol.toPrimitive] + if (void 0 !== e) { + var i = e.call(t, r || 'default') + if ('object' != typeof i) return i + throw new TypeError('@@toPrimitive must return a primitive value.') + } + return ('string' === r ? String : Number)(t) +} import config from '../config.js' import { parse as mapBiblatex, parseBibtex as mapBibtex } from '../mapping/index.js' -import { parse as parseValue } from './value.js' +import { parse as parseValue, parseAnnotation } from './value.js' import { required } from './constants.js' - function validate(entries, requirements) { const problems = [] - for (const { type, label, properties } of entries) { if (type in requirements) { const missing = [] @@ -23,7 +76,6 @@ function validate(entries, requirements) { problems.push([label, `invalid type: "${type}"`]) } } - if (problems.length) { throw new RangeError( ['Invalid entries:'] @@ -32,14 +84,11 @@ function validate(entries, requirements) { ) } } - function parseEntryValues(entry) { const output = {} - if ('language' in entry.properties) { output.language = parseValue(entry.properties.language, 'language') } - for (const property in entry.properties) { const value = entry.properties[property] if (value === '') { @@ -47,22 +96,30 @@ function parseEntryValues(entry) { } output[property] = parseValue(value + '', property, output.language) } - - return { ...entry, properties: output } + for (const property in entry.annotations) { + for (const annotation in entry.annotations[property]) { + output[property + '+an:' + annotation] = parseAnnotation( + entry.annotations[property][annotation] + ) + } + } + return _objectSpread( + _objectSpread({}, entry), + {}, + { + properties: output, + } + ) } - export function parse(entries) { if (config.parse.strict) { validate(entries, required.biblatex) } - return mapBiblatex(entries.map(parseEntryValues)) } - export function parseBibtex(entries) { if (config.parse.strict) { validate(entries, required.bibtex) } - return mapBibtex(entries.map(parseEntryValues)) } diff --git a/src/citation-js/plugin-bibtex/input/file.js b/src/citation-js/plugin-bibtex/input/file.js index 1df042e..de8f2a1 100644 --- a/src/citation-js/plugin-bibtex/input/file.js +++ b/src/citation-js/plugin-bibtex/input/file.js @@ -1,88 +1,191 @@ +function ownKeys(e, r) { + var t = Object.keys(e) + if (Object.getOwnPropertySymbols) { + var o = Object.getOwnPropertySymbols(e) + r && + (o = o.filter(function (r) { + return Object.getOwnPropertyDescriptor(e, r).enumerable + })), + t.push.apply(t, o) + } + return t +} +function _objectSpread(e) { + for (var r = 1; r < arguments.length; r++) { + var t = null != arguments[r] ? arguments[r] : {} + r % 2 + ? ownKeys(Object(t), !0).forEach(function (r) { + _defineProperty(e, r, t[r]) + }) + : Object.getOwnPropertyDescriptors + ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) + : ownKeys(Object(t)).forEach(function (r) { + Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)) + }) + } + return e +} +function _defineProperty(obj, key, value) { + key = _toPropertyKey(key) + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true, + }) + } else { + obj[key] = value + } + return obj +} +function _toPropertyKey(t) { + var i = _toPrimitive(t, 'string') + return 'symbol' == typeof i ? i : i + '' +} +function _toPrimitive(t, r) { + if ('object' != typeof t || !t) return t + var e = t[Symbol.toPrimitive] + if (void 0 !== e) { + var i = e.call(t, r || 'default') + if ('object' != typeof i) return i + throw new TypeError('@@toPrimitive must return a primitive value.') + } + return ('string' === r ? String : Number)(t) +} import { util, logger } from '../../core/index.js' - import moo from 'moo' +import config from '../config.js' import { defaultStrings } from './constants.js' - -const identifier = /[a-zA-Z_][a-zA-Z0-9_:-]*/ +const identifier = /[a-zA-Z_][a-zA-Z0-9_:+-]*/ const whitespace = { comment: /%.*/, - whitespace: { match: /\s+/, lineBreaks: true }, + whitespace: { + match: /\s+/, + lineBreaks: true, + }, } - const lexer = moo.states({ main: { - junk: { match: /@[cC][oO][mM][mM][eE][nN][tT].+|[^@]+/, lineBreaks: true }, - at: { match: '@', push: 'entry' }, - }, - entry: { - ...whitespace, - otherEntryType: { - match: /[sS][tT][rR][iI][nN][gG]|[pP][rR][eE][aA][mM][bB][lL][eE]/, - next: 'otherEntryContents', + junk: { + match: /@[cC][oO][mM][mM][eE][nN][tT].+|[^@]+/, + lineBreaks: true, }, - dataEntryType: { - match: identifier, - next: 'dataEntryContents', + at: { + match: '@', + push: 'entry', }, }, - otherEntryContents: { - ...whitespace, - lbrace: { match: /[{(]/, next: 'fields' }, - }, - dataEntryContents: { - ...whitespace, - lbrace: { match: /[{(]/, next: 'dataEntryContents' }, - label: /[^,\s]+/, - comma: { match: ',', next: 'fields' }, - }, - fields: { - ...whitespace, - identifier, - number: /-?\d+/, - hash: '#', - equals: '=', - comma: ',', - quote: { match: '"', push: 'quotedLiteral' }, - lbrace: { match: '{', push: 'bracedLiteral' }, - rbrace: { match: /[})]/, pop: true }, - }, + entry: _objectSpread( + _objectSpread({}, whitespace), + {}, + { + otherEntryType: { + match: /[sS][tT][rR][iI][nN][gG]|[pP][rR][eE][aA][mM][bB][lL][eE]/, + next: 'otherEntryContents', + }, + dataEntryType: { + match: identifier, + next: 'dataEntryContents', + }, + } + ), + otherEntryContents: _objectSpread( + _objectSpread({}, whitespace), + {}, + { + lbrace: { + match: /[{(]/, + next: 'fields', + }, + } + ), + dataEntryContents: _objectSpread( + _objectSpread({}, whitespace), + {}, + { + lbrace: { + match: /[{(]/, + next: 'dataEntryContents', + }, + label: /[^,\s]+/, + comma: { + match: ',', + next: 'fields', + }, + } + ), + fields: _objectSpread( + _objectSpread({}, whitespace), + {}, + { + identifier, + number: /-?\d+/, + hash: '#', + equals: '=', + comma: ',', + quote: { + match: '"', + push: 'quotedLiteral', + }, + lbrace: { + match: '{', + push: 'bracedLiteral', + }, + rbrace: { + match: /[})]/, + pop: true, + }, + } + ), quotedLiteral: { - lbrace: { match: '{', push: 'bracedLiteral' }, - quote: { match: '"', pop: true }, - text: { match: /(?:\\[\\{]|[^{"])+/, lineBreaks: true }, + lbrace: { + match: '{', + push: 'bracedLiteral', + }, + quote: { + match: '"', + pop: true, + }, + text: { + match: /(?:\\[\\{]|[^{"])+/, + lineBreaks: true, + }, }, bracedLiteral: { - lbrace: { match: '{', push: 'bracedLiteral' }, - rbrace: { match: '}', pop: true }, - text: { match: /(?:\\[\\{}]|[^{}])+/, lineBreaks: true }, + lbrace: { + match: '{', + push: 'bracedLiteral', + }, + rbrace: { + match: '}', + pop: true, + }, + text: { + match: /(?:\\[\\{}]|[^{}])+/, + lineBreaks: true, + }, }, }) - const delimiters = { '(': ')', '{': '}', } - export const bibtexGrammar = new util.Grammar( { Main() { const entries = [] - while (true) { while (this.matchToken('junk')) { this.consumeToken('junk') } - if (this.matchEndOfFile()) { break } - entries.push(this.consumeRule('Entry')) } - return entries.filter(Boolean) }, - _() { let oldToken while (oldToken !== this.token) { @@ -91,23 +194,18 @@ export const bibtexGrammar = new util.Grammar( this.consumeToken('comment', true) } }, - Entry() { this.consumeToken('at') this.consumeRule('_') - const type = ( this.matchToken('otherEntryType') ? this.consumeToken('otherEntryType') : this.consumeToken('dataEntryType') ).value.toLowerCase() - this.consumeRule('_') const openBrace = this.consumeToken('lbrace').value this.consumeRule('_') - let result - if (type === 'string') { const [key, value] = this.consumeRule('Field') this.state.strings[key] = value @@ -115,16 +213,18 @@ export const bibtexGrammar = new util.Grammar( this.consumeRule('Expression') } else { const label = this.consumeToken('label').value - this.consumeRule('_') this.consumeToken('comma') this.consumeRule('_') - - const properties = this.consumeRule('EntryBody') - - result = { type, label, properties } + const entryBody = this.consumeRule('EntryBody') + result = _objectSpread( + { + type, + label, + }, + entryBody + ) } - this.consumeRule('_') const closeBrace = this.consumeToken('rbrace').value if (closeBrace !== delimiters[openBrace]) { @@ -133,17 +233,36 @@ export const bibtexGrammar = new util.Grammar( `entry started with "${openBrace}", but ends with "${closeBrace}"` ) } - return result }, - EntryBody() { - const properties = {} - + const output = { + properties: {}, + } while (this.matchToken('identifier')) { const [field, value] = this.consumeRule('Field') - properties[field] = value - + let annotationField + let annotationName = 'default' + if (field.endsWith(config.biber.annotationMarker)) { + annotationField = field.slice(0, -config.biber.annotationMarker.length) + } else if ( + field.includes(config.biber.annotationMarker + config.biber.namedAnnotationMarker) + ) { + ;[annotationField, annotationName] = field.split( + config.biber.annotationMarker + config.biber.namedAnnotationMarker + ) + } + if (annotationField) { + if (!output.annotations) { + output.annotations = {} + } + if (!output.annotations[annotationField]) { + output.annotations[annotationField] = {} + } + output.annotations[annotationField][annotationName] = value + } else { + output.properties[field] = value + } this.consumeRule('_') if (this.consumeToken('comma', true)) { this.consumeRule('_') @@ -151,36 +270,27 @@ export const bibtexGrammar = new util.Grammar( break } } - - return properties + return output }, - Field() { const field = this.consumeToken('identifier').value.toLowerCase() - this.consumeRule('_') this.consumeToken('equals') this.consumeRule('_') - const value = this.consumeRule('Expression') - return [field, value] }, - Expression() { let output = this.consumeRule('ExpressionPart') this.consumeRule('_') - while (this.matchToken('hash')) { this.consumeToken('hash') this.consumeRule('_') output += this.consumeRule('ExpressionPart').toString() this.consumeRule('_') } - return output }, - ExpressionPart() { if (this.matchToken('identifier')) { return this.state.strings[this.consumeToken('identifier').value.toLowerCase()] || '' @@ -192,7 +302,6 @@ export const bibtexGrammar = new util.Grammar( return this.consumeRule('BracketString') } }, - QuoteString() { let output = '' this.consumeToken('quote') @@ -202,7 +311,6 @@ export const bibtexGrammar = new util.Grammar( this.consumeToken('quote') return output }, - BracketString() { let output = '' this.consumeToken('lbrace') @@ -212,7 +320,6 @@ export const bibtexGrammar = new util.Grammar( this.consumeToken('rbrace') return output }, - Text() { if (this.matchToken('lbrace')) { return `{${this.consumeRule('BracketString')}}` @@ -225,7 +332,6 @@ export const bibtexGrammar = new util.Grammar( strings: defaultStrings, } ) - export function parse(text) { return bibtexGrammar.parse(lexer.reset(text)) } diff --git a/src/citation-js/plugin-bibtex/input/index.js b/src/citation-js/plugin-bibtex/input/index.js index 3677c48..e55fb9e 100644 --- a/src/citation-js/plugin-bibtex/input/index.js +++ b/src/citation-js/plugin-bibtex/input/index.js @@ -1,28 +1,8 @@ import { parse as parseFile } from './file.js' import { parse as parseBibtxt } from './bibtxt.js' import { parse as parseEntries, parseBibtex } from './entries.js' - -/** - * @constant {module:@citation-js/core.plugins~pluginRef} ref - * @memberof module:@citation-js/plugin-bibtex - * @default '@bibtex' - */ export const ref = '@bibtex' - -/** - * @namespace formats - * @type module:@citation-js/core.plugins.input~format,module:@citation-js/core.plugins.input~parsers - * @memberof module:@citation-js/plugin-bibtex - */ export const formats = { - /** - * BibLaTeX file. - * - * @type module:@citation-js/core.plugins.input~parsers - * @memberof module:@citation-js/plugin-bibtex.formats - * @property {module:@citation-js/core.plugins.input~dataParser} parse - * @property {module:@citation-js/core.plugins.input~typeParser} parseType - */ '@biblatex/text': { parse: parseFile, parseType: { @@ -30,99 +10,35 @@ export const formats = { predicate: /@\s{0,5}[A-Za-z]{1,13}\s{0,5}\{\s{0,5}[^@{}"=,\\\s]{0,100}\s{0,5},[\s\S]*\}/, }, }, - - /** - * BibLaTeX object. - * - * ```js - * { - * type: '...', - * label: '...', - * properties: {...} - * } - * ``` - * - * @type module:@citation-js/core.plugins.input~parsers - * @memberof module:@citation-js/plugin-bibtex.formats - * @property {module:@citation-js/core.plugins.input~dataParser} parse - * @property {module:@citation-js/core.plugins.input~typeParser} parseType - */ '@biblatex/entry+object': { parse(input) { return parseEntries([input]) }, parseType: { dataType: 'SimpleObject', - propertyConstraint: { props: ['type', 'label', 'properties'] }, + propertyConstraint: { + props: ['type', 'label', 'properties'], + }, }, }, - - /** - * Array of {@link module:@citation-js/plugin-bibtex.formats."@biblatex/entries+list"|BibLaTeX objects}. - * - * @type module:@citation-js/core.plugins.input~parsers - * @memberof module:@citation-js/plugin-bibtex.formats - * @property {module:@citation-js/core.plugins.input~dataParser} parse - * @property {module:@citation-js/core.plugins.input~typeParser} parseType - */ '@biblatex/entries+list': { parse: parseEntries, - parseType: { elementConstraint: '@biblatex/entry+object' }, + parseType: { + elementConstraint: '@biblatex/entry+object', + }, }, - - /** - * BibTeX file. - * - * @type module:@citation-js/core.plugins.input~parsers - * @memberof module:@citation-js/plugin-bibtex.formats - * @property {module:@citation-js/core.plugins.input~dataParser} parse - * @property {module:@citation-js/core.plugins.input~format} outputs - */ '@bibtex/text': { parse: parseFile, outputs: '@bibtex/entries+list', }, - - /** - * BibTeX object. - * - * ```js - * { - * type: '...', - * label: '...', - * properties: {...} - * } - * ``` - * - * @type module:@citation-js/core.plugins.input~parsers - * @memberof module:@citation-js/plugin-bibtex.formats - * @property {module:@citation-js/core.plugins.input~dataParser} parse - */ '@bibtex/entry+object': { parse(input) { return parseBibtex([input]) }, }, - - /** - * Array of {@link module:@citation-js/plugin-bibtex.formats."@bibtex/entries+list"|BibTeX objects}. - * - * @type module:@citation-js/core.plugins.input~parsers - * @memberof module:@citation-js/plugin-bibtex.formats - * @property {module:@citation-js/core.plugins.input~dataParser} parse - */ '@bibtex/entries+list': { parse: parseBibtex, }, - - /** - * Bib.TXT file. - * - * @type module:@citation-js/core.plugins.input~parsers - * @memberof module:@citation-js/plugin-bibtex.formats - * @property {module:@citation-js/core.plugins.input~dataParser} parse - * @property {module:@citation-js/core.plugins.input~typeParser} parseType - */ '@bibtxt/text': { parse: parseBibtxt, parseType: { diff --git a/src/citation-js/plugin-bibtex/input/name.js b/src/citation-js/plugin-bibtex/input/name.js index d59673b..49c82b8 100644 --- a/src/citation-js/plugin-bibtex/input/name.js +++ b/src/citation-js/plugin-bibtex/input/name.js @@ -1,99 +1,60 @@ -// @ts-nocheck -/** - * @access private - * @param {String} - * @returns {Boolean|null} true if uppercase, false if lowercase, null if neither - */ export function getStringCase(string) { const a = string.toUpperCase() const b = string.toLowerCase() - for (let i = 0; i < string.length; i++) { if (a[i] !== b[i]) { return a[i] === string[i] } } - return null } - -/** - * @access private - * @param {Array} parts - * @return {String|undefined} - */ export function formatNameParts(parts) { if (parts.length === 0) { return undefined } - let piece = '' - while (parts.length > 1) { const { value, hyphenated } = parts.shift() piece += value + (hyphenated ? '-' : ' ') } - const output = piece + parts[0].value return output[0] && output } - -/** - * @access private - * @param {Array} parts - * @param {Boolean} [orderGiven=true] - also consider the given name - * @return {Array} - */ -export function orderNameParts(parts, /* istanbul ignore next */ orderGiven = true) { +export function orderNameParts(parts, orderGiven = true) { const given = [] const undecided = [] - if (orderGiven) { while (parts.length > 1 && parts[0].upperCase !== false) { given.push(...undecided) undecided.length = 0 - while (parts.length > 1 && parts[0].upperCase !== false && !parts[0].hyphenated) { given.push(parts.shift()) } - while (parts.length > 0 && parts[0].upperCase !== false && parts[0].hyphenated) { undecided.push(parts.shift()) } } } - const prefix = [] const family = [] - while (parts.length > 1) { prefix.push(...family) family.length = 0 - while (parts.length > 1 && parts[0].upperCase === false) { prefix.push(parts.shift()) } - while (parts.length > 0 && parts[0].upperCase !== false) { family.push(parts.shift()) } } - if (undecided.length) { family.unshift(...undecided) } if (parts.length) { family.push(parts[0]) } - return [formatNameParts(given), formatNameParts(prefix), formatNameParts(family)] } - -/** - * @access private - * @param {Array>} pieces - * @return {Object} - */ export function orderNamePieces(pieces) { if (pieces[0][0].label) { const name = {} @@ -102,17 +63,14 @@ export function orderNamePieces(pieces) { } return name } - const name = {} const [given, prefix, family] = orderNameParts(pieces[0], pieces.length === 1) - if (family) { name.family = family } if (prefix) { name.prefix = prefix } - if (pieces.length === 3) { name.given = formatNameParts(pieces[2]) name.suffix = formatNameParts(pieces[1]) @@ -121,6 +79,5 @@ export function orderNamePieces(pieces) { } else if (given) { name.given = given } - return name } diff --git a/src/citation-js/plugin-bibtex/input/unicode.js b/src/citation-js/plugin-bibtex/input/unicode.js index 28ad506..ea1b183 100644 --- a/src/citation-js/plugin-bibtex/input/unicode.js +++ b/src/citation-js/plugin-bibtex/input/unicode.js @@ -155,6 +155,8 @@ const unicode = { textinterrobangdown: '⸘', textcommabelow: null, copyright: '©', + }, + mathCommands: { Gamma: 'Γ', Delta: 'Δ', Theta: 'Θ', diff --git a/src/citation-js/plugin-bibtex/input/value.js b/src/citation-js/plugin-bibtex/input/value.js index 1d63a0e..46a568d 100644 --- a/src/citation-js/plugin-bibtex/input/value.js +++ b/src/citation-js/plugin-bibtex/input/value.js @@ -1,72 +1,191 @@ +function ownKeys(e, r) { + var t = Object.keys(e) + if (Object.getOwnPropertySymbols) { + var o = Object.getOwnPropertySymbols(e) + r && + (o = o.filter(function (r) { + return Object.getOwnPropertyDescriptor(e, r).enumerable + })), + t.push.apply(t, o) + } + return t +} +function _objectSpread(e) { + for (var r = 1; r < arguments.length; r++) { + var t = null != arguments[r] ? arguments[r] : {} + r % 2 + ? ownKeys(Object(t), !0).forEach(function (r) { + _defineProperty(e, r, t[r]) + }) + : Object.getOwnPropertyDescriptors + ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) + : ownKeys(Object(t)).forEach(function (r) { + Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)) + }) + } + return e +} +function _defineProperty(obj, key, value) { + key = _toPropertyKey(key) + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true, + }) + } else { + obj[key] = value + } + return obj +} +function _toPropertyKey(t) { + var i = _toPrimitive(t, 'string') + return 'symbol' == typeof i ? i : i + '' +} +function _toPrimitive(t, r) { + if ('object' != typeof t || !t) return t + var e = t[Symbol.toPrimitive] + if (void 0 !== e) { + var i = e.call(t, r || 'default') + if ('object' != typeof i) return i + throw new TypeError('@@toPrimitive must return a primitive value.') + } + return ('string' === r ? String : Number)(t) +} import { util } from '../../core/index.js' import moo from 'moo' import config from '../config.js' import * as constants from './constants.js' import { orderNamePieces, formatNameParts, getStringCase } from './name.js' - const text = { + commandBegin: { + match: '\\begin', + push: 'environment', + }, command: { match: /\\(?:[a-zA-Z]+|.) */, - type: moo.keywords({ - commandBegin: '\\begin', - commandEnd: '\\end', - }), value: (s) => s.slice(1).trim(), }, - lbrace: { match: '{', push: 'bracedLiteral' }, - mathShift: { match: '$', push: 'mathLiteral' }, + lbrace: { + match: '{', + push: 'bracedLiteral', + }, + mathShift: { + match: '$', + push: 'mathLiteral', + }, whitespace: { match: /[\s]+|~/, lineBreaks: true, - // \xa0 = Non-breakable space value(token) { return token === '~' ? '\xa0' : ' ' }, }, } - const lexer = moo.states({ - stringLiteral: { - ...text, - text: /[^{$}\s~\\]+/, - }, - namesLiteral: { - and: /\s+[aA][nN][dD]\s+/, - comma: ',', - hyphen: '-', - equals: '=', - ...text, - text: /[^{$}\s~\\,=-]+/, - }, - listLiteral: { - and: /\s+and\s+/, - ...text, - text: /[^{$}\s~\\]+/, - }, - separatedLiteral: { - comma: ',', - ...text, - text: /[^{$}\s~\\,]+/, - }, - bracedLiteral: { - ...text, - rbrace: { match: '}', pop: true }, - text: /[^{$}\s~\\]+/, - }, - mathLiteral: { - ...text, - mathShift: { match: '$', pop: true }, - script: /[\^_]/, - text: /[^{$}\s~\\^_]+/, - }, + stringLiteral: _objectSpread( + _objectSpread({}, text), + {}, + { + text: /[^{$}\s~\\]+/, + } + ), + namesLiteral: _objectSpread( + _objectSpread( + { + and: /\s+[aA][nN][dD]\s+/, + comma: ',', + hyphen: '-', + equals: '=', + }, + text + ), + {}, + { + text: /[^{$}\s~\\,=-]+/, + } + ), + listLiteral: _objectSpread( + _objectSpread( + { + and: /\s+and\s+/, + }, + text + ), + {}, + { + text: /[^{$}\s~\\]+/, + } + ), + separatedLiteral: _objectSpread( + _objectSpread( + { + comma: ',', + }, + text + ), + {}, + { + text: /[^{$}\s~\\,]+/, + } + ), + annotation: _objectSpread( + _objectSpread({}, text), + {}, + { + colon: ':', + equals: '=', + comma: ',', + semicolon: ';', + quote: '"', + itemCount: /\d+/, + text: /[^{$}\s~\\":;,=]+/, + } + ), + bracedLiteral: _objectSpread( + _objectSpread({}, text), + {}, + { + rbrace: { + match: '}', + pop: true, + }, + text: /[^{$}\s~\\]+/, + } + ), + mathLiteral: _objectSpread( + _objectSpread({}, text), + {}, + { + mathShift: { + match: '$', + pop: true, + }, + script: /[\^_]/, + text: /[^{$}\s~\\^_]+/, + } + ), + environment: _objectSpread( + _objectSpread( + { + commandEnd: { + match: '\\end', + pop: true, + }, + }, + text + ), + {}, + { + text: /[^{$}\s~\\]+/, + } + ), }) - function flattenConsString(string) { - // eslint-disable-next-line no-unused-expressions string[0] return string } - function applyFormatting(text, format) { if (format in constants.formatting) { return text && constants.formatting[format].join(text) @@ -74,7 +193,6 @@ function applyFormatting(text, format) { return text } } - export const valueGrammar = new util.Grammar( { String() { @@ -84,15 +202,12 @@ export const valueGrammar = new util.Grammar( } return flattenConsString(output) }, - StringNames() { const list = [] - while (true) { this.consumeToken('whitespace', true) list.push(this.consumeRule('Name')) this.consumeToken('whitespace', true) - if (this.matchEndOfFile()) { return list } else { @@ -100,13 +215,10 @@ export const valueGrammar = new util.Grammar( } } }, - Name() { const pieces = [] - while (true) { pieces.push(this.consumeRule('NamePiece')) - if (this.matchEndOfFile() || this.matchToken('and')) { return orderNamePieces(pieces) } else { @@ -115,20 +227,20 @@ export const valueGrammar = new util.Grammar( } } }, - NamePiece() { const parts = [] - while (true) { const part = this.consumeRule('NameToken') - if (part.label) { - part.label = formatNameParts([...parts, { value: part.label }]) + part.label = formatNameParts([ + ...parts, + { + value: part.label, + }, + ]) return [part] } - parts.push(part) - if (this.matchEndOfFile() || this.matchToken('and') || this.matchToken('comma')) { return parts } else { @@ -138,48 +250,45 @@ export const valueGrammar = new util.Grammar( } } }, - NameToken() { let upperCase = null let value = '' - while (true) { - // If needed, test regular text for case if (upperCase === null && this.matchToken('text')) { const text = this.consumeToken().value value += text upperCase = getStringCase(text) - - // If end of name part, return up } else if ( this.matchEndOfFile() || this.matchToken('and') || this.matchToken('comma') || this.matchToken('whitespace') ) { - return { value, upperCase } - - // Same for hyphen, but note it is hyphenated + return { + value, + upperCase, + } } else if (this.matchToken('hyphen')) { - return { value, upperCase, hyphenated: true } - - // If equals we are in BibLaTeX extended mode - // 'family=Last, given=First, prefix=von' + return { + value, + upperCase, + hyphenated: true, + } } else if (this.matchToken('equals')) { this.consumeToken('equals') const text = this.consumeRule('NamePiece') if (text[0].label) { value += '=' + text[0].label } - return { value: formatNameParts(text), label: value } - - // Else consume other text + return { + value: formatNameParts(text), + label: value, + } } else { value += this.consumeRule('Text') } } }, - StringList() { const list = [] while (!this.matchEndOfFile()) { @@ -188,12 +297,10 @@ export const valueGrammar = new util.Grammar( output += this.consumeRule('Text') } list.push(flattenConsString(output)) - this.consumeToken('and', true) } return list.length === 1 ? list[0] : list }, - StringSeparated() { const list = [] while (!this.matchEndOfFile()) { @@ -202,13 +309,11 @@ export const valueGrammar = new util.Grammar( output += this.consumeRule('Text') } list.push(output.trim()) - this.consumeToken('comma', true) this.consumeToken('whitespace', true) } return list }, - StringVerbatim() { let output = '' while (!this.matchEndOfFile()) { @@ -216,7 +321,6 @@ export const valueGrammar = new util.Grammar( } return flattenConsString(output) }, - StringUri() { const uri = this.consumeRule('StringVerbatim') try { @@ -226,50 +330,117 @@ export const valueGrammar = new util.Grammar( return uri } } catch (e) { - // malformed URI return encodeURI(uri) } }, - StringTitleCase() { this.state.sentenceCase = true let output = '' - while (!this.matchEndOfFile()) { output += this.consumeRule('Text') } - return flattenConsString(output) }, - + Annotations() { + const annotations = {} + while (true) { + const { scope, item, part, value } = this.consumeRule('Annotation') + if (scope === 'part') { + if (!annotations.part) { + annotations.part = [] + } + if (!annotations.part[item]) { + annotations.part[item] = {} + } + annotations.part[item][part] = value + } else if (scope === 'item') { + if (!annotations.item) { + annotations.item = [] + } + annotations.item[item] = value + } else { + annotations.field = value + } + if (this.matchEndOfFile()) { + break + } else { + this.consumeToken('semicolon') + this.consumeRule('_') + } + } + return annotations + }, + Annotation() { + const annotation = {} + if (this.matchToken('itemCount')) { + annotation.item = parseInt(this.consumeToken('itemCount')) - 1 + if (this.matchToken('colon')) { + this.consumeToken('colon') + annotation.part = this.consumeToken('text') + annotation.scope = 'part' + } else { + annotation.scope = 'item' + } + } else { + annotation.scope = 'field' + } + this.consumeToken('equals') + this.consumeRule('_') + if (this.matchToken('quote')) { + this.consumeToken('quote') + let literal = '' + while (!this.matchToken('quote')) { + if ( + this.matchToken('itemCount') || + this.matchToken('colon') || + this.matchToken('comma') || + this.matchToken('semicolon') || + this.matchToken('equals') + ) { + literal += this.token.value + this.token = this.lexer.next() + } else { + literal += this.consumeRule('Text') + } + } + this.consumeToken('quote') + annotation.value = flattenConsString(literal) + this.consumeRule('_') + } else { + annotation.value = [] + let output = '' + while (true) { + output += this.consumeRule('Text') + if (this.matchToken('comma')) { + this.consumeToken('comma') + this.consumeRule('_') + annotation.value.push(flattenConsString(output)) + output = '' + } else if (this.matchEndOfFile() || this.matchToken('semicolon')) { + annotation.value.push(flattenConsString(output)) + break + } + } + } + return annotation + }, BracketString() { + var _this$state let output = '' this.consumeToken('lbrace') - const sentenceCase = this.state.sentenceCase - // If the bracket string starts with a command the sentence case behavior - // is maintained within the brackets. this.state.sentenceCase = sentenceCase && this.matchToken('command') - this.state.partlyLowercase &&= this.state.sentenceCase - + ;(_this$state = this.state).partlyLowercase && + (_this$state.partlyLowercase = this.state.sentenceCase) while (!this.matchToken('rbrace')) { output += this.consumeRule('Text') } - - // topLevel meaning that the bracket string is not top level but a direct - // child of the top level value. const topLevel = sentenceCase && !this.state.sentenceCase - // Protect the case of the bracket string if it is a direct child of the top - // level, and the string is partly lowercase. const protectCase = topLevel && this.state.partlyLowercase - // Restore the sentence case of the outside of the brackets. this.state.sentenceCase = sentenceCase - this.consumeToken('rbrace') - return protectCase ? applyFormatting(output, 'nocase') : output }, - MathString() { let output = '' this.consumeToken('mathShift') @@ -283,10 +454,8 @@ export const valueGrammar = new util.Grammar( const formatName = constants.mathScriptFormatting[script] output += constants.formatting[formatName].join(text.join('')) } - continue } - if (this.matchToken('command')) { const command = this.token.value if (command in constants.mathScriptFormatting) { @@ -296,15 +465,12 @@ export const valueGrammar = new util.Grammar( continue } } - output += this.consumeRule('Text') } this.consumeToken('mathShift') return output }, - Text() { - /* eslint-disable padded-blocks */ if (this.matchToken('lbrace')) { return this.consumeRule('BracketString') } else if (this.matchToken('mathShift')) { @@ -316,90 +482,64 @@ export const valueGrammar = new util.Grammar( } else if (this.matchToken('command')) { return this.consumeRule('Command') } - /* eslint-enable padded-blocks */ - const text = this.consumeToken('text').value.replace( constants.ligaturePattern, (ligature) => constants.ligatures[ligature] ) - const afterPunctuation = this.state.afterPunctuation this.state.afterPunctuation = /[?!.:]$/.test(text) - - // If the text fragment is not topLevel and has a case, check whether lowercase if (!this.state.sentenceCase) { - this.state.partlyLowercase ||= text === text.toLowerCase() && text !== text.toUpperCase() + var _this$state2 + ;(_this$state2 = this.state).partlyLowercase || + (_this$state2.partlyLowercase = + text === text.toLowerCase() && text !== text.toUpperCase()) return text } - - // Unicode-safe splitting (as in, accounting for surrogate pairs) const [first, ...otherCharacters] = text const rest = otherCharacters.join('') const restLowerCase = rest.toLowerCase() - - // Word case should be preserved for proper nouns (e.g. those with capital - // letters in other places than the first letter). if (rest !== restLowerCase) { return text } - if (!afterPunctuation) { return text.toLowerCase() } - return first + restLowerCase - // return first.toUpperCase() + restLowerCase }, - Command() { const commandToken = this.consumeToken('command') const command = commandToken.value - - // formatting envs if (command in constants.formattingEnvs) { const text = this.consumeRule('Env') const format = constants.formattingEnvs[command] return applyFormatting(text, format) - - // formatting commands } else if (command in constants.formattingCommands) { const text = this.consumeRule('BracketString') const format = constants.formattingCommands[command] return applyFormatting(text, format) - - // commands } else if (command in constants.commands) { return constants.commands[command] - - // diacritics + } else if (command in constants.mathCommands) { + return constants.mathCommands[command] } else if (command in constants.diacritics && !this.matchEndOfFile()) { const text = this.consumeRule('Text') const diacritic = text[0] + constants.diacritics[command] return diacritic.normalize('NFC') + text.slice(1) - - // argument commands } else if (command in constants.argumentCommands) { const func = constants.argumentCommands[command] const args = [] let arity = func.length - while (arity-- > 0) { this.consumeToken('whitespace', true) args.push(this.consumeRule('BracketString')) } - return func(...args) - - // escapes } else if (/^[&%$#_{}]$/.test(command)) { return commandToken.text.slice(1) - - // unknown commands } else { return commandToken.text } }, - Env() { let output = '' while (!this.matchEndOfFile() && !this.matchToken('rbrace')) { @@ -407,20 +547,15 @@ export const valueGrammar = new util.Grammar( } return output }, - EnclosedEnv() { this.consumeToken('commandBegin') const beginEnv = this.consumeRule('BracketString') - let output = '' - while (!this.matchToken('commandEnd')) { output += this.consumeRule('Text') } - const end = this.consumeToken('commandEnd') const endEnv = this.consumeRule('BracketString') - if (beginEnv !== endEnv) { throw new SyntaxError( this.lexer.formatError( @@ -429,9 +564,13 @@ export const valueGrammar = new util.Grammar( ) ) } - return applyFormatting(output, constants.formattingEnvs[beginEnv]) }, + _() { + while (this.matchToken('whitespace')) { + this.consumeToken('whitespace') + } + }, }, { sentenceCase: false, @@ -439,24 +578,19 @@ export const valueGrammar = new util.Grammar( afterPunctuation: true, } ) - function singleLanguageIsEnglish(language) { return constants.sentenceCaseLanguages.includes(language.toLowerCase()) } - function isEnglish(languages) { if (Array.isArray(languages)) { return languages.every(singleLanguageIsEnglish) } return singleLanguageIsEnglish(languages) } - function getMainRule(fieldType, languages) { if (fieldType[1] === 'name') { - /* istanbul ignore next: does not exist */ return fieldType[0] === 'list' ? 'StringNames' : 'Name' } - if (fieldType[1] === 'title') { const option = config.parse.sentenceCase if (option === 'always' || (option === 'english' && isEnglish(languages))) { @@ -465,7 +599,6 @@ function getMainRule(fieldType, languages) { return 'String' } } - switch (fieldType[0] === 'field' ? fieldType[1] : fieldType[0]) { case 'list': return 'StringList' @@ -481,12 +614,10 @@ function getMainRule(fieldType, languages) { return 'String' } } - function getLexerState(fieldType) { if (fieldType[1] === 'name') { return 'namesLiteral' } - switch (fieldType[0]) { case 'list': return 'listLiteral' @@ -497,7 +628,6 @@ function getLexerState(fieldType) { return 'stringLiteral' } } - export function parse(text, field, languages = []) { const fieldType = constants.fieldTypes[field] || [] return valueGrammar.parse( @@ -509,3 +639,13 @@ export function parse(text, field, languages = []) { getMainRule(fieldType, languages) ) } +export function parseAnnotation(text) { + return valueGrammar.parse( + lexer.reset(text, { + state: 'annotation', + line: 0, + col: 0, + }), + 'Annotations' + ) +} diff --git a/src/citation-js/plugin-bibtex/mapping/biblatex.js b/src/citation-js/plugin-bibtex/mapping/biblatex.js index 667ed20..7ca0bf7 100644 --- a/src/citation-js/plugin-bibtex/mapping/biblatex.js +++ b/src/citation-js/plugin-bibtex/mapping/biblatex.js @@ -8,7 +8,10 @@ const nonSpec = [ target: 'accessed', when: { source: false, - target: { note: false, addendum: false }, + target: { + note: false, + addendum: false, + }, }, convert: { toSource(accessed) { @@ -20,7 +23,9 @@ const nonSpec = [ source: 'numpages', target: 'number-of-pages', when: { - source: { pagetotal: false }, + source: { + pagetotal: false, + }, target: false, }, }, @@ -51,7 +56,9 @@ const nonSpec = [ target: 'custom', convert: { toTarget(S2ID) { - return { S2ID } + return { + S2ID, + } }, toSource({ S2ID }) { return S2ID @@ -59,13 +66,14 @@ const nonSpec = [ }, }, ] - const aliases = [ { source: 'annote', target: 'annote', when: { - source: { annotation: false }, + source: { + annotation: false, + }, target: false, }, }, @@ -74,7 +82,9 @@ const aliases = [ target: 'publisher-place', convert: Converters.PICK, when: { - source: { location: false }, + source: { + location: false, + }, target: false, }, }, @@ -83,7 +93,9 @@ const aliases = [ target: 'PMID', convert: Converters.EPRINT, when: { - source: { eprinttype: false }, + source: { + eprinttype: false, + }, target: false, }, }, @@ -113,7 +125,6 @@ const aliases = [ }, }, ] - export default new util.Translator([ ...aliases, ...nonSpec, @@ -131,9 +142,9 @@ export default new util.Translator([ target: 'annote', }, { - source: 'author', + source: ['author', 'author+an:orcid'], target: 'author', - convert: Converters.NAMES, + convert: Converters.NAMES_ORCID, }, { source: 'library', @@ -148,19 +159,14 @@ export default new util.Translator([ target: 'container-author', convert: Converters.NAMES, }, - - // Regarding maintitle, booktitle & journaltitle: - // When importing, maintitle is preferred, since it represents the - // larger container. When exporting, booktitle is preferred since - // it is more common, unless number-of-volumes is present indicating a - // multi-volume book. - // journaltitle is only used for articles. { source: ['maintitle', 'mainsubtitle', 'maintitleaddon'], target: 'container-title', when: { source: true, - target: { 'number-of-volumes': true }, + target: { + 'number-of-volumes': true, + }, }, convert: Converters.TITLE, }, @@ -168,7 +174,9 @@ export default new util.Translator([ source: ['booktitle', 'booksubtitle', 'booktitleaddon'], target: 'container-title', when: { - source: { maintitle: false }, + source: { + maintitle: false, + }, target: { 'number-of-volumes': false, type(type) { @@ -182,7 +190,9 @@ export default new util.Translator([ source: ['journaltitle', 'journalsubtitle', 'journaltitleaddon'], target: 'container-title', when: { - source: { [TYPE]: 'article' }, + source: { + [TYPE]: 'article', + }, target: { type: ['article', 'article-newspaper', 'article-journal', 'article-magazine'], }, @@ -193,7 +203,9 @@ export default new util.Translator([ source: 'shortjournal', target: 'container-title-short', when: { - source: { [TYPE]: 'article' }, + source: { + [TYPE]: 'article', + }, target: { type: ['article', 'article-newspaper', 'article-journal', 'article-magazine'], }, @@ -302,7 +314,6 @@ export default new util.Translator([ typeKey = 'techreport' } } - return [types.source[type] || 'document', typeKey || subtype] }, toSource(type, genre) { @@ -313,7 +324,11 @@ export default new util.Translator([ }, { source: TYPE, - when: { target: { type: false } }, + when: { + target: { + type: false, + }, + }, convert: { toSource() { return 'misc' @@ -338,7 +353,12 @@ export default new util.Translator([ source: ['eventtitle', 'eventtitleaddon'], target: 'event', convert: Converters.EVENT_TITLE, - when: { source: false, target: { 'event-title': false } }, + when: { + source: false, + target: { + 'event-title': false, + }, + }, }, { source: LABEL, @@ -401,7 +421,9 @@ export default new util.Translator([ target: 'issued', convert: Converters.YEAR_MONTH, when: { - source: { date: false }, + source: { + date: false, + }, target: false, }, }, @@ -409,8 +431,12 @@ export default new util.Translator([ source: 'location', target: 'jurisdiction', when: { - source: { type: 'patent' }, - target: { type: 'patent' }, + source: { + type: 'patent', + }, + target: { + type: 'patent', + }, }, }, { @@ -426,7 +452,12 @@ export default new util.Translator([ { source: 'langid', target: 'language', - when: { source: { language: false }, target: false }, + when: { + source: { + language: false, + }, + target: false, + }, }, { source: 'note', @@ -435,12 +466,21 @@ export default new util.Translator([ { source: 'addendum', target: 'note', - when: { source: { note: false }, target: false }, + when: { + source: { + note: false, + }, + target: false, + }, }, { source: 'eid', target: 'number', - when: { target: { type: ['article-journal'] } }, + when: { + target: { + type: ['article-journal'], + }, + }, }, { source: ['isan', 'ismn', 'isrn', 'iswc'], @@ -463,8 +503,12 @@ export default new util.Translator([ source: 'number', target: 'number', when: { - source: { [TYPE]: ['patent', 'report', 'techreport', 'legislation'] }, - target: { type: ['patent', 'report', 'legislation'] }, + source: { + [TYPE]: ['patent', 'report', 'techreport', 'legislation'], + }, + target: { + type: ['patent', 'report', 'legislation'], + }, }, }, { @@ -489,7 +533,11 @@ export default new util.Translator([ { source: 'pages', target: 'page', - when: { source: { bookpagination: [undefined, 'page'] } }, + when: { + source: { + bookpagination: [undefined, 'page'], + }, + }, convert: Converters.PAGES, }, { @@ -517,9 +565,6 @@ export default new util.Translator([ when: { source: true, target: { - // All except: - // - thesis, report: institution - // - webpage: organization type: [ 'article', 'article-journal', @@ -576,7 +621,7 @@ export default new util.Translator([ publisher: false, }, target: { - type: 'webpage', // TODO paper-conference? + type: 'webpage', }, }, }, @@ -616,8 +661,12 @@ export default new util.Translator([ source: ['pages', 'bookpagination'], target: 'section', when: { - source: { bookpagination: 'section' }, - target: { page: false }, + source: { + bookpagination: 'section', + }, + target: { + page: false, + }, }, convert: { toTarget(section) { @@ -640,7 +689,12 @@ export default new util.Translator([ { source: 'shorttitle', target: 'shortTitle', - when: { source: false, target: { 'title-short': false } }, + when: { + source: false, + target: { + 'title-short': false, + }, + }, }, { source: ['title', 'subtitle', 'titleaddon'], diff --git a/src/citation-js/plugin-bibtex/mapping/biblatexTypes.js b/src/citation-js/plugin-bibtex/mapping/biblatexTypes.js index 74f336a..d3f760e 100644 --- a/src/citation-js/plugin-bibtex/mapping/biblatexTypes.js +++ b/src/citation-js/plugin-bibtex/mapping/biblatexTypes.js @@ -10,9 +10,11 @@ const biblatexTypes = { mvcollection: 'book', incollection: 'chapter', dataset: 'dataset', + manual: 'report', + misc: 'document', online: 'webpage', patent: 'patent', - periodical: 'article-journal', + periodical: 'periodical', proceedings: 'book', mvproceedings: 'book', inproceedings: 'paper-conference', @@ -20,7 +22,7 @@ const biblatexTypes = { mvreference: 'book', inreference: 'entry', report: 'report', - software: 'book', + software: 'software', thesis: 'thesis', unpublished: 'manuscript', artwork: 'graphic', @@ -32,7 +34,9 @@ const biblatexTypes = { letter: 'personal_communication', movie: 'motion_picture', music: 'musical_score', + performance: 'performance', review: 'review', + standard: 'standard', video: 'motion_picture', conference: 'paper-conference', electronic: 'webpage', @@ -50,12 +54,17 @@ const biblatexTypes = { book: 'book', broadcast: 'audio', chapter: 'inbook', + classic: 'unpublished', + collection: 'misc', dataset: 'dataset', + document: 'misc', entry: 'inreference', 'entry-dictionary': 'inreference', 'entry-encyclopedia': 'inreference', + event: 'misc', figure: 'artwork', graphic: 'artwork', + hearing: 'legal', interview: 'audio', legal_case: 'jurisdiction', legislation: 'legislation', @@ -64,14 +73,19 @@ const biblatexTypes = { musical_score: 'music', 'paper-conference': 'inproceedings', patent: 'patent', + performance: 'performance', + periodical: 'periodical', personal_communication: 'letter', post: 'online', 'post-weblog': 'online', + regulation: 'legal', report: 'report', review: 'review', 'review-book': 'review', + software: 'software', song: 'music', speech: 'audio', + standard: 'standard', thesis: 'thesis', treaty: 'legal', webpage: 'online', diff --git a/src/citation-js/plugin-bibtex/mapping/bibtex.js b/src/citation-js/plugin-bibtex/mapping/bibtex.js index 48ef98c..385bc19 100644 --- a/src/citation-js/plugin-bibtex/mapping/bibtex.js +++ b/src/citation-js/plugin-bibtex/mapping/bibtex.js @@ -8,7 +8,9 @@ export default new util.Translator([ target: 'accessed', when: { source: false, - target: { note: false }, + target: { + note: false, + }, }, convert: { toSource(accessed) { @@ -113,6 +115,10 @@ export default new util.Translator([ }, }, }, + { + source: 'doi', + target: 'DOI', + }, { source: 'edition', target: 'edition', @@ -127,6 +133,14 @@ export default new util.Translator([ target: ['id', 'citation-key', 'author', 'issued', 'year-suffix', 'title'], convert: Converters.LABEL, }, + { + source: 'isbn', + target: 'ISBN', + }, + { + source: 'issn', + target: 'ISSN', + }, { source: 'number', target: 'issue', @@ -162,8 +176,24 @@ export default new util.Translator([ source: 'number', target: 'number', when: { - source: { [TYPE]: ['patent', 'report', 'techreport'] }, - target: { type: ['patent', 'report'] }, + source: { + [TYPE]: ['patent', 'report', 'techreport'], + }, + target: { + type: ['patent', 'report'], + }, + }, + }, + { + source: 'eid', + target: 'number', + when: { + source: { + number: false, + }, + target: { + type: ['article-journal'], + }, }, }, { @@ -177,7 +207,6 @@ export default new util.Translator([ convert: Converters.PICK, when: { target: { - // All except manuscript, paper-conference, techreport and thesis type: [ 'article', 'article-journal', @@ -229,8 +258,12 @@ export default new util.Translator([ target: 'publisher', convert: Converters.PICK, when: { - source: { publisher: false }, - target: { type: 'paper-conference' }, + source: { + publisher: false, + }, + target: { + type: 'paper-conference', + }, }, }, { @@ -242,7 +275,9 @@ export default new util.Translator([ publisher: false, organization: false, }, - target: { type: 'report' }, + target: { + type: 'report', + }, }, }, { @@ -255,7 +290,9 @@ export default new util.Translator([ organization: false, publisher: false, }, - target: { type: 'thesis' }, + target: { + type: 'thesis', + }, }, }, { @@ -286,9 +323,7 @@ export default new util.Translator([ target: ['type', 'genre'], convert: { toTarget(sourceType, subType) { - /* istanbul ignore next */ const type = types.source[sourceType] || 'document' - if (subType) { return [type, subType] } else if (sourceType === 'mastersthesis') { @@ -301,7 +336,6 @@ export default new util.Translator([ }, toSource(targetType, genre) { const type = types.target[targetType] || 'misc' - if (/^(master'?s|diploma) thesis$/i.test(genre)) { return ['mastersthesis'] } else if (/^(phd|doctoral) thesis$/i.test(genre)) { @@ -315,7 +349,9 @@ export default new util.Translator([ { source: TYPE, when: { - target: { type: false }, + target: { + type: false, + }, }, convert: { toSource() { @@ -323,6 +359,10 @@ export default new util.Translator([ }, }, }, + { + source: 'url', + target: 'URL', + }, { source: 'howpublished', target: 'URL', diff --git a/src/citation-js/plugin-bibtex/mapping/bibtexTypes.js b/src/citation-js/plugin-bibtex/mapping/bibtexTypes.js index 3b4db1c..58f9336 100644 --- a/src/citation-js/plugin-bibtex/mapping/bibtexTypes.js +++ b/src/citation-js/plugin-bibtex/mapping/bibtexTypes.js @@ -7,7 +7,9 @@ const bibtexTypes = { inbook: 'chapter', incollection: 'chapter', inproceedings: 'paper-conference', + manual: 'report', mastersthesis: 'thesis', + misc: 'document', phdthesis: 'thesis', proceedings: 'book', techreport: 'report', diff --git a/src/citation-js/plugin-bibtex/mapping/crossref.js b/src/citation-js/plugin-bibtex/mapping/crossref.js new file mode 100644 index 0000000..b201f57 --- /dev/null +++ b/src/citation-js/plugin-bibtex/mapping/crossref.js @@ -0,0 +1,126 @@ +function ownKeys(e, r) { + var t = Object.keys(e) + if (Object.getOwnPropertySymbols) { + var o = Object.getOwnPropertySymbols(e) + r && + (o = o.filter(function (r) { + return Object.getOwnPropertyDescriptor(e, r).enumerable + })), + t.push.apply(t, o) + } + return t +} +function _objectSpread(e) { + for (var r = 1; r < arguments.length; r++) { + var t = null != arguments[r] ? arguments[r] : {} + r % 2 + ? ownKeys(Object(t), !0).forEach(function (r) { + _defineProperty(e, r, t[r]) + }) + : Object.getOwnPropertyDescriptors + ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) + : ownKeys(Object(t)).forEach(function (r) { + Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)) + }) + } + return e +} +function _defineProperty(obj, key, value) { + key = _toPropertyKey(key) + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true, + }) + } else { + obj[key] = value + } + return obj +} +function _toPropertyKey(t) { + var i = _toPrimitive(t, 'string') + return 'symbol' == typeof i ? i : i + '' +} +function _toPrimitive(t, r) { + if ('object' != typeof t || !t) return t + var e = t[Symbol.toPrimitive] + if (void 0 !== e) { + var i = e.call(t, r || 'default') + if ('object' != typeof i) return i + throw new TypeError('@@toPrimitive must return a primitive value.') + } + return ('string' === r ? String : Number)(t) +} +const BOOK = new Set(['book', 'inbook', 'bookinbook', 'suppbook']) +const BOOK_PART = new Set(['inbook', 'bookinbook', 'suppbook']) +const COLLECTION = new Set([ + 'collection', + 'reference', + 'incollection', + 'inreference', + 'suppcollection', +]) +const COLLECTION_PART = new Set(['incollection', 'inreference', 'suppcollection']) +const PROCEEDINGS = new Set(['proceedings', 'inproceedings']) +const PROCEEDINGS_PART = new Set(['inproceedings']) +const PERIODICAL_PART = new Set(['article', 'suppperiodical']) +const TITLE_MAP = { + mvbook: ['main', BOOK], + mvcollection: ['main', COLLECTION], + mvreference: ['main', COLLECTION], + mvproceedings: ['main', PROCEEDINGS], + book: ['book', BOOK_PART], + collection: ['book', COLLECTION_PART], + reference: ['book', COLLECTION_PART], + proceedings: ['book', PROCEEDINGS_PART], + periodical: ['journal', PERIODICAL_PART], +} +export function crossref(target, entry, registry) { + if (entry.crossref in registry) { + const parent = registry[entry.crossref] + if (parent.properties === entry) { + return entry + } + const data = _objectSpread({}, crossref(parent.type, parent.properties, registry)) + delete data.ids + delete data.crossref + delete data.xref + delete data.entryset + delete data.entrysubtype + delete data.execute + delete data.label + delete data.options + delete data.presort + delete data.related + delete data.relatedoptions + delete data.relatedstring + delete data.relatedtype + delete data.shortand + delete data.shortandintro + delete data.sortkey + if ((parent.type === 'mvbook' || parent.type === 'book') && BOOK_PART.has(target)) { + data.bookauthor = data.author + } + if (parent.type in TITLE_MAP) { + const [prefix, targets] = TITLE_MAP[parent.type] + if (targets.has(target)) { + data[prefix + 'title'] = data.title + data[prefix + 'subtitle'] = data.subtitle + if (prefix !== 'journal') { + data[prefix + 'titleaddon'] = data.titleaddon + } + delete data.title + delete data.subtitle + delete data.titleaddon + delete data.shorttitle + delete data.sorttitle + delete data.indextitle + delete data.indexsorttitle + } + } + return Object.assign(data, entry) + } + return entry +} diff --git a/src/citation-js/plugin-bibtex/mapping/index.js b/src/citation-js/plugin-bibtex/mapping/index.js index 37bf6e9..b11404e 100644 --- a/src/citation-js/plugin-bibtex/mapping/index.js +++ b/src/citation-js/plugin-bibtex/mapping/index.js @@ -13,7 +13,6 @@ function _objectWithoutProperties(source, excluded) { } return target } - function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {} var target = {} @@ -26,56 +25,35 @@ function _objectWithoutPropertiesLoose(source, excluded) { } return target } - -function _toPropertyKey(arg) { - var key = _toPrimitive(arg, 'string') - return typeof key === 'symbol' ? key : String(key) -} - -function _toPrimitive(input, hint) { - if (typeof input !== 'object' || input === null) return input - var prim = input[Symbol.toPrimitive] - if (prim !== undefined) { - var res = prim.call(input, hint || 'default') - if (typeof res !== 'object') return res - throw new TypeError('@@toPrimitive must return a primitive value.') - } - return (hint === 'string' ? String : Number)(input) -} - -function ownKeys(object, enumerableOnly) { - var keys = Object.keys(object) +function ownKeys(e, r) { + var t = Object.keys(e) if (Object.getOwnPropertySymbols) { - var symbols = Object.getOwnPropertySymbols(object) - if (enumerableOnly) { - symbols = symbols.filter(function (sym) { - return Object.getOwnPropertyDescriptor(object, sym).enumerable - }) - } - keys.push.apply(keys, symbols) + var o = Object.getOwnPropertySymbols(e) + r && + (o = o.filter(function (r) { + return Object.getOwnPropertyDescriptor(e, r).enumerable + })), + t.push.apply(t, o) } - return keys + return t } - -function _objectSpread(target) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i] != null ? arguments[i] : {} - if (i % 2) { - ownKeys(Object(source), true).forEach(function (key) { - _defineProperty(target, key, source[key]) - }) - } else if (Object.getOwnPropertyDescriptors) { - Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) - } else { - ownKeys(Object(source)).forEach(function (key) { - Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)) - }) - } +function _objectSpread(e) { + for (var r = 1; r < arguments.length; r++) { + var t = null != arguments[r] ? arguments[r] : {} + r % 2 + ? ownKeys(Object(t), !0).forEach(function (r) { + _defineProperty(e, r, t[r]) + }) + : Object.getOwnPropertyDescriptors + ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) + : ownKeys(Object(t)).forEach(function (r) { + Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)) + }) } - return target + return e } - function _defineProperty(obj, key, value) { + key = _toPropertyKey(key) if (key in obj) { Object.defineProperty(obj, key, { value: value, @@ -88,32 +66,29 @@ function _defineProperty(obj, key, value) { } return obj } - +function _toPropertyKey(t) { + var i = _toPrimitive(t, 'string') + return 'symbol' == typeof i ? i : i + '' +} +function _toPrimitive(t, r) { + if ('object' != typeof t || !t) return t + var e = t[Symbol.toPrimitive] + if (void 0 !== e) { + var i = e.call(t, r || 'default') + if ('object' != typeof i) return i + throw new TypeError('@@toPrimitive must return a primitive value.') + } + return ('string' === r ? String : Number)(t) +} import { TYPE, LABEL } from './shared.js' import biblatex from './biblatex.js' import bibtex from './bibtex.js' - -function crossref(entry, registry) { - if (entry.crossref in registry) { - const parent = registry[entry.crossref].properties - - if (parent === entry) { - return entry - } - - return Object.assign({}, crossref(parent, registry), entry) - } - - return entry -} - +import { crossref } from './crossref.js' function _parse(input, spec) { const registry = {} - for (const entry of input) { registry[entry.label] = entry } - return input.map(({ type, label, properties }) => spec.convertToTarget( _objectSpread( @@ -121,12 +96,11 @@ function _parse(input, spec) { [TYPE]: type, [LABEL]: label, }, - crossref(properties, registry) + crossref(type, properties, registry) ) ) ) } - function _format(input, spec) { return input.map((entry) => { const _spec$convertToSource = spec.convertToSource(entry), @@ -135,7 +109,6 @@ function _format(input, spec) { _spec$convertToSource, [TYPE, LABEL].map(_toPropertyKey) ) - return { type, label, @@ -143,7 +116,6 @@ function _format(input, spec) { } }) } - export function parseBibtex(input) { return _parse(input, bibtex) } diff --git a/src/citation-js/plugin-bibtex/mapping/shared.js b/src/citation-js/plugin-bibtex/mapping/shared.js index 82e018f..500b588 100644 --- a/src/citation-js/plugin-bibtex/mapping/shared.js +++ b/src/citation-js/plugin-bibtex/mapping/shared.js @@ -1,10 +1,23 @@ -//@ts-nocheck +// @ts-nocheck import { util } from '../../core/index.js' import config from '../config.js' const stopWords = new Set(['the', 'a', 'an']) const unsafeChars = /(?:<\/?.*?>|[\u0020-\u002F\u003A-\u0040\u005B-\u005E\u0060\u007B-\u007F])+/g const unicode = /[^\u0020-\u007F]+/g - +function isLabelSafe(text) { + return !config.format.checkLabel || !text.match(unsafeChars) +} +function formatLabelFromId(id) { + if (id === null) { + return 'null' + } else if (id === undefined) { + return 'undefined' + } else if (config.format.checkLabel) { + return id.toString().replace(unsafeChars, '') + } else { + return id.toString() + } +} function firstWord(text) { if (!text) { return '' @@ -16,7 +29,6 @@ function firstWord(text) { .find((word) => word.length && !stopWords.has(word.toLowerCase())) } } - const name = new util.Translator([ { source: 'given', @@ -108,7 +120,6 @@ export function parseDate(date) { const year = +parts[0].replace(/^Y(?=-?\d{4}\d+)/, '').replace(/X/g, '0') const month = +parts[1] const day = +parts[2] - if (!month || month > 20) { return [year] } else if (!day) { @@ -121,21 +132,16 @@ export function parseMonth(value) { if (value == null) { return [] } - if (+value) { return [parseInt(value, 10)] } - value = value.trim().toLowerCase() - if (value in MONTHS) { return [MONTHS[value]] } - const parts = value.split(/\s+/) let month let day - if (parts[0] in MONTHS) { month = MONTHS[parts[0]] day = parseInt(parts[1]) @@ -143,26 +149,21 @@ export function parseMonth(value) { month = MONTHS[parts[1]] day = parseInt(parts[0]) } - return day ? [month, day] : month ? [month] : [] } export function formatLabel(author, issued, suffix, title) { let label = '' - if (author && author[0]) { label += firstWord(author[0].family || author[0].literal) } - if (issued && issued['date-parts'] && issued['date-parts'][0]) { label += issued['date-parts'][0][0] } - if (suffix) { label += suffix } else if (title) { label += firstWord(title) } - return label } export const Converters = { @@ -170,7 +171,6 @@ export const Converters = { toTarget(...args) { return args.find(Boolean) }, - toSource(value) { return [value] }, @@ -188,7 +188,6 @@ export const Converters = { 'date-parts': parts, } }, - toSource(date) { if ('date-parts' in date) { return date['date-parts'] @@ -215,11 +214,12 @@ export const Converters = { } } }, - toSource(date) { if ('date-parts' in date) { const [year, month, day] = date['date-parts'][0] return [year.toString(), month ? (day ? `${months[month - 1]} ${day}` : month) : undefined] + } else { + return [] } }, }, @@ -229,11 +229,21 @@ export const Converters = { return id } }, - toSource(id) { return [id, 'pubmed'] }, }, + EVENT_TITLE: { + toTarget(title, addon) { + if (addon) { + title += ' (' + addon + ')' + } + return title + }, + toSource(title) { + return title.match(/^(.+)(?: \((.+)\))?$/).slice(1, 3) + }, + }, HOW_PUBLISHED: { toTarget(howPublished) { if (howPublished.startsWith('http')) { @@ -245,7 +255,6 @@ export const Converters = { toTarget(list) { return list.join(',') }, - toSource(list) { return list.split(',') }, @@ -254,26 +263,13 @@ export const Converters = { toTarget(label) { return [label, label] }, - toSource(id, label, author, issued, suffix, title) { - let safeId - - if (id === null) { - safeId = 'null' - } else if (id === undefined) { - safeId = 'undefined' - } else { - safeId = id.toString().replace(unsafeChars, '') - } - - if (config.format.useIdAsLabel) { - return safeId - } - - if (label && !unsafeChars.test(label)) { + if (label && isLabelSafe(label)) { return label + } else if (config.format.useIdAsLabel) { + return formatLabelFromId(id) } else { - return formatLabel(author, issued, suffix, title) || safeId + return formatLabel(author, issued, suffix, title) || formatLabelFromId(id) } }, }, @@ -281,16 +277,59 @@ export const Converters = { toTarget(list) { return list.map(name.convertToTarget) }, - toSource(list) { return list.map(name.convertToSource) }, }, + NAMES_ORCID: { + toTarget(list, orcid) { + return list.map((inputName, i) => { + var _orcid$item + const outputName = name.convertToTarget(inputName) + if ( + typeof (orcid === null || + orcid === void 0 || + (_orcid$item = orcid.item) === null || + _orcid$item === void 0 + ? void 0 + : _orcid$item[i]) === 'string' + ) { + outputName._orcid = orcid.item[i] + } + return outputName + }) + }, + toSource(list) { + const names = [] + const orcid = [] + for (let i = 0; i < list.length; i++) { + names.push(name.convertToSource(list[i])) + if (list[i]._orcid) { + orcid[i] = list[i]._orcid + } + } + return [ + names, + orcid.length + ? { + item: orcid, + } + : undefined, + ] + }, + }, + PAGES: { + toTarget(pages) { + return pages.replace(/[–—]/, '-') + }, + toSource(pages) { + return pages.replace('-', '--') + }, + }, STANDARD_NUMBERS: { toTarget(...args) { return args.find(Boolean) }, - toSource(number) { const match = number.toString().match(STANDARD_NUMBERS_PATTERN) return match ? match.slice(1, 5) : [] @@ -308,10 +347,8 @@ export const Converters = { if (subtitle) { title += ': ' + subtitle } - return title }, - toSource(title) { return [title] }, diff --git a/src/citation-js/plugin-bibtex/output/bibtex.js b/src/citation-js/plugin-bibtex/output/bibtex.js index dc8cbbc..487f93f 100644 --- a/src/citation-js/plugin-bibtex/output/bibtex.js +++ b/src/citation-js/plugin-bibtex/output/bibtex.js @@ -1,10 +1,23 @@ -function formatEntry({ type, label, properties }, dict) { - const fields = Object.entries(properties).map(([field, value]) => - dict.listItem.join(`${field} = {${value}},`) - ) - return dict.entry.join(`@${type}{${label},${dict.list.join(fields.join(''))}}`) +import config from '../config.js' +function formatField(field, value, dict) { + return dict.listItem.join(`${field} = {${value}},`) +} +function formatEntry(entry, dict) { + const fields = [] + for (const field in entry.properties) { + fields.push(formatField(field, entry.properties[field], dict)) + if (entry.annotations && entry.annotations[field]) { + for (const annotation in entry.annotations[field]) { + let annotationField = field + config.biber.annotationMarker + if (annotation !== 'default') { + annotationField += config.biber.namedAnnotationMarker + annotation + } + fields.push(formatField(annotationField, entry.annotations[field][annotation], dict)) + } + } + } + return dict.entry.join(`@${entry.type}{${entry.label},${dict.list.join(fields.join(''))}}`) } - export function format(src, dict) { const entries = src.map((entry) => formatEntry(entry, dict)).join('') return dict.bibliographyContainer.join(entries) diff --git a/src/citation-js/plugin-bibtex/output/bibtxt.js b/src/citation-js/plugin-bibtex/output/bibtxt.js index cd9ae94..a3d3e30 100644 --- a/src/citation-js/plugin-bibtex/output/bibtxt.js +++ b/src/citation-js/plugin-bibtex/output/bibtxt.js @@ -4,7 +4,6 @@ function formatEntry({ type, label, properties }, dict) { .map(([field, value]) => dict.listItem.join(`${field}: ${value}`)) return dict.entry.join(`[${label}]${dict.list.join(fields.join(''))}`) } - export function format(src, dict) { const entries = src.map((entry) => formatEntry(entry, dict)).join('\n') return dict.bibliographyContainer.join(entries) diff --git a/src/citation-js/plugin-bibtex/output/entries.js b/src/citation-js/plugin-bibtex/output/entries.js index 68b8b6b..14fe73a 100644 --- a/src/citation-js/plugin-bibtex/output/entries.js +++ b/src/citation-js/plugin-bibtex/output/entries.js @@ -1,21 +1,28 @@ import { format as mapBiblatex, formatBibtex as mapBibtex } from '../mapping/index.js' -import { format as formatValue } from './value.js' - +import { format as formatValue, formatAnnotation } from './value.js' function formatEntryValues({ type, label, properties }) { - const output = {} - - for (const property in properties) { - const value = properties[property] - output[property] = formatValue(property, value) - } - - return { + const output = { type, label, - properties: output, + properties: {}, + } + for (const property in properties) { + const value = properties[property] + const [field, annotation] = property.split('+an:') + if (annotation) { + if (!output.annotations) { + output.annotations = {} + } + if (!output.annotations[field]) { + output.annotations[field] = {} + } + output.annotations[field][annotation] = formatAnnotation(value) + } else { + output.properties[property] = formatValue(property, value) + } } + return output } - export function format(entries) { return mapBiblatex(entries).map(formatEntryValues) } diff --git a/src/citation-js/plugin-bibtex/output/index.js b/src/citation-js/plugin-bibtex/output/index.js index 00bc32f..0dac912 100644 --- a/src/citation-js/plugin-bibtex/output/index.js +++ b/src/citation-js/plugin-bibtex/output/index.js @@ -2,12 +2,10 @@ import { plugins } from '../../core/index.js' import { format as mapBiblatex, formatBibtex as mapBibtex } from './entries.js' import { format } from './bibtex.js' import { format as formatBibtxt } from './bibtxt.js' - const factory = function (mapper, formatter) { return function (data, opts = {}) { const { type, format = type || 'text' } = opts data = mapper(data) - if (format === 'object') { return data } else if (plugins.dict.has(format)) { @@ -17,7 +15,6 @@ const factory = function (mapper, formatter) { } } } - export default { bibtex: factory(mapBibtex, format), biblatex: factory(mapBiblatex, format), diff --git a/src/citation-js/plugin-bibtex/output/value.js b/src/citation-js/plugin-bibtex/output/value.js index 91582a5..734d6ca 100644 --- a/src/citation-js/plugin-bibtex/output/value.js +++ b/src/citation-js/plugin-bibtex/output/value.js @@ -1,22 +1,24 @@ -//@ts-nocheck -import { diacritics, commands, ligatures, fieldTypes } from '../input/constants.js' +// @ts-nocheck +import config from '../config.js' +import { diacritics, commands, mathCommands, ligatures, fieldTypes } from '../input/constants.js' const unicode = {} - for (const command in commands) { unicode[commands[command]] = command } - for (const diacritic in diacritics) { unicode[diacritics[diacritic]] = diacritic } - for (const ligature in ligatures) { unicode[ligatures[ligature]] = ligature } - +const mathUnicode = {} +for (const command in mathCommands) { + mathUnicode[mathCommands[command]] = command +} const UNSAFE_UNICODE = /[^a-zA-Z0-9\s!"#%&'()*+,\-./:;=?@[\]{}\u0300-\u0308\u030a-\u030c\u0332\u0323\u0327\u0328\u0361\u0326]/g const DIACRITIC_PATTERN = /.[\u0300-\u0308\u030a-\u030c\u0332\u0323\u0327\u0328\u0361\u0326]+/g +const LONE_DIACRITIC_PATTERN = /[\u0300-\u0308\u030a-\u030c\u0332\u0323\u0327\u0328\u0361\u0326]/g const listDelimiters = { separated: ',', list: ' and ', @@ -30,18 +32,27 @@ const richTextMappings = { 'span style="font-variant:small-caps;"': '\\textsc{', 'span class="nocase"': '{', } - +function escapeCharacter(char) { + if (char in unicode) { + return unicode[char] in ligatures ? unicode[char] : `\\${unicode[char]}{}` + } else if (char in mathUnicode) { + return `$\\${mathUnicode[char]}$` + } else { + return '' + } +} function escapeValue(value) { + if (!config.format.asciiOnly) { + return value + } return value .normalize('NFKD') - .replace(UNSAFE_UNICODE, (char) => - char in unicode ? (unicode[char] in ligatures ? unicode[char] : `\\${unicode[char]}{}`) : '' - ) + .replace(UNSAFE_UNICODE, (char) => escapeCharacter(char)) .replace(DIACRITIC_PATTERN, (match) => Array.from(match).reduce((subject, diacritic) => `{\\${unicode[diacritic]} ${subject}}`) ) + .replace(LONE_DIACRITIC_PATTERN, '') } - function formatRichText(value) { const closingTags = [] let tokens = value.split(/<(\/?(?:i|b|sc|sup|sub|span)|span .*?)>/g) @@ -60,59 +71,46 @@ function formatRichText(value) { }) return tokens.join('') } - function formatName(name) { if (name.family && !name.prefix && !name.given & !name.suffix) { return name.family.includes(listDelimiters.list) ? name.family : `{${name.family}}` } - const parts = [''] - if (name.prefix && name.family) { parts[0] += name.prefix + ' ' } - if (name.family) { parts[0] += name.family } - if (name.suffix) { parts.push(name.suffix) parts.push(name.given || '') } else { parts.push(name.given) } - return escapeValue(parts.join(', ').trim()) } - function formatTitle(title) { return formatRichText(title) .split(/(:\s*)/) - .map((part, i) => (i % 2 ? part : part.replace(/(?!^)\b[a-z]*[A-Z].*?\b/g, '{$&}'))) + .map((part, i) => (i % 2 ? part : part.replace(/([^\\])\b([a-z]*[A-Z].*?)\b/g, '$1{$2}'))) .join('') } - function formatSingleValue(value, valueType) { switch (valueType) { case 'title': return formatTitle(value) - case 'literal': return formatRichText(value.toString()) - case 'name': return formatName(value) - case 'verbatim': case 'uri': return value.toString() - default: return escapeValue(value.toString()) } } - function formatList(values, valueType, listType) { const delimiter = listDelimiters[listType] return values @@ -122,17 +120,51 @@ function formatList(values, valueType, listType) { }) .join(delimiter) } - +function formatAnnotationValue(values) { + if (Array.isArray(values)) { + return values.map((value) => escapeValue(value).replace(/([;,"])/g, '{$1}')).join(', ') + } else { + return '"' + escapeValue(values).replace(/(["])/g, '{$1}') + '"' + } +} export function format(field, value) { if (!(field in fieldTypes)) { return formatSingleValue(value, 'verbatim') } - const [listType, valueType] = fieldTypes[field] - if (listType in listDelimiters) { return formatList(value, valueType, listType) } else { return formatSingleValue(value, valueType) } } +export function formatAnnotation(value) { + const annotations = [] + if (value.field) { + annotations.push('=' + formatAnnotationValue(value.field)) + } + if (value.item) { + for (const [itemCount, itemValue] of Object.entries(value.item)) { + if (!itemValue) { + continue + } + const i = parseInt(itemCount) + 1 + annotations.push(i + '=' + formatAnnotationValue(itemValue)) + } + } + if (value.part) { + for (const [itemCount, itemValue] of Object.entries(value.part)) { + if (!itemValue) { + continue + } + const i = parseInt(itemCount) + 1 + for (const part in itemValue) { + if (!itemValue[part]) { + continue + } + annotations.push(i + ':' + part + '=' + formatAnnotationValue(itemValue[part])) + } + } + } + return annotations.join('; ') +} diff --git a/src/citation-js/plugin-cff/index.js b/src/citation-js/plugin-cff/index.js index 18cdd2f..e62844f 100644 --- a/src/citation-js/plugin-cff/index.js +++ b/src/citation-js/plugin-cff/index.js @@ -708,6 +708,7 @@ function format(input, options = {}) { } if (input.length) { + // @ts-ignore cff.references = input.map(refTranslator.convertToSource) } diff --git a/src/citation-js/plugin-csl/affix.js b/src/citation-js/plugin-csl/affix.js index 2efda2f..37ecd00 100644 --- a/src/citation-js/plugin-csl/affix.js +++ b/src/citation-js/plugin-csl/affix.js @@ -1,38 +1,10 @@ -/** - * Get a rendered affix - * - * @access private - * - * @param {CSL} source - source element - * @param {String|Cite~wrapper} affix - * - * @return {String} Rendered affixs - */ const getAffix = (source, affix) => typeof affix === 'function' ? affix(source) : typeof affix === 'string' ? affix : '' - -// This simplified Regex only fails when one of the attributes of the top-level -// element contains a right angle bracket (">"). const htmlRegex = /^([^>]+>)([\s\S]+)(<[^<]+)$/i - -/** - * Pre/append things to entry - * - * @access private - * - * @param {String} value - HTML string - * @param {CSL} source - source element - * @param {Object} wrapping - append / prepend configuration - * @param {String|Cite~wrapper} [wrapping.prepend] - * @param {String|Cite~wrapper} [wrapping.append] - * - * @return {String} Wrapped HTML string - */ const getWrappedEntry = (value, source, { prepend, append }) => { const [, start = '', content = value, end = ''] = value.match(htmlRegex) || [] const prefix = getAffix(source, prepend) const suffix = getAffix(source, append) return start + prefix + content + suffix + end } - export { getWrappedEntry } diff --git a/src/citation-js/plugin-csl/attr.js b/src/citation-js/plugin-csl/attr.js index 291404a..2e42182 100644 --- a/src/citation-js/plugin-csl/attr.js +++ b/src/citation-js/plugin-csl/attr.js @@ -1,27 +1,4 @@ -/** - * Add data-* attribute to a HTML string - * - * @access private - * - * @param {String} string - HTML string - * @param {String} name - attribute name - * @param {String} value - attribute value - * - * @return {String} HTML string with attribute - */ const getAttributedEntry = (string, name, value) => string.replace(/^\s*<[a-z]+/i, `$& data-${name}="${value}"`) - -/** - * Add CSL identifiers to entry - * - * @access private - * - * @param {String} value - HTML string - * @param {String} id - ID - * - * @return {String} HTML string with CSL ID - */ const getPrefixedEntry = (value, id) => getAttributedEntry(value, 'csl-entry-id', id) - export { getAttributedEntry, getPrefixedEntry } diff --git a/src/citation-js/plugin-csl/bibliography.js b/src/citation-js/plugin-csl/bibliography.js index 2ccbe28..b88db15 100644 --- a/src/citation-js/plugin-csl/bibliography.js +++ b/src/citation-js/plugin-csl/bibliography.js @@ -1,130 +1,31 @@ -// @ts-nocheck import { util } from '../core/index.js' import prepareEngine from './engines.js' import { getPrefixedEntry } from './attr.js' - -/** - * Get a rendered affix - * - * @access private - * - * @param {CSL} source - source element - * @param {String|Cite~wrapper} affix - * - * @return {String} Rendered affixs - */ const getAffix = (source, affix) => (typeof affix === 'function' ? affix(source) : affix || '') - -/** - * This plugin adds the output format `bibliography`, and accepts the following specific options: - * - * * `prepend` (`String`, `Function`): prepend static or dynamic text to each entry - * * `append` (`String`, `Function`): append static or dynamic text to each entry - * * `nosort` (`Boolean`, default: `false`): do not sort according to the style-defined rules - * * `asEntryArray` (`Boolean`, default: `false`): return an array of entries consisting of an id and the output for that individual entry - * - * Here's an example for `prepend` and `append`: - * - * ```js - * let cite = new Cite({ id: 'a', title: 'Item A' }) - * - * cite.format('bibliography', { append: ' [foobar]' }) - * // 'Item A. (n.d.). [foobar]\n' - * - * cite.format('bibliography', { prepend (entry) { return `${entry.id}: ` } }) - * // 'a: Item A. (n.d.).\n' - * ``` - * - * And here's another example, possibly more realistic: - * - * ```js - * let cite = new Cite('Q30000000') - * - * let date = (new Date()).toLocaleDateString() - * - * cite.format('bibliography', { - * format: 'html', - * template: 'apa', - * prepend (entry) { - * return `[${entry.id}]: ` - * }, - * append: ` [Retrieved on ${date}]` - * }) - * - * // `
    - * //
    - * // [Q30000000]: Miccadei, S., De Leo, R., Zammarchi, E., Natali, P. G., & Civitareale, D. (2002). The Synergistic Activity of Thyroid Transcription Factor 1 and Pax 8 Relies on the Promoter/Enhancer Interplay. Molecular Endocrinology, 16(4), 837–846. https://doi.org/10.1210/MEND.16.4.0808 [Retrieved on 2018-7-10] - * //
    - * //
    ` - * ``` - * - * This prepends `[$ID]: ` to each entry, where `$ID` is the ID of that entry, and appends ` [Retrieved on $DATE]`, where `$DATE` is today (constant for all entries). - * - * Here's an example for `asEntryArray`: - * - * ```js - * const cite = new Cite([ - * { id: 'a', title: 'Item A', issued: { literal: 2021 } }, - * { id: 'b', title: 'Item B', issued: { literal: 2021 } } - * ]) - * - * cite.format('bibliography', { asEntryArray: true }) - * // [ - * // [ - * // "a" - * // "Item A. (2021).\n" - * // ], - * // [ - * // "b" - * // "Item B. (2021).\n" - * // ] - * // ] - * ``` - * - * @memberof module:@citation-js/plugin-csl.output - * @implements module:@citation-js/core.plugins.output~formatter - * @method bibliography - * - * @param {Array} data - * @param {Object} [options={}] - * @param {String} [options.template='apa'] - * @param {String} [options.lang] - * @param {String} [options.format='text'] - * @param {Booolean} [options.asEntryArray=false] - * @param {Booolean} [options.nosort=false] - * @param {String|Array} [options.entry] - * @param {Cite~wrapper} [options.prepend] - * @param {Cite~wrapper} [options.append] - * - * @return {String} output - */ export default function bibliography(data, options = {}) { const { template = 'apa', lang, format = 'text', nosort = false } = options const ids = options.entry ? [].concat(options.entry) : data.map(({ id }) => id) data = util.downgradeCsl(data) - const citeproc = prepareEngine(data, template, lang, format) const sortedIds = citeproc.updateItems(ids, nosort) - if (options.append || options.prepend) { const items = data.reduce((items, entry) => { items[entry.id] = entry return items }, {}) - citeproc.sys.wrapBibliographyEntry = function (id) { const entry = items[id] return [getAffix(entry, options.prepend), getAffix(entry, options.append)] } } - + if (options.hyperlinks) { + citeproc.opt.development_extensions.wrap_url_and_doi = true + } const bibliography = citeproc.makeBibliography() const [{ bibstart, bibend }, bibBody] = bibliography const entries = bibBody.map((element, index) => getPrefixedEntry(element, sortedIds[index])) - if (options.asEntryArray) { return entries.map((element, index) => [sortedIds[index], element]) } - return bibstart + entries.join('') + bibend } diff --git a/src/citation-js/plugin-csl/citation.js b/src/citation-js/plugin-csl/citation.js index a4ef417..3f9e575 100644 --- a/src/citation-js/plugin-csl/citation.js +++ b/src/citation-js/plugin-csl/citation.js @@ -1,110 +1,38 @@ -// @ts-nocheck import { util } from '../core/index.js' import prepareEngine from './engines.js' - -/** - * https://citeproc-js.readthedocs.io/en/latest/csl-json/markup.html#cite-items - * - * @typedef {Object} module:@citation-js/plugin-csl.output~CiteItem - * @property {String} id - */ - -/** - * https://citeproc-js.readthedocs.io/en/latest/csl-json/markup.html#citations - * - * @typedef {Object} module:@citation-js/plugin-csl.output~Citation - * @property {Array} citationItems - * @property {Object} properties - * @property {Number} properties.noteIndex - */ - -/** - * @access private - * @param {String|module:@citation-js/plugin-csl.output~CiteItem} citeItem - * @return {module:@citation-js/plugin-csl.output~CiteItem} citeItem - */ function prepareCiteItem(citeItem) { - return typeof citeItem === 'object' ? citeItem : { id: citeItem } + return typeof citeItem === 'object' + ? citeItem + : { + id: citeItem, + } } - -/** - * @access private - * @param {String|Array|Array|module:@citation-js/plugin-csl.output~CiteItem|module:@citation-js/plugin-csl.output~Citation} citation - * @return {module:@citation-js/plugin-csl.output~Citation} citation - */ function prepareCitation(citation) { if (citation.citationItems) { return citation } - return { citationItems: [].concat(citation).map(prepareCiteItem), - properties: { noteIndex: 0 }, + properties: { + noteIndex: 0, + }, } } - -/** - * @access private - * @param {Array|Array|Array} [context=[]] - * @return {Array} citations - */ function prepareCitations(context) { if (!context) { return [] } return context.map(prepareCitation) } - -/** - * Here's an example for `entry`: - * - * ```js - * let cite = new Cite([ - * { id: 'a', title: 'Item A', issued: { 'date-parts': [[2016]] } }, - * { id: 'b', title: 'Item B', issued: { 'date-parts': [[2017]] } }, - * { id: 'c', title: 'Item C', issued: { 'date-parts': [[2018]] } } - * ]) - * - * cite.format('citation') - * // '(“Item A,” 2016; “Item B,” 2017; “Item C,” 2018)' - * - * cite.format('citation', { entry: ['a', 'b'] }) - * // '(“Item A,” 2016; “Item B,” 2017)' - * - * cite.format('citation', { entry: 'a' }) - * // '(“Item A,” 2016)' - * - * cite.format('citation', { entry: [{ id: 'a', label: 'page', locator: 123 }] }) - * // '(“Item A,” 2016, p. 123)' - * ``` - * - * @memberof module:@citation-js/plugin-csl.output - * @implements module:@citation-js/core.plugins.output~formatter - * @method citation - * - * @param {Array} data - * @param {Object} [options={}] - * @param {String} [options.template='apa'] - * @param {String} [options.lang] - * @param {String} [options.format='text'] - * @param {module:@citation-js/plugin-csl.output~Entries} [options.entry] - list of ids or cite-items of entries to include in the citation (defaults to all) - * @param {Array} [options.citationsPre=[]] - * @param {Array} [options.citationsPost=[]] - * - * @return {String} output - */ export default function citation(data, options = {}) { const { template = 'apa', lang, format = 'text' } = options const ids = data.map(({ id }) => id) const entries = options.entry ? options.entry : ids data = util.downgradeCsl(data) - const citeproc = prepareEngine(data, template, lang, format) - const before = prepareCitations(options.citationsPre) const citation = prepareCitation(entries) const after = prepareCitations(options.citationsPost) const output = citeproc.rebuildProcessorState([...before, citation, ...after], format, []) - return output[before.length][2] } diff --git a/src/citation-js/plugin-csl/engines.js b/src/citation-js/plugin-csl/engines.js index 40fad5f..a461c49 100644 --- a/src/citation-js/plugin-csl/engines.js +++ b/src/citation-js/plugin-csl/engines.js @@ -1,10 +1,6 @@ -// @ts-nocheck import CSL from 'citeproc' - import { templates } from './styles.js' import { locales } from './locales.js' - -// BEGIN add sys function const proxied = Symbol.for('proxied') const getWrapperProxy = function (original) { const proxy = function (state, entry) { @@ -17,63 +13,27 @@ const getWrapperProxy = function (original) { proxy[proxied] = true return proxy } - for (const format in CSL.Output.Formats) { const original = CSL.Output.Formats[format]['@bibliography/entry'] - - /* istanbul ignore if: currently unreachable */ if (!original || original[proxied]) { continue } - CSL.Output.Formats[format]['@bibliography/entry'] = getWrapperProxy(original) } -// END - -/** - * @access private - * @param {String} locale - locale code - * @return {String} locale XML - */ function retrieveLocale(locale) { if (locales.has(locale)) { return locales.get(locale) } - const unnormalised = locale.replace('-', '_') if (locales.has(unnormalised)) { return locales.get(unnormalised) } - - // Should only occur when a cs:style default-locale is unknown return {} } - -/** - * Object containing CSL Engines - * - * @access private - * @constant - */ const engines = {} - -/** - * Retrieve CSL parsing engine - * - * @access private - * - * @param {String} style - CSL style id - * @param {String} lang - Language code - * @param {String} template - CSL XML template - * @param {module:output/csl~retrieveItem} retrieveItem - Code to retreive item - * @param {module:output/csl~retrieveLocale} retrieveLocale - Code to retreive locale - * - * @return {Object} CSL Engine - */ const fetchEngine = function (style, locale, styleXml, retrieveItem, retrieveLocale) { const engineHash = `${style}|${locale}` let engine - if (engines[engineHash] instanceof CSL.Engine) { engine = engines[engineHash] engine.sys.retrieveItem = retrieveItem @@ -81,40 +41,27 @@ const fetchEngine = function (style, locale, styleXml, retrieveItem, retrieveLoc engine.updateItems([]) } else { engine = engines[engineHash] = new CSL.Engine( - { retrieveLocale, retrieveItem }, + { + retrieveLocale, + retrieveItem, + }, styleXml, locale, true ) } - return engine } - -/** - * Prepare CSL parsing engine - * - * @access private - * - * @param {Array} data - * @param {String} templateName - * @param {String} language - * @param {String} format - * - * @return {Object} CSL Engine - */ const prepareEngine = function (data, style, locale, format) { if (!CSL.Output.Formats[format] || !CSL.Output.Formats[format]['@bibliography/entry']) { throw new TypeError(`Cannot find format '${format}'`) } - const items = data.reduce((store, entry) => { store[entry.id] = entry return store }, {}) const template = templates.get(templates.has(style) ? style : 'apa') locale = locales.has(locale) ? locale : undefined - const callback = function (key) { if (Object.prototype.hasOwnProperty.call(items, key)) { return items[key] @@ -122,12 +69,10 @@ const prepareEngine = function (data, style, locale, format) { throw new Error(`Cannot find entry with id '${key}'`) } } - const engine = fetchEngine(style, locale, template, callback, retrieveLocale) engine.setOutputFormat(format) - + engine.opt.development_extensions.wrap_url_and_doi = false return engine } - export default prepareEngine export { fetchEngine } diff --git a/src/citation-js/plugin-csl/index.js b/src/citation-js/plugin-csl/index.js index e0f2d01..b8c6994 100644 --- a/src/citation-js/plugin-csl/index.js +++ b/src/citation-js/plugin-csl/index.js @@ -1,156 +1,17 @@ -// @ts-nocheck -/** - * ## Formats - * - * Formats and other features added by this plugin. General output options: - * - * * `template`: the style template to use. Currently, the following are built-in: - * * `apa` (default) - * * `vancouver` - * * `harvard1` - * * `lang`: the locale to use. Currently, the following are built-in: - * * `en-US` (default) - * * `es-ES` - * * `de-DE` - * * `fr-FR` - * * `nl-NL` - * * `format`: output (markup) format. Note: this doesn't support the output format dictionaries - * * `entry` (`String`, `Array[String]`): entry ID or list of entry IDs to identify the items to cite - * - * For all formats and format-specific options, check out {@link module:@citation-js/plugin-csl.output}. - * - * @module @citation-js/plugin-csl - */ - -/** - * @callback module:output/csl~retrieveItem - * @param {String} id - Citation id - * @return {CSL} CSL Citation object - */ - -/** - * @callback module:output/csl~retrieveLocale - * @param {String} lang - Language code - * @return {String} CSL Locale - */ - -/** - * @typedef {module:@citation-js/plugin-csl.output~Entry|Array} module:@citation-js/plugin-csl.output~Entries - */ - -/** - * @typedef {String|module:@citation-js/plugin-csl.output~DetailedEntry} module:@citation-js/plugin-csl.output~Entry - */ - -/** - * Corresponds directly to the {@link https://citeproc-js.readthedocs.io/en/latest/csl-json/markup.html#cite-items|cite-items of citeproc-js}. - * - * @typedef {Object} module:@citation-js/plugin-csl.output~DetailedEntry - * @property {Number|String} [locator] - page, figure, section etc. number of the citation - * @property {String} [label='page'] - type of locator to accompany the locator number - * @property {Boolean} [suppress-author=false] - * @property {Boolean} [author-only=false] - * @property {String} [prefix] - * @property {String} [suffix] - */ - import { plugins } from '../core/index.js' - import { locales } from './locales.js' import { templates } from './styles.js' import engine from './engines.js' - import bibliography from './bibliography.js' import citation from './citation.js' - plugins.add('@csl', { - /** - * @namespace output - * @type Object - * @memberof module:@citation-js/plugin-csl - */ output: { bibliography, citation, }, - - /** - * @namespace config - * @memberof module:@citation-js/plugin-csl - */ config: { - /** - * The configuration object also exposes an internal method to prepare a Citeproc engine with given data and configuration: - * - * ```js - * let config = plugins.config.get('@csl') - * - * let citeproc = plugins.engine( - * [{ ... }], // data - * 'apa', // style - * 'en-US', // locale - * 'html' // format - * ) - * - * let sortedIds = citeproc.updateItems( - * [...] // data ids - * ) - * let makeBibliography = citeproc.makeBibliography() - * ``` - * - * @memberof module:@citation-js/plugin-csl.config - * @method engine - * @param {module:@citation-js/core~CSL} data - * @param {String} style - * @param {String} locale - * @param {String} format - */ engine, - - /** - * Different [CSL Locales](https://github.com/citation-style-language/locales) can be registered like this: - * - * ```js - * let language = 'en-GB' - * let locale = '...' // The actual XML file - * - * let config = plugins.config.get('@csl') - * config.locales.add(language, locale) - * - * let example = new Cite(...) - * example.format('bibliography', { - * format: 'html', - * template: 'apa', - * lang: language - * }) - * ``` - * - * @memberof module:@citation-js/plugin-csl.config - * @var {module:@citation-js/core.util.Register} locales - */ locales, - - /** - * Different [CSL Templates](https://github.com/citation-style-language/styles) can be registered like this: - * - * ```js - * let templateName = 'custom' - * let template = '' // The actual XML file - * - * let config = plugins.config.get('@csl') - * config.templates.add(templateName, template) - * - * let example = new Cite(...) - * example.format('bibliography', { - * format: 'html', - * template: templateName, - * lang: 'en-US' - * }) - * ``` - * - * @memberof module:@citation-js/plugin-csl.config - * @var {module:@citation-js/core.util.Register} templates - */ templates, }, }) diff --git a/src/generator.js b/src/generator.js index 766f69f..5bb94e5 100644 --- a/src/generator.js +++ b/src/generator.js @@ -47,6 +47,8 @@ const rehypeCitationGenerator = (Cite) => { let bibtexFile = [] /** @type {string} */ // @ts-ignore const inputCiteformat = options.csl || file?.data?.frontmatter?.csl || defaultCiteFormat + /** @type {string[] | false} */ // @ts-ignore + const noCite = options.noCite || file?.data?.frontmatter?.csl || false const inputLang = options.lang || 'en-US' const config = Cite.plugins.config.get('@csl') const citeFormat = await loadCSL(Cite, inputCiteformat, options.path) @@ -142,8 +144,12 @@ const rehypeCitationGenerator = (Cite) => { ] }) - if (options.noCite) { - citeproc.updateItems(options.noCite.map((x) => x.replace('@', ''))) + if (noCite) { + if (noCite.length === 1 && noCite[0] === '@*') { + citeproc.updateItems(citationIds) + } else { + citeproc.updateItems(noCite.map((x) => x.replace('@', ''))) + } } if ( diff --git a/src/utils.js b/src/utils.js index c36a194..aaf44bb 100644 --- a/src/utils.js +++ b/src/utils.js @@ -49,8 +49,8 @@ export const getBibliography = async (options, file) => { typeof options.bibliography === 'string' ? [options.bibliography] : options.bibliography // @ts-ignore } else if (file?.data?.frontmatter?.bibliography) { - // @ts-ignore bibliography = + // @ts-ignore typeof file.data.frontmatter.bibliography === 'string' ? [file.data.frontmatter.bibliography] : file.data.frontmatter.bibliography diff --git a/test/generator.js b/test/generator.js index cf31f90..08a5ad1 100644 --- a/test/generator.js +++ b/test/generator.js @@ -3,7 +3,7 @@ import * as assert from 'uvu/assert' import { rehype } from 'rehype' import dedent from 'dedent' import vancouver from '../styles/vancouver.js' -import zhCN from '../locales/zh-CN' +import zhCN from '../locales/zh-CN.js' import Cite from '../src/cite.js' import rehypeCitationGenerator from '../src/generator.js' diff --git a/test/index.js b/test/index.js index 40223ab..6cd3e95 100644 --- a/test/index.js +++ b/test/index.js @@ -169,6 +169,20 @@ rehypeCitationTest('no-cite', async () => { assert.is(result, expected) }) +rehypeCitationTest('no-cite catch all', async () => { + const result = await processHtml('
    text
    ', { + noCite: ['@*'], + }) + const expected = dedent`
    text
    +
    MacFarlane, J. (2006). Pandoc: a universal document converter. https://pandoc.org/
    +
    Nash, J. (1950). Equilibrium points in n-person games. Proceedings of the National Academy of Sciences, 36(1), 48–49.
    +
    Nash, J. (1951). Non-cooperative games. Annals of Mathematics, 286–295.
    +
    Verma, S., & Rubin, J. (2018). Fairness definitions explained. 2018 Ieee/Acm International Workshop on Software Fairness (Fairware), 1–7.
    +
    Xie, Y. (2016). Bookdown: authoring books and technical documents with R markdown. CRC Press.
    +
    ` + assert.is(result, expected) +}) + rehypeCitationTest('handle prefix, suffix and locator', async () => { const result = await processHtml(dedent`
    [see @Nash1950, 5-6 suffix]
    `, { suppressBibliography: true, @@ -227,7 +241,6 @@ rehypeCitationTest('throw error if invalid url path', async () => { assert.unreachable('should have thrown') } catch (err) { assert.instance(err, Error) - assert.match(err.message, 'This format is not supported or recognized') } })