You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
159 lines
5.1 KiB
159 lines
5.1 KiB
'use strict';
|
|
|
|
const Tokenizer = require('parse5/lib/tokenizer');
|
|
const foreignContent = require('parse5/lib/common/foreign-content');
|
|
const unicode = require('parse5/lib/common/unicode');
|
|
const HTML = require('parse5/lib/common/html');
|
|
|
|
//Aliases
|
|
const $ = HTML.TAG_NAMES;
|
|
const NS = HTML.NAMESPACES;
|
|
|
|
//ParserFeedbackSimulator
|
|
//Simulates adjustment of the Tokenizer which performed by standard parser during tree construction.
|
|
class ParserFeedbackSimulator {
|
|
constructor(tokenizer) {
|
|
this.tokenizer = tokenizer;
|
|
|
|
this.namespaceStack = [];
|
|
this.namespaceStackTop = -1;
|
|
this._enterNamespace(NS.HTML);
|
|
}
|
|
|
|
getNextToken() {
|
|
const token = this.tokenizer.getNextToken();
|
|
|
|
if (token.type === Tokenizer.START_TAG_TOKEN) {
|
|
this._handleStartTagToken(token);
|
|
} else if (token.type === Tokenizer.END_TAG_TOKEN) {
|
|
this._handleEndTagToken(token);
|
|
} else if (token.type === Tokenizer.NULL_CHARACTER_TOKEN && this.inForeignContent) {
|
|
token.type = Tokenizer.CHARACTER_TOKEN;
|
|
token.chars = unicode.REPLACEMENT_CHARACTER;
|
|
} else if (this.skipNextNewLine) {
|
|
if (token.type !== Tokenizer.HIBERNATION_TOKEN) {
|
|
this.skipNextNewLine = false;
|
|
}
|
|
|
|
if (token.type === Tokenizer.WHITESPACE_CHARACTER_TOKEN && token.chars[0] === '\n') {
|
|
if (token.chars.length === 1) {
|
|
return this.getNextToken();
|
|
}
|
|
|
|
token.chars = token.chars.substr(1);
|
|
}
|
|
}
|
|
|
|
return token;
|
|
}
|
|
|
|
//Namespace stack mutations
|
|
_enterNamespace(namespace) {
|
|
this.namespaceStackTop++;
|
|
this.namespaceStack.push(namespace);
|
|
|
|
this.inForeignContent = namespace !== NS.HTML;
|
|
this.currentNamespace = namespace;
|
|
this.tokenizer.allowCDATA = this.inForeignContent;
|
|
}
|
|
|
|
_leaveCurrentNamespace() {
|
|
this.namespaceStackTop--;
|
|
this.namespaceStack.pop();
|
|
|
|
this.currentNamespace = this.namespaceStack[this.namespaceStackTop];
|
|
this.inForeignContent = this.currentNamespace !== NS.HTML;
|
|
this.tokenizer.allowCDATA = this.inForeignContent;
|
|
}
|
|
|
|
//Token handlers
|
|
_ensureTokenizerMode(tn) {
|
|
if (tn === $.TEXTAREA || tn === $.TITLE) {
|
|
this.tokenizer.state = Tokenizer.MODE.RCDATA;
|
|
} else if (tn === $.PLAINTEXT) {
|
|
this.tokenizer.state = Tokenizer.MODE.PLAINTEXT;
|
|
} else if (tn === $.SCRIPT) {
|
|
this.tokenizer.state = Tokenizer.MODE.SCRIPT_DATA;
|
|
} else if (
|
|
tn === $.STYLE ||
|
|
tn === $.IFRAME ||
|
|
tn === $.XMP ||
|
|
tn === $.NOEMBED ||
|
|
tn === $.NOFRAMES ||
|
|
tn === $.NOSCRIPT
|
|
) {
|
|
this.tokenizer.state = Tokenizer.MODE.RAWTEXT;
|
|
}
|
|
}
|
|
|
|
_handleStartTagToken(token) {
|
|
let tn = token.tagName;
|
|
|
|
if (tn === $.SVG) {
|
|
this._enterNamespace(NS.SVG);
|
|
} else if (tn === $.MATH) {
|
|
this._enterNamespace(NS.MATHML);
|
|
}
|
|
|
|
if (this.inForeignContent) {
|
|
if (foreignContent.causesExit(token)) {
|
|
this._leaveCurrentNamespace();
|
|
return;
|
|
}
|
|
|
|
const currentNs = this.currentNamespace;
|
|
|
|
if (currentNs === NS.MATHML) {
|
|
foreignContent.adjustTokenMathMLAttrs(token);
|
|
} else if (currentNs === NS.SVG) {
|
|
foreignContent.adjustTokenSVGTagName(token);
|
|
foreignContent.adjustTokenSVGAttrs(token);
|
|
}
|
|
|
|
foreignContent.adjustTokenXMLAttrs(token);
|
|
|
|
tn = token.tagName;
|
|
|
|
if (!token.selfClosing && foreignContent.isIntegrationPoint(tn, currentNs, token.attrs)) {
|
|
this._enterNamespace(NS.HTML);
|
|
}
|
|
} else {
|
|
if (tn === $.PRE || tn === $.TEXTAREA || tn === $.LISTING) {
|
|
this.skipNextNewLine = true;
|
|
} else if (tn === $.IMAGE) {
|
|
token.tagName = $.IMG;
|
|
}
|
|
|
|
this._ensureTokenizerMode(tn);
|
|
}
|
|
}
|
|
|
|
_handleEndTagToken(token) {
|
|
let tn = token.tagName;
|
|
|
|
if (!this.inForeignContent) {
|
|
const previousNs = this.namespaceStack[this.namespaceStackTop - 1];
|
|
|
|
if (previousNs === NS.SVG && foreignContent.SVG_TAG_NAMES_ADJUSTMENT_MAP[tn]) {
|
|
tn = foreignContent.SVG_TAG_NAMES_ADJUSTMENT_MAP[tn];
|
|
}
|
|
|
|
//NOTE: check for exit from integration point
|
|
if (foreignContent.isIntegrationPoint(tn, previousNs, token.attrs)) {
|
|
this._leaveCurrentNamespace();
|
|
}
|
|
} else if (
|
|
(tn === $.SVG && this.currentNamespace === NS.SVG) ||
|
|
(tn === $.MATH && this.currentNamespace === NS.MATHML)
|
|
) {
|
|
this._leaveCurrentNamespace();
|
|
}
|
|
|
|
// NOTE: adjust end tag name as well for consistency
|
|
if (this.currentNamespace === NS.SVG) {
|
|
foreignContent.adjustTokenSVGTagName(token);
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = ParserFeedbackSimulator;
|