2
0
mirror of https://github.com/tenrok/BBob.git synced 2026-05-15 11:59:37 +03:00
Files
bbob/packages/bbob-parser/Parser.js
T
2018-06-11 22:31:02 +02:00

178 lines
4.0 KiB
JavaScript

const {
convertTokenToText,
getTagName,
getTokenColumn,
getTokenLine,
getTokenValue,
isAttrNameToken,
isAttrValueToken,
isTagStart,
isTagToken,
isTextToken,
isTagEnd,
} = require('./Tokenizer');
const Tokenizer = require('./Tokenizer');
const TokenChar = Tokenizer.CHAR;
const getChar = Tokenizer.getChar;
const createTagNode = (name, attrs = {}, content = []) => ({ tag: name, attrs, content });
/**
*
{
tag: 'div',
attrs: {
class: 'foo'
},
content: ['hello world!']
}
*/
module.exports = class Parser {
constructor(tokens, options = {}) {
this.tokens = tokens;
this.options = options;
}
parse() {
const nodes = [];
const nestedNodes = [];
const curTags = [];
const curTagsAttrName = [];
const closableTags = this.findNestedTags(this.tokens);
const isNestedTag = token => closableTags.indexOf(getTokenValue(token)) >= 0;
const getCurTag = () => {
if (curTags.length) {
return curTags[curTags.length - 1];
}
return null;
};
const createCurTag = (token) => {
curTags.push(createTagNode(getTokenValue(token)));
};
const getCurTagAttrName = () => {
if (curTagsAttrName.length) {
return curTagsAttrName[curTagsAttrName.length - 1];
}
return null;
};
const createCurTagAttrName = (token) => {
curTagsAttrName.push(getTokenValue(token));
};
const clearCurTagAttrName = () => {
if (curTagsAttrName.length) {
curTagsAttrName.pop();
}
};
const clearCurTag = () => {
if (curTags.length) {
curTags.pop();
clearCurTagAttrName();
}
};
const getNodes = () => {
if (nestedNodes.length) {
const nestedNode = nestedNodes[nestedNodes.length - 1];
return nestedNode.content;
}
return nodes;
};
let token;
// eslint-disable-next-line no-cond-assign
while (token = this.tokens.shift()) {
if (!token) {
// eslint-disable-next-line no-continue
continue;
}
if (isTagToken(token)) {
if (this.isAllowedTag(getTagName(token))) {
// [tag]
if (isTagStart(token)) {
createCurTag(token);
if (isNestedTag(token)) {
nestedNodes.push(getCurTag());
} else {
getNodes().push(getCurTag());
clearCurTag();
}
}
// [/tag]
if (isTagEnd(token)) {
clearCurTag();
const lastNestedNode = nestedNodes.pop();
if (lastNestedNode) {
getNodes().push(lastNestedNode);
} else {
console.warn(`Inconsistent tag '${getTokenValue(token)}' on line ${getTokenLine(token)} and column ${getTokenColumn(token)}`);
}
}
} else {
getNodes().push(convertTokenToText(token));
}
}
if (getCurTag()) {
if (isAttrNameToken(token)) {
createCurTagAttrName(token);
getCurTag().attrs[getCurTagAttrName()] = null;
} else if (isAttrValueToken(token)) {
getCurTag().attrs[getCurTagAttrName()] = getTokenValue(token);
clearCurTagAttrName();
} else if (isTextToken(token)) {
getCurTag().content.push(getTokenValue(token));
}
} else if (isTextToken(token)) {
getNodes().push(getTokenValue(token));
}
}
return nodes;
}
findNestedTags(tokens) {
const tags = tokens.filter(isTagToken).reduce((acc, token) => {
acc[getTokenValue(token)] = true;
return acc;
}, {});
const closeChar = getChar(TokenChar.SLASH);
return Object.keys(tags).reduce((arr, key) => {
if (tags[key] && tags[closeChar + key]) {
arr.push(key);
}
return arr;
}, []);
}
isAllowedTag(value) {
if (this.options.allowOnlyTags && this.options.allowOnlyTags.length) {
return this.options.allowOnlyTags.indexOf(value) >= 0;
}
return true;
}
};