Skip to content

Commit 118474b

Browse files
committed
feat: feedme now returns a proper writable stream
BREAKING CHANGES: since it's now a writable stream and not a readable stream, it emits a `finish` event when it ends, instead of an `end` event
1 parent 4692819 commit 118474b

13 files changed

Lines changed: 265 additions & 255 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ const http = require('http');
5151
http.get('https://nodejs.org/en/feed/blog.xml', (res) => {
5252
let parser = new FeedMe(true);
5353
res.pipe(parser);
54-
parser.on('end', () => {
54+
parser.on('finish', () => {
5555
console.log(parser.done());
5656
});
5757
});

lib/feedme.js

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
const EventYoshi = require('eventyoshi');
1+
const { Writable } = require('stream');
22
const XMLFeedParser = require('./xmlfeedparser');
33
const JSONFeedParser = require('./jsonfeedparser');
44

55

6-
module.exports = class FeedMe extends EventYoshi {
6+
module.exports = class FeedMe extends Writable {
77
/**
88
* Creates an instance of a parser. Parser can be JSON/XML.
99
*
@@ -15,45 +15,46 @@ module.exports = class FeedMe extends EventYoshi {
1515
super();
1616
this._buffer = buffer;
1717
this._parser = null;
18+
}
1819

19-
// Make yoshi behave stream like.
20-
this.writable = true;
20+
_proxyEvents() {
21+
const parserEmit = this._parser.parser.emit;
22+
this._parser.parser.emit = (event, value) => {
23+
parserEmit.call(this._parser.parser, event, value);
24+
if (event !== 'error') {
25+
this.emit(event, value);
26+
}
27+
};
28+
this._parser.parser.on('error', this.emit.bind(this, 'error'));
2129
}
2230

2331
/**
2432
* @param {Buffer} data
2533
*/
26-
write(data) {
34+
_write(data, encoding, callback) {
2735
const str = data.toString();
2836

2937
// First find out what type of feed this is.
30-
if (/^\s*</.test(str)) {
31-
this._parser = XMLFeedParser(this._buffer);
32-
33-
} else if (/^\s*[{[]/.test(str)) {
34-
this._parser = JSONFeedParser(this._buffer);
35-
this.emit('type', 'json');
36-
37-
} else {
38-
this.emit('error', new Error('Not a correctly formatted feed'));
39-
}
40-
41-
if (this._parser) {
42-
this.add(this._parser);
43-
this.proxy('write', 'end', 'done', 'close');
44-
this._parser.write(data);
38+
if (!this._parser) {
39+
if (/^\s*</.test(str)) {
40+
this._parser = new XMLFeedParser(this._buffer);
41+
this._proxyEvents();
42+
43+
} else if (/^\s*[{[]/.test(str)) {
44+
this._parser = new JSONFeedParser(this._buffer);
45+
this.emit('type', 'json');
46+
this._proxyEvents();
47+
48+
} else {
49+
callback(new Error('Not a correctly formatted feed'));
50+
return;
51+
}
4552
}
4653

54+
this._parser.write(data, encoding, callback);
4755
}
4856

49-
/**
50-
* @param {!Buffer} data
51-
*/
52-
end(data) {
53-
if (data && data.length) {
54-
this.write(data);
55-
} else {
56-
this.emit('end');
57-
}
57+
done() {
58+
return this._parser.done();
5859
}
5960
};

lib/jsonfeedparser.js

Lines changed: 124 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const { Writable } = require('stream');
12
const clarinet = require('clarinet');
23

34
/**
@@ -30,129 +31,137 @@ const cleanObject = (obj) => {
3031
* @param {boolean} buffer If true, will buffer entire object.
3132
* @return {clarinet.Stream}
3233
*/
33-
module.exports = (buffer) => {
34-
const parser = clarinet.createStream();
35-
const stack = [];
36-
let currObj = {};
37-
let currKey = 'feed';
38-
let inArray = false;
39-
let feedJustFound = false;
40-
41-
// Look feed object in case this is a json encoded atom feed.
42-
// Place these underlying keys onto the root object.
43-
const findfeed = (key) => {
44-
if (key === 'feed') {
45-
feedJustFound = true;
46-
parser.removeListener('openobject', findfeed);
47-
parser.removeListener('key', findfeed);
48-
}
49-
};
50-
51-
const onvalue = (value) => {
52-
feedJustFound = false;
53-
currObj[currKey] = value;
54-
if (stack.length === 1) {
55-
parser.emit(currKey, value);
56-
if (!buffer) { delete currObj[currKey]; }
57-
}
58-
if (inArray) {
59-
currKey++;
60-
}
61-
};
34+
module.exports = class JSONFeedParser extends Writable {
35+
constructor(buffer) {
36+
super();
37+
this._buffer = buffer;
38+
const parser = this.parser = clarinet.createStream();
39+
const stack = [];
40+
this._currObj = {};
41+
this._currKey = 'feed';
42+
let inArray = false;
43+
let feedJustFound = false;
44+
45+
// Look feed object in case this is a json encoded atom feed.
46+
// Place these underlying keys onto the root object.
47+
const findfeed = (key) => {
48+
if (key === 'feed') {
49+
feedJustFound = true;
50+
parser.removeListener('openobject', findfeed);
51+
parser.removeListener('key', findfeed);
52+
}
53+
};
6254

63-
const onopenobject = (key) => {
64-
if (feedJustFound) {
55+
const onvalue = (value) => {
6556
feedJustFound = false;
66-
currKey = key;
67-
return;
68-
}
69-
let obj = currObj[currKey] = {};
70-
stack.push({
71-
obj: currObj,
72-
key: currKey,
73-
arr: inArray,
74-
});
75-
currObj = obj;
76-
currKey = key;
77-
inArray = false;
78-
};
79-
80-
const onkey = (key) => { currKey = key; };
81-
82-
const oncloseobject = () => {
83-
let parent = stack.pop();
84-
if (!parent) { return; }
85-
currObj = parent.obj;
86-
currKey = parent.key;
87-
inArray = parent.arr;
88-
89-
// Clean object.
90-
currObj[currKey] = cleanObject(currObj[currKey]);
91-
92-
// Emit key in feed if curr is parent.
93-
if (stack.length === 1) {
94-
parser.emit(currKey, currObj[currKey]);
95-
if (!buffer) { delete currObj[currKey]; }
96-
97-
// Or parent is array.
98-
} else if (inArray) {
99-
if (stack.length === 2) {
100-
let key = stack[1].key;
101-
let event = key === 'entry' || key === 'items' ?
102-
'item' : stack[1].key;
103-
parser.emit(event, currObj[currKey]);
104-
if (!buffer) { currObj.splice(currKey, 1); }
57+
this._currObj[this._currKey] = value;
58+
if (stack.length === 1) {
59+
parser.emit(this._currKey, value);
60+
if (!buffer) { delete this._currObj[this._currKey]; }
10561
}
62+
if (inArray) {
63+
this._currKey++;
64+
}
65+
};
10666

107-
if (stack.length > 2 || buffer) { currKey++; }
108-
}
109-
};
110-
111-
const onopenarray = () => {
112-
feedJustFound = false;
113-
let obj = currObj[currKey] = [];
114-
stack.push({
115-
obj: currObj,
116-
key: currKey,
117-
arr: inArray,
118-
});
119-
currObj = obj;
120-
currKey = 0;
121-
inArray = true;
122-
};
123-
124-
const onclosearray = () => {
125-
let parent = stack.pop();
126-
currObj = parent.obj;
127-
currKey = parent.key;
128-
inArray = parent.arr;
129-
130-
if (stack.length === 1) {
131-
if (!buffer) { delete currObj[currKey]; }
132-
} else if (inArray) {
133-
currKey++;
134-
}
135-
};
136-
137-
parser.on('openobject', findfeed);
138-
parser.on('key', findfeed);
139-
parser.on('value', onvalue);
140-
parser.on('openobject', onopenobject);
141-
parser.on('key', onkey);
142-
parser.on('closeobject', oncloseobject);
143-
parser.on('openarray', onopenarray);
144-
parser.on('closearray', onclosearray);
145-
146-
parser.done = () => {
147-
if (!buffer) { return; }
148-
let root = currObj[currKey];
67+
const onopenobject = (key) => {
68+
if (feedJustFound) {
69+
feedJustFound = false;
70+
this._currKey = key;
71+
return;
72+
}
73+
let obj = this._currObj[this._currKey] = {};
74+
stack.push({
75+
obj: this._currObj,
76+
key: this._currKey,
77+
arr: inArray,
78+
});
79+
this._currObj = obj;
80+
this._currKey = key;
81+
inArray = false;
82+
};
83+
84+
const onkey = (key) => { this._currKey = key; };
85+
86+
const oncloseobject = () => {
87+
let parent = stack.pop();
88+
if (!parent) { return; }
89+
this._currObj = parent.obj;
90+
this._currKey = parent.key;
91+
inArray = parent.arr;
92+
93+
// Clean object.
94+
this._currObj[this._currKey] = cleanObject(this._currObj[this._currKey]);
95+
96+
// Emit key in feed if curr is parent.
97+
if (stack.length === 1) {
98+
parser.emit(this._currKey, this._currObj[this._currKey]);
99+
if (!buffer) { delete this._currObj[this._currKey]; }
100+
101+
// Or parent is array.
102+
} else if (inArray) {
103+
if (stack.length === 2) {
104+
let key = stack[1].key;
105+
let event = key === 'entry' || key === 'items' ?
106+
'item' : stack[1].key;
107+
let data = this._currObj[this._currKey];
108+
parser.emit(event, data);
109+
if (!buffer) { this._currObj.splice(this._currKey, 1); }
110+
}
111+
112+
if (stack.length > 2 || buffer) { this._currKey++; }
113+
}
114+
};
115+
116+
const onopenarray = () => {
117+
feedJustFound = false;
118+
let obj = this._currObj[this._currKey] = [];
119+
stack.push({
120+
obj: this._currObj,
121+
key: this._currKey,
122+
arr: inArray,
123+
});
124+
this._currObj = obj;
125+
this._currKey = 0;
126+
inArray = true;
127+
};
128+
129+
const onclosearray = () => {
130+
let parent = stack.pop();
131+
this._currObj = parent.obj;
132+
this._currKey = parent.key;
133+
inArray = parent.arr;
134+
135+
if (stack.length === 1) {
136+
if (!buffer) { delete this._currObj[this._currKey]; }
137+
} else if (inArray) {
138+
this._currKey++;
139+
}
140+
};
141+
142+
parser.on('openobject', findfeed);
143+
parser.on('key', findfeed);
144+
parser.on('value', onvalue);
145+
parser.on('openobject', onopenobject);
146+
parser.on('key', onkey);
147+
parser.on('closeobject', oncloseobject);
148+
parser.on('openarray', onopenarray);
149+
parser.on('closearray', onclosearray);
150+
}
151+
152+
_write(chunk, encoding, callback) {
153+
this.parser.write(chunk, encoding);
154+
callback(null);
155+
}
156+
157+
done() {
158+
if (!this._buffer) { return; }
159+
let root = this._currObj[this._currKey];
149160
root.type = 'json';
150161
if (Array.isArray(root.entry)) {
151162
root.items = root.entry;
152163
}
153164
delete root.entry;
154165
return root;
155-
};
156-
157-
return parser;
166+
}
158167
};

0 commit comments

Comments
 (0)