UNPKG

@bbob/parser

Version:

A BBCode to AST Parser part of @bbob

283 lines (282 loc) 9.02 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.js"); var _Token = require("./Token.js"); var _NodeList = require("./NodeList.js"); var createList = function() { return new _NodeList.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 activeTagNode = null; /** * Temp buffer of tag attributes * @private * @type {NodeList} */ var activeTagNodesAttrName = null; /** * Cache for nested tags checks */ var nestedTagsMap = new Set(); function getValue(tokenValue) { return caseFreeTags ? tokenValue.toLowerCase() : tokenValue; } function isTokenNested(token) { var tokenValue = token.getValue(); var value = getValue(tokenValue); var isTokenNested = (tokenizer || {}).isTokenNested; if (!nestedTagsMap.has(value) && typeof isTokenNested === "function") { if (isTokenNested(value)) { nestedTagsMap.add(value); return true; } } return nestedTagsMap.has(value); } /** * @private */ function isTagNested(tagName) { return Boolean(nestedTagsMap.has(getValue(tagName))); } /** * @private */ function isTagAllowed(value) { if (onlyAllowTags.length) { return onlyAllowTags.indexOf(value.toLowerCase()) >= 0; } return true; } /** * Flushes temp tag nodes and its attributes buffers * @private */ function activeTagNodeFlush() { if (activeTagNode) { activeTagNode = null; activeTagNodesAttrName = null; } } /** * @private */ function getNodesContent() { var lastNestedNode = nestedNodes.last(); if (lastNestedNode && (0, _pluginhelper.isTagNode)(lastNestedNode)) { return lastNestedNode.content; } return nodes.ref(); } /** * @private */ function nodesAppendAsString(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 nodesAppend(node) { var nodes = getNodesContent(); if (Array.isArray(nodes) && typeof node !== "undefined") { if ((0, _pluginhelper.isTagNode)(node)) { if (isTagAllowed(node.tag)) { nodes.push(node.toTagNode()); } else { nodesAppendAsString(nodes, node); } } else { nodes.push(node); } } } /** * @private * @param {Token} token */ function tagHandleStart(token) { activeTagNodeFlush(); var tagNode = _pluginhelper.TagNode.create(token.getValue(), {}, [], { from: token.getStart(), to: token.getEnd() }); var isNested = isTokenNested(token); activeTagNode = tagNode; if (isNested) { nestedNodes.push(tagNode); } else { nodesAppend(tagNode); } } /** * @private * @param {Token} token */ function tagHandleEnd(token) { var tagName = token.getValue().slice(1); var lastNestedNode = nestedNodes.flush(); activeTagNodeFlush(); if (lastNestedNode) { if ((0, _pluginhelper.isTagNode)(lastNestedNode)) { lastNestedNode.setEnd({ from: token.getStart(), to: token.getEnd() }); } nodesAppend(lastNestedNode); } else if (!isTagNested(tagName)) { nodesAppend(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 nodeHandle(token) { var tokenValue = token.getValue(); var isNested = isTagNested(token.toString()); if (activeTagNode) { switch(token.type){ case _Token.TYPE_ATTR_NAME: activeTagNodesAttrName = tokenValue; if (tokenValue) { activeTagNode.attr(tokenValue, ""); } break; case _Token.TYPE_ATTR_VALUE: if (activeTagNodesAttrName) { activeTagNode.attr(activeTagNodesAttrName, tokenValue); activeTagNodesAttrName = null; } else { activeTagNode.attr(tokenValue, tokenValue); } break; case _Token.TYPE_SPACE: case _Token.TYPE_NEW_LINE: case _Token.TYPE_WORD: if (isNested) { activeTagNode.append(tokenValue); } else { nodesAppend(tokenValue); } break; case _Token.TYPE_TAG: // if tag is not allowed, just pass it as is nodesAppend(token.toString({ openTag: openTag, closeTag: closeTag })); break; } } else if (token.isText()) { nodesAppend(tokenValue); } else if (token.isTag()) { // if tag is not allowed, just pass it as is nodesAppend(token.toString({ openTag: openTag, closeTag: closeTag })); } } /** * @private * @param {Token} token */ function onToken(token) { if (token.isTag()) { // [tag] if (token.isStart()) { tagHandleStart(token); } // [/tag] if (token.isEnd()) { tagHandleEnd(token); } } else { nodeHandle(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, whitespaceInTags: options.whitespaceInTags }); // eslint-disable-next-line no-unused-vars var tokens = tokenizer.tokenize(); // handles situations where we opened tag, but forget to 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 do { var node = nestedNodes.flush(); if ((0, _pluginhelper.isTagNode)(node) && isTagNested(node.tag)) { nodesAppendAsString(getNodesContent(), node, false); } else if (typeof node !== "undefined") { nodesAppend(node); } }while (nestedNodes.has()); return nodes.ref(); } var _default = parse;