Skip to content
This repository was archived by the owner on Feb 9, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions src/HtmlParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {escapeQuotes} from './utils';

* @type {Object}
*/
const ignoredInvalidTags = /^<\s*(?:script|style)/i;

const detect = {
comment: /^<!--/,
endTag: /^<\//,
Expand All @@ -30,12 +32,14 @@ export default class HtmlParser {
* @param {string} stream The initial parse stream contents.
* @param {Object} options The options
* @param {boolean} options.autoFix Set to true to automatically fix errors
* @param {boolean} options.allowInvalidHTML Continue parsing invalid HTML, dropping minimal characters
*/
constructor(stream = '', options = {}) {
this.stream = stream;

let fix = false;
const fixedTokenOptions = {};
this.allowInvalidHTML = options.allowInvalidHTML;

for (let key in supports) {
if (supports.hasOwnProperty(key)) {
Expand All @@ -48,10 +52,10 @@ export default class HtmlParser {

if (fix) {
this._readToken = fixedReadTokenFactory(this, fixedTokenOptions, () => this._readTokenImpl());
this._peekToken = fixedReadTokenFactory(this, fixedTokenOptions, () => this._peekTokenImpl());
this._peekToken = fixedReadTokenFactory(this, fixedTokenOptions, () => this._peekTokenImpl(this.allowInvalidHTML));
} else {
this._readToken = this._readTokenImpl;
this._peekToken = this._peekTokenImpl;
this._peekToken = () => this._peekTokenImpl(this.allowInvalidHTML);
}
}

Expand Down Expand Up @@ -80,19 +84,31 @@ export default class HtmlParser {
* @returns {?Token}
*/
_readTokenImpl() {
const token = this._peekTokenImpl();
const token = this._peekTokenImpl(this.allowInvalidHTML);
if (token) {
this.stream = this.stream.slice(token.length);
return token;
}
}

_consumeInvalidHtml() {
this.stream = this.stream.slice(1);

let token = this._peekTokenImpl(false);
while (this.stream.length > 0 && token == null) {
this.stream = this.stream.slice(1);
token = this._peekTokenImpl(false);
}

return token;
}

/**
* The implementation of token peeking.
*
* @returns {?Token}
*/
_peekTokenImpl() {
_peekTokenImpl(allowInvalidHTML) {
for (let type in detect) {
if (detect.hasOwnProperty(type)) {
if (detect[type].test(this.stream)) {
Expand All @@ -106,6 +122,8 @@ export default class HtmlParser {
token.text = this.stream.substr(0, token.length);
return token;
}
} else if (allowInvalidHTML && this.stream.length && token == null && !(ignoredInvalidTags.test(this.stream))) {
return this._consumeInvalidHtml();
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/fixedReadTokenFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const EMPTY = /^(AREA|BASE|BASEFONT|BR|COL|FRAME|HR|IMG|INPUT|ISINDEX|LINK|META|
*
* @type {RegExp}
*/
const CLOSESELF = /^(COLGROUP|DD|DT|LI|OPTIONS|P|TD|TFOOT|TH|THEAD|TR)$/i;
const CLOSESELF = /^(COLGROUP|DD|DT|LI|OPTIONS|P|TD|TFOOT|TH|THEAD|TR|BR)$/i;

/**
* Corrects a token.
Expand Down
2 changes: 1 addition & 1 deletion test/helpers/testParse.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function parsesCompletely(s, options, theTest) {

export function fixes(s, theTest) {
return () => {
const parser = typeof s === 'string' ? new HtmlParser(s, {autoFix: true}) : s;
const parser = typeof s === 'string' ? new HtmlParser(s, {autoFix: true, allowInvalidHTML: true}) : s;
let tok = parser.readToken();
let str = '';
while (tok) {
Expand Down
4 changes: 4 additions & 0 deletions test/unit/tags.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ describe('HtmlParser (tags)', () => {
expect(str).to.equal('</DIV>');
});

it('recovers from broken html', fixes('<div> hello <br \\="" /> </div><h2>Example</h2>', s => {
expect(s).to.equal('<div> hello br \\="" /> </div><h2>Example</h2>');
}));

it('fixes missing end tag', fixes('<div><i></div>foo', s => {
expect(s).to.equal('<div><i></i></div>foo');
}));
Expand Down