• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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