@iebh/reflib 2.6.6 → 2.7.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/.ignore +1 -1
- package/LICENSE +20 -20
- package/README.md +274 -274
- 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/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 +395 -392
- package/modules/default.js +7 -7
- package/modules/endnoteEnl.js +237 -237
- package/modules/endnoteEnlX.js +85 -85
- package/modules/endnoteXml.js +474 -442
- package/modules/interface.js +47 -47
- package/modules/json.js +79 -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 +51 -51
- package/package.json +66 -66
- package/shared/camelCase.js +17 -17
- package/shared/emitter.js +23 -23
- package/shared/streamEmitter.js +61 -61
package/modules/endnoteXml.js
CHANGED
|
@@ -1,442 +1,474 @@
|
|
|
1
|
-
import camelCase from '../shared/camelCase.js';
|
|
2
|
-
import Emitter from '../shared/emitter.js';
|
|
3
|
-
|
|
4
|
-
// This import is overwritten by the 'browser' field in package.json with the shimmed version
|
|
5
|
-
import { WritableStream as XMLParser } from 'htmlparser2/lib/WritableStream';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Read an EndnoteXML file, returning an Emitter analogue
|
|
9
|
-
*
|
|
10
|
-
* @see modules/inhterface.js
|
|
11
|
-
* @param {Stream} stream Stream primative to encapsulate
|
|
12
|
-
* @returns {Object} An Emitter analogue defined in `../shared/Emitter.js`
|
|
13
|
-
*/
|
|
14
|
-
export function readStream(stream) {
|
|
15
|
-
let emitter = Emitter();
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* The current reference being appended to
|
|
19
|
-
* @type {Object}
|
|
20
|
-
*/
|
|
21
|
-
let ref = {};
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Stack of nodes we are currently traversed into
|
|
26
|
-
* @type {array<Object>}
|
|
27
|
-
*/
|
|
28
|
-
let stack = [];
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Whether to append incoming text blocks to the previous block
|
|
33
|
-
* This is necessary as XMLParser splits text into multiple calls so we need to know whether to append or treat this item as a continuation of the previous one
|
|
34
|
-
* @type {boolean}
|
|
35
|
-
*/
|
|
36
|
-
let textAppend = false;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* The options/callbacks for the parser
|
|
40
|
-
* @type {Object}
|
|
41
|
-
*/
|
|
42
|
-
let parserOptions = {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
.replace(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (!ref.
|
|
83
|
-
if (textAppend) {
|
|
84
|
-
ref.
|
|
85
|
-
} else {
|
|
86
|
-
ref.
|
|
87
|
-
}
|
|
88
|
-
} else if (
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
+
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
[
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
['
|
|
223
|
-
['
|
|
224
|
-
['
|
|
225
|
-
['
|
|
226
|
-
['
|
|
227
|
-
['
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
.
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
*
|
|
304
|
-
* @
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
.
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
{rl: '
|
|
370
|
-
{rl: '
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
{rl: '
|
|
378
|
-
{rl: '
|
|
379
|
-
{rl: '
|
|
380
|
-
{rl: '
|
|
381
|
-
{rl: '
|
|
382
|
-
{rl: '
|
|
383
|
-
{rl: '
|
|
384
|
-
{rl: '
|
|
385
|
-
{rl: '
|
|
386
|
-
{rl: '
|
|
387
|
-
{rl: '
|
|
388
|
-
{rl: '
|
|
389
|
-
{rl: '
|
|
390
|
-
{rl: '
|
|
391
|
-
{rl: '
|
|
392
|
-
{rl: '
|
|
393
|
-
{rl: '
|
|
394
|
-
{rl: '
|
|
395
|
-
{rl: '
|
|
396
|
-
{rl: '
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
{rl: '
|
|
405
|
-
{rl: '
|
|
406
|
-
{rl: '
|
|
407
|
-
{rl: '
|
|
408
|
-
{rl: '
|
|
409
|
-
{rl: '
|
|
410
|
-
{rl: '
|
|
411
|
-
{rl: '
|
|
412
|
-
{rl: '
|
|
413
|
-
{rl: '
|
|
414
|
-
{rl: '
|
|
415
|
-
{rl: '
|
|
416
|
-
{rl: '
|
|
417
|
-
{rl: '
|
|
418
|
-
{rl: '
|
|
419
|
-
{rl: '
|
|
420
|
-
{rl: '
|
|
421
|
-
{rl: '
|
|
422
|
-
{rl: '
|
|
423
|
-
{rl: '
|
|
424
|
-
{rl: '
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
}
|
|
1
|
+
import camelCase from '../shared/camelCase.js';
|
|
2
|
+
import Emitter from '../shared/emitter.js';
|
|
3
|
+
|
|
4
|
+
// This import is overwritten by the 'browser' field in package.json with the shimmed version
|
|
5
|
+
import { WritableStream as XMLParser } from 'htmlparser2/lib/WritableStream';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Read an EndnoteXML file, returning an Emitter analogue
|
|
9
|
+
*
|
|
10
|
+
* @see modules/inhterface.js
|
|
11
|
+
* @param {Stream} stream Stream primative to encapsulate
|
|
12
|
+
* @returns {Object} An Emitter analogue defined in `../shared/Emitter.js`
|
|
13
|
+
*/
|
|
14
|
+
export function readStream(stream) {
|
|
15
|
+
let emitter = Emitter();
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The current reference being appended to
|
|
19
|
+
* @type {Object}
|
|
20
|
+
*/
|
|
21
|
+
let ref = {};
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Stack of nodes we are currently traversed into
|
|
26
|
+
* @type {array<Object>}
|
|
27
|
+
*/
|
|
28
|
+
let stack = [];
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Whether to append incoming text blocks to the previous block
|
|
33
|
+
* This is necessary as XMLParser splits text into multiple calls so we need to know whether to append or treat this item as a continuation of the previous one
|
|
34
|
+
* @type {boolean}
|
|
35
|
+
*/
|
|
36
|
+
let textAppend = false;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The options/callbacks for the parser
|
|
40
|
+
* @type {Object}
|
|
41
|
+
*/
|
|
42
|
+
let parserOptions = {
|
|
43
|
+
onopentag(name, attrs) {
|
|
44
|
+
textAppend = false;
|
|
45
|
+
stack.push({
|
|
46
|
+
name: camelCase(name),
|
|
47
|
+
attrs,
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
onclosetag(name) {
|
|
51
|
+
if (name == 'record') {
|
|
52
|
+
if (ref.title) ref.title = ref.title // htmlparser2 handles the '<title>' tag in a really bizarre way so we have to pull apart the <style> bits when parsing
|
|
53
|
+
.replace(/^.*<style.*>(.*)<\/style>.*$/m, '$1')
|
|
54
|
+
.replace(/^\s+/, '')
|
|
55
|
+
.replace(/\s+$/, '')
|
|
56
|
+
emitter.emit('ref', translateRawToRef(ref));
|
|
57
|
+
stack = []; // Trash entire stack when hitting end of <record/> node
|
|
58
|
+
ref = {}; // Reset the ref state
|
|
59
|
+
} else {
|
|
60
|
+
stack.pop();
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
ontext(text) {
|
|
64
|
+
let parentName = stack.at(-1)?.name;
|
|
65
|
+
let gParentName = stack.at(-2)?.name;
|
|
66
|
+
if (text && text.startsWith('\n')) { // Need to crop text - likely a prettified XML output or Zotero style XML file
|
|
67
|
+
text = text
|
|
68
|
+
.replace(/^\n\s*/gm, '')
|
|
69
|
+
.replace(/\n\s*$/gm, '')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (parentName == 'title') {
|
|
73
|
+
if (textAppend) {
|
|
74
|
+
ref.title += text;
|
|
75
|
+
} else {
|
|
76
|
+
ref.title = text;
|
|
77
|
+
}
|
|
78
|
+
} else if (
|
|
79
|
+
(parentName == 'style' && gParentName == 'author')
|
|
80
|
+
|| (parentName == 'author' && text)
|
|
81
|
+
) {
|
|
82
|
+
if (!ref.authors) ref.authors = [];
|
|
83
|
+
if (textAppend) {
|
|
84
|
+
ref.authors[ref.authors.length - 1] += text;
|
|
85
|
+
} else {
|
|
86
|
+
ref.authors.push(text);
|
|
87
|
+
}
|
|
88
|
+
} else if (
|
|
89
|
+
(parentName == 'style' && gParentName == 'keyword')
|
|
90
|
+
|| (parentName == 'keyword' && text)
|
|
91
|
+
) {
|
|
92
|
+
if (!ref.keywords) ref.keywords = [];
|
|
93
|
+
if (textAppend) {
|
|
94
|
+
ref.keywords[ref.keywords.length - 1] += text;
|
|
95
|
+
} else {
|
|
96
|
+
ref.keywords.push(text);
|
|
97
|
+
}
|
|
98
|
+
} else if (
|
|
99
|
+
(parentName == 'style' && gParentName == 'url')
|
|
100
|
+
|| (parentName == 'url' && text)
|
|
101
|
+
) {
|
|
102
|
+
if (!ref.urls) ref.urls = [];
|
|
103
|
+
if (textAppend) {
|
|
104
|
+
ref.urls[ref.urls.length - 1] += text;
|
|
105
|
+
} else {
|
|
106
|
+
ref.urls.push(text);
|
|
107
|
+
}
|
|
108
|
+
} else if (parentName == 'style' && translations.fields.rawMap.has(gParentName)) { // Text within <style/> tag
|
|
109
|
+
if (textAppend || ref[gParentName]) { // Text already exists? Append (handles node-expats silly multi-text per escape character "feature")
|
|
110
|
+
ref[gParentName] += text;
|
|
111
|
+
} else {
|
|
112
|
+
ref[gParentName] = text;
|
|
113
|
+
}
|
|
114
|
+
} else if (['recNumber', 'refType'].includes(parentName)) { // Simple setters like <rec-number/>
|
|
115
|
+
if (textAppend || ref[parentName]) {
|
|
116
|
+
ref[parentName] += text;
|
|
117
|
+
} else {
|
|
118
|
+
ref[parentName] = text;
|
|
119
|
+
}
|
|
120
|
+
} else if (text && translations.fields.rawMap.has(parentName) && !['authors', 'keyword', 'url'].includes(parentName)) { // Zotero style simple field allocation
|
|
121
|
+
ref[parentName] = text;
|
|
122
|
+
} else if (gParentName == 'titles' && parentName == 'secondaryTitle' && text) { // Zotero "Journal" field translation
|
|
123
|
+
ref.secondaryTitle = text;
|
|
124
|
+
} // Implied else - ignore node entirely, likely a parent node containing children we actually want to process
|
|
125
|
+
textAppend = true; // Always set the next call to the text emitter handler as an append operation
|
|
126
|
+
},
|
|
127
|
+
onend() {
|
|
128
|
+
emitter.emit('end');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Queue up the parser in the next tick (so we can return the emitter first)
|
|
133
|
+
setTimeout(() => {
|
|
134
|
+
if (typeof stream.pipe === 'function') {
|
|
135
|
+
let parser = new XMLParser(parserOptions, {
|
|
136
|
+
decodeEntities: false, // htmlparser2 chokes if the input has unescaped '<' or '>' in the input - which Zotero does, so we have to handle this ourselves
|
|
137
|
+
xmlMode: true, // Needed to handle self-closing tags
|
|
138
|
+
});
|
|
139
|
+
stream.on('data', ()=> emitter.emit('progress', stream.bytesRead))
|
|
140
|
+
stream.pipe(parser)
|
|
141
|
+
return;
|
|
142
|
+
} else {
|
|
143
|
+
console.error('Error with stream, check "streamEmitter.js" if on browser')
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
return emitter;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Write references to a file
|
|
153
|
+
*
|
|
154
|
+
* @see modules/interface.js
|
|
155
|
+
*
|
|
156
|
+
* @param {Stream} stream Writable stream to output to
|
|
157
|
+
* @param {Object} [options] Additional options to use when parsing
|
|
158
|
+
* @param {string} [options.defaultType='journalArticle'] Default citation type to assume when no other type is specified
|
|
159
|
+
* @param {string} [options.filePath="c:\\"] "Fake" internal source file path the citation library was exported from, must end with backslashes
|
|
160
|
+
* @param {string} [options.fileName="EndNote.enl"] "Fake" internal source file name the citation library was exported from
|
|
161
|
+
* @param {function} [options.formatDate] Date formatter to translate between a JS Date object and the EndNote YYYY-MM-DD format
|
|
162
|
+
*
|
|
163
|
+
* @returns {Object} A writable stream analogue defined in `modules/interface.js`
|
|
164
|
+
*/
|
|
165
|
+
export function writeStream(stream, options) {
|
|
166
|
+
let settings = {
|
|
167
|
+
defaultType: 'journalArticle',
|
|
168
|
+
filePath: 'c:\\',
|
|
169
|
+
fileName: 'EndNote.enl',
|
|
170
|
+
formatDate: value => value instanceof Date ? value.toISOString().substr(0, 10) : value,
|
|
171
|
+
...options,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// Cached values so we don't need to keep recomputing
|
|
175
|
+
let encodedName = xmlEscape(settings.fileName);
|
|
176
|
+
let refsSeen = 0;
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
start: ()=> {
|
|
180
|
+
stream.write('<?xml version="1.0" encoding="UTF-8" ?><xml><records>');
|
|
181
|
+
return Promise.resolve();
|
|
182
|
+
},
|
|
183
|
+
write: ref => {
|
|
184
|
+
let refType = translations.types.rlMap.get(ref.type || settings.defaultType);
|
|
185
|
+
if (!refType) {
|
|
186
|
+
console.warn(`Invalid reference type: "${ref.type}", defaulting to journal article`);
|
|
187
|
+
refType = translations.types.rlMap.get('journalArticle')
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
refsSeen++;
|
|
191
|
+
let recNumber = ref.recNumber || refsSeen;
|
|
192
|
+
|
|
193
|
+
stream.write(
|
|
194
|
+
'<record>'
|
|
195
|
+
// Preamble
|
|
196
|
+
+ `<database name="${settings.fileName}" path="${settings.filePath}${settings.fileName}">${encodedName}</database>`
|
|
197
|
+
+ `<source-app name="EndNote" version="16.0">EndNote</source-app>`
|
|
198
|
+
+ `<rec-number>${recNumber}</rec-number>`
|
|
199
|
+
+ `<foreign-keys><key app="EN" db-id="s55prpsswfsepue0xz25pxai2p909xtzszzv">${recNumber}</key></foreign-keys>`
|
|
200
|
+
|
|
201
|
+
// Type
|
|
202
|
+
+ `<ref-type name="${refType.rawText}">${refType.rawId}</ref-type>`
|
|
203
|
+
|
|
204
|
+
// Authors
|
|
205
|
+
+ '<contributors><authors>'
|
|
206
|
+
+ (ref.authors || []).map(author => `<author><style face="normal" font="default" size="100%">${xmlEscape(author)}</style></author>`)
|
|
207
|
+
+ '</authors></contributors>'
|
|
208
|
+
|
|
209
|
+
// Titles
|
|
210
|
+
+ '<titles>'
|
|
211
|
+
+ (ref.title ? `<title><style face="normal" font="default" size="100%">${xmlEscape(ref.title)}</style></title>` : '')
|
|
212
|
+
+ (ref.journal ? `<secondary-title><style face="normal" font="default" size="100%">${xmlEscape(ref.journal)}</style></secondary-title>` : '')
|
|
213
|
+
+ (ref.titleShort ? `<short-title><style face="normal" font="default" size="100%">${xmlEscape(ref.titleShort)}</style></short-title>` : '')
|
|
214
|
+
+ (ref.journalAlt ? `<alt-title><style face="normal" font="default" size="100%">${xmlEscape(ref.journalAlt)}</style></alt-title>` : '')
|
|
215
|
+
+ '</titles>'
|
|
216
|
+
|
|
217
|
+
// Periodical
|
|
218
|
+
+ (ref.periodical ? `<periodical><full-title><style face="normal" font="default" size="100%">${xmlEscape(ref.periodical)}</style></full-title></periodical>` : '')
|
|
219
|
+
|
|
220
|
+
// Simple field key/vals
|
|
221
|
+
+ [
|
|
222
|
+
['abstract', 'abstract'],
|
|
223
|
+
['accessDate', 'access-date'],
|
|
224
|
+
['accession', 'accession-num'],
|
|
225
|
+
['address', 'auth-address'],
|
|
226
|
+
['caption', 'caption'],
|
|
227
|
+
['databaseProvider', 'remote-database-provider'],
|
|
228
|
+
['database', 'remote-database-name'],
|
|
229
|
+
['doi', 'electronic-resource-num'],
|
|
230
|
+
['isbn', 'isbn'],
|
|
231
|
+
['accessionNum', 'accession-num'],
|
|
232
|
+
['label', 'label'],
|
|
233
|
+
['language', 'language'],
|
|
234
|
+
['notes', 'notes'],
|
|
235
|
+
['number', 'number'],
|
|
236
|
+
['pages', 'pages'],
|
|
237
|
+
['researchNotes', 'research-notes'],
|
|
238
|
+
['section', 'section'],
|
|
239
|
+
['volume', 'volume'],
|
|
240
|
+
['workType', 'work-type'],
|
|
241
|
+
['custom1', 'custom1'],
|
|
242
|
+
['custom2', 'custom2'],
|
|
243
|
+
['custom3', 'custom3'],
|
|
244
|
+
['custom4', 'custom4'],
|
|
245
|
+
['custom5', 'custom5'],
|
|
246
|
+
['custom6', 'custom6'],
|
|
247
|
+
['custom7', 'custom7'],
|
|
248
|
+
]
|
|
249
|
+
.filter(([rlKey]) => ref[rlKey]) // Remove empty fields
|
|
250
|
+
.map(([rlKey, rawKey]) =>
|
|
251
|
+
`<${rawKey}><style face="normal" font="default" size="100%">${xmlEscape(ref[rlKey])}</style></${rawKey}>`
|
|
252
|
+
)
|
|
253
|
+
.join('')
|
|
254
|
+
|
|
255
|
+
// Dates
|
|
256
|
+
+ (
|
|
257
|
+
ref.date && ref.year && ref.date instanceof Date ?
|
|
258
|
+
`<dates><year><style face="normal" font="default" size="100%">${xmlEscape(ref.year)}</style></year>`
|
|
259
|
+
+ `<pub-dates><date><style face="normal" font="default" size="100%">${settings.formatDate(ref.date)}</style></date></pub-dates></dates>`
|
|
260
|
+
: ref.date && ref.year ?
|
|
261
|
+
`<dates><year><style face="normal" font="default" size="100%">${xmlEscape(ref.year)}</style></year>`
|
|
262
|
+
+ `<pub-dates><date><style face="normal" font="default" size="100%">${ref.date}</style></date></pub-dates></dates>`
|
|
263
|
+
: ref.date ?
|
|
264
|
+
`<dates><pub-dates><date><style face="normal" font="default" size="100%">${xmlEscape(ref.date)}</style></date></pub-dates></dates>`
|
|
265
|
+
: ref.year ?
|
|
266
|
+
`<dates><year><style face="normal" font="default" size="100%">${xmlEscape(ref.year)}</style></year></dates>`
|
|
267
|
+
: ''
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
// Urls
|
|
271
|
+
+ (ref.urls ?
|
|
272
|
+
'<urls><related-urls>'
|
|
273
|
+
+ [].concat(ref.urls || [])
|
|
274
|
+
.map(url => `<url><style face="normal" font="default" size="100%">${xmlEscape(url)}</style></url>`)
|
|
275
|
+
.join('')
|
|
276
|
+
+ '</related-urls></urls>'
|
|
277
|
+
: '')
|
|
278
|
+
|
|
279
|
+
// Keywords
|
|
280
|
+
+ (ref.keywords ?
|
|
281
|
+
'<keywords>'
|
|
282
|
+
+ [].concat(ref.keywords || [])
|
|
283
|
+
.map(keyword => `<keyword><style face="normal" font="default" size="100%">${xmlEscape(keyword)}</style></keyword>`)
|
|
284
|
+
.join('')
|
|
285
|
+
+ '</keywords>'
|
|
286
|
+
: '')
|
|
287
|
+
|
|
288
|
+
+ '</record>'
|
|
289
|
+
);
|
|
290
|
+
return Promise.resolve();
|
|
291
|
+
},
|
|
292
|
+
end: ()=> {
|
|
293
|
+
stream.write('</records></xml>');
|
|
294
|
+
return new Promise((resolve, reject) =>
|
|
295
|
+
stream.end(err => err ? reject(err) : resolve())
|
|
296
|
+
);
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Utility function to take the raw XML output object and translate it into a Reflib object
|
|
304
|
+
* @param {Object} xRef Raw XML object to process
|
|
305
|
+
* @returns {Object} The translated Reflib object output
|
|
306
|
+
*/
|
|
307
|
+
export function translateRawToRef(xRef) {
|
|
308
|
+
let recOut = {
|
|
309
|
+
...Object.fromEntries(
|
|
310
|
+
translations.fields.collection
|
|
311
|
+
.filter(field => xRef[field.raw]) // Only include fields we have a value for
|
|
312
|
+
.map(field => [ // Translate Raw -> Reflib spec
|
|
313
|
+
field.rl,
|
|
314
|
+
Array.isArray(xRef[field.raw]) ? xRef[field.raw].map(xmlUnescape)
|
|
315
|
+
: xmlUnescape(xRef[field.raw])
|
|
316
|
+
])
|
|
317
|
+
),
|
|
318
|
+
type: translations.types.rawMap.get(+xRef.refType || 17)?.rl,
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
return recOut;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Default string -> XML encoder
|
|
327
|
+
* @param {string} str The input string to encode
|
|
328
|
+
* @returns {string} The XML "safe" string
|
|
329
|
+
*/
|
|
330
|
+
export function xmlEscape(str) {
|
|
331
|
+
return ('' + str)
|
|
332
|
+
.replace(/&/g, '&')
|
|
333
|
+
.replace(/\r/g, ' ')
|
|
334
|
+
.replace(/</g, '<')
|
|
335
|
+
.replace(/>/g, '>')
|
|
336
|
+
.replace(/"/g, '"')
|
|
337
|
+
.replace(/'/g, ''');
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Default XML -> string decodeer
|
|
343
|
+
* @param {string} str The input string to decode
|
|
344
|
+
* @returns {string} The actual string
|
|
345
|
+
*/
|
|
346
|
+
export function xmlUnescape(str) {
|
|
347
|
+
return ('' + str)
|
|
348
|
+
.replace(/&/g, '&')
|
|
349
|
+
.replace(/&#(xD|13);/g, '\r')
|
|
350
|
+
.replace(/</g, '<')
|
|
351
|
+
.replace(/>/g, '>')
|
|
352
|
+
.replace(/"/g, '"')
|
|
353
|
+
.replace(/'/g, "'")
|
|
354
|
+
.replace(/\s+$/gm, '') // Trim line-end whitespace
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Lookup tables for this module
|
|
360
|
+
* @type {Object}
|
|
361
|
+
* @property {array<Object>} fields Field translations between Reflib (`rl`) and the raw format (`raw`)
|
|
362
|
+
* @property {array<Object>} types Field translations between Reflib (`rl`) and the raw format types as raw text (`rawText`) and numeric ID (`rawId`)
|
|
363
|
+
*/
|
|
364
|
+
export let translations = {
|
|
365
|
+
// Field translations {{{
|
|
366
|
+
fields: {
|
|
367
|
+
collection: [
|
|
368
|
+
// Field translations in priority order (EndNote first then Zotero)
|
|
369
|
+
{rl: 'recNumber', raw: 'recNumber'},
|
|
370
|
+
{rl: 'title', raw: 'title'},
|
|
371
|
+
{rl: 'journal', raw: 'secondaryTitle'},
|
|
372
|
+
{rl: 'address', raw: 'authAddress'},
|
|
373
|
+
{rl: 'researchNotes', raw: 'researchNotes'},
|
|
374
|
+
{rl: 'type', raw: 'FIXME'},
|
|
375
|
+
{rl: 'authors', raw: 'authors'},
|
|
376
|
+
{rl: 'pages', raw: 'pages'},
|
|
377
|
+
{rl: 'volume', raw: 'volume'},
|
|
378
|
+
{rl: 'number', raw: 'number'},
|
|
379
|
+
{rl: 'isbn', raw: 'isbn'},
|
|
380
|
+
{rl: 'accessionNum', raw: 'accessionNum'},
|
|
381
|
+
{rl: 'abstract', raw: 'abstract'},
|
|
382
|
+
{rl: 'label', raw: 'label'},
|
|
383
|
+
{rl: 'caption', raw: 'caption'},
|
|
384
|
+
{rl: 'notes', raw: 'notes'},
|
|
385
|
+
{rl: 'custom1', raw: 'custom1'},
|
|
386
|
+
{rl: 'custom2', raw: 'custom2'},
|
|
387
|
+
{rl: 'custom3', raw: 'custom3'},
|
|
388
|
+
{rl: 'custom4', raw: 'custom4'},
|
|
389
|
+
{rl: 'custom5', raw: 'custom5'},
|
|
390
|
+
{rl: 'custom6', raw: 'custom6'},
|
|
391
|
+
{rl: 'custom7', raw: 'custom7'},
|
|
392
|
+
{rl: 'doi', raw: 'electronicResourceNum'},
|
|
393
|
+
{rl: 'year', raw: 'year'},
|
|
394
|
+
{rl: 'date', raw: 'date'},
|
|
395
|
+
{rl: 'keywords', raw: 'keywords'},
|
|
396
|
+
{rl: 'urls', raw: 'urls'},
|
|
397
|
+
],
|
|
398
|
+
rawMap: new Map(), // Calculated later for quicker lookup
|
|
399
|
+
},
|
|
400
|
+
// }}}
|
|
401
|
+
// Ref type translations {{{
|
|
402
|
+
types: {
|
|
403
|
+
collection: [
|
|
404
|
+
{rl: 'aggregatedDatabase', rawText: 'Aggregated Database', rawId: 55},
|
|
405
|
+
{rl: 'ancientText', rawText: 'Ancient Text', rawId: 51},
|
|
406
|
+
{rl: 'artwork', rawText: 'Artwork', rawId: 2},
|
|
407
|
+
{rl: 'audioVisualMaterial', rawText: 'Audiovisual Material', rawId: 3},
|
|
408
|
+
{rl: 'bill', rawText: 'Bill', rawId: 4},
|
|
409
|
+
{rl: 'blog', rawText: 'Blog', rawId: 56},
|
|
410
|
+
{rl: 'book', rawText: 'Book', rawId: 6},
|
|
411
|
+
{rl: 'bookSection', rawText: 'Book Section', rawId: 5},
|
|
412
|
+
{rl: 'case', rawText: 'Case', rawId: 7},
|
|
413
|
+
{rl: 'catalog', rawText: 'Catalog', rawId: 8},
|
|
414
|
+
{rl: 'chartOrTable', rawText: 'Chart or Table', rawId: 38},
|
|
415
|
+
{rl: 'classicalWork', rawText: 'Classical Work', rawId: 49},
|
|
416
|
+
{rl: 'computerProgram', rawText: 'Computer Program', rawId: 9},
|
|
417
|
+
{rl: 'conferencePaper', rawText: 'Conference Paper', rawId: 47},
|
|
418
|
+
{rl: 'conferenceProceedings', rawText: 'Conference Proceedings', rawId: 10},
|
|
419
|
+
{rl: 'dataset', rawText: 'Dataset', rawId: 59},
|
|
420
|
+
{rl: 'dictionary', rawText: 'Dictionary', rawId: 52},
|
|
421
|
+
{rl: 'editedBook', rawText: 'Edited Book', rawId: 28},
|
|
422
|
+
{rl: 'electronicArticle', rawText: 'Electronic Article', rawId: 43},
|
|
423
|
+
{rl: 'electronicBook', rawText: 'Electronic Book', rawId: 44},
|
|
424
|
+
{rl: 'electronicBookSection', rawText: 'Electronic Book Section', rawId: 60},
|
|
425
|
+
{rl: 'encyclopedia', rawText: 'Encyclopedia', rawId: 53},
|
|
426
|
+
{rl: 'equation', rawText: 'Equation', rawId: 39},
|
|
427
|
+
{rl: 'figure', rawText: 'Figure', rawId: 37},
|
|
428
|
+
{rl: 'filmOrBroadcast', rawText: 'Film or Broadcast', rawId: 21},
|
|
429
|
+
{rl: 'generic', rawText: 'Generic', rawId: 13},
|
|
430
|
+
{rl: 'governmentDocument', rawText: 'Government Document', rawId: 46},
|
|
431
|
+
{rl: 'grant', rawText: 'Grant', rawId: 54},
|
|
432
|
+
{rl: 'hearing', rawText: 'Hearing', rawId: 14},
|
|
433
|
+
{rl: 'journalArticle', rawText: 'Journal Article', rawId: 17},
|
|
434
|
+
{rl: 'legalRuleOrRegulation', rawText: 'Legal Rule or Regulation', rawId: 50},
|
|
435
|
+
{rl: 'magazineArticle', rawText: 'Magazine Article', rawId: 19},
|
|
436
|
+
{rl: 'manuscript', rawText: 'Manuscript', rawId: 36},
|
|
437
|
+
{rl: 'map', rawText: 'Map', rawId: 20},
|
|
438
|
+
{rl: 'music', rawText: 'Music', rawId: 61},
|
|
439
|
+
{rl: 'newspaperArticle', rawText: 'Newspaper Article', rawId: 23},
|
|
440
|
+
{rl: 'onlineDatabase', rawText: 'Online Database', rawId: 45},
|
|
441
|
+
{rl: 'onlineMultimedia', rawText: 'Online Multimedia', rawId: 48},
|
|
442
|
+
{rl: 'pamphlet', rawText: 'Pamphlet', rawId: 24},
|
|
443
|
+
{rl: 'patent', rawText: 'Patent', rawId: 25},
|
|
444
|
+
{rl: 'personalCommunication', rawText: 'Personal Communication', rawId: 26},
|
|
445
|
+
{rl: 'report', rawText: 'Report', rawId: 27},
|
|
446
|
+
{rl: 'serial', rawText: 'Serial', rawId: 57},
|
|
447
|
+
{rl: 'standard', rawText: 'Standard', rawId: 58},
|
|
448
|
+
{rl: 'statute', rawText: 'Statute', rawId: 31},
|
|
449
|
+
{rl: 'thesis', rawText: 'Thesis', rawId: 32},
|
|
450
|
+
{rl: 'unpublished', rawText: 'Unpublished Work', rawId: 34},
|
|
451
|
+
{rl: 'web', rawText: 'Web Page', rawId: 12},
|
|
452
|
+
],
|
|
453
|
+
rlMap: new Map(), // Calculated later for quicker lookup
|
|
454
|
+
rawMap: new Map(), // Calculated later for quicker lookup
|
|
455
|
+
},
|
|
456
|
+
// }}}
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* @see modules/interface.js
|
|
462
|
+
*/
|
|
463
|
+
export function setup() {
|
|
464
|
+
// Create lookup object of translations.field translations
|
|
465
|
+
translations.fields.collection.forEach(c => {
|
|
466
|
+
translations.fields.rawMap.set(c.raw, c);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
// Create lookup object of translations.types with key as .rl / val as the full object
|
|
470
|
+
translations.types.collection.forEach(c => {
|
|
471
|
+
translations.types.rlMap.set(c.rl, c);
|
|
472
|
+
translations.types.rawMap.set(c.rawId, c);
|
|
473
|
+
});
|
|
474
|
+
}
|