@iebh/reflib 2.0.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/.eslintrc.cjs +14 -0
- package/.ignore +1 -0
- package/LICENSE +20 -0
- package/README.md +268 -0
- package/lib/browser.js +8 -0
- package/lib/default.js +7 -0
- package/lib/fields.js +161 -0
- package/lib/formats.js +61 -0
- package/lib/getModule.js +37 -0
- package/lib/identifyFormat.js +13 -0
- package/lib/readFile.js +34 -0
- package/lib/readStream.js +21 -0
- package/lib/uploadFile.js +69 -0
- package/lib/writeFile.js +23 -0
- package/lib/writeStream.js +16 -0
- package/modules/default.js +4 -0
- package/modules/endnoteXml.js +407 -0
- package/modules/interface.js +45 -0
- package/modules/json.js +59 -0
- package/modules/medline.js +625 -0
- package/modules/ris.js +345 -0
- package/package.json +51 -0
- package/shared/camelCase.js +16 -0
- package/shared/emitter.js +21 -0
- package/shared/streamEmitter.js +37 -0
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
import Emitter from '../shared/emitter.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @see modules/interface.js
|
|
5
|
+
* @param {Object} [options] Additional options to use when parsing
|
|
6
|
+
* @param {string} [options.defaultType='journalArticle'] Default citation type to assume when no other type is specified
|
|
7
|
+
* @param {string} [options.delimeter='\r'] How to split multi-line items
|
|
8
|
+
* @param {string} [options.reformatAuthors=true] Reformat Medline author format to more closely match the Reflib standard
|
|
9
|
+
* @param {string} [options.journal='long'] Whether to use the 'long' journal name or the 'short' varient when parsing references
|
|
10
|
+
* @param {boolean} [options.parseAddress=true] Try to recompose the `address` property from all author address information
|
|
11
|
+
* @param {boolean} [options.parseDoi=true] Try to parse the DOI from the article identifiers
|
|
12
|
+
* @param {boolean} [options.parseYear=true] If truthy try to parse the year field from the date
|
|
13
|
+
*
|
|
14
|
+
* @param {array<Object>} [options.fieldsReplace] If truthy adopt apply the field replacements, usually from medlineComplex fields to other values
|
|
15
|
+
* @param {string} [options.fieldsReplace.from] Field to copy/move the value from, if undefined `reformat` must be specified
|
|
16
|
+
* @param {string} options.fieldsReplace.to Field to copy/move the value to
|
|
17
|
+
* @param {string} [options.fieldsReplace.delete=true] Whether to remove the orignal 'from' field if successful (i.e. reformat doesn't return false)
|
|
18
|
+
* @param {function} [options.fieldsReplace.reformat] Optional function called as `(value, ref)` to provide the new field value. If return value is boolean `false` no action is taken
|
|
19
|
+
*/
|
|
20
|
+
export function readStream(stream, options) {
|
|
21
|
+
let settings = {
|
|
22
|
+
defaultType: 'journalArticle',
|
|
23
|
+
delimeter: '\r',
|
|
24
|
+
reformatAuthors: true,
|
|
25
|
+
journal: 'long',
|
|
26
|
+
parseAddress: true,
|
|
27
|
+
parseDoi: true,
|
|
28
|
+
parseYear: true,
|
|
29
|
+
fieldsReplace: [],
|
|
30
|
+
...options,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Settings parsing {{{
|
|
34
|
+
|
|
35
|
+
/*
|
|
36
|
+
settings.fieldsReplace.push({
|
|
37
|
+
to: 'debugPre',
|
|
38
|
+
reformat: (v, ref) => {
|
|
39
|
+
console.log('DEBUG:PRE', ref);
|
|
40
|
+
return false;
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
// Translate type
|
|
46
|
+
settings.fieldsReplace.push({
|
|
47
|
+
from: 'type',
|
|
48
|
+
to: 'type',
|
|
49
|
+
delete: false,
|
|
50
|
+
reformat: (v, ref) => translations.types.rawMap[v] || settings.defaultType,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Reformat authors
|
|
54
|
+
settings.fieldsReplace.push({
|
|
55
|
+
to: 'authors',
|
|
56
|
+
reformat: (authors, ref) => (ref.medlineAuthorsShort || ref.medlineAuthorsFull || []).map(author =>
|
|
57
|
+
author.replace(/^(?<last>[\w\-]+?) (?<initials>\w+)$/, (match, last, initials) => {
|
|
58
|
+
return (
|
|
59
|
+
last && initials ? last + ', ' + initials.split('').map(i => `${i}.`).join(' ')
|
|
60
|
+
: last ? last
|
|
61
|
+
: match
|
|
62
|
+
)
|
|
63
|
+
})
|
|
64
|
+
),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Add rule for where the journal field comes from
|
|
68
|
+
settings.fieldsReplace.push({
|
|
69
|
+
to: 'journal',
|
|
70
|
+
reformat: settings.journal.long
|
|
71
|
+
? (v, ref) => ref.medlineJournalFull || ref.medlineJournalShort
|
|
72
|
+
: (v, ref) => ref.medlineJournalShort || ref.medlineJournalLong,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Allow parsing of Address
|
|
76
|
+
if (settings.parseAddress)
|
|
77
|
+
settings.fieldsReplace.push({
|
|
78
|
+
from: 'medlineAuthorsAffiliation',
|
|
79
|
+
to: 'address',
|
|
80
|
+
delete: false,
|
|
81
|
+
reformat: v => {
|
|
82
|
+
if (!v) return false;
|
|
83
|
+
return v.join(settings.delimeter);
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Allow parsing of DOIs
|
|
88
|
+
if (settings.parseDoi)
|
|
89
|
+
settings.fieldsReplace.push({
|
|
90
|
+
from: 'medlineArticleID',
|
|
91
|
+
to: 'doi',
|
|
92
|
+
delete: false,
|
|
93
|
+
reformat: v => /(?<doi>[\w\.\/\_]+) \[doi\]/.exec(v)?.groups.doi || false,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Allow parsing of years
|
|
97
|
+
if (settings.parseYear)
|
|
98
|
+
settings.fieldsReplace.push({
|
|
99
|
+
from: 'date',
|
|
100
|
+
to: 'year',
|
|
101
|
+
delete: false,
|
|
102
|
+
reformat: v => /(?<year>\d{4}\b)/.exec(v)?.groups.year || false,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
/*
|
|
106
|
+
settings.fieldsReplace.push({
|
|
107
|
+
to: 'debugPost',
|
|
108
|
+
reformat: (v, ref) => {
|
|
109
|
+
console.log('DEBUG:POST', ref);
|
|
110
|
+
return false;
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
*/
|
|
114
|
+
|
|
115
|
+
// }}}
|
|
116
|
+
|
|
117
|
+
let emitter = Emitter();
|
|
118
|
+
|
|
119
|
+
let buffer = ''; // Incomming text buffer lines if the chunk we're given isn't enough to parse a reference yet
|
|
120
|
+
|
|
121
|
+
// Queue up the parser in the next tick (so we can return the emitter first)
|
|
122
|
+
setTimeout(()=> {
|
|
123
|
+
stream
|
|
124
|
+
.on('data', chunkBuffer => {
|
|
125
|
+
buffer += chunkBuffer.toString(); // Append incomming data to the partial-buffer we're holding in memory
|
|
126
|
+
|
|
127
|
+
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
|
|
128
|
+
let bufferSplitter = /(\r\n|\n){2,}/g; // RegExp to use per segment (multiple calls to .exec() stores state because JS is a hellscape)
|
|
129
|
+
|
|
130
|
+
let bufferSegment;
|
|
131
|
+
while (bufferSegment = bufferSplitter.exec(buffer)) {
|
|
132
|
+
let parsedRef = parseRef(buffer.substring(bufferCrop, bufferSegment.index), settings); // Parse the ref from the start+end points
|
|
133
|
+
emitter.emit('ref', parsedRef);
|
|
134
|
+
|
|
135
|
+
bufferCrop = bufferSegment.index + bufferSegment[0].length; // Set start of next ref + cropping index to last seen offset + match
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
buffer = buffer.substring(bufferCrop); // Shift-truncate the buffer so we're ready to input more data on the next call
|
|
139
|
+
})
|
|
140
|
+
.on('error', e => emitter.emit('error', e))
|
|
141
|
+
.on('end', ()=> {
|
|
142
|
+
if (buffer.replace(/\s+/, '')) { // Anything left in the to-drain buffer?
|
|
143
|
+
// Drain remaining buffer into parser before exiting
|
|
144
|
+
emitter.emit('ref', parseRef(buffer, settings));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Signal that we're done
|
|
148
|
+
emitter.emit('end');
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
return emitter;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @see modules/interface.js
|
|
158
|
+
* @param {Object} [options] Additional options to use when parsing
|
|
159
|
+
* @param {string} [options.defaultType='journalArticle'] Default citation type to assume when no other type is specified
|
|
160
|
+
* @param {string} [options.delimeter='\r'] How to split multi-line items
|
|
161
|
+
*/
|
|
162
|
+
export function writeStream(stream, options) {
|
|
163
|
+
let settings = {
|
|
164
|
+
defaultType: 'journalArticle',
|
|
165
|
+
delimeter: '\r',
|
|
166
|
+
...options,
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
start() {
|
|
171
|
+
return Promise.resolve();
|
|
172
|
+
},
|
|
173
|
+
write: xRef => {
|
|
174
|
+
let ref = { // Assign defaults if not already present
|
|
175
|
+
type: settings.defaultType,
|
|
176
|
+
title: '<NO TITLE>',
|
|
177
|
+
...xRef,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
stream.write(
|
|
181
|
+
translations.fields.collectionOutput
|
|
182
|
+
.filter(f => ref[f.rl]) // Has field?
|
|
183
|
+
.flatMap(f =>
|
|
184
|
+
f.rl == 'type' // Translate type field
|
|
185
|
+
? 'TY - ' + (translations.types.rlMap.get(ref.type) || translations.types.rlMap.get(settings.defaultType)).raw
|
|
186
|
+
: f.rl == 'title' // Special formatting for authors which should follow the title
|
|
187
|
+
? [
|
|
188
|
+
'TI - ' + ref.title,
|
|
189
|
+
...(ref.authors || []).flatMap((a, i) => [
|
|
190
|
+
ref.medlineAuthorsFull?.[i] ? `FAU - ${ref.medlineAuthorsFull[i]}` : `FAU - ${authors[i]}`,
|
|
191
|
+
ref.medlineAuthorsShort?.[i] && `AU - ${ref.medlineAuthorsShort[i]}`,
|
|
192
|
+
ref.medlineAuthorsAffiliation?.[i] && `AD - ${ref.medlineAuthorsAffiliation[i]}`,
|
|
193
|
+
ref.medlineAuthorsId?.[i] && `AUID- ${ref.medlineAuthorsId[i]}`,
|
|
194
|
+
].filter(Boolean)),
|
|
195
|
+
]
|
|
196
|
+
: f.outputSkip ? []
|
|
197
|
+
: f.outputRepeat && Array.isArray(ref[f.rl]) // Repeat array types
|
|
198
|
+
? ref[f.rl].map(item => f.raw.padEnd(4, ' ') + '- ' + item)
|
|
199
|
+
: Array.isArray(ref[f.rl]) // Flatten arrays into text
|
|
200
|
+
? f.raw.padEnd(4, ' ') + '- ' + ref[f.rl].join(settings.delimeter)
|
|
201
|
+
: f.raw.padEnd(4, ' ') + '- ' + ref[f.rl] // Regular field output
|
|
202
|
+
)
|
|
203
|
+
.concat(['\n'])
|
|
204
|
+
.join('\n')
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
return Promise.resolve();
|
|
208
|
+
},
|
|
209
|
+
end() {
|
|
210
|
+
return new Promise((resolve, reject) =>
|
|
211
|
+
stream.end(err => err ? reject(err) : resolve())
|
|
212
|
+
);
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Parse a single RIS format reference from a block of text
|
|
220
|
+
* This function is used internally by parseStream() for each individual reference
|
|
221
|
+
* @param {string} refString Raw RIS string composing the start -> end of the ref
|
|
222
|
+
* @param {Object} settings Additional settings to pass, this should be initialized + parsed by the calling function for efficiency, see readStream() for full spec
|
|
223
|
+
*/
|
|
224
|
+
export function parseRef(refString, settings) {
|
|
225
|
+
let ref = {}; // Reference under construction
|
|
226
|
+
let lastField; // Last field object we saw, used to append values if they don't match the default RIS key=val one-liner
|
|
227
|
+
let didWrap = false; // Whether the input was taken over multiple lines - if so obey `trimDotSuffix` before accepting
|
|
228
|
+
|
|
229
|
+
refString
|
|
230
|
+
.split(/[\r\n|\n]/) // Split into lines
|
|
231
|
+
.forEach(line => {
|
|
232
|
+
let parsedLine = /^\s*(?<key>[A-Z]+?)\s*-\s+(?<value>.*)$/s.exec(line)?.groups;
|
|
233
|
+
|
|
234
|
+
if (!parsedLine) { // Doesn't match key=val spec
|
|
235
|
+
line = line.trimStart();
|
|
236
|
+
if (line.replace(/\s+/, '') && lastField) { // Line isn't just whitespace + We have a field to append to - append with \r delimiters
|
|
237
|
+
if (lastField.inputArray) { // Treat each line feed like an array entry
|
|
238
|
+
ref[lastField.rl].push(line);
|
|
239
|
+
} else { // Assume we append each line entry as a single-line string
|
|
240
|
+
didWrap = true;
|
|
241
|
+
ref[lastField.rl] += ' ' + line;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return; // Stop processing this line
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
let fieldLookup = translations.fields.rawMap.get(parsedLine.key);
|
|
248
|
+
|
|
249
|
+
if (lastField?.trimDotSuffix) {
|
|
250
|
+
ref[lastField.rl] = ref[lastField.rl].replace(/\.$/, '');
|
|
251
|
+
didWrap = false;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (!fieldLookup) { // Skip unknown field translations
|
|
255
|
+
lastField = null;
|
|
256
|
+
return;
|
|
257
|
+
} else if (fieldLookup.inputArray) { // Should this `rl` key be treated like an appendable array?
|
|
258
|
+
if (!ref[fieldLookup.rl]) { // Array doesn't exist yet
|
|
259
|
+
ref[fieldLookup.rl] = [parsedLine.value];
|
|
260
|
+
} else {
|
|
261
|
+
ref[fieldLookup.rl].push(parsedLine.value);
|
|
262
|
+
}
|
|
263
|
+
lastField = fieldLookup;
|
|
264
|
+
} else { // Simple key=val
|
|
265
|
+
ref[fieldLookup.rl] = parsedLine.value;
|
|
266
|
+
lastField = fieldLookup;
|
|
267
|
+
}
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
// Post processing {{{
|
|
271
|
+
// Apply field replacement / reformat rules
|
|
272
|
+
if (settings.fieldsReplace?.length > 0)
|
|
273
|
+
settings.fieldsReplace.forEach(replacement => {
|
|
274
|
+
let newVal = replacement.from ? ref[replacement.from] : null;
|
|
275
|
+
|
|
276
|
+
// Apply reformat if we have one
|
|
277
|
+
if (replacement.reformat) {
|
|
278
|
+
newVal = replacement.reformat(newVal, ref);
|
|
279
|
+
if (newVal === false) return; // Skip boolean false
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Copy field 'from' -> 'to'
|
|
283
|
+
ref[replacement.to] = newVal;
|
|
284
|
+
|
|
285
|
+
// Delete 'from' field
|
|
286
|
+
if (replacement.from && (replacement.delete ?? true))
|
|
287
|
+
delete ref[replacement.from];
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
return ref;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Lookup tables for this module
|
|
296
|
+
* @type {Object}
|
|
297
|
+
* @property {array<Object>} fields Field translations between RefLib (`rl`) and the raw format (`raw`)
|
|
298
|
+
* @property {array<Object>} types Field translations between RefLib (`rl`) and the raw format types as raw text (`rawText`) and numeric ID (`rawId`)
|
|
299
|
+
* @property {boolean} isArray Whether the field should append to any existing `rl` field and be treated like an array of data
|
|
300
|
+
* @property {number|boolean} [sort] Sort order when outputting, use boolean `false` to disable the field on output
|
|
301
|
+
* @property {boolean} [outputSkip=false] Dont output this field at all
|
|
302
|
+
* @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
|
|
303
|
+
* @property {boolean} [inputArray=false] Forcably cast the field as an array when reading, even if there is only one value
|
|
304
|
+
* @property {boolean} [trimDotSuffix=false] Remove any trailing dot character IF the input string spans multiple lines
|
|
305
|
+
*/
|
|
306
|
+
export let translations = {
|
|
307
|
+
// Field translations {{{
|
|
308
|
+
fields: {
|
|
309
|
+
collection: [
|
|
310
|
+
// Based on the spec at https://www.nlm.nih.gov/bsd/mms/medlineelements.html
|
|
311
|
+
// Any field beginning with `medline*` is non-standard but included to prevent data loss when converted back to Medline format
|
|
312
|
+
{rl: 'medlinePMID', raw: 'PMID', sort: 0},
|
|
313
|
+
{rl: 'medlineOwner', raw: 'OWN', sort: 1},
|
|
314
|
+
{rl: 'medlineStatus', raw: 'STAT', sort: 2},
|
|
315
|
+
{rl: 'medlineDateRevised', raw: 'DR', sort: 3},
|
|
316
|
+
{rl: 'medlineISSN', raw: 'IS', outputRepeat: true, inputArray: true, sort: 4},
|
|
317
|
+
{rl: 'date', raw: 'DP', sort: 5},
|
|
318
|
+
{rl: 'title', raw: 'TI', sort: 6, trimDotSuffix: true},
|
|
319
|
+
{rl: 'doi', raw: 'LID', sort: 7},
|
|
320
|
+
{rl: 'abstract', raw: 'AB', sort: 8},
|
|
321
|
+
{rl: 'medlineCopyright', raw: 'CI', sort: 7},
|
|
322
|
+
|
|
323
|
+
// NOTE: Authors get special treatment when formatting so all these fields are skipped
|
|
324
|
+
{rl: 'medlineAuthorsFull', raw: 'FAU', sort: 10, outputSkip: true, inputArray: true},
|
|
325
|
+
{rl: 'medlineAuthorsShort', raw: 'AU', sort: 10, outputSkip: true, inputArray: true},
|
|
326
|
+
{rl: 'medlineAuthorsAffiliation', raw: 'AD', sort: 10, outputSkip: true, inputArray: true},
|
|
327
|
+
{rl: 'medlineAuthorsId', raw: 'AUID', sort: 10, outputSkip: true, inputArray: true},
|
|
328
|
+
|
|
329
|
+
{rl: 'language', raw: 'LA', sort: 11},
|
|
330
|
+
{rl: 'medlineGrantNumber', raw: 'GR', sort: 12},
|
|
331
|
+
{rl: 'type', raw: 'PT', sort: 14},
|
|
332
|
+
{rl: 'medlineTypeSecondary', raw: 'PTX'}, // Populated with any other write to PT field as array
|
|
333
|
+
{rl: 'medlineDateElectronicPublication', raw: 'DEP', sort: 15},
|
|
334
|
+
{rl: 'address', raw: 'PL', sort: 16},
|
|
335
|
+
{rl: 'medlineJournalShort', raw: 'TA', sort: 17},
|
|
336
|
+
{rl: 'medlineJournalFull', raw: 'JT', sort: 18},
|
|
337
|
+
{rl: 'medlineNLMID', raw: 'JID', sort: 19},
|
|
338
|
+
{rl: 'medlineSubset', raw: 'SB', sort: 20},
|
|
339
|
+
{rl: 'medlineOwnerOtherTerm', raw: 'OTO', sort: 21},
|
|
340
|
+
{rl: 'keywords', raw: 'OT', outputRepeat: true, inputArray: true, sort: 22},
|
|
341
|
+
{rl: 'medlineEntrezDate', raw: 'EDAT', sort: 23},
|
|
342
|
+
{rl: 'medlineDateMesh', raw: 'MHDA', sort: 24},
|
|
343
|
+
{rl: 'medlinePublicationHistoryStatus', raw: 'PHST', outputRepeat: true, inputArray: true, sort: 25},
|
|
344
|
+
{rl: 'medlineArticleID', raw: 'AID', sort: 26},
|
|
345
|
+
{rl: 'medlineStatusPublication', raw: 'PST', sort: 27},
|
|
346
|
+
{rl: 'medlineSource', raw: 'SO', sort: 27},
|
|
347
|
+
{rl: 'notes', raw: 'GN', inputArray: true, outputRepeat: true, sort: 28},
|
|
348
|
+
|
|
349
|
+
{rl: 'isbn', raw: 'ISBN'},
|
|
350
|
+
{rl: 'volume', raw: 'VI'},
|
|
351
|
+
{rl: 'medlineVolumeTitle', raw: 'VTI'},
|
|
352
|
+
{rl: 'pages', raw: 'PG'},
|
|
353
|
+
{rl: 'medlineInvestigatorAffiliation', raw: 'IRAD'},
|
|
354
|
+
{rl: 'medlineInvestigatorName', raw: 'IR'},
|
|
355
|
+
{rl: 'medlineInvestigatorNameFull', raw: 'FIR'},
|
|
356
|
+
{rl: 'medlineTitleBook', raw: 'BTI'},
|
|
357
|
+
{rl: 'medlineTitleCollection', raw: 'CTI'},
|
|
358
|
+
{rl: 'medlineConflictOfInterestStatement', raw: 'COIS'},
|
|
359
|
+
{rl: 'medlineCorporateAuthor', raw: 'CN'},
|
|
360
|
+
{rl: 'medlineDateCreate', raw: 'CRDT'},
|
|
361
|
+
{rl: 'medlineDateCreated', raw: 'DA'},
|
|
362
|
+
{rl: 'medlineDateCompleted', raw: 'DCOM'},
|
|
363
|
+
{rl: 'medlineEdition', raw: 'EN'},
|
|
364
|
+
{rl: 'medlineEditor', raw: 'ED'},
|
|
365
|
+
{rl: 'medlineEditorFull', raw: 'FED'},
|
|
366
|
+
{rl: 'medlineGeneSymbol', raw: 'GS'},
|
|
367
|
+
{rl: 'medlineISSN', raw: 'IS'},
|
|
368
|
+
{rl: 'number', raw: 'IP'},
|
|
369
|
+
{rl: 'medlineManuscriptID', raw: 'MID'},
|
|
370
|
+
{rl: 'medlineMeshTerms', raw: 'MH', outputRepeat: true, inputArray: true},
|
|
371
|
+
{rl: 'medlineReferenceCount', raw: 'RF'},
|
|
372
|
+
{rl: 'medlineAbstractOther', raw: 'OAB'},
|
|
373
|
+
{rl: 'medlineCopyrightOther', raw: 'OCI'},
|
|
374
|
+
{rl: 'medlineIDOther', raw: 'OID'},
|
|
375
|
+
{rl: 'medlinePersonalName', raw: 'PS'},
|
|
376
|
+
{rl: 'medlinePersonalNameFull', raw: 'FPS'},
|
|
377
|
+
{rl: 'medlinePublishingModel', raw: 'PUBM'},
|
|
378
|
+
{rl: 'medlinePubMedCentralID', raw: 'PMC'},
|
|
379
|
+
{rl: 'medlinePubMedCentralRelease', raw: 'PMCR'},
|
|
380
|
+
{rl: 'medlineRegistryNumber', raw: 'RN'},
|
|
381
|
+
{rl: 'medlineSubstanceName', raw: 'NM'},
|
|
382
|
+
{rl: 'medlineSecondarySource', raw: 'SI'},
|
|
383
|
+
{rl: 'medlineSpaceFlightMission', raw: 'SFM'},
|
|
384
|
+
{rl: 'medlineSubset', raw: 'SB'},
|
|
385
|
+
{rl: 'medlineTitleTransliterated', raw: 'TT', sort: 6},
|
|
386
|
+
],
|
|
387
|
+
collectionOutput: [], // Sorted + filtered version of the above to use when outputting
|
|
388
|
+
rawMap: new Map(), // Calculated later for quicker lookup
|
|
389
|
+
rlMap: new Map(), // Calculated later for quicker lookup
|
|
390
|
+
},
|
|
391
|
+
// }}}
|
|
392
|
+
// Ref type translations {{{
|
|
393
|
+
types: {
|
|
394
|
+
collection: [
|
|
395
|
+
// Formats we support as a translation (or near-enough translation)
|
|
396
|
+
// Note that the preferred translation should be first
|
|
397
|
+
|
|
398
|
+
// High priority translations
|
|
399
|
+
{raw: 'Blog', rl: 'blog'},
|
|
400
|
+
{raw: 'Case Reports', rl: 'case'},
|
|
401
|
+
{raw: 'Catalog', rl: 'catalog'},
|
|
402
|
+
{raw: 'Chart', rl: 'chartOrTable'},
|
|
403
|
+
{raw: 'Database', rl: 'aggregatedDatabase'},
|
|
404
|
+
{raw: 'Dataset', rl: 'dataset'},
|
|
405
|
+
{raw: 'Dictionary', rl: 'dictionary'},
|
|
406
|
+
{raw: 'Encyclopedia', rl: 'encyclopedia'},
|
|
407
|
+
{raw: 'Journal Article', rl: 'journalArticle'},
|
|
408
|
+
{raw: 'Legal Case', rl: 'legalRuleOrRegulation'},
|
|
409
|
+
{raw: 'Manuscript', rl: 'manuscript'},
|
|
410
|
+
{raw: 'Map', rl: 'map'},
|
|
411
|
+
{raw: 'Newspaper Article', rl: 'newspaperArticle'},
|
|
412
|
+
{raw: 'Patent', rl: 'patent'},
|
|
413
|
+
{raw: 'Preprint', rl: 'unpublished'},
|
|
414
|
+
{raw: 'Tables', rl: 'chartOrTable'},
|
|
415
|
+
{raw: 'Technical Report', rl: 'report'},
|
|
416
|
+
{raw: 'Unpublished Work', rl: 'unpublished'},
|
|
417
|
+
{raw: 'Video-Audio Media', rl: 'audioVisualMaterial'},
|
|
418
|
+
{raw: 'Web Archive', rl: 'web'},
|
|
419
|
+
|
|
420
|
+
// Lower priority translations
|
|
421
|
+
{raw: 'Address', rl: 'personalCommunication'},
|
|
422
|
+
{raw: 'Advertisement', rl: 'audioVisualMaterial'},
|
|
423
|
+
{raw: 'Almanac', rl: 'book'},
|
|
424
|
+
{raw: 'Anecdotes', rl: 'blog'},
|
|
425
|
+
{raw: 'Animation', rl: 'filmOrBroadcast'},
|
|
426
|
+
{raw: 'Annual Report', rl: 'report'},
|
|
427
|
+
{raw: 'Aphorisms and Proverbs', rl: 'pamphlet'},
|
|
428
|
+
{raw: 'Architectural Drawing', rl: 'figure'},
|
|
429
|
+
{raw: 'Autobiography', rl: 'book'},
|
|
430
|
+
{raw: 'Bibliography', rl: 'catalog'},
|
|
431
|
+
{raw: 'Biobibliography', rl: 'book'},
|
|
432
|
+
{raw: 'Biography', rl: 'book'},
|
|
433
|
+
{raw: 'Book Illustrations', rl: 'audioVisualMaterial'},
|
|
434
|
+
{raw: 'Book Review', rl: 'magazineArticle'},
|
|
435
|
+
{raw: 'Caricature', rl: 'artwork'},
|
|
436
|
+
{raw: 'Cartoon', rl: 'audioVisualMaterial'},
|
|
437
|
+
{raw: 'Catalog, Bookseller', rl: 'catalog'},
|
|
438
|
+
{raw: 'Catalog, Commercial', rl: 'catalog'},
|
|
439
|
+
{raw: 'Catalog, Drug', rl: 'catalog'},
|
|
440
|
+
{raw: 'Catalog, Publisher', rl: 'catalog'},
|
|
441
|
+
{raw: 'Catalog, Union', rl: 'catalog'},
|
|
442
|
+
{raw: 'Chronology', rl: 'book'},
|
|
443
|
+
{raw: 'Classical Article', rl: 'classicalWork'},
|
|
444
|
+
{raw: 'Clinical Conference', rl: 'conferenceProceedings'},
|
|
445
|
+
{raw: 'Clinical Study', rl: 'dataset'},
|
|
446
|
+
{raw: 'Clinical Trial, Phase III', rl: 'dataset'},
|
|
447
|
+
{raw: 'Clinical Trial, Phase II', rl: 'dataset'},
|
|
448
|
+
{raw: 'Clinical Trial, Phase I', rl: 'dataset'},
|
|
449
|
+
{raw: 'Clinical Trial, Phase IV', rl: 'dataset'},
|
|
450
|
+
{raw: 'Clinical Trial Protocol', rl: 'dataset'},
|
|
451
|
+
{raw: 'Clinical Trial', rl: 'dataset'},
|
|
452
|
+
{raw: 'Clinical Trial, Veterinary', rl: 'dataset'},
|
|
453
|
+
{raw: 'Consensus Development Conference, NIH', rl: 'conferenceProceedings'},
|
|
454
|
+
{raw: 'Consensus Development Conference', rl: 'conferenceProceedings'},
|
|
455
|
+
{raw: 'Corrected and Republished Article', rl: 'journalArticle'},
|
|
456
|
+
{raw: 'Database', rl: 'onlineDatabase'},
|
|
457
|
+
{raw: 'Dictionary, Chemical', rl: 'dictionary'},
|
|
458
|
+
{raw: 'Dictionary, Classical', rl: 'dictionary'},
|
|
459
|
+
{raw: 'Dictionary, Dental', rl: 'dictionary'},
|
|
460
|
+
{raw: 'Dictionary, Medical', rl: 'dictionary'},
|
|
461
|
+
{raw: 'Dictionary, Pharmaceutic', rl: 'dictionary'},
|
|
462
|
+
{raw: 'Dictionary, Polyglot', rl: 'dictionary'},
|
|
463
|
+
{raw: 'Documentaries and Factual Films', rl: 'filmOrBroadcast'},
|
|
464
|
+
{raw: 'Drawing', rl: 'audioVisualMaterial'},
|
|
465
|
+
{raw: 'Ephemera', rl: 'artwork'},
|
|
466
|
+
{raw: 'Eulogy', rl: 'personalCommunication'},
|
|
467
|
+
{raw: 'Formulary, Dental', rl: 'dataset'},
|
|
468
|
+
{raw: 'Formulary, Homeopathic', rl: 'dataset'},
|
|
469
|
+
{raw: 'Formulary, Hospital', rl: 'dataset'},
|
|
470
|
+
{raw: 'Formulary', rl: 'dataset'},
|
|
471
|
+
{raw: 'Funeral Sermon', rl: 'personalCommunication'},
|
|
472
|
+
{raw: 'Government Publication', rl: 'governmentDocument'},
|
|
473
|
+
{raw: 'Graphic Novel', rl: 'artwork'},
|
|
474
|
+
{raw: 'Historical Article', rl: 'ancientText'},
|
|
475
|
+
{raw: 'Incunabula', rl: 'ancientText'},
|
|
476
|
+
{raw: 'Incunabula', rl: 'ancientText'},
|
|
477
|
+
{raw: 'Instructional Film and Video', rl: 'filmOrBroadcast'},
|
|
478
|
+
{raw: 'Introductory Journal Article', rl: 'journalArticle'},
|
|
479
|
+
{raw: 'Legislation', rl: 'statute'},
|
|
480
|
+
{raw: 'Letter', rl: 'personalCommunication'},
|
|
481
|
+
{raw: 'Manuscript, Medical', rl: 'manuscript'},
|
|
482
|
+
{raw: 'Movable Books', rl: 'books'},
|
|
483
|
+
{raw: 'News', rl: 'newspaperArticle'},
|
|
484
|
+
{raw: 'Pharmacopoeia, Homeopathic', rl: 'book'},
|
|
485
|
+
{raw: 'Pharmacopoeia', rl: 'book'},
|
|
486
|
+
{raw: 'Photograph', rl: 'audioVisualMaterial'},
|
|
487
|
+
{raw: 'Pictorial Work', rl: 'audioVisualMaterial'},
|
|
488
|
+
{raw: 'Poetry', rl: 'artwork'},
|
|
489
|
+
{raw: 'Portrait', rl: 'artwork'},
|
|
490
|
+
{raw: 'Postcard', rl: 'audioVisualMaterial'},
|
|
491
|
+
{raw: 'Poster', rl: 'audioVisualMaterial'},
|
|
492
|
+
{raw: 'Public Service Announcement', rl: 'governmentDocument'},
|
|
493
|
+
{raw: 'Research Support, N.I.H., Extramural', rl: 'grant'},
|
|
494
|
+
{raw: 'Research Support, N.I.H., Intramural', rl: 'grant'},
|
|
495
|
+
{raw: 'Research Support, Non-U.S. Gov\'t', rl: 'grant'},
|
|
496
|
+
{raw: 'Research Support, U.S. Government', rl: 'grant'},
|
|
497
|
+
{raw: 'Research Support, U.S. Gov\'t, Non-P.H.S.', rl: 'grant'},
|
|
498
|
+
{raw: 'Research Support, U.S. Gov\'t, P.H.S.', rl: 'grant'},
|
|
499
|
+
{raw: 'Resource Guide', rl: 'standard'},
|
|
500
|
+
{raw: 'Retracted Publication', rl: 'journalArticle'},
|
|
501
|
+
{raw: 'Retraction of Publication', rl: 'journalArticle'},
|
|
502
|
+
{raw: 'Sermon', rl: 'journalArticle'},
|
|
503
|
+
{raw: 'Statistics', rl: 'chartOrTable'},
|
|
504
|
+
{raw: 'Study Guide', rl: 'standard'},
|
|
505
|
+
{raw: 'Terminology', rl: 'catalog'},
|
|
506
|
+
{raw: 'Textbook', rl: 'book'},
|
|
507
|
+
{raw: 'Unedited Footage', rl: 'audioVisualMaterial'},
|
|
508
|
+
{raw: 'Webcast', rl: 'web'},
|
|
509
|
+
{raw: 'Wit and Humor', rl: 'artwork'},
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
// Unsupported - Please map these and submit a PR if you think something obvious is mising
|
|
513
|
+
{rl: '', raw: 'Abbreviations'},
|
|
514
|
+
{rl: '', raw: 'Abstracts'},
|
|
515
|
+
{rl: '', raw: 'Academic Dissertation'},
|
|
516
|
+
{rl: '', raw: 'Account Book'},
|
|
517
|
+
{rl: '', raw: 'Adaptive Clinical Trial'},
|
|
518
|
+
{rl: '', raw: 'Atlas'},
|
|
519
|
+
{rl: '', raw: 'Bookplate'},
|
|
520
|
+
{rl: '', raw: 'Broadside'},
|
|
521
|
+
{rl: '', raw: 'Calendar'},
|
|
522
|
+
{rl: '', raw: 'Collected Correspondence'},
|
|
523
|
+
{rl: '', raw: 'Collected Work'},
|
|
524
|
+
{rl: '', raw: 'Collection'},
|
|
525
|
+
{rl: '', raw: 'Comment'},
|
|
526
|
+
{rl: '', raw: 'Comparative Study'},
|
|
527
|
+
{rl: '', raw: 'Congress'},
|
|
528
|
+
{rl: '', raw: 'Controlled Clinical Trial'},
|
|
529
|
+
{rl: '', raw: 'Cookbook'},
|
|
530
|
+
{rl: '', raw: 'Diary'},
|
|
531
|
+
{rl: '', raw: 'Directory'},
|
|
532
|
+
{rl: '', raw: 'Dispensatory'},
|
|
533
|
+
{rl: '', raw: 'Duplicate Publication'},
|
|
534
|
+
{rl: '', raw: 'Editorial'},
|
|
535
|
+
{rl: '', raw: 'Electronic Supplementary Materials'},
|
|
536
|
+
{rl: '', raw: 'English Abstract'},
|
|
537
|
+
{rl: '', raw: 'Equivalence Trial'},
|
|
538
|
+
{rl: '', raw: 'Essay'},
|
|
539
|
+
{rl: '', raw: 'Evaluation Study'},
|
|
540
|
+
{rl: '', raw: 'Examination Questions'},
|
|
541
|
+
{rl: '', raw: 'Exhibition'},
|
|
542
|
+
{rl: '', raw: 'Expression of Concern'},
|
|
543
|
+
{rl: '', raw: 'Festschrift'},
|
|
544
|
+
{rl: '', raw: 'Fictional Work'},
|
|
545
|
+
{rl: '', raw: 'Form'},
|
|
546
|
+
{rl: '', raw: 'legalRuleOrRegulation'},
|
|
547
|
+
{rl: '', raw: 'Guidebook'},
|
|
548
|
+
{rl: '', raw: 'Guideline'},
|
|
549
|
+
{rl: '', raw: 'Handbook'},
|
|
550
|
+
{rl: '', raw: 'Herbal'},
|
|
551
|
+
{rl: '', raw: 'Index'},
|
|
552
|
+
{rl: '', raw: 'Interactive Tutorial'},
|
|
553
|
+
{rl: '', raw: 'Interview'},
|
|
554
|
+
{rl: '', raw: 'Juvenile Literature'},
|
|
555
|
+
{rl: '', raw: 'Laboratory Manual'},
|
|
556
|
+
{rl: '', raw: 'Lecture Note'},
|
|
557
|
+
{rl: '', raw: 'Meeting Abstract'},
|
|
558
|
+
{rl: '', raw: 'Meta-Analysis'},
|
|
559
|
+
{rl: '', raw: 'Monograph'},
|
|
560
|
+
{rl: '', raw: 'Multicenter Study'},
|
|
561
|
+
{rl: '', raw: 'Nurses Instruction'},
|
|
562
|
+
{rl: '', raw: 'Observational Study'},
|
|
563
|
+
{rl: '', raw: 'Observational Study, Veterinary'},
|
|
564
|
+
{rl: '', raw: 'Outline'},
|
|
565
|
+
{rl: '', raw: 'Overall'},
|
|
566
|
+
{rl: '', raw: 'Patient Education Handout'},
|
|
567
|
+
{rl: '', raw: 'Periodical'},
|
|
568
|
+
{rl: '', raw: 'Periodical Index'},
|
|
569
|
+
{rl: '', raw: 'Personal Narrative'},
|
|
570
|
+
{rl: '', raw: 'Phrases'},
|
|
571
|
+
{rl: '', raw: 'Popular Work'},
|
|
572
|
+
{rl: '', raw: 'Practice Guideline'},
|
|
573
|
+
{rl: '', raw: 'Pragmatic Clinical Trial'},
|
|
574
|
+
{rl: '', raw: 'Price List'},
|
|
575
|
+
{rl: '', raw: 'Problems and Exercises'},
|
|
576
|
+
{rl: '', raw: 'Program'},
|
|
577
|
+
{rl: '', raw: 'Programmed Instruction'},
|
|
578
|
+
{rl: '', raw: 'Prospectus'},
|
|
579
|
+
{rl: '', raw: 'Publication Components'},
|
|
580
|
+
{rl: '', raw: 'Publication Formats'},
|
|
581
|
+
{rl: '', raw: 'Published Erratum'},
|
|
582
|
+
{rl: '', raw: 'Randomized Controlled Trial'},
|
|
583
|
+
{rl: '', raw: 'Randomized Controlled Trial, Veterinary'},
|
|
584
|
+
{rl: '', raw: 'Research Support, American Recovery and Reinvestment Act'},
|
|
585
|
+
{rl: '', raw: 'Review'},
|
|
586
|
+
{rl: '', raw: 'Scientific Integrity Review'},
|
|
587
|
+
{rl: '', raw: 'Study Characteristics'},
|
|
588
|
+
{rl: '', raw: 'Support of Research'},
|
|
589
|
+
{rl: '', raw: 'Systematic Review'},
|
|
590
|
+
{rl: '', raw: 'Twin Study'},
|
|
591
|
+
{rl: '', raw: 'Union List'},
|
|
592
|
+
{rl: '', raw: 'Validation Study'},
|
|
593
|
+
],
|
|
594
|
+
rawMap: new Map(), // Calculated later for quicker lookup
|
|
595
|
+
rlMap: new Map(), // Calculated later for quicker lookup
|
|
596
|
+
},
|
|
597
|
+
// }}}
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* @see modules/interface.js
|
|
603
|
+
*/
|
|
604
|
+
export function setup() {
|
|
605
|
+
// Sort the field set by sort field
|
|
606
|
+
translations.fields.collectionOutput = translations.fields.collection
|
|
607
|
+
.filter(f => f.sort !== false)
|
|
608
|
+
.sort((a, b) => (a.sort ?? 1000) == (b.sort ?? 1000) ? 0
|
|
609
|
+
: (a.sort ?? 1000) < (b.sort ?? 1000) ? -1
|
|
610
|
+
: 1
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
// Create lookup object of translations.fields with key as .rl / val as the full object
|
|
614
|
+
translations.fields.collection.forEach(c => {
|
|
615
|
+
translations.fields.rlMap.set(c.rl, c);
|
|
616
|
+
translations.fields.rawMap.set(c.raw, c);
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
// Create lookup object of ref.types with key as .rl / val as the full object
|
|
620
|
+
translations.types.collection.forEach(c => {
|
|
621
|
+
translations.types.rlMap.set(c.rl, c);
|
|
622
|
+
translations.types.rawMap.set(c.raw, c);
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
}
|