mirror of
https://github.com/tenrok/BBob.git
synced 2026-05-15 11:59:37 +03:00
182 lines
5.0 KiB
JavaScript
182 lines
5.0 KiB
JavaScript
const Tokenizer = require("./Tokenizer");
|
|
const TokenType = Tokenizer.TYPE;
|
|
const TokenChar = Tokenizer.CHAR;
|
|
const getCharCode = Tokenizer.getCharCode;
|
|
|
|
const isTextToken = (token) => {
|
|
const type = token[Tokenizer.TOKEN.TYPE_ID];
|
|
|
|
return type === TokenType.SPACE || type === TokenType.NEW_LINE || type === TokenType.WORD
|
|
};
|
|
|
|
const isTagToken = (token) => token[Tokenizer.TOKEN.TYPE_ID] === TokenType.TAG;
|
|
|
|
const isTagStart = (token) => !isTagEnd(token);
|
|
|
|
const isTagEnd = (token) => getTokenValue(token).charCodeAt(0) === TokenChar.SLASH;
|
|
|
|
const isAttrNameToken = (token) => token[Tokenizer.TOKEN.TYPE_ID] === TokenType.ATTR_NAME;
|
|
|
|
const isAttrValueToken = (token) => token[Tokenizer.TOKEN.TYPE_ID] === TokenType.ATTR_VALUE;
|
|
|
|
const getTagName = (token) => {
|
|
const value = getTokenValue(token);
|
|
|
|
return isTagEnd(token) ? value.slice(1) : value
|
|
};
|
|
|
|
const convertTagToText = (token) => {
|
|
let text = getCharCode(TokenChar.OPEN_BRAKET);
|
|
|
|
if (isTagEnd(token)) {
|
|
text += getCharCode(TokenChar.SLASH)
|
|
}
|
|
|
|
text += getTokenValue(token);
|
|
text += getCharCode(TokenChar.CLOSE_BRAKET);
|
|
|
|
return text
|
|
};
|
|
|
|
const getTokenValue = (token) => token[Tokenizer.TOKEN.VALUE_ID];
|
|
|
|
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 tokens = this.tokens;
|
|
const nodes = [];
|
|
const nestedNodes = [];
|
|
const curTags = [];
|
|
const curTagsAttrName = [];
|
|
|
|
const getCurTag = () => {
|
|
if (curTags.length) {
|
|
return curTags[curTags.length - 1]
|
|
}
|
|
|
|
return null
|
|
};
|
|
|
|
const createCurTag = (token) => {
|
|
curTags.push(createTagNode(getTokenValue(token)))
|
|
};
|
|
|
|
const clearCurTag = () => {
|
|
if (curTags.length) {
|
|
curTags.pop();
|
|
|
|
clearCurTagAttrName()
|
|
}
|
|
};
|
|
|
|
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 getNodes = () => {
|
|
if (nestedNodes.length) {
|
|
const nestedNode = nestedNodes[nestedNodes.length - 1];
|
|
return nestedNode.content
|
|
}
|
|
|
|
return nodes
|
|
};
|
|
|
|
let token;
|
|
while (token = tokens.shift()) {
|
|
if (!token) {
|
|
continue;
|
|
}
|
|
|
|
if (isTagToken(token)) {
|
|
if (this.isAllowedTag(getTagName(token))) {
|
|
// [tag]
|
|
if (isTagStart(token)) {
|
|
createCurTag(token);
|
|
|
|
if (this.isCloseTag(getTokenValue(token))) {
|
|
nestedNodes.push(getCurTag())
|
|
} else {
|
|
getNodes().push(getCurTag());
|
|
clearCurTag()
|
|
}
|
|
}
|
|
|
|
// [/tag]
|
|
if (isTagEnd(token)) {
|
|
clearCurTag();
|
|
|
|
const lastNestedNode = nestedNodes.pop();
|
|
|
|
if (lastNestedNode) {
|
|
getNodes().push(lastNestedNode)
|
|
} else {
|
|
debugger;
|
|
console.warn(`Inconsistent tag '${getTokenValue(token)}'`);
|
|
}
|
|
}
|
|
} else {
|
|
getNodes().push(convertTagToText(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
|
|
}
|
|
|
|
isCloseTag(value) {
|
|
return this.options.closableTags && this.options.closableTags.indexOf(value) >= 0
|
|
}
|
|
|
|
isAllowedTag(value) {
|
|
if (this.options.allowOnlyTags && this.options.allowOnlyTags.length) {
|
|
return this.options.allowOnlyTags.indexOf(value) >= 0
|
|
}
|
|
|
|
return true
|
|
}
|
|
}; |