mirror of
https://github.com/tenrok/BBob.git
synced 2026-05-15 11:59:37 +03:00
203 lines
4.2 KiB
JavaScript
203 lines
4.2 KiB
JavaScript
const {
|
|
convertTokenToText,
|
|
getTagName,
|
|
getTokenColumn,
|
|
getTokenLine,
|
|
getTokenValue,
|
|
isAttrNameToken,
|
|
isAttrValueToken,
|
|
isTagStart,
|
|
isTagToken,
|
|
isTextToken,
|
|
isTagEnd,
|
|
} = require('./Token');
|
|
|
|
const Tokenizer = require('./Tokenizer');
|
|
const TagNode = require('./TagNode');
|
|
|
|
const createTagNode = (tag, attrs = {}, content = []) => new TagNode(tag, attrs, content);
|
|
|
|
/**
|
|
*
|
|
{
|
|
tag: 'div',
|
|
attrs: {
|
|
class: 'foo'
|
|
},
|
|
content: ['hello world!']
|
|
}
|
|
*/
|
|
class Parser {
|
|
constructor(input, options = {}) {
|
|
this.createTokenizer(input);
|
|
|
|
this.options = options;
|
|
|
|
this.nodes = [];
|
|
this.nestedNodes = [];
|
|
this.tagNodes = [];
|
|
this.tagNodesAttrName = [];
|
|
}
|
|
|
|
createTokenizer(input) {
|
|
this.tokenizer = new Tokenizer(input, {
|
|
onToken: (token) => {
|
|
this.parseToken(token);
|
|
},
|
|
});
|
|
}
|
|
|
|
isTagNested(token) {
|
|
return this.tokenizer.isTokenNested(token);
|
|
}
|
|
|
|
/**
|
|
* @return {TagNode}
|
|
*/
|
|
getTagNode() {
|
|
if (this.tagNodes.length) {
|
|
return this.tagNodes[this.tagNodes.length - 1];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
createTagNode(token) {
|
|
this.tagNodes.push(createTagNode(getTokenValue(token)));
|
|
}
|
|
|
|
createTagNodeAttrName(token) {
|
|
this.tagNodesAttrName.push(getTokenValue(token));
|
|
}
|
|
|
|
getTagNodeAttrName() {
|
|
if (this.tagNodesAttrName.length) {
|
|
return this.tagNodesAttrName[this.tagNodesAttrName.length - 1];
|
|
}
|
|
|
|
return this.getTagNode().tag;
|
|
}
|
|
|
|
clearTagNodeAttrName() {
|
|
if (this.tagNodesAttrName.length) {
|
|
this.tagNodesAttrName.pop();
|
|
}
|
|
}
|
|
|
|
clearTagNode() {
|
|
if (this.tagNodes.length) {
|
|
this.tagNodes.pop();
|
|
|
|
this.clearTagNodeAttrName();
|
|
}
|
|
}
|
|
|
|
getNodes() {
|
|
if (this.nestedNodes.length) {
|
|
const nestedNode = this.nestedNodes[this.nestedNodes.length - 1];
|
|
return nestedNode.content;
|
|
}
|
|
|
|
return this.nodes;
|
|
}
|
|
|
|
appendNode(tag) {
|
|
this.getNodes().push(tag);
|
|
}
|
|
|
|
handleTagStart(token) {
|
|
if (isTagStart(token)) {
|
|
this.createTagNode(token);
|
|
|
|
if (this.isTagNested(token)) {
|
|
this.nestedNodes.push(this.getTagNode());
|
|
} else {
|
|
this.appendNode(this.getTagNode());
|
|
this.clearTagNode();
|
|
}
|
|
}
|
|
}
|
|
|
|
handleTagEnd(token) {
|
|
if (isTagEnd(token)) {
|
|
this.clearTagNode();
|
|
|
|
const lastNestedNode = this.nestedNodes.pop();
|
|
|
|
if (lastNestedNode) {
|
|
this.appendNode(lastNestedNode);
|
|
} else {
|
|
// eslint-disable-next-line no-console
|
|
console.warn(`Inconsistent tag '${getTokenValue(token)}' on line ${getTokenLine(token)} and column ${getTokenColumn(token)}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
handleTagToken(token) {
|
|
if (isTagToken(token)) {
|
|
if (this.isAllowedTag(getTagName(token))) {
|
|
// [tag]
|
|
this.handleTagStart(token);
|
|
|
|
// [/tag]
|
|
this.handleTagEnd(token);
|
|
} else {
|
|
this.appendNode(convertTokenToText(token));
|
|
}
|
|
}
|
|
}
|
|
|
|
handleTagNode(token) {
|
|
const tagNode = this.getTagNode();
|
|
|
|
if (tagNode) {
|
|
if (isAttrNameToken(token)) {
|
|
this.createTagNodeAttrName(token);
|
|
tagNode.attr(this.getTagNodeAttrName(), null);
|
|
} else if (isAttrValueToken(token)) {
|
|
tagNode.attr(this.getTagNodeAttrName(), getTokenValue(token));
|
|
this.clearTagNodeAttrName();
|
|
} else if (isTextToken(token)) {
|
|
tagNode.append(getTokenValue(token));
|
|
}
|
|
} else if (isTextToken(token)) {
|
|
this.appendNode(getTokenValue(token));
|
|
}
|
|
}
|
|
|
|
parseToken(token) {
|
|
this.handleTagToken(token);
|
|
this.handleTagNode(token);
|
|
}
|
|
|
|
parse() {
|
|
if (this.tokens) {
|
|
let token;
|
|
// eslint-disable-next-line no-cond-assign
|
|
while (token = this.tokens.shift()) {
|
|
if (!token) {
|
|
// eslint-disable-next-line no-continue
|
|
continue;
|
|
}
|
|
|
|
this.parseToken(token);
|
|
}
|
|
} else {
|
|
this.tokens = this.tokenizer.tokenize();
|
|
}
|
|
|
|
return this.nodes;
|
|
}
|
|
|
|
isAllowedTag(value) {
|
|
if (this.options.onlyAllowTags && this.options.onlyAllowTags.length) {
|
|
return this.options.onlyAllowTags.indexOf(value) >= 0;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
module.exports = Parser;
|
|
module.exports.createTagNode = createTagNode;
|