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