@iebh/reflib 2.8.0 → 2.8.2
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/.ignore +1 -1
- package/LICENSE +20 -20
- package/README.md +297 -297
- package/app.js +87 -85
- package/lib/browser.js +30 -30
- package/lib/default.js +30 -30
- package/lib/downloadFile.js +94 -94
- package/lib/fields.js +158 -158
- package/lib/formats.js +85 -85
- package/lib/getFormatList.js +12 -0
- package/lib/getModule.js +39 -39
- package/lib/getRefDoi.js +16 -16
- package/lib/identifyFormat.js +13 -13
- package/lib/readFile.js +63 -63
- package/lib/readStream.js +21 -21
- package/lib/uploadFile.js +71 -71
- package/lib/writeFile.js +32 -32
- package/lib/writeStream.js +16 -16
- package/modules/bibtex.js +401 -401
- package/modules/default.js +7 -7
- package/modules/endnoteEnl.js +237 -237
- package/modules/endnoteEnlX.js +85 -85
- package/modules/endnoteXml.js +410 -474
- package/modules/interface.js +47 -47
- package/modules/json.js +109 -79
- package/modules/medline.js +638 -638
- package/modules/ris.js +383 -383
- package/modules/shims/JSONStream-browser.js +43 -43
- package/modules/shims/WritableStream-browser.js +52 -52
- package/package.json +68 -70
- package/shared/camelCase.js +17 -17
- package/shared/emitter.js +23 -23
- package/shared/parseArgs.js +104 -104
- package/shared/streamEmitter.js +61 -61
package/modules/ris.js
CHANGED
|
@@ -1,383 +1,383 @@
|
|
|
1
|
-
import Emitter from '../shared/emitter.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Parse a RIS file from a readable stream
|
|
5
|
-
*
|
|
6
|
-
* @see modules/interface.js
|
|
7
|
-
*
|
|
8
|
-
* @param {Stream} stream The readable stream to accept data from
|
|
9
|
-
* @param {Object} [options] Additional options to use when parsing
|
|
10
|
-
* @param {string} [options.defaultType='report'] Default citation type to assume when no other type is specified
|
|
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
|
|
14
|
-
*
|
|
15
|
-
* @returns {Object} A readable stream analogue defined in `modules/interface.js`
|
|
16
|
-
*/
|
|
17
|
-
export function readStream(stream, options) {
|
|
18
|
-
let settings = {
|
|
19
|
-
defaultType: 'journalArticle',
|
|
20
|
-
delimeter: '\r',
|
|
21
|
-
convertAbtract: true,
|
|
22
|
-
convertCity: true,
|
|
23
|
-
...options,
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
let emitter = Emitter();
|
|
27
|
-
|
|
28
|
-
let buffer = ''; // Incomming text buffer lines if the chunk we're given isn't enough to parse a reference yet
|
|
29
|
-
|
|
30
|
-
// Queue up the parser in the next tick (so we can return the emitter first)
|
|
31
|
-
setTimeout(()=> {
|
|
32
|
-
stream
|
|
33
|
-
.on('data', chunkBuffer => {
|
|
34
|
-
emitter.emit('progress', stream.bytesRead);
|
|
35
|
-
buffer += chunkBuffer.toString(); // Append incomming data to the partial-buffer we're holding in memory
|
|
36
|
-
|
|
37
|
-
let bufferCrop = 0; // How many bytes to shift off the front of the buffer based on the last full reference we saw, should end up at the last byte offset of buffer that is valid to shift-truncate to
|
|
38
|
-
let bufferSplitter = /(\r\n|\n)ER\s+-\s*(\r\n|\n)/g; // RegExp to use per segment (multiple calls to .exec() stores state because JS is a hellscape)
|
|
39
|
-
|
|
40
|
-
let bufferSegment;
|
|
41
|
-
while (bufferSegment = bufferSplitter.exec(buffer)) {
|
|
42
|
-
let parsedRef = parseRef(buffer.substring(bufferCrop, bufferSegment.index), settings); // Parse the ref from the start+end points
|
|
43
|
-
|
|
44
|
-
emitter.emit('ref', parsedRef);
|
|
45
|
-
|
|
46
|
-
bufferCrop = bufferSegment.index + bufferSegment[0].length; // Set start of next ref + cropping index to last seen offset + match
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
buffer = buffer.substring(bufferCrop); // Shift-truncate the buffer so we're ready to input more data on the next call
|
|
50
|
-
})
|
|
51
|
-
.on('error', e => emitter.emit('error', e))
|
|
52
|
-
.on('end', ()=> {
|
|
53
|
-
if (buffer.replace(/\s+/, '')) { // Anything left in the to-drain buffer?
|
|
54
|
-
// Drain remaining buffer into parser before exiting
|
|
55
|
-
emitter.emit('ref', parseRef(buffer, settings));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Signal that we're done
|
|
59
|
-
emitter.emit('end');
|
|
60
|
-
})
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
return emitter;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Write a RIS file to a writable stream
|
|
69
|
-
*
|
|
70
|
-
* @see modules/interface.js
|
|
71
|
-
*
|
|
72
|
-
* @param {Stream} stream The writable stream to write to
|
|
73
|
-
*
|
|
74
|
-
* @param {Object} [options] Additional options to use when parsing
|
|
75
|
-
* @param {string} [options.defaultType='journalArticle'] Default citation type to assume when no other type is specified
|
|
76
|
-
* @param {string} [options.delimeter='\r'] How to split multi-line items
|
|
77
|
-
*
|
|
78
|
-
* @returns {Object} A writable stream analogue defined in `modules/interface.js`
|
|
79
|
-
*/
|
|
80
|
-
export function writeStream(stream, options) {
|
|
81
|
-
let settings = {
|
|
82
|
-
defaultType: 'journalArticle',
|
|
83
|
-
delimeter: '\r',
|
|
84
|
-
...options,
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
start() {
|
|
90
|
-
return Promise.resolve();
|
|
91
|
-
},
|
|
92
|
-
write: xRef => {
|
|
93
|
-
let ref = { // Assign defaults if not already present
|
|
94
|
-
type: settings.defaultType,
|
|
95
|
-
title: '<NO TITLE>',
|
|
96
|
-
...xRef,
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
// Parse `pages` back into `_pageStart` + `_pageEnd` meta keys
|
|
100
|
-
if (xRef.pages) {
|
|
101
|
-
var pageRanges = /^(?<_pageStart>.+?)(-(?<_pageEnd>.+))?$/.exec(xRef.pages)?.groups;
|
|
102
|
-
Object.assign(ref, pageRanges);
|
|
103
|
-
delete ref.pages;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
stream.write(
|
|
107
|
-
translations.fields.collectionOutput
|
|
108
|
-
.filter(f => ref[f.rl]) // Has field?
|
|
109
|
-
.flatMap(f =>
|
|
110
|
-
f.rl == 'type' // Translate type field
|
|
111
|
-
? 'TY - ' + (translations.types.rlMap[ref.type] || 'JOUR')
|
|
112
|
-
: f.outputRepeat && Array.isArray(ref[f.rl]) // Repeat array types
|
|
113
|
-
? ref[f.rl].map(item => `${f.raw} - ${item}`)
|
|
114
|
-
: Array.isArray(ref[f.rl]) // Flatten arrays into text
|
|
115
|
-
? `${f.raw} - ${ref[f.rl].join(settings.delimeter)}`
|
|
116
|
-
: `${f.raw} - ${ref[f.rl]}` // Regular field output
|
|
117
|
-
)
|
|
118
|
-
.concat(['ER - \n\n'])
|
|
119
|
-
.join('\n')
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
return Promise.resolve();
|
|
123
|
-
},
|
|
124
|
-
end() {
|
|
125
|
-
return new Promise((resolve, reject) =>
|
|
126
|
-
stream.end(err => err ? reject(err) : resolve())
|
|
127
|
-
);
|
|
128
|
-
},
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Parse a single RIS format reference from a block of text
|
|
135
|
-
* This function is used internally by parseStream() for each individual reference
|
|
136
|
-
*
|
|
137
|
-
* @param {string} refString Raw RIS string composing the start -> end of the ref
|
|
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
|
|
140
|
-
* @returns {ReflibRef} The parsed reference
|
|
141
|
-
*/
|
|
142
|
-
export function parseRef(refString, settings) {
|
|
143
|
-
let ref = {}; // Reference under construction
|
|
144
|
-
let lastField; // Last field object we saw, used to append values if they don't match the default RIS key=val one-liner
|
|
145
|
-
|
|
146
|
-
refString
|
|
147
|
-
.split(/[\r\n|\n]/) // Split into lines
|
|
148
|
-
.forEach(line => {
|
|
149
|
-
let parsedLine = /^\s*(?<key>[A-Z0-9]+?)\s+-\s+(?<value>.*)$/s.exec(line)?.groups;
|
|
150
|
-
if (!parsedLine) { // Doesn't match key=val spec
|
|
151
|
-
if (line.replace(/\s+/, '') && lastField) { // Line isn't just whitespace + We have a field to append to - append with \r delimiters
|
|
152
|
-
if (lastField.inputArray) { // Treat each line feed like an array entry
|
|
153
|
-
ref[lastField.rl].push(line);
|
|
154
|
-
} else { // Assume we append each line entry as a string with settings.delimeter
|
|
155
|
-
ref[lastField.rl] += settings.delimeter + line;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return; // Stop processing this line
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (parsedLine.key == 'ER') return; // Skip 'ER' defiition lines - this is probably due to the buffer draining
|
|
162
|
-
let fieldLookup = translations.fields.rawMap.get(parsedLine.key);
|
|
163
|
-
|
|
164
|
-
if (!fieldLookup) { // Skip unknown field translations
|
|
165
|
-
lastField = null;
|
|
166
|
-
return;
|
|
167
|
-
} else if (fieldLookup.rl == 'type') { // Special handling for ref types
|
|
168
|
-
ref[fieldLookup.rl] = translations.types.rawMap.get(parsedLine.value)?.rl || settings.defaultType;
|
|
169
|
-
lastField = fieldLookup; // Track last key so we can append to it on the next cycle
|
|
170
|
-
} else if (fieldLookup.inputArray) { // Should this `rl` key be treated like an appendable array?
|
|
171
|
-
if (!ref[fieldLookup.rl] || !Array.isArray(ref[fieldLookup.rl])) { // Array doesn't exist yet
|
|
172
|
-
ref[fieldLookup.rl] = [parsedLine.value];
|
|
173
|
-
} else {
|
|
174
|
-
ref[fieldLookup.rl].push(parsedLine.value);
|
|
175
|
-
}
|
|
176
|
-
lastField = fieldLookup;
|
|
177
|
-
} else { // Simple key=val
|
|
178
|
-
ref[fieldLookup.rl] = parsedLine.value;
|
|
179
|
-
lastField = fieldLookup;
|
|
180
|
-
}
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
// Post processing
|
|
184
|
-
// Page mangling {{{
|
|
185
|
-
if (ref._pageStart || ref._pageEnd) {
|
|
186
|
-
ref.pages = [ref._pageStart, ref._pageEnd]
|
|
187
|
-
.filter(Boolean) // Remove duds
|
|
188
|
-
.join('-');
|
|
189
|
-
delete ref._pageStart;
|
|
190
|
-
delete ref._pageEnd;
|
|
191
|
-
}
|
|
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
|
-
// }}}
|
|
205
|
-
|
|
206
|
-
return ref;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Lookup tables for this module
|
|
212
|
-
* @type {Object}
|
|
213
|
-
* @property {array<Object>} fields Field translations between Reflib (`rl`) and the raw format (`raw`)
|
|
214
|
-
* @property {array<Object>} types Field translations between Reflib (`rl`) and the raw format types as raw text (`rawText`) and numeric ID (`rawId`)
|
|
215
|
-
* @property {boolean} isArray Whether the field should append to any existing `rl` field and be treated like an array of data
|
|
216
|
-
* @property {number|boolean} [sort] Sort order when outputting, use boolean `false` to disable the field on output
|
|
217
|
-
* @property {boolean} [outputRepeat=false] Whether to repeat the output field if multiple values are present, if disabled arrays are flattened into a string with newlines instead
|
|
218
|
-
* @property {boolean} [inputArray=false] Forcably cast the field as an array when reading, even if there is only one value
|
|
219
|
-
*/
|
|
220
|
-
export let translations = {
|
|
221
|
-
// Field translations {{{
|
|
222
|
-
fields: {
|
|
223
|
-
collection: [
|
|
224
|
-
{ rl: "authors", raw: "A1", sort: false, inputArray: true },
|
|
225
|
-
{ rl: "authors", raw: "A2", sort: false, inputArray: true },
|
|
226
|
-
{ rl: "authors", raw: "A3", sort: false, inputArray: true },
|
|
227
|
-
{ rl: "authors", raw: "A4", sort: false, inputArray: true },
|
|
228
|
-
{ rl: "abstract", raw: "AB" },
|
|
229
|
-
{ rl: "address", raw: "AD" },
|
|
230
|
-
{ rl: "accessionNum", raw: "AN" },
|
|
231
|
-
{
|
|
232
|
-
rl: "authors",
|
|
233
|
-
raw: "AU",
|
|
234
|
-
sort: 4,
|
|
235
|
-
outputRepeat: true,
|
|
236
|
-
inputArray: true,
|
|
237
|
-
},
|
|
238
|
-
{ rl: "custom1", raw: "C1" },
|
|
239
|
-
{ rl: "custom2", raw: "C2" },
|
|
240
|
-
{ rl: "custom3", raw: "C3" },
|
|
241
|
-
{ rl: "custom4", raw: "C4" },
|
|
242
|
-
{ rl: "custom5", raw: "C5" },
|
|
243
|
-
{ rl: "custom6", raw: "C6" },
|
|
244
|
-
{ rl: "custom7", raw: "C7" },
|
|
245
|
-
{ rl: "custom8", raw: "C8" },
|
|
246
|
-
{ rl: "caption", raw: "CA" },
|
|
247
|
-
{ rl: "fallbackCity", raw: "CY" },
|
|
248
|
-
{ rl: "date", raw: "DA" },
|
|
249
|
-
{ rl: "database", raw: "DB" },
|
|
250
|
-
{ rl: "doi", raw: "DO" },
|
|
251
|
-
{ rl: "databaseProvider", raw: "DP" },
|
|
252
|
-
{ rl: "_pageEnd", raw: "EP", sort: 6 },
|
|
253
|
-
{ rl: "edition", raw: "ET", sort: 7 },
|
|
254
|
-
{ rl: "number", raw: "IS", sort: 8 },
|
|
255
|
-
{ rl: "journal", raw: "J1", sort: false },
|
|
256
|
-
{ rl: "journal", raw: "JF", sort: 3 },
|
|
257
|
-
{ rl: "keywords", raw: "KW", outputRepeat: true, inputArray: true },
|
|
258
|
-
{ rl: "urls", raw: "L1", sort: false, inputArray: true },
|
|
259
|
-
{ rl: "urls", raw: "L2", sort: false, inputArray: true },
|
|
260
|
-
{ rl: "urls", raw: "L3", sort: false, inputArray: true },
|
|
261
|
-
{ rl: "urls", raw: "L4", sort: false, inputArray: true },
|
|
262
|
-
{ rl: "language", raw: "LA" },
|
|
263
|
-
{ rl: "label", raw: "LB" },
|
|
264
|
-
{ rl: "urls", raw: "LK", sort: false, inputArray: true },
|
|
265
|
-
{ rl: "notes", raw: "N1" },
|
|
266
|
-
{ rl: "fallbackAbstract", raw: "N2" },
|
|
267
|
-
{ rl: "publisher", raw: "PB" },
|
|
268
|
-
{ rl: "year", raw: "PY" },
|
|
269
|
-
{ rl: "isbn", raw: "SN" },
|
|
270
|
-
{ rl: "_pageStart", raw: "SP", sort: 5 },
|
|
271
|
-
{ rl: "title", raw: "T1", sort: false },
|
|
272
|
-
{ rl: "journal", raw: "T2", sort: false },
|
|
273
|
-
{ rl: "title", raw: "TI", sort: 1 },
|
|
274
|
-
{ rl: "type", raw: "TY", sort: 0 }, // TY must be the lowest number
|
|
275
|
-
{ rl: "urls", raw: "UR", outputRepeat: true, inputArray: true },
|
|
276
|
-
{ rl: "volume", raw: "VL" },
|
|
277
|
-
{ rl: "date", raw: "Y1" },
|
|
278
|
-
{ rl: "accessDate", raw: "Y2" },
|
|
279
|
-
|
|
280
|
-
// These are non-standard fields but we keep these here anyway to prevent data loss
|
|
281
|
-
{ rl: "RISID", raw: "ID" },
|
|
282
|
-
{ rl: "RISShortTitle", raw: "ST" },
|
|
283
|
-
{ rl: "RISOriginalPublication", raw: "OP" },
|
|
284
|
-
],
|
|
285
|
-
collectionOutput: [], // Sorted + filtered version of the above to use when outputting
|
|
286
|
-
rawMap: new Map(), // Calculated later for quicker lookup
|
|
287
|
-
rlMap: new Map(), // Calculated later for quicker lookup
|
|
288
|
-
},
|
|
289
|
-
// }}}
|
|
290
|
-
// Ref type translations {{{
|
|
291
|
-
types: {
|
|
292
|
-
collection: [
|
|
293
|
-
// Place high-priority translations at the top (when we translate BACK we need to know which of multiple keys to prioritize)
|
|
294
|
-
{ rl: "audioVisualMaterial", raw: "ADVS" },
|
|
295
|
-
{ rl: "journalArticle", raw: "JOUR" },
|
|
296
|
-
{ rl: "personalCommunication", raw: "PCOMM" },
|
|
297
|
-
{ rl: "filmOrBroadcast", raw: "VIDEO" },
|
|
298
|
-
|
|
299
|
-
// Low priority below this line
|
|
300
|
-
{ rl: "unknown", raw: "ABST" },
|
|
301
|
-
{ rl: "aggregatedDatabase", raw: "AGGR" },
|
|
302
|
-
{ rl: "ancientText", raw: "ANCIENT" },
|
|
303
|
-
{ rl: "artwork", raw: "ART" },
|
|
304
|
-
{ rl: "bill", raw: "BILL" },
|
|
305
|
-
{ rl: "blog", raw: "BLOG" },
|
|
306
|
-
{ rl: "book", raw: "BOOK" },
|
|
307
|
-
{ rl: "case", raw: "CASE" },
|
|
308
|
-
{ rl: "bookSection", raw: "CHAP" },
|
|
309
|
-
{ rl: "chartOrTable", raw: "CHART" },
|
|
310
|
-
{ rl: "classicalWork", raw: "CLSWK" },
|
|
311
|
-
{ rl: "computerProgram", raw: "COMP" },
|
|
312
|
-
{ rl: "conferenceProceedings", raw: "CONF" },
|
|
313
|
-
{ rl: "conferencePaper", raw: "CPAPER" },
|
|
314
|
-
{ rl: "catalog", raw: "CTLG" },
|
|
315
|
-
{ rl: "dataset", raw: "DATA" },
|
|
316
|
-
{ rl: "onlineDatabase", raw: "DBASE" },
|
|
317
|
-
{ rl: "dictionary", raw: "DICT" },
|
|
318
|
-
{ rl: "electronicBook", raw: "EBOOK" },
|
|
319
|
-
{ rl: "electronicBookSection", raw: "ECHAP" },
|
|
320
|
-
{ rl: "editedBook", raw: "EDBOOK" },
|
|
321
|
-
{ rl: "electronicArticle", raw: "EJOUR" },
|
|
322
|
-
{ rl: "web", raw: "ELEC" },
|
|
323
|
-
{ rl: "encyclopedia", raw: "ENCYC" },
|
|
324
|
-
{ rl: "equation", raw: "EQUA" },
|
|
325
|
-
{ rl: "figure", raw: "FIGURE" },
|
|
326
|
-
{ rl: "generic", raw: "GEN" },
|
|
327
|
-
{ rl: "governmentDocument", raw: "GOVDOC" },
|
|
328
|
-
{ rl: "grant", raw: "GRANT" },
|
|
329
|
-
{ rl: "hearing", raw: "HEARING" },
|
|
330
|
-
{ rl: "personalCommunication", raw: "ICOMM" },
|
|
331
|
-
{ rl: "newspaperArticle", raw: "INPR" },
|
|
332
|
-
{ rl: "journalArticle", raw: "JFULL" },
|
|
333
|
-
{ rl: "legalRuleOrRegulation", raw: "LEGAL" },
|
|
334
|
-
{ rl: "manuscript", raw: "MANSCPT" },
|
|
335
|
-
{ rl: "map", raw: "MAP" },
|
|
336
|
-
{ rl: "magazineArticle", raw: "MGZN" },
|
|
337
|
-
{ rl: "filmOrBroadcast", raw: "MPCT" },
|
|
338
|
-
{ rl: "onlineMultimedia", raw: "MULTI" },
|
|
339
|
-
{ rl: "music", raw: "MUSIC" },
|
|
340
|
-
{ rl: "newspaperArticle", raw: "NEWS" },
|
|
341
|
-
{ rl: "pamphlet", raw: "PAMP" },
|
|
342
|
-
{ rl: "patent", raw: "PAT" },
|
|
343
|
-
{ rl: "report", raw: "RPRT" },
|
|
344
|
-
{ rl: "serial", raw: "SER" },
|
|
345
|
-
{ rl: "audioVisualMaterial", raw: "SLIDE" },
|
|
346
|
-
{ rl: "audioVisualMaterial", raw: "SOUND" },
|
|
347
|
-
{ rl: "standard", raw: "STAND" },
|
|
348
|
-
{ rl: "statute", raw: "STAT" },
|
|
349
|
-
{ rl: "thesis", raw: "THES" },
|
|
350
|
-
{ rl: "unpublished", raw: "UNPB" },
|
|
351
|
-
],
|
|
352
|
-
rawMap: new Map(), // Calculated later for quicker lookup
|
|
353
|
-
rlMap: new Map(), // Calculated later for quicker lookup
|
|
354
|
-
},
|
|
355
|
-
// }}}
|
|
356
|
-
};
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* @see modules/interface.js
|
|
361
|
-
*/
|
|
362
|
-
export function setup() {
|
|
363
|
-
// Sort the field set by sort field
|
|
364
|
-
translations.fields.collectionOutput = translations.fields.collection
|
|
365
|
-
.filter(f => f.sort !== false)
|
|
366
|
-
.sort((a, b) => (a.sort ?? 1000) == (b.sort ?? 1000) ? 0
|
|
367
|
-
: (a.sort ?? 1000) < (b.sort ?? 1000) ? -1
|
|
368
|
-
: 1
|
|
369
|
-
)
|
|
370
|
-
|
|
371
|
-
// Create lookup object of translations.fields with key as .rl / val as the full object
|
|
372
|
-
translations.fields.collection.forEach(c => {
|
|
373
|
-
translations.fields.rlMap.set(c.rl, c);
|
|
374
|
-
translations.fields.rawMap.set(c.raw, c);
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
// Create lookup object of ref.types with key as .rl / val as the full object
|
|
378
|
-
translations.types.collection.forEach(c => {
|
|
379
|
-
translations.types.rlMap.set(c.rl, c);
|
|
380
|
-
translations.types.rawMap.set(c.raw, c);
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
}
|
|
1
|
+
import Emitter from '../shared/emitter.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parse a RIS file from a readable stream
|
|
5
|
+
*
|
|
6
|
+
* @see modules/interface.js
|
|
7
|
+
*
|
|
8
|
+
* @param {Stream} stream The readable stream to accept data from
|
|
9
|
+
* @param {Object} [options] Additional options to use when parsing
|
|
10
|
+
* @param {string} [options.defaultType='report'] Default citation type to assume when no other type is specified
|
|
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
|
|
14
|
+
*
|
|
15
|
+
* @returns {Object} A readable stream analogue defined in `modules/interface.js`
|
|
16
|
+
*/
|
|
17
|
+
export function readStream(stream, options) {
|
|
18
|
+
let settings = {
|
|
19
|
+
defaultType: 'journalArticle',
|
|
20
|
+
delimeter: '\r',
|
|
21
|
+
convertAbtract: true,
|
|
22
|
+
convertCity: true,
|
|
23
|
+
...options,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
let emitter = Emitter();
|
|
27
|
+
|
|
28
|
+
let buffer = ''; // Incomming text buffer lines if the chunk we're given isn't enough to parse a reference yet
|
|
29
|
+
|
|
30
|
+
// Queue up the parser in the next tick (so we can return the emitter first)
|
|
31
|
+
setTimeout(()=> {
|
|
32
|
+
stream
|
|
33
|
+
.on('data', chunkBuffer => {
|
|
34
|
+
emitter.emit('progress', stream.bytesRead);
|
|
35
|
+
buffer += chunkBuffer.toString(); // Append incomming data to the partial-buffer we're holding in memory
|
|
36
|
+
|
|
37
|
+
let bufferCrop = 0; // How many bytes to shift off the front of the buffer based on the last full reference we saw, should end up at the last byte offset of buffer that is valid to shift-truncate to
|
|
38
|
+
let bufferSplitter = /(\r\n|\n)ER\s+-\s*(\r\n|\n)/g; // RegExp to use per segment (multiple calls to .exec() stores state because JS is a hellscape)
|
|
39
|
+
|
|
40
|
+
let bufferSegment;
|
|
41
|
+
while (bufferSegment = bufferSplitter.exec(buffer)) {
|
|
42
|
+
let parsedRef = parseRef(buffer.substring(bufferCrop, bufferSegment.index), settings); // Parse the ref from the start+end points
|
|
43
|
+
|
|
44
|
+
emitter.emit('ref', parsedRef);
|
|
45
|
+
|
|
46
|
+
bufferCrop = bufferSegment.index + bufferSegment[0].length; // Set start of next ref + cropping index to last seen offset + match
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
buffer = buffer.substring(bufferCrop); // Shift-truncate the buffer so we're ready to input more data on the next call
|
|
50
|
+
})
|
|
51
|
+
.on('error', e => emitter.emit('error', e))
|
|
52
|
+
.on('end', ()=> {
|
|
53
|
+
if (buffer.replace(/\s+/, '')) { // Anything left in the to-drain buffer?
|
|
54
|
+
// Drain remaining buffer into parser before exiting
|
|
55
|
+
emitter.emit('ref', parseRef(buffer, settings));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Signal that we're done
|
|
59
|
+
emitter.emit('end');
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
return emitter;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Write a RIS file to a writable stream
|
|
69
|
+
*
|
|
70
|
+
* @see modules/interface.js
|
|
71
|
+
*
|
|
72
|
+
* @param {Stream} stream The writable stream to write to
|
|
73
|
+
*
|
|
74
|
+
* @param {Object} [options] Additional options to use when parsing
|
|
75
|
+
* @param {string} [options.defaultType='journalArticle'] Default citation type to assume when no other type is specified
|
|
76
|
+
* @param {string} [options.delimeter='\r'] How to split multi-line items
|
|
77
|
+
*
|
|
78
|
+
* @returns {Object} A writable stream analogue defined in `modules/interface.js`
|
|
79
|
+
*/
|
|
80
|
+
export function writeStream(stream, options) {
|
|
81
|
+
let settings = {
|
|
82
|
+
defaultType: 'journalArticle',
|
|
83
|
+
delimeter: '\r',
|
|
84
|
+
...options,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
start() {
|
|
90
|
+
return Promise.resolve();
|
|
91
|
+
},
|
|
92
|
+
write: xRef => {
|
|
93
|
+
let ref = { // Assign defaults if not already present
|
|
94
|
+
type: settings.defaultType,
|
|
95
|
+
title: '<NO TITLE>',
|
|
96
|
+
...xRef,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Parse `pages` back into `_pageStart` + `_pageEnd` meta keys
|
|
100
|
+
if (xRef.pages) {
|
|
101
|
+
var pageRanges = /^(?<_pageStart>.+?)(-(?<_pageEnd>.+))?$/.exec(xRef.pages)?.groups;
|
|
102
|
+
Object.assign(ref, pageRanges);
|
|
103
|
+
delete ref.pages;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
stream.write(
|
|
107
|
+
translations.fields.collectionOutput
|
|
108
|
+
.filter(f => ref[f.rl]) // Has field?
|
|
109
|
+
.flatMap(f =>
|
|
110
|
+
f.rl == 'type' // Translate type field
|
|
111
|
+
? 'TY - ' + (translations.types.rlMap[ref.type] || 'JOUR')
|
|
112
|
+
: f.outputRepeat && Array.isArray(ref[f.rl]) // Repeat array types
|
|
113
|
+
? ref[f.rl].map(item => `${f.raw} - ${item}`)
|
|
114
|
+
: Array.isArray(ref[f.rl]) // Flatten arrays into text
|
|
115
|
+
? `${f.raw} - ${ref[f.rl].join(settings.delimeter)}`
|
|
116
|
+
: `${f.raw} - ${ref[f.rl]}` // Regular field output
|
|
117
|
+
)
|
|
118
|
+
.concat(['ER - \n\n'])
|
|
119
|
+
.join('\n')
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
return Promise.resolve();
|
|
123
|
+
},
|
|
124
|
+
end() {
|
|
125
|
+
return new Promise((resolve, reject) =>
|
|
126
|
+
stream.end(err => err ? reject(err) : resolve())
|
|
127
|
+
);
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Parse a single RIS format reference from a block of text
|
|
135
|
+
* This function is used internally by parseStream() for each individual reference
|
|
136
|
+
*
|
|
137
|
+
* @param {string} refString Raw RIS string composing the start -> end of the ref
|
|
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
|
|
140
|
+
* @returns {ReflibRef} The parsed reference
|
|
141
|
+
*/
|
|
142
|
+
export function parseRef(refString, settings) {
|
|
143
|
+
let ref = {}; // Reference under construction
|
|
144
|
+
let lastField; // Last field object we saw, used to append values if they don't match the default RIS key=val one-liner
|
|
145
|
+
|
|
146
|
+
refString
|
|
147
|
+
.split(/[\r\n|\n]/) // Split into lines
|
|
148
|
+
.forEach(line => {
|
|
149
|
+
let parsedLine = /^\s*(?<key>[A-Z0-9]+?)\s+-\s+(?<value>.*)$/s.exec(line)?.groups;
|
|
150
|
+
if (!parsedLine) { // Doesn't match key=val spec
|
|
151
|
+
if (line.replace(/\s+/, '') && lastField) { // Line isn't just whitespace + We have a field to append to - append with \r delimiters
|
|
152
|
+
if (lastField.inputArray) { // Treat each line feed like an array entry
|
|
153
|
+
ref[lastField.rl].push(line);
|
|
154
|
+
} else { // Assume we append each line entry as a string with settings.delimeter
|
|
155
|
+
ref[lastField.rl] += settings.delimeter + line;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return; // Stop processing this line
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (parsedLine.key == 'ER') return; // Skip 'ER' defiition lines - this is probably due to the buffer draining
|
|
162
|
+
let fieldLookup = translations.fields.rawMap.get(parsedLine.key);
|
|
163
|
+
|
|
164
|
+
if (!fieldLookup) { // Skip unknown field translations
|
|
165
|
+
lastField = null;
|
|
166
|
+
return;
|
|
167
|
+
} else if (fieldLookup.rl == 'type') { // Special handling for ref types
|
|
168
|
+
ref[fieldLookup.rl] = translations.types.rawMap.get(parsedLine.value)?.rl || settings.defaultType;
|
|
169
|
+
lastField = fieldLookup; // Track last key so we can append to it on the next cycle
|
|
170
|
+
} else if (fieldLookup.inputArray) { // Should this `rl` key be treated like an appendable array?
|
|
171
|
+
if (!ref[fieldLookup.rl] || !Array.isArray(ref[fieldLookup.rl])) { // Array doesn't exist yet
|
|
172
|
+
ref[fieldLookup.rl] = [parsedLine.value];
|
|
173
|
+
} else {
|
|
174
|
+
ref[fieldLookup.rl].push(parsedLine.value);
|
|
175
|
+
}
|
|
176
|
+
lastField = fieldLookup;
|
|
177
|
+
} else { // Simple key=val
|
|
178
|
+
ref[fieldLookup.rl] = parsedLine.value;
|
|
179
|
+
lastField = fieldLookup;
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
// Post processing
|
|
184
|
+
// Page mangling {{{
|
|
185
|
+
if (ref._pageStart || ref._pageEnd) {
|
|
186
|
+
ref.pages = [ref._pageStart, ref._pageEnd]
|
|
187
|
+
.filter(Boolean) // Remove duds
|
|
188
|
+
.join('-');
|
|
189
|
+
delete ref._pageStart;
|
|
190
|
+
delete ref._pageEnd;
|
|
191
|
+
}
|
|
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
|
+
// }}}
|
|
205
|
+
|
|
206
|
+
return ref;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Lookup tables for this module
|
|
212
|
+
* @type {Object}
|
|
213
|
+
* @property {array<Object>} fields Field translations between Reflib (`rl`) and the raw format (`raw`)
|
|
214
|
+
* @property {array<Object>} types Field translations between Reflib (`rl`) and the raw format types as raw text (`rawText`) and numeric ID (`rawId`)
|
|
215
|
+
* @property {boolean} isArray Whether the field should append to any existing `rl` field and be treated like an array of data
|
|
216
|
+
* @property {number|boolean} [sort] Sort order when outputting, use boolean `false` to disable the field on output
|
|
217
|
+
* @property {boolean} [outputRepeat=false] Whether to repeat the output field if multiple values are present, if disabled arrays are flattened into a string with newlines instead
|
|
218
|
+
* @property {boolean} [inputArray=false] Forcably cast the field as an array when reading, even if there is only one value
|
|
219
|
+
*/
|
|
220
|
+
export let translations = {
|
|
221
|
+
// Field translations {{{
|
|
222
|
+
fields: {
|
|
223
|
+
collection: [
|
|
224
|
+
{ rl: "authors", raw: "A1", sort: false, inputArray: true },
|
|
225
|
+
{ rl: "authors", raw: "A2", sort: false, inputArray: true },
|
|
226
|
+
{ rl: "authors", raw: "A3", sort: false, inputArray: true },
|
|
227
|
+
{ rl: "authors", raw: "A4", sort: false, inputArray: true },
|
|
228
|
+
{ rl: "abstract", raw: "AB" },
|
|
229
|
+
{ rl: "address", raw: "AD" },
|
|
230
|
+
{ rl: "accessionNum", raw: "AN" },
|
|
231
|
+
{
|
|
232
|
+
rl: "authors",
|
|
233
|
+
raw: "AU",
|
|
234
|
+
sort: 4,
|
|
235
|
+
outputRepeat: true,
|
|
236
|
+
inputArray: true,
|
|
237
|
+
},
|
|
238
|
+
{ rl: "custom1", raw: "C1" },
|
|
239
|
+
{ rl: "custom2", raw: "C2" },
|
|
240
|
+
{ rl: "custom3", raw: "C3" },
|
|
241
|
+
{ rl: "custom4", raw: "C4" },
|
|
242
|
+
{ rl: "custom5", raw: "C5" },
|
|
243
|
+
{ rl: "custom6", raw: "C6" },
|
|
244
|
+
{ rl: "custom7", raw: "C7" },
|
|
245
|
+
{ rl: "custom8", raw: "C8" },
|
|
246
|
+
{ rl: "caption", raw: "CA" },
|
|
247
|
+
{ rl: "fallbackCity", raw: "CY" },
|
|
248
|
+
{ rl: "date", raw: "DA" },
|
|
249
|
+
{ rl: "database", raw: "DB" },
|
|
250
|
+
{ rl: "doi", raw: "DO" },
|
|
251
|
+
{ rl: "databaseProvider", raw: "DP" },
|
|
252
|
+
{ rl: "_pageEnd", raw: "EP", sort: 6 },
|
|
253
|
+
{ rl: "edition", raw: "ET", sort: 7 },
|
|
254
|
+
{ rl: "number", raw: "IS", sort: 8 },
|
|
255
|
+
{ rl: "journal", raw: "J1", sort: false },
|
|
256
|
+
{ rl: "journal", raw: "JF", sort: 3 },
|
|
257
|
+
{ rl: "keywords", raw: "KW", outputRepeat: true, inputArray: true },
|
|
258
|
+
{ rl: "urls", raw: "L1", sort: false, inputArray: true },
|
|
259
|
+
{ rl: "urls", raw: "L2", sort: false, inputArray: true },
|
|
260
|
+
{ rl: "urls", raw: "L3", sort: false, inputArray: true },
|
|
261
|
+
{ rl: "urls", raw: "L4", sort: false, inputArray: true },
|
|
262
|
+
{ rl: "language", raw: "LA" },
|
|
263
|
+
{ rl: "label", raw: "LB" },
|
|
264
|
+
{ rl: "urls", raw: "LK", sort: false, inputArray: true },
|
|
265
|
+
{ rl: "notes", raw: "N1" },
|
|
266
|
+
{ rl: "fallbackAbstract", raw: "N2" },
|
|
267
|
+
{ rl: "publisher", raw: "PB" },
|
|
268
|
+
{ rl: "year", raw: "PY" },
|
|
269
|
+
{ rl: "isbn", raw: "SN" },
|
|
270
|
+
{ rl: "_pageStart", raw: "SP", sort: 5 },
|
|
271
|
+
{ rl: "title", raw: "T1", sort: false },
|
|
272
|
+
{ rl: "journal", raw: "T2", sort: false },
|
|
273
|
+
{ rl: "title", raw: "TI", sort: 1 },
|
|
274
|
+
{ rl: "type", raw: "TY", sort: 0 }, // TY must be the lowest number
|
|
275
|
+
{ rl: "urls", raw: "UR", outputRepeat: true, inputArray: true },
|
|
276
|
+
{ rl: "volume", raw: "VL" },
|
|
277
|
+
{ rl: "date", raw: "Y1" },
|
|
278
|
+
{ rl: "accessDate", raw: "Y2" },
|
|
279
|
+
|
|
280
|
+
// These are non-standard fields but we keep these here anyway to prevent data loss
|
|
281
|
+
{ rl: "RISID", raw: "ID" },
|
|
282
|
+
{ rl: "RISShortTitle", raw: "ST" },
|
|
283
|
+
{ rl: "RISOriginalPublication", raw: "OP" },
|
|
284
|
+
],
|
|
285
|
+
collectionOutput: [], // Sorted + filtered version of the above to use when outputting
|
|
286
|
+
rawMap: new Map(), // Calculated later for quicker lookup
|
|
287
|
+
rlMap: new Map(), // Calculated later for quicker lookup
|
|
288
|
+
},
|
|
289
|
+
// }}}
|
|
290
|
+
// Ref type translations {{{
|
|
291
|
+
types: {
|
|
292
|
+
collection: [
|
|
293
|
+
// Place high-priority translations at the top (when we translate BACK we need to know which of multiple keys to prioritize)
|
|
294
|
+
{ rl: "audioVisualMaterial", raw: "ADVS" },
|
|
295
|
+
{ rl: "journalArticle", raw: "JOUR" },
|
|
296
|
+
{ rl: "personalCommunication", raw: "PCOMM" },
|
|
297
|
+
{ rl: "filmOrBroadcast", raw: "VIDEO" },
|
|
298
|
+
|
|
299
|
+
// Low priority below this line
|
|
300
|
+
{ rl: "unknown", raw: "ABST" },
|
|
301
|
+
{ rl: "aggregatedDatabase", raw: "AGGR" },
|
|
302
|
+
{ rl: "ancientText", raw: "ANCIENT" },
|
|
303
|
+
{ rl: "artwork", raw: "ART" },
|
|
304
|
+
{ rl: "bill", raw: "BILL" },
|
|
305
|
+
{ rl: "blog", raw: "BLOG" },
|
|
306
|
+
{ rl: "book", raw: "BOOK" },
|
|
307
|
+
{ rl: "case", raw: "CASE" },
|
|
308
|
+
{ rl: "bookSection", raw: "CHAP" },
|
|
309
|
+
{ rl: "chartOrTable", raw: "CHART" },
|
|
310
|
+
{ rl: "classicalWork", raw: "CLSWK" },
|
|
311
|
+
{ rl: "computerProgram", raw: "COMP" },
|
|
312
|
+
{ rl: "conferenceProceedings", raw: "CONF" },
|
|
313
|
+
{ rl: "conferencePaper", raw: "CPAPER" },
|
|
314
|
+
{ rl: "catalog", raw: "CTLG" },
|
|
315
|
+
{ rl: "dataset", raw: "DATA" },
|
|
316
|
+
{ rl: "onlineDatabase", raw: "DBASE" },
|
|
317
|
+
{ rl: "dictionary", raw: "DICT" },
|
|
318
|
+
{ rl: "electronicBook", raw: "EBOOK" },
|
|
319
|
+
{ rl: "electronicBookSection", raw: "ECHAP" },
|
|
320
|
+
{ rl: "editedBook", raw: "EDBOOK" },
|
|
321
|
+
{ rl: "electronicArticle", raw: "EJOUR" },
|
|
322
|
+
{ rl: "web", raw: "ELEC" },
|
|
323
|
+
{ rl: "encyclopedia", raw: "ENCYC" },
|
|
324
|
+
{ rl: "equation", raw: "EQUA" },
|
|
325
|
+
{ rl: "figure", raw: "FIGURE" },
|
|
326
|
+
{ rl: "generic", raw: "GEN" },
|
|
327
|
+
{ rl: "governmentDocument", raw: "GOVDOC" },
|
|
328
|
+
{ rl: "grant", raw: "GRANT" },
|
|
329
|
+
{ rl: "hearing", raw: "HEARING" },
|
|
330
|
+
{ rl: "personalCommunication", raw: "ICOMM" },
|
|
331
|
+
{ rl: "newspaperArticle", raw: "INPR" },
|
|
332
|
+
{ rl: "journalArticle", raw: "JFULL" },
|
|
333
|
+
{ rl: "legalRuleOrRegulation", raw: "LEGAL" },
|
|
334
|
+
{ rl: "manuscript", raw: "MANSCPT" },
|
|
335
|
+
{ rl: "map", raw: "MAP" },
|
|
336
|
+
{ rl: "magazineArticle", raw: "MGZN" },
|
|
337
|
+
{ rl: "filmOrBroadcast", raw: "MPCT" },
|
|
338
|
+
{ rl: "onlineMultimedia", raw: "MULTI" },
|
|
339
|
+
{ rl: "music", raw: "MUSIC" },
|
|
340
|
+
{ rl: "newspaperArticle", raw: "NEWS" },
|
|
341
|
+
{ rl: "pamphlet", raw: "PAMP" },
|
|
342
|
+
{ rl: "patent", raw: "PAT" },
|
|
343
|
+
{ rl: "report", raw: "RPRT" },
|
|
344
|
+
{ rl: "serial", raw: "SER" },
|
|
345
|
+
{ rl: "audioVisualMaterial", raw: "SLIDE" },
|
|
346
|
+
{ rl: "audioVisualMaterial", raw: "SOUND" },
|
|
347
|
+
{ rl: "standard", raw: "STAND" },
|
|
348
|
+
{ rl: "statute", raw: "STAT" },
|
|
349
|
+
{ rl: "thesis", raw: "THES" },
|
|
350
|
+
{ rl: "unpublished", raw: "UNPB" },
|
|
351
|
+
],
|
|
352
|
+
rawMap: new Map(), // Calculated later for quicker lookup
|
|
353
|
+
rlMap: new Map(), // Calculated later for quicker lookup
|
|
354
|
+
},
|
|
355
|
+
// }}}
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* @see modules/interface.js
|
|
361
|
+
*/
|
|
362
|
+
export function setup() {
|
|
363
|
+
// Sort the field set by sort field
|
|
364
|
+
translations.fields.collectionOutput = translations.fields.collection
|
|
365
|
+
.filter(f => f.sort !== false)
|
|
366
|
+
.sort((a, b) => (a.sort ?? 1000) == (b.sort ?? 1000) ? 0
|
|
367
|
+
: (a.sort ?? 1000) < (b.sort ?? 1000) ? -1
|
|
368
|
+
: 1
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
// Create lookup object of translations.fields with key as .rl / val as the full object
|
|
372
|
+
translations.fields.collection.forEach(c => {
|
|
373
|
+
translations.fields.rlMap.set(c.rl, c);
|
|
374
|
+
translations.fields.rawMap.set(c.raw, c);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// Create lookup object of ref.types with key as .rl / val as the full object
|
|
378
|
+
translations.types.collection.forEach(c => {
|
|
379
|
+
translations.types.rlMap.set(c.rl, c);
|
|
380
|
+
translations.types.rawMap.set(c.raw, c);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
}
|