@bbob/parser
Version:
A BBCode to AST Parser part of @bbob
300 lines (299 loc) • 9.41 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
default: function() {
return _default;
},
parse: function() {
return parse;
}
});
var _pluginhelper = require("@bbob/plugin-helper");
var _lexer = require("./lexer");
var NodeList = /*#__PURE__*/ function() {
"use strict";
function NodeList() {
this.n = [];
}
var _proto = NodeList.prototype;
_proto.last = function last() {
if (Array.isArray(this.n) && this.n.length > 0 && typeof this.n[this.n.length - 1] !== "undefined") {
return this.n[this.n.length - 1];
}
return null;
};
_proto.flush = function flush() {
return this.n.length ? this.n.pop() : false;
};
_proto.push = function push(value) {
this.n.push(value);
};
_proto.toArray = function toArray() {
return this.n;
};
return NodeList;
}();
var createList = function() {
return new NodeList();
};
function parse(input, opts) {
if (opts === void 0) opts = {};
var options = opts;
var openTag = options.openTag || _pluginhelper.OPEN_BRAKET;
var closeTag = options.closeTag || _pluginhelper.CLOSE_BRAKET;
var onlyAllowTags = (options.onlyAllowTags || []).filter(Boolean).map(function(tag) {
return tag.toLowerCase();
});
var caseFreeTags = options.caseFreeTags || false;
var tokenizer = null;
/**
* Result AST of nodes
* @private
* @type {NodeList}
*/ var nodes = createList();
/**
* Temp buffer of nodes that's nested to another node
* @private
*/ var nestedNodes = createList();
/**
* Temp buffer of nodes [tag..]...[/tag]
* @private
* @type {NodeList}
*/ var tagNodes = createList();
/**
* Temp buffer of tag attributes
* @private
* @type {NodeList}
*/ var tagNodesAttrName = createList();
/**
* Cache for nested tags checks
*/ var nestedTagsMap = new Set();
function isTokenNested(token) {
var tokenValue = token.getValue();
var value = caseFreeTags ? tokenValue.toLowerCase() : tokenValue;
var isTokenNested = (tokenizer || {}).isTokenNested;
if (!nestedTagsMap.has(value) && isTokenNested && isTokenNested(value)) {
nestedTagsMap.add(value);
return true;
}
return nestedTagsMap.has(value);
}
/**
* @private
*/ function isTagNested(tagName) {
return Boolean(nestedTagsMap.has(caseFreeTags ? tagName.toLowerCase() : tagName));
}
/**
* @private
*/ function isAllowedTag(value) {
if (onlyAllowTags.length) {
return onlyAllowTags.indexOf(value.toLowerCase()) >= 0;
}
return true;
}
/**
* Flushes temp tag nodes and its attributes buffers
* @private
*/ function flushTagNodes() {
if (tagNodes.flush()) {
tagNodesAttrName.flush();
}
}
/**
* @private
*/ function getNodes() {
var lastNestedNode = nestedNodes.last();
if (lastNestedNode && (0, _pluginhelper.isTagNode)(lastNestedNode)) {
return lastNestedNode.content;
}
return nodes.toArray();
}
/**
* @private
*/ function appendNodeAsString(nodes, node, isNested) {
if (isNested === void 0) isNested = true;
if (Array.isArray(nodes) && typeof node !== "undefined") {
nodes.push(node.toTagStart({
openTag: openTag,
closeTag: closeTag
}));
if (Array.isArray(node.content) && node.content.length) {
node.content.forEach(function(item) {
nodes.push(item);
});
if (isNested) {
nodes.push(node.toTagEnd({
openTag: openTag,
closeTag: closeTag
}));
}
}
}
}
/**
* @private
*/ function appendNodes(nodes, node) {
if (Array.isArray(nodes) && typeof node !== "undefined") {
if ((0, _pluginhelper.isTagNode)(node)) {
if (isAllowedTag(node.tag)) {
nodes.push(node.toTagNode());
} else {
appendNodeAsString(nodes, node);
}
} else {
nodes.push(node);
}
}
}
/**
* @private
* @param {Token} token
*/ function handleTagStart(token) {
flushTagNodes();
var tagNode = _pluginhelper.TagNode.create(token.getValue(), {}, [], {
from: token.getStart(),
to: token.getEnd()
});
var isNested = isTokenNested(token);
tagNodes.push(tagNode);
if (isNested) {
nestedNodes.push(tagNode);
} else {
var nodes = getNodes();
appendNodes(nodes, tagNode);
}
}
/**
* @private
* @param {Token} token
*/ function handleTagEnd(token) {
var tagName = token.getValue().slice(1);
var lastNestedNode = nestedNodes.flush();
flushTagNodes();
if (lastNestedNode) {
var nodes = getNodes();
if ((0, _pluginhelper.isTagNode)(lastNestedNode)) {
lastNestedNode.setEnd({
from: token.getStart(),
to: token.getEnd()
});
}
appendNodes(nodes, lastNestedNode);
} else if (!isTagNested(tagName)) {
var nodes1 = getNodes();
appendNodes(nodes1, token.toString({
openTag: openTag,
closeTag: closeTag
}));
} else if (typeof options.onError === "function") {
var tag = token.getValue();
var line = token.getLine();
var column = token.getColumn();
options.onError({
tagName: tag,
lineNumber: line,
columnNumber: column
});
}
}
/**
* @private
* @param {Token} token
*/ function handleTag(token) {
// [tag]
if (token.isStart()) {
handleTagStart(token);
}
// [/tag]
if (token.isEnd()) {
handleTagEnd(token);
}
}
/**
* @private
* @param {Token} token
*/ function handleNode(token) {
/**
* @type {TagNode}
*/ var activeTagNode = tagNodes.last();
var tokenValue = token.getValue();
var isNested = isTagNested(token.toString());
var nodes = getNodes();
if (activeTagNode !== null) {
if (token.isAttrName()) {
tagNodesAttrName.push(tokenValue);
var attrName = tagNodesAttrName.last();
if (attrName) {
activeTagNode.attr(attrName, "");
}
} else if (token.isAttrValue()) {
var attrName1 = tagNodesAttrName.last();
if (attrName1) {
activeTagNode.attr(attrName1, tokenValue);
tagNodesAttrName.flush();
} else {
activeTagNode.attr(tokenValue, tokenValue);
}
} else if (token.isText()) {
if (isNested) {
activeTagNode.append(tokenValue);
} else {
appendNodes(nodes, tokenValue);
}
} else if (token.isTag()) {
// if tag is not allowed, just pass it as is
appendNodes(nodes, token.toString({
openTag: openTag,
closeTag: closeTag
}));
}
} else if (token.isText()) {
appendNodes(nodes, tokenValue);
} else if (token.isTag()) {
// if tag is not allowed, just pass it as is
appendNodes(nodes, token.toString({
openTag: openTag,
closeTag: closeTag
}));
}
}
/**
* @private
* @param {Token} token
*/ function onToken(token) {
if (token.isTag()) {
handleTag(token);
} else {
handleNode(token);
}
}
var lexer = opts.createTokenizer ? opts.createTokenizer : _lexer.createLexer;
tokenizer = lexer(input, {
onToken: onToken,
openTag: openTag,
closeTag: closeTag,
onlyAllowTags: options.onlyAllowTags,
contextFreeTags: options.contextFreeTags,
caseFreeTags: options.caseFreeTags,
enableEscapeTags: options.enableEscapeTags
});
// eslint-disable-next-line no-unused-vars
var tokens = tokenizer.tokenize();
// handles situations where we open tag, but forgot close them
// for ex [q]test[/q][u]some[/u][q]some [u]some[/u] // forgot to close [/q]
// so we need to flush nested content to nodes array
var lastNestedNode = nestedNodes.flush();
if (lastNestedNode !== null && lastNestedNode && (0, _pluginhelper.isTagNode)(lastNestedNode) && isTagNested(lastNestedNode.tag)) {
appendNodeAsString(getNodes(), lastNestedNode, false);
}
return nodes.toArray();
}
var _default = parse;
;