@iebh/reflib 2.0.2 → 2.0.5
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/browser.js +1 -0
- package/lib/default.js +2 -1
- package/lib/formats.js +2 -2
- package/lib/uploadFile.js +3 -2
- package/modules/endnoteXml.js +103 -65
- package/package.json +1 -1
- package/shared/streamEmitter.js +5 -24
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Compatibility
|
|
|
18
18
|
| EndNote ENLX | `.enlx` | :x: | :x: |
|
|
19
19
|
| EndNote XML | `.xml` | :heavy_check_mark: | :heavy_check_mark: |
|
|
20
20
|
| JSON | `.json` | :heavy_check_mark: | :heavy_check_mark: |
|
|
21
|
-
| Medline | `.nbib` | :
|
|
21
|
+
| Medline | `.nbib` | :heavy_check_mark: | :heavy_check_mark: |
|
|
22
22
|
| RIS | `.ris` / `.txt` | :heavy_check_mark: | :heavy_check_mark: |
|
|
23
23
|
| Tab Separated Values | `.tsv` | :x: | :x: |
|
|
24
24
|
|
package/lib/browser.js
CHANGED
|
@@ -5,4 +5,5 @@ import {readStream} from './readStream.js';
|
|
|
5
5
|
import {uploadFile} from './uploadFile.js';
|
|
6
6
|
import {writeStream} from './writeStream.js';
|
|
7
7
|
|
|
8
|
+
export {identifyFormat, formats, getModule, readStream, uploadFile, writeStream};
|
|
8
9
|
export default {identifyFormat, formats, getModule, readStream, uploadFile, writeStream};
|
package/lib/default.js
CHANGED
|
@@ -6,4 +6,5 @@ import {readFile} from './readFile.js';
|
|
|
6
6
|
import {writeStream} from './writeStream.js';
|
|
7
7
|
import {writeFile} from './writeFile.js';
|
|
8
8
|
|
|
9
|
-
export
|
|
9
|
+
export {identifyFormat, formats, getModule, readStream, readFile, writeStream, writeFile};
|
|
10
|
+
export default {identifyFormat, formats, getModule, readStream, readFile, writeStream, writeFile};
|
package/lib/formats.js
CHANGED
package/lib/uploadFile.js
CHANGED
|
@@ -6,7 +6,7 @@ import StreamEmitter from '../shared/streamEmitter.js';
|
|
|
6
6
|
/**
|
|
7
7
|
* Prompt the user for a file then read it as a Reflib event emitter
|
|
8
8
|
* @param {Object} [options] Additional options when prompting the user
|
|
9
|
-
* @param {File} [options.
|
|
9
|
+
* @param {File} [options.file] The File object to process, omitting this will prompt the user to select a file
|
|
10
10
|
* @param {function} [options.onStart] Async function called as `(File)` when starting the read stage
|
|
11
11
|
* @param {function} [options.onProgress] Function called as `(position, totalSize)` when processing the file
|
|
12
12
|
* @param {function} [options.onEnd] Async function called as `()` when the read stage has completed
|
|
@@ -49,9 +49,10 @@ export function uploadFile(options) {
|
|
|
49
49
|
return Promise.resolve()
|
|
50
50
|
.then(()=> settings.onStart && settings.onStart(settings.file))
|
|
51
51
|
.then(()=> new Promise((resolve, reject) => {
|
|
52
|
+
|
|
52
53
|
let streamer = readStream(
|
|
53
54
|
identifiedType.id,
|
|
54
|
-
StreamEmitter(settings.file.stream()
|
|
55
|
+
StreamEmitter(settings.file.stream()),
|
|
55
56
|
{
|
|
56
57
|
...settings,
|
|
57
58
|
size: settings.file.size,
|
package/modules/endnoteXml.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import camelCase from '../shared/camelCase.js';
|
|
2
2
|
import Emitter from '../shared/emitter.js';
|
|
3
|
-
import {WritableStream as XMLParser} from 'htmlparser2/lib/WritableStream';
|
|
4
3
|
|
|
4
|
+
// TODO: CF: Don't need to import both, it depends if we are on browser or node
|
|
5
|
+
import * as htmlparser2 from "htmlparser2";
|
|
6
|
+
import {WritableStream as XMLParser} from 'htmlparser2/lib/WritableStream';
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* @see modules/interface.js
|
|
@@ -30,75 +32,111 @@ export function readStream(stream) {
|
|
|
30
32
|
*/
|
|
31
33
|
let textAppend = false;
|
|
32
34
|
|
|
35
|
+
/**
|
|
36
|
+
* The options/callbacks for the parser
|
|
37
|
+
* @type {Object}
|
|
38
|
+
*/
|
|
39
|
+
let parserOptions = {
|
|
40
|
+
xmlMode: true,
|
|
41
|
+
decodeEntities: false, // Handled below
|
|
42
|
+
onopentag(name, attrs) {
|
|
43
|
+
textAppend = false;
|
|
44
|
+
stack.push({
|
|
45
|
+
name: camelCase(name),
|
|
46
|
+
attrs,
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
onclosetag(name) {
|
|
50
|
+
if (name == 'record') {
|
|
51
|
+
if (ref.title) ref.title = ref.title // htmlparser2 handles the '<title>' tag in a really bizare way so we have to pull apart the <style> bits when parsing
|
|
52
|
+
.replace(/^.*<style.*>(.*)<\/style>.*$/m, '$1')
|
|
53
|
+
.replace(/^\s+/, '')
|
|
54
|
+
.replace(/\s+$/, '')
|
|
55
|
+
emitter.emit('ref', translateRawToRef(ref));
|
|
56
|
+
stack = []; // Trash entire stack when hitting end of <record/> node
|
|
57
|
+
ref = {}; // Reset the ref state
|
|
58
|
+
} else {
|
|
59
|
+
stack.pop();
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
ontext(text) {
|
|
63
|
+
let parentName = stack[stack.length - 1]?.name;
|
|
64
|
+
let gParentName = stack[stack.length - 2]?.name;
|
|
65
|
+
if (parentName == 'title') {
|
|
66
|
+
if (textAppend) {
|
|
67
|
+
ref.title += text;
|
|
68
|
+
} else {
|
|
69
|
+
ref.title = text;
|
|
70
|
+
}
|
|
71
|
+
} else if (parentName == 'style' && gParentName == 'author') {
|
|
72
|
+
if (!ref.authors) ref.authors = [];
|
|
73
|
+
if (textAppend) {
|
|
74
|
+
ref.authors[ref.authors.length - 1] += xmlUnescape(text);
|
|
75
|
+
} else {
|
|
76
|
+
ref.authors.push(xmlUnescape(text));
|
|
77
|
+
}
|
|
78
|
+
} else if (parentName == 'style' && gParentName == 'keyword') {
|
|
79
|
+
if (!ref.keywords) ref.keywords = [];
|
|
80
|
+
if (textAppend) {
|
|
81
|
+
ref.keywords[ref.keywords.length - 1] += xmlUnescape(text);
|
|
82
|
+
} else {
|
|
83
|
+
ref.keywords.push(xmlUnescape(text));
|
|
84
|
+
}
|
|
85
|
+
} else if (parentName == 'style') { // Text within <style/> tag
|
|
86
|
+
if (textAppend || ref[gParentName]) { // Text already exists? Append (handles node-expats silly multi-text per escape character "feature")
|
|
87
|
+
ref[gParentName] += xmlUnescape(text);
|
|
88
|
+
} else {
|
|
89
|
+
ref[gParentName] = xmlUnescape(text);
|
|
90
|
+
}
|
|
91
|
+
} else if (['recNumber', 'refType'].includes(parentName)) { // Simple setters like <rec-number/>
|
|
92
|
+
if (textAppend || ref[parentName]) {
|
|
93
|
+
ref[parentName] += xmlUnescape(text);
|
|
94
|
+
} else {
|
|
95
|
+
ref[parentName] = xmlUnescape(text);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
textAppend = true; // Always set the next call to the text emitter handler as an append operation
|
|
99
|
+
},
|
|
100
|
+
onend() {
|
|
101
|
+
emitter.emit('end');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
33
104
|
|
|
34
105
|
// Queue up the parser in the next tick (so we can return the emitter first)
|
|
35
|
-
setTimeout(()=> {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
106
|
+
setTimeout(() => {
|
|
107
|
+
|
|
108
|
+
if (typeof stream.pipe === 'function') {
|
|
109
|
+
// We are on the node.js client
|
|
110
|
+
let parser = new XMLParser(parserOptions);
|
|
111
|
+
stream.pipe(parser)
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// TODO: CF: We may want to consider moving to a DIY parser for speed and memory efficiency
|
|
116
|
+
if (typeof stream.getReader === 'function') {
|
|
117
|
+
// We are on the browser
|
|
118
|
+
var reader = stream.getReader();
|
|
119
|
+
var parser = new htmlparser2.Parser(parserOptions);
|
|
120
|
+
parseXMLOnBrowser();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function parseXMLOnBrowser() {
|
|
125
|
+
reader.read().then(({done, value}) => {
|
|
126
|
+
if (done) {
|
|
127
|
+
parser.end();
|
|
55
128
|
} else {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
let parentName = stack[stack.length - 1]?.name;
|
|
61
|
-
let gParentName = stack[stack.length - 2]?.name;
|
|
62
|
-
if (parentName == 'title') {
|
|
63
|
-
if (textAppend) {
|
|
64
|
-
ref.title += text;
|
|
65
|
-
} else {
|
|
66
|
-
ref.title = text;
|
|
67
|
-
}
|
|
68
|
-
} else if (parentName == 'style' && gParentName == 'author') {
|
|
69
|
-
if (!ref.authors) ref.authors = [];
|
|
70
|
-
if (textAppend) {
|
|
71
|
-
ref.authors[ref.authors.length - 1] += xmlUnescape(text);
|
|
72
|
-
} else {
|
|
73
|
-
ref.authors.push(xmlUnescape(text));
|
|
74
|
-
}
|
|
75
|
-
} else if (parentName == 'style' && gParentName == 'keyword') {
|
|
76
|
-
if (!ref.keywords) ref.keywords = [];
|
|
77
|
-
if (textAppend) {
|
|
78
|
-
ref.keywords[ref.keywords.length - 1] += xmlUnescape(text);
|
|
79
|
-
} else {
|
|
80
|
-
ref.keywords.push(xmlUnescape(text));
|
|
81
|
-
}
|
|
82
|
-
} else if (parentName == 'style') { // Text within <style/> tag
|
|
83
|
-
if (textAppend || ref[gParentName]) { // Text already exists? Append (handles node-expats silly multi-text per escape character "feature")
|
|
84
|
-
ref[gParentName] += xmlUnescape(text);
|
|
85
|
-
} else {
|
|
86
|
-
ref[gParentName] = xmlUnescape(text);
|
|
87
|
-
}
|
|
88
|
-
} else if (['recNumber', 'refType'].includes(parentName)) { // Simple setters like <rec-number/>
|
|
89
|
-
if (textAppend || ref[parentName]) {
|
|
90
|
-
ref[parentName] += xmlUnescape(text);
|
|
91
|
-
} else {
|
|
92
|
-
ref[parentName] = xmlUnescape(text);
|
|
93
|
-
}
|
|
129
|
+
var text = new TextDecoder().decode(value);
|
|
130
|
+
parser.write(text);
|
|
131
|
+
text = null; // Free up memory
|
|
132
|
+
parseXMLOnBrowser();
|
|
94
133
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
})
|
|
134
|
+
})
|
|
135
|
+
}
|
|
98
136
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
})
|
|
137
|
+
console.error("This line should not be hit!");
|
|
138
|
+
|
|
139
|
+
})
|
|
102
140
|
|
|
103
141
|
return emitter;
|
|
104
142
|
}
|
package/package.json
CHANGED
package/shared/streamEmitter.js
CHANGED
|
@@ -3,7 +3,7 @@ import Emitter from '../shared/emitter.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* Wrapper for streams which transforms a given input into an emitter pattern
|
|
5
5
|
* This is designed to let regular `node:stream.Readable` objects pass through without alteration but browser based stream objects get wrapped
|
|
6
|
-
* @param {stream.Readable|
|
|
6
|
+
* @param {stream.Readable|ReadableStream} inStream The input stream to wrap
|
|
7
7
|
* @returns {stream.Readable|Emitter} Either the unedited node compatible stream or an event emitter with the same behaviour
|
|
8
8
|
*
|
|
9
9
|
* @emits data Emitted as `(chunk)` on each data chunk
|
|
@@ -11,27 +11,8 @@ import Emitter from '../shared/emitter.js';
|
|
|
11
11
|
* @emits error Emitted as `(Error)` on any read error
|
|
12
12
|
*/
|
|
13
13
|
export default function streamEmitter(inStream) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
let readCycle = ()=> {
|
|
19
|
-
inStream
|
|
20
|
-
.read()
|
|
21
|
-
.then(({value, done}) => {
|
|
22
|
-
if (done) {
|
|
23
|
-
emitter.emit('end');
|
|
24
|
-
} else {
|
|
25
|
-
emitter.emit('data', utf8Decoder.decode(value, {stream: true}));
|
|
26
|
-
setTimeout(readCycle); // Loop into next read if not already finished
|
|
27
|
-
}
|
|
28
|
-
})
|
|
29
|
-
.catch(e => emitter.emit('error', e))
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
// Keep downstream libraries happy by stubbing stream-like functions
|
|
33
|
-
emitter.setEncoding = ()=> {};
|
|
34
|
-
|
|
35
|
-
setTimeout(readCycle); // Queue up initial read cycle on next tick
|
|
36
|
-
return emitter;
|
|
14
|
+
// FIXME: Need to examine inStream and multiplex
|
|
15
|
+
// inStream.pipeTo - a browser stream - passthru
|
|
16
|
+
// !inStream.pipeTo - probably Node stream - need to glue pipeTo as a promiseable
|
|
17
|
+
return inStream;
|
|
37
18
|
}
|