@iebh/reflib 2.5.8 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,6 +12,7 @@ Compatibility
12
12
 
13
13
  | Library | Extension(s) | Read | Write |
14
14
  |------------------------|-----------------|--------------------|--------------------|
15
+ | BibTeX | `.bib` | :heavy_check_mark: | :heavy_check_mark: |
15
16
  | Comma Separated Values | `.csv` | :x: | :x: |
16
17
  | EndNote ENL | `.enl` | :heavy_check_mark: | (untested) |
17
18
  | EndNote ENLX | `.enlx` | :heavy_check_mark: | :x: |
@@ -24,6 +25,8 @@ Compatibility
24
25
 
25
26
  **Notes on different formats**:
26
27
 
28
+ * Not all formats are one-to-one translations, some have differing field definitions or type definitions
29
+ * BibTeX support is only provided for "well-formatted" files - i.e. correct use of newlines rather than one-single-line-per-ref, this is to avoid having to implement a full AST parser
27
30
  * Medline seems to implement a totally different [publication type system](https://www.nlm.nih.gov/mesh/pubtypes.html) than others. Reflib will attempt to guess the best match, storing the original type in the `medlineType` key. Should the citation library be exported _back_ to Medline / `.nbib` files this key will take precedence to avoid data loss
28
31
 
29
32
 
@@ -36,40 +39,40 @@ Each reference has the following standardized fields, these are translated from
36
39
 
37
40
  | Field | Type | Description |
38
41
  |------------------|-----------------|----------------------------------------------------------------------------------------|
39
- | recNumber | `number` | The sorting number of the reference. Not present in RIS files |
40
- | type | `string` | A supported [reference type](#reference-types) (e.g. journalArticle) |
41
- | title | `string` | The reference's main title |
42
- | journal | `string` | The reference's secondary title, this is usually the journal for most published papers |
43
- | authors | `array<string>` | An array of each Author in the originally specified format |
44
- | date | `string` | The raw, internal date of the reference |
45
- | urls | `array<string>` | An array of each URL for the reference |
46
- | pages | `string` | The page reference, usually in the format `123-4` |
47
- | volume | `string` | |
48
- | number | `string` | |
49
- | isbn | `string` | |
50
- | abstract | `string` | |
51
- | label | `string` | |
52
- | caption | `string` | |
53
- | notes | `string` | |
54
- | address | `string` | |
55
- | researchNotes | `string` | |
56
- | keywords | `array<string>` | Optional list of keywords that apply to the reference |
57
- | accessDate | `string` | |
58
- | accession | `string` | [Accession numbers spec](https://support.nlm.nih.gov/knowledgebase/article/KA-03434/en-us), can sometimes be the PubMed ID |
59
- | doi | `string` | |
60
- | section | `string` | |
61
- | language | `string` | |
62
- | researchNotes | `string` | |
63
- | databaseProvider | `string` | |
64
- | database | `string` | |
65
- | workType | `string` | |
66
- | custom1 | `string` | |
67
- | custom2 | `string` | |
68
- | custom3 | `string` | |
69
- | custom4 | `string` | |
70
- | custom5 | `string` | |
71
- | custom6 | `string` | |
72
- | custom7 | `string` | |
42
+ | recNumber | `Number` | The sorting number of the reference. Not present in RIS files |
43
+ | type | `String` | A supported [reference type](#reference-types) (e.g. journalArticle) |
44
+ | title | `String` | The reference's main title |
45
+ | journal | `String` | The reference's secondary title, this is usually the journal for most published papers |
46
+ | authors | `Array<String>` | An array of each Author in the originally specified format |
47
+ | date | `String` | The raw, internal date of the reference |
48
+ | urls | `Array<String>` | An array of each URL for the reference |
49
+ | pages | `String` | The page reference, usually in the format `123-4` |
50
+ | volume | `String` | |
51
+ | number | `String` | |
52
+ | isbn | `String` | |
53
+ | abstract | `String` | |
54
+ | label | `String` | |
55
+ | caption | `String` | |
56
+ | notes | `String` | |
57
+ | address | `String` | |
58
+ | researchNotes | `String` | |
59
+ | keywords | `Array<String>` | Optional list of keywords that apply to the reference |
60
+ | accessDate | `String` | |
61
+ | accession | `String` | [Accession numbers spec](https://support.nlm.nih.gov/knowledgebase/article/KA-03434/en-us), can sometimes be the PubMed ID |
62
+ | doi | `String` | |
63
+ | section | `String` | |
64
+ | language | `String` | |
65
+ | researchNotes | `String` | |
66
+ | databaseProvider | `String` | |
67
+ | database | `String` | |
68
+ | workType | `String` | |
69
+ | custom1 | `String` | |
70
+ | custom2 | `String` | |
71
+ | custom3 | `String` | |
72
+ | custom4 | `String` | |
73
+ | custom5 | `String` | |
74
+ | custom6 | `String` | |
75
+ | custom7 | `String` | |
73
76
 
74
77
 
75
78
  Reference Types
@@ -251,7 +254,7 @@ writeStream(moduleId, outputStream, options)
251
254
  Available: Node + Browser
252
255
  Low level worker of `writeFile()`.
253
256
  Return an object with methods to call to write to a given stream.
254
- The returned object will have a `start()`, `end()` and `write(ref)` function which can be called to write to the original input stream.
257
+ The returned object will have a `start()`, `end()` and `write(ref)` (optional `middle(ref)`) function which can be called to write to the original input stream.
255
258
 
256
259
  ```javascript
257
260
  // Convert a JSON file to EndNoteXML via a stream
package/lib/formats.js CHANGED
@@ -10,6 +10,14 @@
10
10
  * @property {boolean} canWrite Whether the format is supported when writing a citation library
11
11
  */
12
12
  export let formats = {
13
+ bibtex: {
14
+ id: 'bibtex',
15
+ title: 'BibTeX',
16
+ titleShort: 'BibTeX',
17
+ ext: ['.bib'],
18
+ canRead: true,
19
+ canWrite: false,
20
+ },
13
21
  csv: {
14
22
  id: 'csv',
15
23
  title: 'Comma Seperated Values',
package/lib/writeFile.js CHANGED
@@ -22,8 +22,9 @@ export function writeFile(path, refs, options) {
22
22
 
23
23
  return Promise.resolve()
24
24
  .then(()=> writer.start())
25
- .then(()=> refs.reduce((chain, ref) => // Write all refs as a series of promises
26
- chain.then(()=> writer.write(ref))
25
+ .then(()=> refs.reduce((chain, ref, refIndex, refs) => chain // Write all refs as a series of promises
26
+ .then(()=> writer.write(ref))
27
+ .then(()=> refIndex < refs.length && writer.middle && writer.middle(ref))
27
28
  , Promise.resolve()))
28
29
  .then(()=> writer.end())
29
30
  .then(()=> fileStream.close())
@@ -0,0 +1,365 @@
1
+ import Emitter from '../shared/emitter.js';
2
+
3
+ /**
4
+ * Lookup enum for the current parser mode we are in
5
+ *
6
+ * @type {Object<Number>}
7
+ */
8
+ const MODES = {
9
+ REF: 0,
10
+ FIELDS: 1,
11
+ FIELD_START: 2,
12
+ FIELD_VALUE: 3,
13
+ };
14
+
15
+
16
+ /**
17
+ * Parse a BibTeX file from a readable stream
18
+ *
19
+ * @see modules/interface.js
20
+ *
21
+ * @param {Stream} stream The readable stream to accept data from
22
+ * @param {Object} [options] Additional options to use when parsing
23
+ * @param {Boolean} [options.recNumberNumeric=true] Only process the BibTeX ID into a recNumber if its a finite numeric, otherwise disguard
24
+ * @param {Boolean} [options.recNumberRNPrefix=true] Accept `RN${NUMBER}` as recNumber if present
25
+ * @param {Boolean} [options.omitUnkown=false] If true, only keep known reconised fields
26
+ * @param {String} [options.fallbackType='unkown'] Reflib fallback type if the incoming type is unrecognised or unsupported
27
+ * @param {Set<String>} [options.fieldsOverwrite] Set of field names where the value is clobbered rather than appended if discovered more than once
28
+ *
29
+ * @returns {Object} A readable stream analogue defined in `modules/interface.js`
30
+ */
31
+ export function readStream(stream, options) {
32
+ let settings = {
33
+ recNumberNumeric: true,
34
+ recNumberRNPrefix: true,
35
+ omitUnknown: false,
36
+ fallbackType: 'unknown',
37
+ fieldsOverwrite: new Set(['type']),
38
+ ...options,
39
+ };
40
+
41
+ let emitter = Emitter();
42
+ let buffer = '';
43
+ let mode = MODES.REF;
44
+ let state; // Misc state storage when we're digesting ref data
45
+ let ref = {}; // Reference item being constructed
46
+
47
+ // Queue up the parser in the next tick (so we can return the emitter first)
48
+ setTimeout(()=> {
49
+ stream
50
+ .on('error', e => emitter.emit('error', e))
51
+ .on('end', ()=> emitter.emit('end'))
52
+ .on('data', chunkBuffer => {
53
+ emitter.emit('progress', stream.bytesRead);
54
+ buffer += chunkBuffer.toString(); // Append incomming data to the partial-buffer we're holding in memory
55
+
56
+ while (true) {
57
+ let match; // Regex storage for match groups
58
+ if ((mode == MODES.REF) && (match = /^\s*@(?<type>\w+?)\s*\{(?<id>.*?),/s.exec(buffer))) {
59
+ if (settings.recNumberNumeric && isFinite(match.groups.id)) { // Accept numeric recNumber
60
+ ref.recNumber = +match.groups.id;
61
+ } else if (settings.recNumberRNPrefix && /^RN\d+$/.test(match.groups.id)) {
62
+ ref.recNumber = +match.groups.id.slice(2);
63
+ } else if (!settings.recNumberNumeric && match.groups.id) { // Non numeric / finite ID - but we're allowed to accept it anyway
64
+ ref.recNumber = +match.groups.id;
65
+ } // Implied else - No ID, ignore
66
+
67
+ ref.type = match.groups.type;
68
+ mode = MODES.FIELDS;
69
+ state = null;
70
+ } else if (mode == MODES.FIELDS && (match = /^\s*(?<field>\w+?)\s*=\s*/s.exec(buffer))) {
71
+ mode = MODES.FIELD_START;
72
+ state = {field: match.groups.field};
73
+ } else if (mode == MODES.FIELDS && (match = /^\s*\}\s*/s.exec(buffer))) { // End of ref
74
+ emitter.emit('ref', tidyRef(ref, settings));
75
+ mode = MODES.REF;
76
+ ref = {};
77
+ state = null;
78
+ } else if (mode == MODES.FIELD_START && (match = /^\s*(?<fieldWrapper>"|{)\s*/.exec(buffer))) {
79
+ mode = MODES.FIELD_VALUE;
80
+ state.fieldWrapper = match.groups.fieldWrapper;
81
+ } else if (
82
+ // TODO: Note that we use `\r?\n` as delimiters for field values, this is a cheat to avoid having to implement a full AST parser
83
+ // This is a hack but since most BibTeX files use properly formatted BibTeX this should work in the majority of cases
84
+ // This WILL break if given one continuous line of BibTeX though
85
+ // - MC 2026-01-02
86
+ mode == MODES.FIELD_VALUE
87
+ && (
88
+ (
89
+ state.fieldWrapper == '{'
90
+ && (match = /^(?<value>.+?)(?<!\\%)\}\s*,?\s*$/sm.exec(buffer))
91
+ )
92
+ || (
93
+ state.fieldWrapper == '"'
94
+ && (match = /^(?<value>.+?)"\s*,?\s*$/sm.exec(buffer))
95
+ )
96
+ )
97
+ ) {
98
+ mode = MODES.FIELDS;
99
+ if (ref[state.field] !== undefined && settings.fieldsOverwrite.has(state.field)) { // Already have content - and we should overwrite
100
+ ref[state.field] = unescape(match.groups.value);
101
+ } else if (ref[state.field] !== undefined) { // Already have content - append
102
+ ref[state.field] += '\n' + unescape(match.groups.value);
103
+ } else { // Populate initial value
104
+ ref[state.field] = unescape(match.groups.value);
105
+ }
106
+ state = null;
107
+ } else { // Implied else - No match to buffer, let it fill and process next data block
108
+ break;
109
+ }
110
+
111
+ // Crop start of buffer to last match
112
+ buffer = buffer.slice(match[0].length);
113
+ }
114
+ })
115
+ })
116
+
117
+ return emitter;
118
+ }
119
+
120
+
121
+ /**
122
+ * Tidy up a raw BibTeX reference before emitting
123
+ *
124
+ * @param {Object} ref The input raw ref to tidy
125
+ *
126
+ * @param {Object} settings Optimized settings object for fast access
127
+ *
128
+ * @returns {Object} The tidied ref
129
+ */
130
+ export function tidyRef(ref, settings) {
131
+ return Object.fromEntries(
132
+ Object.entries(ref)
133
+ .map(([key, val]) => {
134
+ let rlField = translations.fields.btMap.get(key.toLowerCase());
135
+
136
+ if (key == 'type') { // Special conversion for type
137
+ let rlType = ref.type && translations.types.btMap.get(val.toLowerCase());
138
+ return rlType
139
+ ? [key, rlType.rl] // Can translate incoming type to Reflib type
140
+ : [key, settings.fallbackType] // Unknown Reflib type varient
141
+ } else if (settings.omitUnkown && !rlField) { // Omit unknown fields
142
+ return;
143
+ } else if (rlField && rlField.array) { // Field needs array casting
144
+ return [rlField.rl, val.split(/\n*\s+and\s+/)];
145
+ } else if (rlField && rlField.rl) { // Known BT field but different RL field
146
+ return [rlField.rl, val];
147
+ } else if (!settings.omitUnkown) { // Everything else - add field
148
+ return [key, val];
149
+ }
150
+ })
151
+ .filter(Boolean) // Remove duds
152
+ );
153
+ }
154
+
155
+
156
+ /**
157
+ * Translate a BibTeX encoded string into a regular JS String
158
+ *
159
+ * @param {String} str Input BibTeX encoded string
160
+ * @returns {String} Regular JS output string
161
+ */
162
+ export function unescape(str) {
163
+ return str
164
+ .replace(/\/\*/g, '\n')
165
+ .replace(/\{\\\&\}/g, '&')
166
+ .replace(/\{\\\%\}/g, '%')
167
+ }
168
+
169
+
170
+ /**
171
+ * Translate a JS string into a BibTeX encoded string
172
+ *
173
+ * @param {String} str Input regular JS String
174
+ * @returns {String} BibTeX encoded string
175
+ */
176
+ export function escape(str) {
177
+ return (''+str)
178
+ .replace(/\&/g, '{\\&}')
179
+ .replace(/%/g, '{\\%}')
180
+ }
181
+
182
+
183
+ /**
184
+ * Write a RIS file to a writable stream
185
+ *
186
+ * @see modules/interface.js
187
+ *
188
+ * @param {Stream} stream The writable stream to write to
189
+ *
190
+ * @param {Object} [options] Additional options to use when parsing
191
+ * @param {string} [options.defaultType='Misc'] Default citation type to assume when no other type is specified
192
+ * @param {string} [options.delimeter='\r'] How to split multi-line items
193
+ * @param {Boolean} [options.omitUnkown=false] If true, only keep known reconised fields
194
+ * @param {Set} [options.omitFields] Set of special fields to always omit, either because we are ignoring or because we have special treatment for them
195
+ * @param {Boolean} [options.recNumberRNPrefix=true] Rewrite recNumber fields as `RN${NUMBER}`
196
+ *
197
+ * @returns {Object} A writable stream analogue defined in `modules/interface.js`
198
+ */
199
+ export function writeStream(stream, options) {
200
+ let settings = {
201
+ defaultType: 'Misc',
202
+ delimeter: '\n',
203
+ omitUnkown: false,
204
+ omitFields: new Set(['recNumber', 'type']),
205
+ recNumberRNPrefix: true,
206
+ ...options,
207
+ };
208
+
209
+ return {
210
+ start() {
211
+ return Promise.resolve();
212
+ },
213
+ write: ref => {
214
+ // Fetch Reflib type definition
215
+ let rlType = ref.type && translations.types.rlMap.get(ref.type.toLowerCase());
216
+ let btType = rlType?.bt || settings.defaultType;
217
+
218
+ stream.write(
219
+ '@' + btType + '{'
220
+ + (
221
+ ref.recNumber && settings.recNumberRNPrefix ? `RN${ref.recNumber},`
222
+ : ref.recNumber ? `${ref.recNumber},`
223
+ : ''
224
+ ) + '\n'
225
+ + Object.entries(ref)
226
+ .filter(([key, val]) =>
227
+ val // We have a non-nullish val
228
+ && !settings.omitFields.has(key)
229
+ )
230
+ .reduce((buf, [rawKey, rawVal], keyIndex, keys) => {
231
+ // Fetch Reflib field definition
232
+ let rlField = translations.fields.rlMap.get(rawKey)
233
+ if (!rlField && settings.omitUnkown) return buf; // Unknown field mapping - skip if were omitting unknown fields
234
+
235
+ let key = rlField ? rlField.bt : rawKey; // Use Reflib->BibTeX field mapping if we have one, otherwise use raw key
236
+ let val = escape( // Escape input value, either as an Array via join or as a flat string
237
+ rawKey == 'authors' && Array.isArray(rawVal) ? rawVal.join('\nand ') // Special joining conditions for author field
238
+ : Array.isArray(rawVal) ? rawVal.join(', ') // Treat other arrays as a CSV
239
+ : rawVal // Splat everything else as a string
240
+ );
241
+
242
+ return buf + // Return string buffer of ref under construction
243
+ `${key}={${val}}` // Append ref key=val pair to buffer
244
+ + (keyIndex < keys.length-1 ? ',' : '') // Append comma (if non-last)
245
+ + '\n' // Finish each field with a newline
246
+ }, '')
247
+ + '}\n'
248
+ );
249
+
250
+ return Promise.resolve();
251
+ },
252
+ middle() {
253
+ stream.write('\n');
254
+ },
255
+ end() {
256
+ return new Promise((resolve, reject) =>
257
+ stream.end(err => err ? reject(err) : resolve())
258
+ );
259
+ },
260
+ };
261
+ }
262
+
263
+
264
+ /**
265
+ * Lookup tables for this module
266
+ * @type {Object}
267
+ * @property {Array<Object>} fields Field translations between Reflib (`rl`) and BibTeX format (`bt`)
268
+ */
269
+ export let translations = {
270
+ // Field translations {{{
271
+ fields: {
272
+ collection: [
273
+ // Order by priority (highest at top)
274
+ {rl: 'address', bt: 'address'},
275
+ {rl: 'authors', bt: 'author', array: true},
276
+ {rl: 'doi', bt: 'doi'},
277
+ {rl: 'edition', bt: 'edition'},
278
+ {rl: 'editor', bt: 'editor'},
279
+ {rl: 'journal', bt: 'journal'},
280
+ {rl: 'notes', bt: 'note'},
281
+ {rl: 'number', bt: 'number'},
282
+ {rl: 'pages', bt: 'pages'},
283
+ {rl: 'title', bt: 'booktitle'},
284
+ {rl: 'title', bt: 'title'},
285
+ {rl: 'volume', bt: 'volume'},
286
+ {rl: 'isbn', bt: 'issn'},
287
+
288
+ // Misc
289
+ {bt: 'month'}, // Combined into {rl:'date'}
290
+ {bt: 'type'}, // Ignored
291
+ {bt: 'year'}, // Combined into {rl:'date'}
292
+
293
+ // Nonestandard but used anyway
294
+ {rl: 'abstract', bt: 'abstract'},
295
+ {rl: 'language', bt: 'language'},
296
+ {rl: 'keywords', bt: 'keywords', array: true},
297
+ {rl: 'urls', bt: 'url', array: true},
298
+
299
+ // Unknown how to translate these
300
+ // {bt: 'annote'},
301
+ // {bt: 'email'},
302
+ // {bt: 'chapter'},
303
+ // {bt: 'crossref'},
304
+ // {bt: 'howpublished'},
305
+ // {bt: 'institution'},
306
+ // {bt: 'key'},
307
+ // {bt: 'organization'},
308
+ // {bt: 'publisher'},
309
+ // {bt: 'school'},
310
+ // {bt: 'series'},
311
+ ],
312
+ rlMap: new Map(),
313
+ btMap: new Map(),
314
+ },
315
+ // }}}
316
+ // Ref type translations {{{
317
+ types: {
318
+ collection: [
319
+ // Order by priority (highest at top)
320
+ {rl: 'journalArticle', bt: 'Article'},
321
+ {rl: 'book', bt: 'Book'},
322
+ {rl: 'bookSection', bt: 'InBook'},
323
+ {rl: 'conferencePaper', bt: 'Conference'},
324
+ {rl: 'conferenceProceedings', bt: 'InProceedings'},
325
+ {rl: 'report', bt: 'TechReport'},
326
+ {rl: 'thesis', bt: 'PHDThesis'},
327
+ {rl: 'unknown', bt: 'Misc'},
328
+ {rl: 'unpublished', bt: 'Unpublished'},
329
+
330
+ // Type aliases
331
+ {rl: 'journalArticle', bt: 'Journal Article'},
332
+
333
+ // Unknown how to translate these
334
+ {rl: 'Misc', bt: 'Booklet'},
335
+ {rl: 'Misc', bt: 'InCollection'},
336
+ {rl: 'Misc', bt: 'Manual'},
337
+ {rl: 'Misc', bt: 'MastersThesis'},
338
+ {rl: 'Misc', bt: 'Proceedings'},
339
+ ],
340
+ rlMap: new Map(),
341
+ btMap: new Map(),
342
+ },
343
+ // }}}
344
+ };
345
+
346
+
347
+ /**
348
+ * @see modules/interface.js
349
+ */
350
+ export function setup() {
351
+ // Create lookup object of translations.fields with key as .rl / val as the full object
352
+ translations.fields.collection.forEach(c => {
353
+ if (c.rl) translations.fields.rlMap.set(c.rl.toLowerCase(), c);
354
+ if (c.bt) translations.fields.btMap.set(c.bt, c);
355
+ });
356
+
357
+ // Create lookup object of ref.types with key as .rl / val as the full object
358
+ translations.types.collection.forEach(c => {
359
+ // Append each type to the set, accepting the first in each case as the priority
360
+ let rlLc = c.rl.toLowerCase();
361
+ let btLc = c.bt.toLowerCase();
362
+ if (c.rl && !translations.types.rlMap.has(rlLc)) translations.types.rlMap.set(rlLc, c);
363
+ if (c.bt && !translations.types.btMap.has(btLc)) translations.types.btMap.set(btLc, c);
364
+ });
365
+ }
@@ -1,3 +1,4 @@
1
+ export * as bibtex from './bibtex.js';
1
2
  export * as json from './json.js';
2
3
  export * as endnoteEnl from './endnoteEnl.js';
3
4
  export * as endnoteEnlX from './endnoteEnlX.js';
@@ -30,6 +30,7 @@ export function readStream(stream, options) {
30
30
  * @returns {Object} An object which exposes methods to call to start, write and end the writing process. All methods MUST return a Promise
31
31
  * @property {function<Promise>} start Function to call when beginning to write
32
32
  * @property {function<Promise>} write Function called as `(ref)` when writing a single ref
33
+ * @property {function<promise>} [middle] Function to call after `write()` for each reference if the reference is NOT last, this works similar to `Array.prototype.join()`
33
34
  * @property {function<Promise>} end Function to call when finishing writing, must resolve its Promise when the stream has closed successfully
34
35
  */
35
36
  export function writeStream(stream, refs, options) {
package/modules/ris.js CHANGED
@@ -9,6 +9,8 @@ import Emitter from '../shared/emitter.js';
9
9
  * @param {Object} [options] Additional options to use when parsing
10
10
  * @param {string} [options.defaultType='report'] Default citation type to assume when no other type is specified
11
11
  * @param {string} [options.delimeter='\r'] How to split multi-line items
12
+ * @param {Boolean} [options.convertAbstract=true] If the `fallbackAbstract` field exists but not `abstract` use the former as the latter
13
+ * @param {Boolean} [options.convertCity=true] If the `fallbackCity` field exists but not `city` use the former as the latter
12
14
  *
13
15
  * @returns {Object} A readable stream analogue defined in `modules/interface.js`
14
16
  */
@@ -16,6 +18,8 @@ export function readStream(stream, options) {
16
18
  let settings = {
17
19
  defaultType: 'journalArticle',
18
20
  delimeter: '\r',
21
+ convertAbtract: true,
22
+ convertCity: true,
19
23
  ...options,
20
24
  };
21
25
 
@@ -132,6 +136,7 @@ export function writeStream(stream, options) {
132
136
  *
133
137
  * @param {string} refString Raw RIS string composing the start -> end of the ref
134
138
  * @param {Object} settings Additional settings to pass, this should be initialized + parsed by the calling function for efficiency, see readStream() for full spec
139
+ * @param {Boolean} [settings.convertAbstract=true] If the `fallbackAbstract` field exists but not `abstract` use the former as the latter
135
140
  * @returns {ReflibRef} The parsed reference
136
141
  */
137
142
  export function parseRef(refString, settings) {
@@ -185,6 +190,18 @@ export function parseRef(refString, settings) {
185
190
  delete ref._pageEnd;
186
191
  }
187
192
  // }}}
193
+ // FallbackAbstract -> Abstract (if the latter is missing) {{{
194
+ if ((settings.fallbackAbstract ?? true) && ref.fallbackAbstract && !ref.abstract) {
195
+ ref.abstract = ref.fallbackAbstract;
196
+ delete ref.fallbackAbstract;
197
+ }
198
+ // }}}
199
+ // FallbackCity -> City (if the latter is missing) {{{
200
+ if ((settings.fallbackCity ?? true) && ref.fallbackCity && !ref.abstract) {
201
+ ref.city = ref.fallbackCity;
202
+ delete ref.fallbackCity;
203
+ }
204
+ // }}}
188
205
 
189
206
  return ref;
190
207
  }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@iebh/reflib",
3
- "version": "2.5.8",
3
+ "version": "2.6.0",
4
4
  "description": "Reference / Citation reference library utilities",
5
5
  "scripts": {
6
- "lint": "eslint .",
7
- "test": "mocha",
6
+ "lint": "eslint",
7
+ "test": "testa",
8
8
  "test:browser": "cd test/browser && npm run dev",
9
9
  "test:watch": "nodemon --exec npm run test"
10
10
  },
@@ -20,7 +20,9 @@
20
20
  ],
21
21
  "author": "Matt Carter <m@ttcarter.com> (https://github.com/hash-bang)",
22
22
  "contributors": [
23
- "Connor Forbes <cforbes.software@gmail.com> (https://github.com/connorf25)"
23
+ "Connor Forbes <cforbes.software@gmail.com> (https://github.com/connorf25)",
24
+ "Irene Priya Jose (https://github.com/s5333384)",
25
+ "Tian Liang (https://github.com/Octian)"
24
26
  ],
25
27
  "license": "MIT",
26
28
  "bugs": {
@@ -44,22 +46,21 @@
44
46
  "htmlparser2/lib/esm/WritableStream": "./modules/shims/WritableStream-browser.js",
45
47
  "JSONStream": "./modules/shims/JSONStream-browser.js"
46
48
  },
47
- "devDependencies": {
48
- "@momsfriendlydevco/eslint-config": "^2.3.1",
49
- "chai": "^5.2.0",
50
- "eslint": "^9.31.0",
51
- "mocha": "^11.1.0",
52
- "mocha-logger": "^1.0.8",
53
- "nodemon": "^3.1.9",
54
- "temp": "^0.9.4",
55
- "vite-plugin-replace": "^0.1.1"
56
- },
57
49
  "dependencies": {
58
50
  "@iebh/cacx": "^1.0.3",
59
51
  "@zip.js/zip.js": "^2.7.57",
60
52
  "htmlparser2": "^9.1.0",
61
53
  "JSONStream": "^1.3.5",
54
+ "lodash-es": "^4.17.22",
62
55
  "mitt": "^3.0.1",
63
56
  "sql.js": "^1.12.0"
57
+ },
58
+ "devDependencies": {
59
+ "@momsfriendlydevco/eslint-config": "^2.3.1",
60
+ "@momsfriendlydevco/testa": "^1.1.2",
61
+ "eslint": "^9.31.0",
62
+ "nodemon": "^3.1.9",
63
+ "temp": "^0.9.4",
64
+ "vite-plugin-replace": "^0.1.1"
64
65
  }
65
66
  }