@iebh/reflib 2.2.2 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/lib/formats.js +1 -1
- package/modules/endnoteXml.js +83 -29
- package/modules/json.js +72 -12
- package/package.json +7 -3
- package/shared/streamEmitter.js +2 -1
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Compatibility
|
|
|
18
18
|
| EndNote XML | `.xml` | :heavy_check_mark: | :heavy_check_mark: |
|
|
19
19
|
| JSON | `.json` | :heavy_check_mark: | :heavy_check_mark: |
|
|
20
20
|
| Medline | `.nbib` | :heavy_check_mark: | :heavy_check_mark: |
|
|
21
|
-
| RIS | `.ris`
|
|
21
|
+
| RIS | `.ris` | :heavy_check_mark: | :heavy_check_mark: |
|
|
22
22
|
| Tab Separated Values | `.tsv` | :x: | :x: |
|
|
23
23
|
|
|
24
24
|
|
package/lib/formats.js
CHANGED
package/modules/endnoteXml.js
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import camelCase from '../shared/camelCase.js';
|
|
2
2
|
import Emitter from '../shared/emitter.js';
|
|
3
3
|
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
// Only import WritableStream on node
|
|
5
|
+
// This is needed even with the "browser" field of package.json
|
|
6
|
+
let XMLParser;
|
|
7
|
+
if (typeof window === 'undefined') {
|
|
8
|
+
// We are in node env
|
|
9
|
+
import('htmlparser2/lib/WritableStream').then(module => {
|
|
10
|
+
XMLParser = module.WritableStream;
|
|
11
|
+
});
|
|
12
|
+
} else {
|
|
13
|
+
// Handle or ignore in browser environment
|
|
14
|
+
console.log("WritableStream not loaded in browser.");
|
|
15
|
+
}
|
|
16
|
+
|
|
8
17
|
|
|
9
18
|
/**
|
|
10
|
-
* @see modules/
|
|
19
|
+
* @see modules/inhterface.js
|
|
11
20
|
*/
|
|
12
21
|
export function readStream(stream) {
|
|
13
22
|
let emitter = Emitter();
|
|
@@ -27,12 +36,65 @@ export function readStream(stream) {
|
|
|
27
36
|
|
|
28
37
|
|
|
29
38
|
/**
|
|
30
|
-
* Whether to append
|
|
39
|
+
* Whether to append incoming text blocks to the previous block
|
|
31
40
|
* 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
|
|
32
41
|
* @type {boolean}
|
|
33
42
|
*/
|
|
34
43
|
let textAppend = false;
|
|
35
44
|
|
|
45
|
+
class XMLParserBrowser {
|
|
46
|
+
constructor(passedParserOptions) {
|
|
47
|
+
// Stores XML in text form once read
|
|
48
|
+
this.text = "";
|
|
49
|
+
this.emitter = emitter;
|
|
50
|
+
// Add event listeners to mimic htmlparser2 behavior in the browser
|
|
51
|
+
this.emitter.on('opentag', passedParserOptions.onopentag);
|
|
52
|
+
this.emitter.on('closetag', passedParserOptions.onclosetag);
|
|
53
|
+
this.emitter.on('text', passedParserOptions.ontext);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
write(data) {
|
|
57
|
+
// CF: TODO: Parse data as it comes in chunks for better memory efficiency
|
|
58
|
+
this.text += data;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
end() {
|
|
62
|
+
this.parseXML(this.text);
|
|
63
|
+
// Free memory
|
|
64
|
+
this.text = ''
|
|
65
|
+
this.emitter.emit('end');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
parseXML(xmlString) {
|
|
69
|
+
let parser = new DOMParser();
|
|
70
|
+
let doc = parser.parseFromString(xmlString, 'application/xml');
|
|
71
|
+
this.traverseNode(doc.documentElement);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
traverseNode(node) {
|
|
75
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
76
|
+
let name = camelCase(node.nodeName);
|
|
77
|
+
let attrs = Array.from(node.attributes).reduce((acc, attr) => {
|
|
78
|
+
acc[attr.name] = attr.value;
|
|
79
|
+
return acc;
|
|
80
|
+
}, {});
|
|
81
|
+
|
|
82
|
+
this.emitter.emit('opentag', name, attrs);
|
|
83
|
+
|
|
84
|
+
for (let child of node.childNodes) {
|
|
85
|
+
this.traverseNode(child);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this.emitter.emit('closetag', name);
|
|
89
|
+
} else if (node.nodeType === Node.TEXT_NODE) {
|
|
90
|
+
let text = node.nodeValue.trim();
|
|
91
|
+
if (text) {
|
|
92
|
+
this.emitter.emit('text', text);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
36
98
|
/**
|
|
37
99
|
* The options/callbacks for the parser
|
|
38
100
|
* @type {Object}
|
|
@@ -49,7 +111,7 @@ export function readStream(stream) {
|
|
|
49
111
|
},
|
|
50
112
|
onclosetag(name) {
|
|
51
113
|
if (name == 'record') {
|
|
52
|
-
if (ref.title) ref.title = ref.title // htmlparser2 handles the '<title>' tag in a really
|
|
114
|
+
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
115
|
.replace(/^.*<style.*>(.*)<\/style>.*$/m, '$1')
|
|
54
116
|
.replace(/^\s+/, '')
|
|
55
117
|
.replace(/\s+$/, '')
|
|
@@ -106,34 +168,26 @@ export function readStream(stream) {
|
|
|
106
168
|
// Queue up the parser in the next tick (so we can return the emitter first)
|
|
107
169
|
setTimeout(() => {
|
|
108
170
|
|
|
109
|
-
if (
|
|
171
|
+
if (stream.isBrowser === true) {
|
|
110
172
|
// We are on the node.js client
|
|
111
|
-
|
|
173
|
+
console.log('Loading EndNote library as node.js')
|
|
174
|
+
let parser = new XMLParserBrowser(parserOptions);
|
|
112
175
|
stream.on('data', ()=> emitter.emit('progress', stream.bytesRead))
|
|
113
176
|
stream.pipe(parser)
|
|
114
177
|
return;
|
|
115
178
|
}
|
|
116
179
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
180
|
+
else if (typeof stream.pipe === 'function') {
|
|
181
|
+
// We are on the node.js client
|
|
182
|
+
console.log('Loading EndNote library as node.js')
|
|
183
|
+
let parser = new XMLParser(parserOptions);
|
|
184
|
+
stream.on('data', ()=> emitter.emit('progress', stream.bytesRead))
|
|
185
|
+
stream.pipe(parser)
|
|
123
186
|
return;
|
|
124
187
|
}
|
|
125
188
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (done) {
|
|
129
|
-
parser.end();
|
|
130
|
-
} else {
|
|
131
|
-
var text = new TextDecoder().decode(value);
|
|
132
|
-
parser.write(text);
|
|
133
|
-
text = null; // Free up memory
|
|
134
|
-
parseXMLOnBrowser();
|
|
135
|
-
}
|
|
136
|
-
})
|
|
189
|
+
else {
|
|
190
|
+
console.error('Error determining if on browser or node')
|
|
137
191
|
}
|
|
138
192
|
|
|
139
193
|
})
|
|
@@ -146,8 +200,8 @@ export function readStream(stream) {
|
|
|
146
200
|
* @see modules/interface.js
|
|
147
201
|
* @param {Object} [options] Additional options to use when parsing
|
|
148
202
|
* @param {string} [options.defaultType='journalArticle'] Default citation type to assume when no other type is specified
|
|
149
|
-
* @param {string} [options.filePath="c:\\"] "Fake" internal source file path the citation
|
|
150
|
-
* @param {string} [options.fileName="EndNote.enl"] "Fake" internal source file name the citation
|
|
203
|
+
* @param {string} [options.filePath="c:\\"] "Fake" internal source file path the citation library was exported from, must end with backslashes
|
|
204
|
+
* @param {string} [options.fileName="EndNote.enl"] "Fake" internal source file name the citation library was exported from
|
|
151
205
|
* @param {function} [options.formatDate] Date formatter to translate between a JS Date object and the EndNote YYYY-MM-DD format
|
|
152
206
|
*/
|
|
153
207
|
export function writeStream(stream, options) {
|
|
@@ -409,7 +463,7 @@ export let translations = {
|
|
|
409
463
|
{rl: 'grant', rawText: 'Grant', rawId: 54},
|
|
410
464
|
{rl: 'hearing', rawText: 'Hearing', rawId: 14},
|
|
411
465
|
{rl: 'journalArticle', rawText: 'Journal Article', rawId: 17},
|
|
412
|
-
{rl: 'legalRuleOrRegulation', rawText:'
|
|
466
|
+
{rl: 'legalRuleOrRegulation', rawText: 'Legal Rule or Regulation', rawId: 50},
|
|
413
467
|
{rl: 'magazineArticle', rawText: 'Magazine Article', rawId: 19},
|
|
414
468
|
{rl: 'manuscript', rawText: 'Manuscript', rawId: 36},
|
|
415
469
|
{rl: 'map', rawText: 'Map', rawId: 20},
|
package/modules/json.js
CHANGED
|
@@ -1,6 +1,45 @@
|
|
|
1
1
|
import Emitter from '../shared/emitter.js';
|
|
2
2
|
import JSONStream from 'JSONStream';
|
|
3
3
|
|
|
4
|
+
class BrowserJSONStream {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.text = '';
|
|
7
|
+
this.emitter = Emitter();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
write(data) {
|
|
11
|
+
// CF: TODO: Parse data as it comes in chunks for better memory efficiency
|
|
12
|
+
this.text += data
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
end() {
|
|
16
|
+
try {
|
|
17
|
+
// Parse this.text as JSON
|
|
18
|
+
const jsonArray = JSON.parse(this.text);
|
|
19
|
+
// Free memory
|
|
20
|
+
this.text = ''
|
|
21
|
+
|
|
22
|
+
// For each entry in the json array (as ref):
|
|
23
|
+
jsonArray.forEach(ref => {
|
|
24
|
+
this.emitter.emit('ref', {
|
|
25
|
+
recNumber: this.recNumber++,
|
|
26
|
+
...ref,
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Finished
|
|
31
|
+
this.emitter.emit('end');
|
|
32
|
+
} catch (e) {
|
|
33
|
+
console.error('Error parsing final JSON:', e);
|
|
34
|
+
this.emitter.emit('error', e);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
on(event, listener) {
|
|
39
|
+
this.emitter.on(event, listener);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
4
43
|
/**
|
|
5
44
|
* @see modules/interface.js
|
|
6
45
|
*/
|
|
@@ -11,15 +50,36 @@ export function readStream(stream) {
|
|
|
11
50
|
// Queue up the parser in the next tick (so we can return the emitter first)
|
|
12
51
|
setTimeout(()=> {
|
|
13
52
|
stream.on('data', ()=> emitter.emit('progress', stream.bytesRead));
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
53
|
+
|
|
54
|
+
if (stream.isBrowser === true) {
|
|
55
|
+
// On browser
|
|
56
|
+
console.log('Parsing JSON natively in browser');
|
|
57
|
+
const browserJSONStream = new BrowserJSONStream();
|
|
58
|
+
browserJSONStream.on('ref', (data) => {
|
|
59
|
+
emitter.emit('ref', data);
|
|
60
|
+
});
|
|
61
|
+
browserJSONStream.on('end', () => emitter.emit('end'));
|
|
62
|
+
browserJSONStream.on('error', (error) => emitter.emit('error', error));
|
|
63
|
+
stream.pipe(browserJSONStream);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
else if (typeof stream.pipe === 'function') {
|
|
67
|
+
// On node.js
|
|
68
|
+
console.log('Parsing JSON with node.js library')
|
|
69
|
+
const nodeJSONStream = JSONStream.parse('*')
|
|
70
|
+
nodeJSONStream
|
|
71
|
+
.on('data', ref => emitter.emit('ref', {
|
|
72
|
+
recNumber: recNumber++,
|
|
73
|
+
...ref,
|
|
74
|
+
}))
|
|
75
|
+
.on('end', ()=> emitter.emit('end'))
|
|
76
|
+
.on('error', emitter.emit.bind('error'));
|
|
77
|
+
stream.pipe(nodeJSONStream)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
else {
|
|
81
|
+
console.error('Error determining if on browser or node')
|
|
82
|
+
}
|
|
23
83
|
});
|
|
24
84
|
|
|
25
85
|
return emitter;
|
|
@@ -54,7 +114,7 @@ export function writeStream(stream, options) {
|
|
|
54
114
|
stream.write(']');
|
|
55
115
|
return new Promise((resolve, reject) =>
|
|
56
116
|
stream.end(err => err ? reject(err) : resolve())
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
117
|
+
);
|
|
118
|
+
},
|
|
119
|
+
};
|
|
60
120
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iebh/reflib",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Reference / Citation reference library utilities",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"lint": "eslint lib modules shared test",
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"citations",
|
|
18
18
|
"library"
|
|
19
19
|
],
|
|
20
|
-
"author":
|
|
21
|
-
|
|
20
|
+
"author": "Matt Carter <m@ttcarter.com> (https://github.com/hash-bang)",
|
|
21
|
+
"contributors": [
|
|
22
22
|
"Connor Forbes <cforbes.software@gmail.com> (https://github.com/connorf25)"
|
|
23
23
|
],
|
|
24
24
|
"license": "MIT",
|
|
@@ -38,6 +38,10 @@
|
|
|
38
38
|
},
|
|
39
39
|
"./*": "./lib/*.js"
|
|
40
40
|
},
|
|
41
|
+
"browser": {
|
|
42
|
+
"htmlparser2/lib/WritableStream": false,
|
|
43
|
+
"JSONStream": false
|
|
44
|
+
},
|
|
41
45
|
"devDependencies": {
|
|
42
46
|
"@momsfriendlydevco/eslint-config": "^1.0.9",
|
|
43
47
|
"chai": "^4.3.10",
|
package/shared/streamEmitter.js
CHANGED
|
@@ -10,7 +10,7 @@ import Emitter from '../shared/emitter.js';
|
|
|
10
10
|
* @emits end Emitted as `()` when the input stream has closed
|
|
11
11
|
* @emits error Emitted as `(Error)` on any read error
|
|
12
12
|
*/
|
|
13
|
-
export default function streamEmitter(inStream) {
|
|
13
|
+
export default function streamEmitter (inStream) {
|
|
14
14
|
if (inStream.getReader) { // Assume browser compatible ReadableStream
|
|
15
15
|
/**
|
|
16
16
|
* MC's tiny ReadableStream -> stream.Readable / Emitter pattern
|
|
@@ -21,6 +21,7 @@ export default function streamEmitter(inStream) {
|
|
|
21
21
|
*/
|
|
22
22
|
let reader = new Emitter();
|
|
23
23
|
Object.assign(reader, {
|
|
24
|
+
isBrowser: true, // Tells us we are in browser env
|
|
24
25
|
bytesRead: 0,
|
|
25
26
|
reader: inStream.getReader(),
|
|
26
27
|
textDecoder: new TextDecoder('utf-8'),
|