@iebh/reflib 2.2.3 → 2.3.1

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.
@@ -1,13 +1,22 @@
1
1
  import camelCase from '../shared/camelCase.js';
2
2
  import Emitter from '../shared/emitter.js';
3
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
- // FIXME: CF: Browsers freak out without pollyfills if this is imported
7
- import {WritableStream as XMLParser} from 'htmlparser2/lib/WritableStream';
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/interface.js
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 incomming text blocks to the previous block
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 bizare way so we have to pull apart the <style> bits when parsing
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 (typeof stream.pipe === 'function') {
171
+ if (stream.isBrowser === true) {
110
172
  // We are on the node.js client
111
- let parser = new XMLParser(parserOptions);
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
- // TODO: CF: We may want to consider moving to a DIY parser for speed and memory efficiency
118
- if (typeof stream.getReader === 'function') {
119
- // We are on the browser
120
- var reader = stream.getReader();
121
- var parser = new htmlparser2.Parser(parserOptions);
122
- parseXMLOnBrowser();
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
- function parseXMLOnBrowser() {
127
- reader.read().then(({done, value}) => {
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 lirary was exported from, must end with backslashes
150
- * @param {string} [options.fileName="EndNote.enl"] "Fake" internal source file name the citation lirary was exported from
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:', Legal Rule or Regulation', rawId: 50},
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
- stream.pipe(
15
- JSONStream.parse('*')
16
- .on('data', ref => emitter.emit('ref', {
17
- recNumber: recNumber++,
18
- ...ref,
19
- }))
20
- .on('end', ()=> emitter.emit('end'))
21
- .on('error', emitter.emit.bind('error'))
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.2.3",
3
+ "version": "2.3.1",
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
- "Matt Carter <m@ttcarter.com> (https://github.com/hash-bang)",
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,11 @@
38
38
  },
39
39
  "./*": "./lib/*.js"
40
40
  },
41
+ "browser": {
42
+ "htmlparser2/lib/WritableStream": false,
43
+ "htmlparser2/lib/esm/WritableStream": false,
44
+ "JSONStream": false
45
+ },
41
46
  "devDependencies": {
42
47
  "@momsfriendlydevco/eslint-config": "^1.0.9",
43
48
  "chai": "^4.3.10",
@@ -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'),