1'use strict'; 2 3const { Writable } = require('stream'); 4const Parser = require('parse5/lib/parser'); 5 6class ParserStream extends Writable { 7 constructor(options) { 8 super({ decodeStrings: false }); 9 10 this.parser = new Parser(options); 11 12 this.lastChunkWritten = false; 13 this.writeCallback = null; 14 this.pausedByScript = false; 15 16 this.document = this.parser.treeAdapter.createDocument(); 17 18 this.pendingHtmlInsertions = []; 19 20 this._resume = this._resume.bind(this); 21 this._documentWrite = this._documentWrite.bind(this); 22 this._scriptHandler = this._scriptHandler.bind(this); 23 24 this.parser._bootstrap(this.document, null); 25 } 26 27 //WritableStream implementation 28 _write(chunk, encoding, callback) { 29 if (typeof chunk !== 'string') { 30 throw new TypeError('Parser can work only with string streams.'); 31 } 32 33 this.writeCallback = callback; 34 this.parser.tokenizer.write(chunk, this.lastChunkWritten); 35 this._runParsingLoop(); 36 } 37 38 end(chunk, encoding, callback) { 39 this.lastChunkWritten = true; 40 super.end(chunk || '', encoding, callback); 41 } 42 43 //Scriptable parser implementation 44 _runParsingLoop() { 45 this.parser.runParsingLoopForCurrentChunk(this.writeCallback, this._scriptHandler); 46 } 47 48 _resume() { 49 if (!this.pausedByScript) { 50 throw new Error('Parser was already resumed'); 51 } 52 53 while (this.pendingHtmlInsertions.length) { 54 const html = this.pendingHtmlInsertions.pop(); 55 56 this.parser.tokenizer.insertHtmlAtCurrentPos(html); 57 } 58 59 this.pausedByScript = false; 60 61 //NOTE: keep parsing if we don't wait for the next input chunk 62 if (this.parser.tokenizer.active) { 63 this._runParsingLoop(); 64 } 65 } 66 67 _documentWrite(html) { 68 if (!this.parser.stopped) { 69 this.pendingHtmlInsertions.push(html); 70 } 71 } 72 73 _scriptHandler(scriptElement) { 74 if (this.listenerCount('script') > 0) { 75 this.pausedByScript = true; 76 this.emit('script', scriptElement, this._documentWrite, this._resume); 77 } else { 78 this._runParsingLoop(); 79 } 80 } 81} 82 83module.exports = ParserStream; 84