Skip to content

Commit 24354fb

Browse files
committed
Add resume_saxerror tests and fix async iterator SAX error handling
- Add test/feeds/saxerror.xml: RSS feed with a bare & that triggers a SAX error in strict mode, used to demonstrate both resume behaviours - In handleSaxError, set/clear a _isSaxError flag around emit() so that listeners can distinguish SAX errors from fatal errors synchronously - In Symbol.asyncIterator, ignore resumable SAX errors in onError (check _isSaxError && resume_saxerror) so that parsing continues rather than throwing; fatal errors (resume_saxerror: false, or non-SAX errors) still propagate as exceptions - Tests use write()+end() via setImmediate instead of pipe(): readable-stream v2 calls unpipe() when the destination emits 'error', which prevents _flush from ever being called and the stream from ending https://claude.ai/code/session_01Rx48d2xCMjtzmoynPwet77
1 parent 461e2d8 commit 24354fb

File tree

3 files changed

+66
-0
lines changed

3 files changed

+66
-0
lines changed

lib/feedparser/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,9 @@ FeedParser.prototype.handleEnd = function (){
131131

132132
/** @this {FeedParserInstance} */
133133
FeedParser.prototype.handleSaxError = function (e) {
134+
this._isSaxError = true;
134135
this.emit('error', e);
136+
this._isSaxError = false;
135137
if (this.options.resume_saxerror) {
136138
this.resumeSaxError();
137139
}
@@ -1239,6 +1241,7 @@ FeedParser.prototype._flush = function (done) {
12391241

12401242
/** @this {FeedParserInstance} */
12411243
FeedParser.prototype[Symbol.asyncIterator] = async function* () {
1244+
var self = this;
12421245
var resolve = null;
12431246
var error = null;
12441247
var ended = false;
@@ -1251,6 +1254,9 @@ FeedParser.prototype[Symbol.asyncIterator] = async function* () {
12511254
if (resolve) { resolve(); resolve = null; }
12521255
}
12531256
function onError(err) {
1257+
if (self._isSaxError && self.options.resume_saxerror) {
1258+
return; // Non-fatal: parsing will resume automatically
1259+
}
12541260
error = err;
12551261
if (resolve) { resolve(); resolve = null; }
12561262
}

test/examples.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,43 @@ describe('examples', function () {
1515
assert.equal(items.length, 4);
1616
});
1717

18+
describe('resume_saxerror', function () {
19+
var content = fs.readFileSync(__dirname + '/feeds/saxerror.xml');
20+
21+
// pipe() breaks when 'error' fires on the destination (readable-stream v2
22+
// behaviour), so we write directly and schedule it after the for-await loop
23+
// has started (i.e. after the async iterator has registered its listeners).
24+
function writeAsync(feedparser) {
25+
setImmediate(function () { feedparser.write(content); feedparser.end(); });
26+
}
27+
28+
it('should continue iterating past SAX errors by default (resume_saxerror: true)', async function () {
29+
var feedparser = new FeedParser({ strict: true });
30+
var items = [];
31+
writeAsync(feedparser);
32+
33+
for await (const item of feedparser) {
34+
items.push(item);
35+
}
36+
37+
assert.ok(items.length > 0);
38+
});
39+
40+
it('should throw on SAX errors when resume_saxerror is false', async function () {
41+
var feedparser = new FeedParser({ strict: true, resume_saxerror: false });
42+
writeAsync(feedparser);
43+
44+
var caught = null;
45+
try {
46+
for await (const item of feedparser) {} // eslint-disable-line no-unused-vars
47+
} catch (err) {
48+
caught = err;
49+
}
50+
51+
assert.ok(caught instanceof Error);
52+
});
53+
});
54+
1855
it('should surface errors via try/catch when using async iterator', async function () {
1956
var feedparser = new FeedParser();
2057
var feed = __dirname + '/feeds/notafeed.html';

test/feeds/saxerror.xml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<rss version="2.0">
3+
<channel>
4+
<title>Test Feed</title>
5+
<link>http://example.com</link>
6+
<description>Feed for testing SAX error recovery</description>
7+
<item>
8+
<title>Item 1</title>
9+
<link>http://example.com/item/1</link>
10+
<guid>http://example.com/item/1</guid>
11+
</item>
12+
<item>
13+
<title>Item with invalid & bare ampersand</title>
14+
<link>http://example.com/item/2</link>
15+
<guid>http://example.com/item/2</guid>
16+
</item>
17+
<item>
18+
<title>Item 3</title>
19+
<link>http://example.com/item/3</link>
20+
<guid>http://example.com/item/3</guid>
21+
</item>
22+
</channel>
23+
</rss>

0 commit comments

Comments
 (0)