UNPKG

@bbob/parser

Version:

A BBCode to AST Parser part of @bbob

300 lines (299 loc) 9.41 kB
"use strict"; 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;