@bbob/parser
Advanced tools
Comparing version 2.9.0 to 3.0.0
1664
dist/index.js
(function (global, factory) { | ||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : | ||
typeof define === 'function' && define.amd ? define(['exports'], factory) : | ||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.BbobParser = {})); | ||
})(this, (function (exports) { 'use strict'; | ||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@bbob/plugin-helper')) : | ||
typeof define === 'function' && define.amd ? define(['exports', '@bbob/plugin-helper'], factory) : | ||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.BbobParser = {}, global.pluginHelper)); | ||
})(this, (function (exports, pluginHelper) { 'use strict'; | ||
const N = '\n'; | ||
const TAB = '\t'; | ||
const EQ = '='; | ||
const QUOTEMARK = '"'; | ||
const SPACE = ' '; | ||
const OPEN_BRAKET = '['; | ||
const CLOSE_BRAKET = ']'; | ||
const SLASH = '/'; | ||
const BACKSLASH = '\\'; | ||
// type, value, line, row, | ||
const TOKEN_TYPE_ID = 'type'; // 0; | ||
const TOKEN_VALUE_ID = 'value'; // 1; | ||
const TOKEN_COLUMN_ID = 'row'; // 2; | ||
const TOKEN_LINE_ID = 'line'; // 3; | ||
const TOKEN_TYPE_WORD = 1; // 'word'; | ||
const TOKEN_TYPE_TAG = 2; // 'tag'; | ||
const TOKEN_TYPE_ATTR_NAME = 3; // 'attr-name'; | ||
const TOKEN_TYPE_ATTR_VALUE = 4; // 'attr-value'; | ||
const TOKEN_TYPE_SPACE = 5; // 'space'; | ||
const TOKEN_TYPE_NEW_LINE = 6; // 'new-line'; | ||
/** | ||
* @param {Token} token | ||
* @returns {string} | ||
*/ const getTokenValue = (token)=>{ | ||
if (token && typeof token[TOKEN_VALUE_ID] !== 'undefined') { | ||
return token[TOKEN_VALUE_ID]; | ||
} | ||
return ''; | ||
}; | ||
/** | ||
* @param {Token}token | ||
* @returns {number} | ||
*/ const getTokenLine = (token)=>token && token[TOKEN_LINE_ID] || 0; | ||
const getTokenColumn = (token)=>token && token[TOKEN_COLUMN_ID] || 0; | ||
/** | ||
* @param {Token} token | ||
* @returns {boolean} | ||
*/ const isTextToken = (token)=>{ | ||
if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') { | ||
return token[TOKEN_TYPE_ID] === TOKEN_TYPE_SPACE || token[TOKEN_TYPE_ID] === TOKEN_TYPE_NEW_LINE || token[TOKEN_TYPE_ID] === TOKEN_TYPE_WORD; | ||
} | ||
return false; | ||
}; | ||
/** | ||
* @param {Token} token | ||
* @returns {boolean} | ||
*/ const isTagToken = (token)=>{ | ||
if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') { | ||
return token[TOKEN_TYPE_ID] === TOKEN_TYPE_TAG; | ||
} | ||
return false; | ||
}; | ||
const isTagEnd = (token)=>getTokenValue(token).charCodeAt(0) === pluginHelper.SLASH.charCodeAt(0); | ||
const isTagStart = (token)=>!isTagEnd(token); | ||
const isAttrNameToken = (token)=>{ | ||
if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') { | ||
return token[TOKEN_TYPE_ID] === TOKEN_TYPE_ATTR_NAME; | ||
} | ||
return false; | ||
}; | ||
/** | ||
* @param {Token} token | ||
* @returns {boolean} | ||
*/ const isAttrValueToken = (token)=>{ | ||
if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') { | ||
return token[TOKEN_TYPE_ID] === TOKEN_TYPE_ATTR_VALUE; | ||
} | ||
return false; | ||
}; | ||
const getTagName = (token)=>{ | ||
const value = getTokenValue(token); | ||
return isTagEnd(token) ? value.slice(1) : value; | ||
}; | ||
const convertTagToText = (token)=>{ | ||
let text = pluginHelper.OPEN_BRAKET; | ||
text += getTokenValue(token); | ||
text += pluginHelper.CLOSE_BRAKET; | ||
return text; | ||
}; | ||
class Token { | ||
isEmpty() { | ||
// eslint-disable-next-line no-restricted-globals | ||
return isNaN(this[TOKEN_TYPE_ID]); | ||
} | ||
isText() { | ||
return isTextToken(this); | ||
} | ||
isTag() { | ||
return isTagToken(this); | ||
} | ||
isAttrName() { | ||
return isAttrNameToken(this); | ||
} | ||
isAttrValue() { | ||
return isAttrValueToken(this); | ||
} | ||
isStart() { | ||
return isTagStart(this); | ||
} | ||
isEnd() { | ||
return isTagEnd(this); | ||
} | ||
getName() { | ||
return getTagName(this); | ||
} | ||
getValue() { | ||
return getTokenValue(this); | ||
} | ||
getLine() { | ||
return getTokenLine(this); | ||
} | ||
getColumn() { | ||
return getTokenColumn(this); | ||
} | ||
toString() { | ||
return convertTagToText(this); | ||
} | ||
/** | ||
* @param {String} type | ||
* @param {String} value | ||
* @param line | ||
* @param row | ||
*/ constructor(type, value, line, row){ | ||
this[TOKEN_TYPE_ID] = Number(type); | ||
this[TOKEN_VALUE_ID] = String(value); | ||
this[TOKEN_LINE_ID] = Number(line); | ||
this[TOKEN_COLUMN_ID] = Number(row); | ||
} | ||
} | ||
const TYPE_WORD = TOKEN_TYPE_WORD; | ||
const TYPE_TAG = TOKEN_TYPE_TAG; | ||
const TYPE_ATTR_NAME = TOKEN_TYPE_ATTR_NAME; | ||
const TYPE_ATTR_VALUE = TOKEN_TYPE_ATTR_VALUE; | ||
const TYPE_SPACE = TOKEN_TYPE_SPACE; | ||
const TYPE_NEW_LINE = TOKEN_TYPE_NEW_LINE; | ||
const isTagNode = (el)=>typeof el === 'object' && !!el.tag; | ||
const isStringNode = (el)=>typeof el === 'string'; | ||
const keysReduce = (obj, reduce, def)=>Object.keys(obj).reduce(reduce, def); | ||
const getNodeLength = (node)=>{ | ||
if (isTagNode(node)) { | ||
return node.content.reduce((count, contentNode)=>count + getNodeLength(contentNode), 0); | ||
} | ||
if (isStringNode(node)) { | ||
return node.length; | ||
} | ||
return 0; | ||
}; | ||
/** | ||
* Appends value to Tag Node | ||
* @param {TagNode} node | ||
* @param value | ||
*/ const appendToNode = (node, value)=>{ | ||
node.content.push(value); | ||
}; | ||
/** | ||
* Replaces " to &qquot; | ||
* @param {String} value | ||
*/ const escapeHTML = (value)=>value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''')// eslint-disable-next-line no-script-url | ||
.replace(/(javascript|data|vbscript):/gi, '$1%3A'); | ||
/** | ||
* Acept name and value and return valid html5 attribute string | ||
* @param {String} name | ||
* @param {String} value | ||
* @return {string} | ||
*/ const attrValue = (name, value)=>{ | ||
const type = typeof value; | ||
const types = { | ||
boolean: ()=>value ? `${name}` : '', | ||
number: ()=>`${name}="${value}"`, | ||
string: ()=>`${name}="${escapeHTML(value)}"`, | ||
object: ()=>`${name}="${escapeHTML(JSON.stringify(value))}"` | ||
}; | ||
return types[type] ? types[type]() : ''; | ||
}; | ||
/** | ||
* Transforms attrs to html params string | ||
* @param values | ||
*/ const attrsToString = (values)=>{ | ||
// To avoid some malformed attributes | ||
if (values == null) { | ||
return ''; | ||
} | ||
return keysReduce(values, (arr, key)=>[ | ||
...arr, | ||
attrValue(key, values[key]) | ||
], [ | ||
'' | ||
]).join(' '); | ||
}; | ||
/** | ||
* Gets value from | ||
* @example | ||
* getUniqAttr({ 'foo': true, 'bar': bar' }) => 'bar' | ||
* @param attrs | ||
* @returns {string} | ||
*/ const getUniqAttr = (attrs)=>keysReduce(attrs, (res, key)=>attrs[key] === key ? attrs[key] : null, null); | ||
function CharGrabber(source, options) { | ||
const cursor = { | ||
pos: 0, | ||
len: source.length | ||
}; | ||
const substrUntilChar = (char)=>{ | ||
const { pos } = cursor; | ||
const idx = source.indexOf(char, pos); | ||
return idx >= 0 ? source.substring(pos, idx) : ''; | ||
}; | ||
const includes = (val)=>source.indexOf(val, cursor.pos) >= 0; | ||
const hasNext = ()=>cursor.len > cursor.pos; | ||
const isLast = ()=>cursor.pos === cursor.len; | ||
const skip = (num = 1, silent)=>{ | ||
cursor.pos += num; | ||
if (options && options.onSkip && !silent) { | ||
options.onSkip(); | ||
} | ||
}; | ||
const rest = ()=>source.substring(cursor.pos); | ||
const grabN = (num = 0)=>source.substring(cursor.pos, cursor.pos + num); | ||
const curr = ()=>source[cursor.pos]; | ||
const prev = ()=>{ | ||
const prevPos = cursor.pos - 1; | ||
return typeof source[prevPos] !== 'undefined' ? source[prevPos] : null; | ||
}; | ||
const next = ()=>{ | ||
const nextPos = cursor.pos + 1; | ||
return nextPos <= source.length - 1 ? source[nextPos] : null; | ||
}; | ||
const grabWhile = (cond, silent)=>{ | ||
let start = 0; | ||
if (hasNext()) { | ||
start = cursor.pos; | ||
while(hasNext() && cond(curr())){ | ||
skip(1, silent); | ||
} | ||
} | ||
return source.substring(start, cursor.pos); | ||
}; | ||
/** | ||
* @type {skip} | ||
*/ this.skip = skip; | ||
/** | ||
* @returns {Boolean} | ||
*/ this.hasNext = hasNext; | ||
/** | ||
* @returns {String} | ||
*/ this.getCurr = curr; | ||
/** | ||
* @returns {String} | ||
*/ this.getRest = rest; | ||
/** | ||
* @returns {String} | ||
*/ this.getNext = next; | ||
/** | ||
* @returns {String} | ||
*/ this.getPrev = prev; | ||
/** | ||
* @returns {Boolean} | ||
*/ this.isLast = isLast; | ||
/** | ||
* @returns {Boolean} | ||
*/ this.includes = includes; | ||
/** | ||
* @param {Function} cond | ||
* @param {Boolean} silent | ||
* @return {String} | ||
*/ this.grabWhile = grabWhile; | ||
/** | ||
* @param {Number} num | ||
* @return {String} | ||
*/ this.grabN = grabN; | ||
/** | ||
* Grabs rest of string until it find a char | ||
* @param {String} char | ||
* @return {String} | ||
*/ this.substrUntilChar = substrUntilChar; | ||
} | ||
/** | ||
* Creates a grabber wrapper for source string, that helps to iterate over string char by char | ||
* @param {String} source | ||
* @param {Object} options | ||
* @param {Function} options.onSkip | ||
* @return CharGrabber | ||
*/ const createCharGrabber = (source, options)=>new CharGrabber(source, options); | ||
/** | ||
* Trims string from start and end by char | ||
* @example | ||
* trimChar('*hello*', '*') ==> 'hello' | ||
* @param {String} str | ||
* @param {String} charToRemove | ||
* @returns {String} | ||
*/ const trimChar = (str, charToRemove)=>{ | ||
while(str.charAt(0) === charToRemove){ | ||
// eslint-disable-next-line no-param-reassign | ||
str = str.substring(1); | ||
} | ||
while(str.charAt(str.length - 1) === charToRemove){ | ||
// eslint-disable-next-line no-param-reassign | ||
str = str.substring(0, str.length - 1); | ||
} | ||
return str; | ||
}; | ||
/** | ||
* Unquotes \" to " | ||
* @param str | ||
* @return {String} | ||
*/ const unquote = (str)=>str.replace(pluginHelper.BACKSLASH + pluginHelper.QUOTEMARK, pluginHelper.QUOTEMARK); | ||
function NodeList(values = []) { | ||
const nodes = values; | ||
const getLast = ()=>Array.isArray(nodes) && nodes.length > 0 && typeof nodes[nodes.length - 1] !== 'undefined' ? nodes[nodes.length - 1] : null; | ||
const flushLast = ()=>nodes.length ? nodes.pop() : false; | ||
const push = (value)=>nodes.push(value); | ||
const toArray = ()=>nodes; | ||
this.push = push; | ||
this.toArray = toArray; | ||
this.getLast = getLast; | ||
this.flushLast = flushLast; | ||
} | ||
/** | ||
* | ||
* @param values | ||
* @return {NodeList} | ||
*/ const createList = (values = [])=>new NodeList(values); | ||
const getTagAttrs = (tag, params)=>{ | ||
const uniqAattr = getUniqAttr(params); | ||
if (uniqAattr) { | ||
const tagAttr = attrValue(tag, uniqAattr); | ||
const attrs = { | ||
...params | ||
}; | ||
delete attrs[uniqAattr]; | ||
const attrsStr = attrsToString(attrs); | ||
return `${tagAttr}${attrsStr}`; | ||
} | ||
return `${tag}${attrsToString(params)}`; | ||
}; | ||
class TagNode { | ||
attr(name, value) { | ||
if (typeof value !== 'undefined') { | ||
this.attrs[name] = value; | ||
} | ||
return this.attrs[name]; | ||
} | ||
append(value) { | ||
return appendToNode(this, value); | ||
} | ||
get length() { | ||
return getNodeLength(this); | ||
} | ||
toTagStart({ openTag =OPEN_BRAKET , closeTag =CLOSE_BRAKET } = {}) { | ||
const tagAttrs = getTagAttrs(this.tag, this.attrs); | ||
return `${openTag}${tagAttrs}${closeTag}`; | ||
} | ||
toTagEnd({ openTag =OPEN_BRAKET , closeTag =CLOSE_BRAKET } = {}) { | ||
return `${openTag}${SLASH}${this.tag}${closeTag}`; | ||
} | ||
toTagNode() { | ||
return new TagNode(this.tag.toLowerCase(), this.attrs, this.content); | ||
} | ||
toString({ openTag =OPEN_BRAKET , closeTag =CLOSE_BRAKET } = {}) { | ||
const isEmpty = this.content.length === 0; | ||
const content = this.content.reduce((r, node)=>r + node.toString({ | ||
openTag, | ||
closeTag | ||
}), ''); | ||
const tagStart = this.toTagStart({ | ||
openTag, | ||
closeTag | ||
}); | ||
if (isEmpty) { | ||
return tagStart; | ||
} | ||
return `${tagStart}${content}${this.toTagEnd({ | ||
openTag, | ||
closeTag | ||
})}`; | ||
} | ||
constructor(tag, attrs, content){ | ||
this.tag = tag; | ||
this.attrs = attrs; | ||
this.content = Array.isArray(content) ? content : [ | ||
content | ||
]; | ||
} | ||
} | ||
TagNode.create = (tag, attrs = {}, content = [])=>new TagNode(tag, attrs, content); | ||
TagNode.isOf = (node, type)=>node.tag === type; | ||
// for cases <!-- --> | ||
const EM = '!'; | ||
/** | ||
* Creates a Token entity class | ||
* @param {Number} type | ||
* @param {String} value | ||
* @param {Number} r line number | ||
* @param {Number} cl char number in line | ||
*/ const createToken = (type, value, r = 0, cl = 0)=>new Token(type, value, r, cl); | ||
/** | ||
* @typedef {Object} Lexer | ||
* @property {Function} tokenize | ||
* @property {Function} isTokenNested | ||
*/ /** | ||
* @param {String} buffer | ||
* @param {Object} options | ||
* @param {Function} options.onToken | ||
* @param {String} options.openTag | ||
* @param {String} options.closeTag | ||
* @param {Boolean} options.enableEscapeTags | ||
* @return {Lexer} | ||
*/ function createLexer(buffer, options = {}) { | ||
const STATE_WORD = 0; | ||
const STATE_TAG = 1; | ||
const STATE_TAG_ATTRS = 2; | ||
const TAG_STATE_NAME = 0; | ||
const TAG_STATE_ATTR = 1; | ||
const TAG_STATE_VALUE = 2; | ||
let row = 0; | ||
let col = 0; | ||
let tokenIndex = -1; | ||
let stateMode = STATE_WORD; | ||
let tagMode = TAG_STATE_NAME; | ||
let contextFreeTag = ''; | ||
const tokens = new Array(Math.floor(buffer.length)); | ||
const openTag = options.openTag || pluginHelper.OPEN_BRAKET; | ||
const closeTag = options.closeTag || pluginHelper.CLOSE_BRAKET; | ||
const escapeTags = !!options.enableEscapeTags; | ||
const contextFreeTags = options.contextFreeTags || []; | ||
const onToken = options.onToken || (()=>{}); | ||
const RESERVED_CHARS = [ | ||
closeTag, | ||
openTag, | ||
pluginHelper.QUOTEMARK, | ||
pluginHelper.BACKSLASH, | ||
pluginHelper.SPACE, | ||
pluginHelper.TAB, | ||
pluginHelper.EQ, | ||
pluginHelper.N, | ||
EM | ||
]; | ||
const NOT_CHAR_TOKENS = [ | ||
openTag, | ||
pluginHelper.SPACE, | ||
pluginHelper.TAB, | ||
pluginHelper.N | ||
]; | ||
const WHITESPACES = [ | ||
pluginHelper.SPACE, | ||
pluginHelper.TAB | ||
]; | ||
const SPECIAL_CHARS = [ | ||
pluginHelper.EQ, | ||
pluginHelper.SPACE, | ||
pluginHelper.TAB | ||
]; | ||
const isCharReserved = (char)=>RESERVED_CHARS.indexOf(char) >= 0; | ||
const isNewLine = (char)=>char === pluginHelper.N; | ||
const isWhiteSpace = (char)=>WHITESPACES.indexOf(char) >= 0; | ||
const isCharToken = (char)=>NOT_CHAR_TOKENS.indexOf(char) === -1; | ||
const isSpecialChar = (char)=>SPECIAL_CHARS.indexOf(char) >= 0; | ||
const isEscapableChar = (char)=>char === openTag || char === closeTag || char === pluginHelper.BACKSLASH; | ||
const isEscapeChar = (char)=>char === pluginHelper.BACKSLASH; | ||
const onSkip = ()=>{ | ||
col++; | ||
}; | ||
const unq = (val)=>unquote(trimChar(val, pluginHelper.QUOTEMARK)); | ||
const checkContextFreeMode = (name, isClosingTag)=>{ | ||
if (contextFreeTag !== '' && isClosingTag) { | ||
contextFreeTag = ''; | ||
} | ||
if (contextFreeTag === '' && contextFreeTags.includes(name)) { | ||
contextFreeTag = name; | ||
} | ||
}; | ||
const chars = createCharGrabber(buffer, { | ||
onSkip | ||
}); | ||
/** | ||
* Emits newly created token to subscriber | ||
* @param {Number} type | ||
* @param {String} value | ||
*/ function emitToken(type, value) { | ||
const token = createToken(type, value, row, col); | ||
onToken(token); | ||
tokenIndex += 1; | ||
tokens[tokenIndex] = token; | ||
} | ||
function nextTagState(tagChars, isSingleValueTag) { | ||
if (tagMode === TAG_STATE_ATTR) { | ||
const validAttrName = (char)=>!(char === pluginHelper.EQ || isWhiteSpace(char)); | ||
const name = tagChars.grabWhile(validAttrName); | ||
const isEnd = tagChars.isLast(); | ||
const isValue = tagChars.getCurr() !== pluginHelper.EQ; | ||
tagChars.skip(); | ||
if (isEnd || isValue) { | ||
emitToken(TYPE_ATTR_VALUE, unq(name)); | ||
} else { | ||
emitToken(TYPE_ATTR_NAME, name); | ||
} | ||
if (isEnd) { | ||
return TAG_STATE_NAME; | ||
} | ||
if (isValue) { | ||
return TAG_STATE_ATTR; | ||
} | ||
return TAG_STATE_VALUE; | ||
} | ||
if (tagMode === TAG_STATE_VALUE) { | ||
let stateSpecial = false; | ||
const validAttrValue = (char)=>{ | ||
// const isEQ = char === EQ; | ||
const isQM = char === pluginHelper.QUOTEMARK; | ||
const prevChar = tagChars.getPrev(); | ||
const nextChar = tagChars.getNext(); | ||
const isPrevSLASH = prevChar === pluginHelper.BACKSLASH; | ||
const isNextEQ = nextChar === pluginHelper.EQ; | ||
const isWS = isWhiteSpace(char); | ||
// const isPrevWS = isWhiteSpace(prevChar); | ||
const isNextWS = isWhiteSpace(nextChar); | ||
if (stateSpecial && isSpecialChar(char)) { | ||
return true; | ||
} | ||
if (isQM && !isPrevSLASH) { | ||
stateSpecial = !stateSpecial; | ||
if (!stateSpecial && !(isNextEQ || isNextWS)) { | ||
return false; | ||
} | ||
} | ||
if (!isSingleValueTag) { | ||
return isWS === false; | ||
// return (isEQ || isWS) === false; | ||
} | ||
return true; | ||
}; | ||
const name1 = tagChars.grabWhile(validAttrValue); | ||
tagChars.skip(); | ||
emitToken(TYPE_ATTR_VALUE, unq(name1)); | ||
if (tagChars.isLast()) { | ||
return TAG_STATE_NAME; | ||
} | ||
return TAG_STATE_ATTR; | ||
} | ||
const validName = (char)=>!(char === pluginHelper.EQ || isWhiteSpace(char) || tagChars.isLast()); | ||
const name2 = tagChars.grabWhile(validName); | ||
emitToken(TYPE_TAG, name2); | ||
checkContextFreeMode(name2); | ||
tagChars.skip(); | ||
// in cases when we has [url=someval]GET[/url] and we dont need to parse all | ||
if (isSingleValueTag) { | ||
return TAG_STATE_VALUE; | ||
} | ||
const hasEQ = tagChars.includes(pluginHelper.EQ); | ||
return hasEQ ? TAG_STATE_ATTR : TAG_STATE_VALUE; | ||
} | ||
function stateTag() { | ||
const currChar = chars.getCurr(); | ||
const nextChar = chars.getNext(); | ||
chars.skip(); | ||
// detect case where we have '[My word [tag][/tag]' or we have '[My last line word' | ||
const substr = chars.substrUntilChar(closeTag); | ||
const hasInvalidChars = substr.length === 0 || substr.indexOf(openTag) >= 0; | ||
if (isCharReserved(nextChar) || hasInvalidChars || chars.isLast()) { | ||
emitToken(TYPE_WORD, currChar); | ||
return STATE_WORD; | ||
} | ||
// [myTag ] | ||
const isNoAttrsInTag = substr.indexOf(pluginHelper.EQ) === -1; | ||
// [/myTag] | ||
const isClosingTag = substr[0] === pluginHelper.SLASH; | ||
if (isNoAttrsInTag || isClosingTag) { | ||
const name = chars.grabWhile((char)=>char !== closeTag); | ||
chars.skip(); // skip closeTag | ||
emitToken(TYPE_TAG, name); | ||
checkContextFreeMode(name, isClosingTag); | ||
return STATE_WORD; | ||
} | ||
return STATE_TAG_ATTRS; | ||
} | ||
function stateAttrs() { | ||
const silent = true; | ||
const tagStr = chars.grabWhile((char)=>char !== closeTag, silent); | ||
const tagGrabber = createCharGrabber(tagStr, { | ||
onSkip | ||
}); | ||
const hasSpace = tagGrabber.includes(pluginHelper.SPACE); | ||
tagMode = TAG_STATE_NAME; | ||
while(tagGrabber.hasNext()){ | ||
tagMode = nextTagState(tagGrabber, !hasSpace); | ||
} | ||
chars.skip(); // skip closeTag | ||
return STATE_WORD; | ||
} | ||
function stateWord() { | ||
if (isNewLine(chars.getCurr())) { | ||
emitToken(TYPE_NEW_LINE, chars.getCurr()); | ||
chars.skip(); | ||
col = 0; | ||
row++; | ||
return STATE_WORD; | ||
} | ||
if (isWhiteSpace(chars.getCurr())) { | ||
const word = chars.grabWhile(isWhiteSpace); | ||
emitToken(TYPE_SPACE, word); | ||
return STATE_WORD; | ||
} | ||
if (chars.getCurr() === openTag) { | ||
if (contextFreeTag) { | ||
const fullTagLen = openTag.length + pluginHelper.SLASH.length + contextFreeTag.length; | ||
const fullTagName = `${openTag}${pluginHelper.SLASH}${contextFreeTag}`; | ||
const foundTag = chars.grabN(fullTagLen); | ||
const isEndContextFreeMode = foundTag === fullTagName; | ||
if (isEndContextFreeMode) { | ||
return STATE_TAG; | ||
} | ||
} else if (chars.includes(closeTag)) { | ||
return STATE_TAG; | ||
} | ||
emitToken(TYPE_WORD, chars.getCurr()); | ||
chars.skip(); | ||
return STATE_WORD; | ||
} | ||
if (escapeTags) { | ||
if (isEscapeChar(chars.getCurr())) { | ||
const currChar = chars.getCurr(); | ||
const nextChar = chars.getNext(); | ||
chars.skip(); // skip the \ without emitting anything | ||
if (isEscapableChar(nextChar)) { | ||
chars.skip(); // skip past the [, ] or \ as well | ||
emitToken(TYPE_WORD, nextChar); | ||
return STATE_WORD; | ||
} | ||
emitToken(TYPE_WORD, currChar); | ||
return STATE_WORD; | ||
} | ||
const isChar = (char)=>isCharToken(char) && !isEscapeChar(char); | ||
const word1 = chars.grabWhile(isChar); | ||
emitToken(TYPE_WORD, word1); | ||
return STATE_WORD; | ||
} | ||
const word2 = chars.grabWhile(isCharToken); | ||
emitToken(TYPE_WORD, word2); | ||
return STATE_WORD; | ||
} | ||
function tokenize() { | ||
stateMode = STATE_WORD; | ||
while(chars.hasNext()){ | ||
switch(stateMode){ | ||
case STATE_TAG: | ||
stateMode = stateTag(); | ||
break; | ||
case STATE_TAG_ATTRS: | ||
stateMode = stateAttrs(); | ||
break; | ||
case STATE_WORD: | ||
default: | ||
stateMode = stateWord(); | ||
break; | ||
} | ||
} | ||
tokens.length = tokenIndex + 1; | ||
return tokens; | ||
} | ||
function isTokenNested(token) { | ||
const value = openTag + pluginHelper.SLASH + token.getValue(); | ||
// potential bottleneck | ||
return buffer.indexOf(value) > -1; | ||
} | ||
return { | ||
tokenize, | ||
isTokenNested | ||
}; | ||
} | ||
// type, value, line, row, | ||
const TOKEN_TYPE_ID = 'type'; // 0; | ||
const TOKEN_VALUE_ID = 'value'; // 1; | ||
const TOKEN_COLUMN_ID = 'row'; // 2; | ||
const TOKEN_LINE_ID = 'line'; // 3; | ||
const TOKEN_TYPE_WORD = 1; // 'word'; | ||
const TOKEN_TYPE_TAG = 2; // 'tag'; | ||
const TOKEN_TYPE_ATTR_NAME = 3; // 'attr-name'; | ||
const TOKEN_TYPE_ATTR_VALUE = 4; // 'attr-value'; | ||
const TOKEN_TYPE_SPACE = 5; // 'space'; | ||
const TOKEN_TYPE_NEW_LINE = 6; // 'new-line'; | ||
/** | ||
* @param {Token} token | ||
* @returns {string} | ||
*/ const getTokenValue = (token)=>{ | ||
if (token && typeof token[TOKEN_VALUE_ID] !== 'undefined') { | ||
return token[TOKEN_VALUE_ID]; | ||
} | ||
return ''; | ||
}; | ||
/** | ||
* @param {Token}token | ||
* @returns {number} | ||
*/ const getTokenLine = (token)=>token && token[TOKEN_LINE_ID] || 0; | ||
const getTokenColumn = (token)=>token && token[TOKEN_COLUMN_ID] || 0; | ||
/** | ||
* @param {Token} token | ||
* @returns {boolean} | ||
*/ const isTextToken = (token)=>{ | ||
if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') { | ||
return token[TOKEN_TYPE_ID] === TOKEN_TYPE_SPACE || token[TOKEN_TYPE_ID] === TOKEN_TYPE_NEW_LINE || token[TOKEN_TYPE_ID] === TOKEN_TYPE_WORD; | ||
} | ||
return false; | ||
}; | ||
/** | ||
* @param {Token} token | ||
* @returns {boolean} | ||
*/ const isTagToken = (token)=>{ | ||
if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') { | ||
return token[TOKEN_TYPE_ID] === TOKEN_TYPE_TAG; | ||
} | ||
return false; | ||
}; | ||
const isTagEnd = (token)=>getTokenValue(token).charCodeAt(0) === SLASH.charCodeAt(0); | ||
const isTagStart = (token)=>!isTagEnd(token); | ||
const isAttrNameToken = (token)=>{ | ||
if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') { | ||
return token[TOKEN_TYPE_ID] === TOKEN_TYPE_ATTR_NAME; | ||
} | ||
return false; | ||
}; | ||
/** | ||
* @param {Token} token | ||
* @returns {boolean} | ||
*/ const isAttrValueToken = (token)=>{ | ||
if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') { | ||
return token[TOKEN_TYPE_ID] === TOKEN_TYPE_ATTR_VALUE; | ||
} | ||
return false; | ||
}; | ||
const getTagName = (token)=>{ | ||
const value = getTokenValue(token); | ||
return isTagEnd(token) ? value.slice(1) : value; | ||
}; | ||
const convertTagToText = (token)=>{ | ||
let text = OPEN_BRAKET; | ||
text += getTokenValue(token); | ||
text += CLOSE_BRAKET; | ||
return text; | ||
}; | ||
class Token { | ||
isEmpty() { | ||
// eslint-disable-next-line no-restricted-globals | ||
return isNaN(this[TOKEN_TYPE_ID]); | ||
} | ||
isText() { | ||
return isTextToken(this); | ||
} | ||
isTag() { | ||
return isTagToken(this); | ||
} | ||
isAttrName() { | ||
return isAttrNameToken(this); | ||
} | ||
isAttrValue() { | ||
return isAttrValueToken(this); | ||
} | ||
isStart() { | ||
return isTagStart(this); | ||
} | ||
isEnd() { | ||
return isTagEnd(this); | ||
} | ||
getName() { | ||
return getTagName(this); | ||
} | ||
getValue() { | ||
return getTokenValue(this); | ||
} | ||
getLine() { | ||
return getTokenLine(this); | ||
} | ||
getColumn() { | ||
return getTokenColumn(this); | ||
} | ||
toString() { | ||
return convertTagToText(this); | ||
} | ||
/** | ||
* @param {String} type | ||
* @param {String} value | ||
* @param line | ||
* @param row | ||
*/ constructor(type, value, line, row){ | ||
this[TOKEN_TYPE_ID] = Number(type); | ||
this[TOKEN_VALUE_ID] = String(value); | ||
this[TOKEN_LINE_ID] = Number(line); | ||
this[TOKEN_COLUMN_ID] = Number(row); | ||
} | ||
} | ||
const TYPE_WORD = TOKEN_TYPE_WORD; | ||
const TYPE_TAG = TOKEN_TYPE_TAG; | ||
const TYPE_ATTR_NAME = TOKEN_TYPE_ATTR_NAME; | ||
const TYPE_ATTR_VALUE = TOKEN_TYPE_ATTR_VALUE; | ||
const TYPE_SPACE = TOKEN_TYPE_SPACE; | ||
const TYPE_NEW_LINE = TOKEN_TYPE_NEW_LINE; | ||
/** | ||
* @public | ||
* @param {String} input | ||
* @param {Object} opts | ||
* @param {Function} opts.createTokenizer | ||
* @param {Array<string>} opts.onlyAllowTags | ||
* @param {Array<string>} opts.contextFreeTags | ||
* @param {Boolean} opts.enableEscapeTags | ||
* @param {String} opts.openTag | ||
* @param {String} opts.closeTag | ||
* @return {Array} | ||
*/ const parse = (input, opts = {})=>{ | ||
const options = opts; | ||
const openTag = options.openTag || pluginHelper.OPEN_BRAKET; | ||
const closeTag = options.closeTag || pluginHelper.CLOSE_BRAKET; | ||
let tokenizer = null; | ||
/** | ||
* Result AST of nodes | ||
* @private | ||
* @type {NodeList} | ||
*/ const nodes = createList(); | ||
/** | ||
* Temp buffer of nodes that's nested to another node | ||
* @private | ||
* @type {NodeList} | ||
*/ const nestedNodes = createList(); | ||
/** | ||
* Temp buffer of nodes [tag..]...[/tag] | ||
* @private | ||
* @type {NodeList} | ||
*/ const tagNodes = createList(); | ||
/** | ||
* Temp buffer of tag attributes | ||
* @private | ||
* @type {NodeList} | ||
*/ const tagNodesAttrName = createList(); | ||
/** | ||
* Cache for nested tags checks | ||
*/ const nestedTagsMap = new Set(); | ||
/** | ||
* | ||
* @param token | ||
* @returns {boolean} | ||
*/ const isTokenNested = (token)=>{ | ||
const value = token.getValue(); | ||
if (!nestedTagsMap.has(value) && tokenizer.isTokenNested && tokenizer.isTokenNested(token)) { | ||
nestedTagsMap.add(value); | ||
return true; | ||
} | ||
return nestedTagsMap.has(value); | ||
}; | ||
/** | ||
* @param tagName | ||
* @returns {boolean} | ||
*/ const isTagNested = (tagName)=>Boolean(nestedTagsMap.has(tagName)); | ||
/** | ||
* @private | ||
* @param {String} value | ||
* @return {boolean} | ||
*/ const isAllowedTag = (value)=>{ | ||
if (options.onlyAllowTags && options.onlyAllowTags.length) { | ||
return options.onlyAllowTags.indexOf(value) >= 0; | ||
} | ||
return true; | ||
}; | ||
/** | ||
* Flushes temp tag nodes and its attributes buffers | ||
* @private | ||
* @return {Array} | ||
*/ const flushTagNodes = ()=>{ | ||
if (tagNodes.flushLast()) { | ||
tagNodesAttrName.flushLast(); | ||
} | ||
}; | ||
/** | ||
* @private | ||
* @return {Array} | ||
*/ const getNodes = ()=>{ | ||
const lastNestedNode = nestedNodes.getLast(); | ||
if (lastNestedNode && Array.isArray(lastNestedNode.content)) { | ||
return lastNestedNode.content; | ||
} | ||
return nodes.toArray(); | ||
}; | ||
/** | ||
* @private | ||
* @param {string|TagNode} node | ||
*/ const appendNodes = (node)=>{ | ||
const items = getNodes(); | ||
if (Array.isArray(items)) { | ||
if (pluginHelper.isTagNode(node)) { | ||
if (isAllowedTag(node.tag)) { | ||
items.push(node.toTagNode()); | ||
} else { | ||
items.push(node.toTagStart({ | ||
openTag, | ||
closeTag | ||
})); | ||
if (node.content.length) { | ||
node.content.forEach((item)=>{ | ||
items.push(item); | ||
}); | ||
items.push(node.toTagEnd({ | ||
openTag, | ||
closeTag | ||
})); | ||
} | ||
} | ||
} else { | ||
items.push(node); | ||
} | ||
} | ||
}; | ||
/** | ||
* @private | ||
* @param {Token} token | ||
*/ const handleTagStart = (token)=>{ | ||
flushTagNodes(); | ||
const tagNode = pluginHelper.TagNode.create(token.getValue()); | ||
const isNested = isTokenNested(token); | ||
tagNodes.push(tagNode); | ||
if (isNested) { | ||
nestedNodes.push(tagNode); | ||
} else { | ||
appendNodes(tagNode); | ||
} | ||
}; | ||
/** | ||
* @private | ||
* @param {Token} token | ||
*/ const handleTagEnd = (token)=>{ | ||
flushTagNodes(); | ||
const lastNestedNode = nestedNodes.flushLast(); | ||
if (lastNestedNode) { | ||
appendNodes(lastNestedNode); | ||
} else if (typeof options.onError === 'function') { | ||
const tag = token.getValue(); | ||
const line = token.getLine(); | ||
const column = token.getColumn(); | ||
options.onError({ | ||
message: `Inconsistent tag '${tag}' on line ${line} and column ${column}`, | ||
tagName: tag, | ||
lineNumber: line, | ||
columnNumber: column | ||
}); | ||
} | ||
}; | ||
/** | ||
* @private | ||
* @param {Token} token | ||
*/ const handleTag = (token)=>{ | ||
// [tag] | ||
if (token.isStart()) { | ||
handleTagStart(token); | ||
} | ||
// [/tag] | ||
if (token.isEnd()) { | ||
handleTagEnd(token); | ||
} | ||
}; | ||
/** | ||
* @private | ||
* @param {Token} token | ||
*/ const handleNode = (token)=>{ | ||
/** | ||
* @type {TagNode} | ||
*/ const lastTagNode = tagNodes.getLast(); | ||
const tokenValue = token.getValue(); | ||
const isNested = isTagNested(token); | ||
if (lastTagNode) { | ||
if (token.isAttrName()) { | ||
tagNodesAttrName.push(tokenValue); | ||
lastTagNode.attr(tagNodesAttrName.getLast(), ''); | ||
} else if (token.isAttrValue()) { | ||
const attrName = tagNodesAttrName.getLast(); | ||
if (attrName) { | ||
lastTagNode.attr(attrName, tokenValue); | ||
tagNodesAttrName.flushLast(); | ||
} else { | ||
lastTagNode.attr(tokenValue, tokenValue); | ||
} | ||
} else if (token.isText()) { | ||
if (isNested) { | ||
lastTagNode.append(tokenValue); | ||
} else { | ||
appendNodes(tokenValue); | ||
} | ||
} else if (token.isTag()) { | ||
// if tag is not allowed, just past it as is | ||
appendNodes(token.toString()); | ||
} | ||
} else if (token.isText()) { | ||
appendNodes(tokenValue); | ||
} else if (token.isTag()) { | ||
// if tag is not allowed, just past it as is | ||
appendNodes(token.toString()); | ||
} | ||
}; | ||
/** | ||
* @private | ||
* @param {Token} token | ||
*/ const onToken = (token)=>{ | ||
if (token.isTag()) { | ||
handleTag(token); | ||
} else { | ||
handleNode(token); | ||
} | ||
}; | ||
tokenizer = (opts.createTokenizer ? opts.createTokenizer : createLexer)(input, { | ||
onToken, | ||
openTag, | ||
closeTag, | ||
onlyAllowTags: options.onlyAllowTags, | ||
contextFreeTags: options.contextFreeTags, | ||
enableEscapeTags: options.enableEscapeTags | ||
}); | ||
// eslint-disable-next-line no-unused-vars | ||
tokenizer.tokenize(); | ||
return nodes.toArray(); | ||
}; | ||
function CharGrabber(source, options) { | ||
const cursor = { | ||
pos: 0, | ||
len: source.length | ||
}; | ||
const substrUntilChar = (char)=>{ | ||
const { pos } = cursor; | ||
const idx = source.indexOf(char, pos); | ||
return idx >= 0 ? source.substring(pos, idx) : ''; | ||
}; | ||
const includes = (val)=>source.indexOf(val, cursor.pos) >= 0; | ||
const hasNext = ()=>cursor.len > cursor.pos; | ||
const isLast = ()=>cursor.pos === cursor.len; | ||
const skip = (num = 1, silent)=>{ | ||
cursor.pos += num; | ||
if (options && options.onSkip && !silent) { | ||
options.onSkip(); | ||
} | ||
}; | ||
const rest = ()=>source.substring(cursor.pos); | ||
const grabN = (num = 0)=>source.substring(cursor.pos, cursor.pos + num); | ||
const curr = ()=>source[cursor.pos]; | ||
const prev = ()=>{ | ||
const prevPos = cursor.pos - 1; | ||
return typeof source[prevPos] !== 'undefined' ? source[prevPos] : null; | ||
}; | ||
const next = ()=>{ | ||
const nextPos = cursor.pos + 1; | ||
return nextPos <= source.length - 1 ? source[nextPos] : null; | ||
}; | ||
const grabWhile = (cond, silent)=>{ | ||
let start = 0; | ||
if (hasNext()) { | ||
start = cursor.pos; | ||
while(hasNext() && cond(curr())){ | ||
skip(1, silent); | ||
} | ||
} | ||
return source.substring(start, cursor.pos); | ||
}; | ||
/** | ||
* @type {skip} | ||
*/ this.skip = skip; | ||
/** | ||
* @returns {Boolean} | ||
*/ this.hasNext = hasNext; | ||
/** | ||
* @returns {String} | ||
*/ this.getCurr = curr; | ||
/** | ||
* @returns {String} | ||
*/ this.getRest = rest; | ||
/** | ||
* @returns {String} | ||
*/ this.getNext = next; | ||
/** | ||
* @returns {String} | ||
*/ this.getPrev = prev; | ||
/** | ||
* @returns {Boolean} | ||
*/ this.isLast = isLast; | ||
/** | ||
* @returns {Boolean} | ||
*/ this.includes = includes; | ||
/** | ||
* @param {Function} cond | ||
* @param {Boolean} silent | ||
* @return {String} | ||
*/ this.grabWhile = grabWhile; | ||
/** | ||
* @param {Number} num | ||
* @return {String} | ||
*/ this.grabN = grabN; | ||
/** | ||
* Grabs rest of string until it find a char | ||
* @param {String} char | ||
* @return {String} | ||
*/ this.substrUntilChar = substrUntilChar; | ||
} | ||
/** | ||
* Creates a grabber wrapper for source string, that helps to iterate over string char by char | ||
* @param {String} source | ||
* @param {Object} options | ||
* @param {Function} options.onSkip | ||
* @return CharGrabber | ||
*/ const createCharGrabber = (source, options)=>new CharGrabber(source, options); | ||
/** | ||
* Trims string from start and end by char | ||
* @example | ||
* trimChar('*hello*', '*') ==> 'hello' | ||
* @param {String} str | ||
* @param {String} charToRemove | ||
* @returns {String} | ||
*/ const trimChar = (str, charToRemove)=>{ | ||
while(str.charAt(0) === charToRemove){ | ||
// eslint-disable-next-line no-param-reassign | ||
str = str.substring(1); | ||
} | ||
while(str.charAt(str.length - 1) === charToRemove){ | ||
// eslint-disable-next-line no-param-reassign | ||
str = str.substring(0, str.length - 1); | ||
} | ||
return str; | ||
}; | ||
/** | ||
* Unquotes \" to " | ||
* @param str | ||
* @return {String} | ||
*/ const unquote = (str)=>str.replace(BACKSLASH + QUOTEMARK, QUOTEMARK); | ||
function NodeList(values = []) { | ||
const nodes = values; | ||
const getLast = ()=>Array.isArray(nodes) && nodes.length > 0 && typeof nodes[nodes.length - 1] !== 'undefined' ? nodes[nodes.length - 1] : null; | ||
const flushLast = ()=>nodes.length ? nodes.pop() : false; | ||
const push = (value)=>nodes.push(value); | ||
const toArray = ()=>nodes; | ||
this.push = push; | ||
this.toArray = toArray; | ||
this.getLast = getLast; | ||
this.flushLast = flushLast; | ||
} | ||
/** | ||
* | ||
* @param values | ||
* @return {NodeList} | ||
*/ const createList = (values = [])=>new NodeList(values); | ||
Object.defineProperty(exports, 'TagNode', { | ||
enumerable: true, | ||
get: function () { return pluginHelper.TagNode; } | ||
}); | ||
exports.default = parse; | ||
exports.parse = parse; | ||
// for cases <!-- --> | ||
const EM = '!'; | ||
/** | ||
* Creates a Token entity class | ||
* @param {Number} type | ||
* @param {String} value | ||
* @param {Number} r line number | ||
* @param {Number} cl char number in line | ||
*/ const createToken = (type, value, r = 0, cl = 0)=>new Token(type, value, r, cl); | ||
/** | ||
* @typedef {Object} Lexer | ||
* @property {Function} tokenize | ||
* @property {Function} isTokenNested | ||
*/ /** | ||
* @param {String} buffer | ||
* @param {Object} options | ||
* @param {Function} options.onToken | ||
* @param {String} options.openTag | ||
* @param {String} options.closeTag | ||
* @param {Boolean} options.enableEscapeTags | ||
* @return {Lexer} | ||
*/ function createLexer(buffer, options = {}) { | ||
const STATE_WORD = 0; | ||
const STATE_TAG = 1; | ||
const STATE_TAG_ATTRS = 2; | ||
const TAG_STATE_NAME = 0; | ||
const TAG_STATE_ATTR = 1; | ||
const TAG_STATE_VALUE = 2; | ||
let row = 0; | ||
let col = 0; | ||
let tokenIndex = -1; | ||
let stateMode = STATE_WORD; | ||
let tagMode = TAG_STATE_NAME; | ||
let contextFreeTag = ''; | ||
const tokens = new Array(Math.floor(buffer.length)); | ||
const openTag = options.openTag || OPEN_BRAKET; | ||
const closeTag = options.closeTag || CLOSE_BRAKET; | ||
const escapeTags = !!options.enableEscapeTags; | ||
const contextFreeTags = options.contextFreeTags || []; | ||
const onToken = options.onToken || (()=>{}); | ||
const RESERVED_CHARS = [ | ||
closeTag, | ||
openTag, | ||
QUOTEMARK, | ||
BACKSLASH, | ||
SPACE, | ||
TAB, | ||
EQ, | ||
N, | ||
EM | ||
]; | ||
const NOT_CHAR_TOKENS = [ | ||
openTag, | ||
SPACE, | ||
TAB, | ||
N | ||
]; | ||
const WHITESPACES = [ | ||
SPACE, | ||
TAB | ||
]; | ||
const SPECIAL_CHARS = [ | ||
EQ, | ||
SPACE, | ||
TAB | ||
]; | ||
const isCharReserved = (char)=>RESERVED_CHARS.indexOf(char) >= 0; | ||
const isNewLine = (char)=>char === N; | ||
const isWhiteSpace = (char)=>WHITESPACES.indexOf(char) >= 0; | ||
const isCharToken = (char)=>NOT_CHAR_TOKENS.indexOf(char) === -1; | ||
const isSpecialChar = (char)=>SPECIAL_CHARS.indexOf(char) >= 0; | ||
const isEscapableChar = (char)=>char === openTag || char === closeTag || char === BACKSLASH; | ||
const isEscapeChar = (char)=>char === BACKSLASH; | ||
const onSkip = ()=>{ | ||
col++; | ||
}; | ||
const unq = (val)=>unquote(trimChar(val, QUOTEMARK)); | ||
const checkContextFreeMode = (name, isClosingTag)=>{ | ||
if (contextFreeTag !== '' && isClosingTag) { | ||
contextFreeTag = ''; | ||
} | ||
if (contextFreeTag === '' && contextFreeTags.includes(name)) { | ||
contextFreeTag = name; | ||
} | ||
}; | ||
const chars = createCharGrabber(buffer, { | ||
onSkip | ||
}); | ||
/** | ||
* Emits newly created token to subscriber | ||
* @param {Number} type | ||
* @param {String} value | ||
*/ function emitToken(type, value) { | ||
const token = createToken(type, value, row, col); | ||
onToken(token); | ||
tokenIndex += 1; | ||
tokens[tokenIndex] = token; | ||
} | ||
function nextTagState(tagChars, isSingleValueTag) { | ||
if (tagMode === TAG_STATE_ATTR) { | ||
const validAttrName = (char)=>!(char === EQ || isWhiteSpace(char)); | ||
const name = tagChars.grabWhile(validAttrName); | ||
const isEnd = tagChars.isLast(); | ||
const isValue = tagChars.getCurr() !== EQ; | ||
tagChars.skip(); | ||
if (isEnd || isValue) { | ||
emitToken(TYPE_ATTR_VALUE, unq(name)); | ||
} else { | ||
emitToken(TYPE_ATTR_NAME, name); | ||
} | ||
if (isEnd) { | ||
return TAG_STATE_NAME; | ||
} | ||
if (isValue) { | ||
return TAG_STATE_ATTR; | ||
} | ||
return TAG_STATE_VALUE; | ||
} | ||
if (tagMode === TAG_STATE_VALUE) { | ||
let stateSpecial = false; | ||
const validAttrValue = (char)=>{ | ||
// const isEQ = char === EQ; | ||
const isQM = char === QUOTEMARK; | ||
const prevChar = tagChars.getPrev(); | ||
const nextChar = tagChars.getNext(); | ||
const isPrevSLASH = prevChar === BACKSLASH; | ||
const isNextEQ = nextChar === EQ; | ||
const isWS = isWhiteSpace(char); | ||
// const isPrevWS = isWhiteSpace(prevChar); | ||
const isNextWS = isWhiteSpace(nextChar); | ||
if (stateSpecial && isSpecialChar(char)) { | ||
return true; | ||
} | ||
if (isQM && !isPrevSLASH) { | ||
stateSpecial = !stateSpecial; | ||
if (!stateSpecial && !(isNextEQ || isNextWS)) { | ||
return false; | ||
} | ||
} | ||
if (!isSingleValueTag) { | ||
return isWS === false; | ||
// return (isEQ || isWS) === false; | ||
} | ||
return true; | ||
}; | ||
const name1 = tagChars.grabWhile(validAttrValue); | ||
tagChars.skip(); | ||
emitToken(TYPE_ATTR_VALUE, unq(name1)); | ||
if (tagChars.isLast()) { | ||
return TAG_STATE_NAME; | ||
} | ||
return TAG_STATE_ATTR; | ||
} | ||
const validName = (char)=>!(char === EQ || isWhiteSpace(char) || tagChars.isLast()); | ||
const name2 = tagChars.grabWhile(validName); | ||
emitToken(TYPE_TAG, name2); | ||
checkContextFreeMode(name2); | ||
tagChars.skip(); | ||
// in cases when we has [url=someval]GET[/url] and we dont need to parse all | ||
if (isSingleValueTag) { | ||
return TAG_STATE_VALUE; | ||
} | ||
const hasEQ = tagChars.includes(EQ); | ||
return hasEQ ? TAG_STATE_ATTR : TAG_STATE_VALUE; | ||
} | ||
function stateTag() { | ||
const currChar = chars.getCurr(); | ||
const nextChar = chars.getNext(); | ||
chars.skip(); | ||
// detect case where we have '[My word [tag][/tag]' or we have '[My last line word' | ||
const substr = chars.substrUntilChar(closeTag); | ||
const hasInvalidChars = substr.length === 0 || substr.indexOf(openTag) >= 0; | ||
if (isCharReserved(nextChar) || hasInvalidChars || chars.isLast()) { | ||
emitToken(TYPE_WORD, currChar); | ||
return STATE_WORD; | ||
} | ||
// [myTag ] | ||
const isNoAttrsInTag = substr.indexOf(EQ) === -1; | ||
// [/myTag] | ||
const isClosingTag = substr[0] === SLASH; | ||
if (isNoAttrsInTag || isClosingTag) { | ||
const name = chars.grabWhile((char)=>char !== closeTag); | ||
chars.skip(); // skip closeTag | ||
emitToken(TYPE_TAG, name); | ||
checkContextFreeMode(name, isClosingTag); | ||
return STATE_WORD; | ||
} | ||
return STATE_TAG_ATTRS; | ||
} | ||
function stateAttrs() { | ||
const silent = true; | ||
const tagStr = chars.grabWhile((char)=>char !== closeTag, silent); | ||
const tagGrabber = createCharGrabber(tagStr, { | ||
onSkip | ||
}); | ||
const hasSpace = tagGrabber.includes(SPACE); | ||
tagMode = TAG_STATE_NAME; | ||
while(tagGrabber.hasNext()){ | ||
tagMode = nextTagState(tagGrabber, !hasSpace); | ||
} | ||
chars.skip(); // skip closeTag | ||
return STATE_WORD; | ||
} | ||
function stateWord() { | ||
if (isNewLine(chars.getCurr())) { | ||
emitToken(TYPE_NEW_LINE, chars.getCurr()); | ||
chars.skip(); | ||
col = 0; | ||
row++; | ||
return STATE_WORD; | ||
} | ||
if (isWhiteSpace(chars.getCurr())) { | ||
const word = chars.grabWhile(isWhiteSpace); | ||
emitToken(TYPE_SPACE, word); | ||
return STATE_WORD; | ||
} | ||
if (chars.getCurr() === openTag) { | ||
if (contextFreeTag) { | ||
const fullTagLen = openTag.length + SLASH.length + contextFreeTag.length; | ||
const fullTagName = `${openTag}${SLASH}${contextFreeTag}`; | ||
const foundTag = chars.grabN(fullTagLen); | ||
const isEndContextFreeMode = foundTag === fullTagName; | ||
if (isEndContextFreeMode) { | ||
return STATE_TAG; | ||
} | ||
} else if (chars.includes(closeTag)) { | ||
return STATE_TAG; | ||
} | ||
emitToken(TYPE_WORD, chars.getCurr()); | ||
chars.skip(); | ||
return STATE_WORD; | ||
} | ||
if (escapeTags) { | ||
if (isEscapeChar(chars.getCurr())) { | ||
const currChar = chars.getCurr(); | ||
const nextChar = chars.getNext(); | ||
chars.skip(); // skip the \ without emitting anything | ||
if (isEscapableChar(nextChar)) { | ||
chars.skip(); // skip past the [, ] or \ as well | ||
emitToken(TYPE_WORD, nextChar); | ||
return STATE_WORD; | ||
} | ||
emitToken(TYPE_WORD, currChar); | ||
return STATE_WORD; | ||
} | ||
const isChar = (char)=>isCharToken(char) && !isEscapeChar(char); | ||
const word1 = chars.grabWhile(isChar); | ||
emitToken(TYPE_WORD, word1); | ||
return STATE_WORD; | ||
} | ||
const word2 = chars.grabWhile(isCharToken); | ||
emitToken(TYPE_WORD, word2); | ||
return STATE_WORD; | ||
} | ||
function tokenize() { | ||
stateMode = STATE_WORD; | ||
while(chars.hasNext()){ | ||
switch(stateMode){ | ||
case STATE_TAG: | ||
stateMode = stateTag(); | ||
break; | ||
case STATE_TAG_ATTRS: | ||
stateMode = stateAttrs(); | ||
break; | ||
case STATE_WORD: | ||
default: | ||
stateMode = stateWord(); | ||
break; | ||
} | ||
} | ||
tokens.length = tokenIndex + 1; | ||
return tokens; | ||
} | ||
function isTokenNested(token) { | ||
const value = openTag + SLASH + token.getValue(); | ||
// potential bottleneck | ||
return buffer.indexOf(value) > -1; | ||
} | ||
return { | ||
tokenize, | ||
isTokenNested | ||
}; | ||
} | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
/** | ||
* @public | ||
* @param {String} input | ||
* @param {Object} opts | ||
* @param {Function} opts.createTokenizer | ||
* @param {Array<string>} opts.onlyAllowTags | ||
* @param {Array<string>} opts.contextFreeTags | ||
* @param {Boolean} opts.enableEscapeTags | ||
* @param {String} opts.openTag | ||
* @param {String} opts.closeTag | ||
* @return {Array} | ||
*/ const parse = (input, opts = {})=>{ | ||
const options = opts; | ||
const openTag = options.openTag || OPEN_BRAKET; | ||
const closeTag = options.closeTag || CLOSE_BRAKET; | ||
let tokenizer = null; | ||
/** | ||
* Result AST of nodes | ||
* @private | ||
* @type {NodeList} | ||
*/ const nodes = createList(); | ||
/** | ||
* Temp buffer of nodes that's nested to another node | ||
* @private | ||
* @type {NodeList} | ||
*/ const nestedNodes = createList(); | ||
/** | ||
* Temp buffer of nodes [tag..]...[/tag] | ||
* @private | ||
* @type {NodeList} | ||
*/ const tagNodes = createList(); | ||
/** | ||
* Temp buffer of tag attributes | ||
* @private | ||
* @type {NodeList} | ||
*/ const tagNodesAttrName = createList(); | ||
/** | ||
* Cache for nested tags checks | ||
*/ const nestedTagsMap = new Set(); | ||
/** | ||
* | ||
* @param token | ||
* @returns {boolean} | ||
*/ const isTokenNested = (token)=>{ | ||
const value = token.getValue(); | ||
if (!nestedTagsMap.has(value) && tokenizer.isTokenNested && tokenizer.isTokenNested(token)) { | ||
nestedTagsMap.add(value); | ||
return true; | ||
} | ||
return nestedTagsMap.has(value); | ||
}; | ||
/** | ||
* @param tagName | ||
* @returns {boolean} | ||
*/ const isTagNested = (tagName)=>Boolean(nestedTagsMap.has(tagName)); | ||
/** | ||
* @private | ||
* @param {String} value | ||
* @return {boolean} | ||
*/ const isAllowedTag = (value)=>{ | ||
if (options.onlyAllowTags && options.onlyAllowTags.length) { | ||
return options.onlyAllowTags.indexOf(value) >= 0; | ||
} | ||
return true; | ||
}; | ||
/** | ||
* Flushes temp tag nodes and its attributes buffers | ||
* @private | ||
* @return {Array} | ||
*/ const flushTagNodes = ()=>{ | ||
if (tagNodes.flushLast()) { | ||
tagNodesAttrName.flushLast(); | ||
} | ||
}; | ||
/** | ||
* @private | ||
* @return {Array} | ||
*/ const getNodes = ()=>{ | ||
const lastNestedNode = nestedNodes.getLast(); | ||
if (lastNestedNode && Array.isArray(lastNestedNode.content)) { | ||
return lastNestedNode.content; | ||
} | ||
return nodes.toArray(); | ||
}; | ||
/** | ||
* @private | ||
* @param {string|TagNode} node | ||
*/ const appendNodes = (node)=>{ | ||
const items = getNodes(); | ||
if (Array.isArray(items)) { | ||
if (isTagNode(node)) { | ||
if (isAllowedTag(node.tag)) { | ||
items.push(node.toTagNode()); | ||
} else { | ||
items.push(node.toTagStart({ | ||
openTag, | ||
closeTag | ||
})); | ||
if (node.content.length) { | ||
node.content.forEach((item)=>{ | ||
items.push(item); | ||
}); | ||
items.push(node.toTagEnd({ | ||
openTag, | ||
closeTag | ||
})); | ||
} | ||
} | ||
} else { | ||
items.push(node); | ||
} | ||
} | ||
}; | ||
/** | ||
* @private | ||
* @param {Token} token | ||
*/ const handleTagStart = (token)=>{ | ||
flushTagNodes(); | ||
const tagNode = TagNode.create(token.getValue()); | ||
const isNested = isTokenNested(token); | ||
tagNodes.push(tagNode); | ||
if (isNested) { | ||
nestedNodes.push(tagNode); | ||
} else { | ||
appendNodes(tagNode); | ||
} | ||
}; | ||
/** | ||
* @private | ||
* @param {Token} token | ||
*/ const handleTagEnd = (token)=>{ | ||
flushTagNodes(); | ||
const lastNestedNode = nestedNodes.flushLast(); | ||
if (lastNestedNode) { | ||
appendNodes(lastNestedNode); | ||
} else if (typeof options.onError === 'function') { | ||
const tag = token.getValue(); | ||
const line = token.getLine(); | ||
const column = token.getColumn(); | ||
options.onError({ | ||
message: `Inconsistent tag '${tag}' on line ${line} and column ${column}`, | ||
tagName: tag, | ||
lineNumber: line, | ||
columnNumber: column | ||
}); | ||
} | ||
}; | ||
/** | ||
* @private | ||
* @param {Token} token | ||
*/ const handleTag = (token)=>{ | ||
// [tag] | ||
if (token.isStart()) { | ||
handleTagStart(token); | ||
} | ||
// [/tag] | ||
if (token.isEnd()) { | ||
handleTagEnd(token); | ||
} | ||
}; | ||
/** | ||
* @private | ||
* @param {Token} token | ||
*/ const handleNode = (token)=>{ | ||
/** | ||
* @type {TagNode} | ||
*/ const lastTagNode = tagNodes.getLast(); | ||
const tokenValue = token.getValue(); | ||
const isNested = isTagNested(token); | ||
if (lastTagNode) { | ||
if (token.isAttrName()) { | ||
tagNodesAttrName.push(tokenValue); | ||
lastTagNode.attr(tagNodesAttrName.getLast(), ''); | ||
} else if (token.isAttrValue()) { | ||
const attrName = tagNodesAttrName.getLast(); | ||
if (attrName) { | ||
lastTagNode.attr(attrName, tokenValue); | ||
tagNodesAttrName.flushLast(); | ||
} else { | ||
lastTagNode.attr(tokenValue, tokenValue); | ||
} | ||
} else if (token.isText()) { | ||
if (isNested) { | ||
lastTagNode.append(tokenValue); | ||
} else { | ||
appendNodes(tokenValue); | ||
} | ||
} else if (token.isTag()) { | ||
// if tag is not allowed, just past it as is | ||
appendNodes(token.toString()); | ||
} | ||
} else if (token.isText()) { | ||
appendNodes(tokenValue); | ||
} else if (token.isTag()) { | ||
// if tag is not allowed, just past it as is | ||
appendNodes(token.toString()); | ||
} | ||
}; | ||
/** | ||
* @private | ||
* @param {Token} token | ||
*/ const onToken = (token)=>{ | ||
if (token.isTag()) { | ||
handleTag(token); | ||
} else { | ||
handleNode(token); | ||
} | ||
}; | ||
tokenizer = (opts.createTokenizer ? opts.createTokenizer : createLexer)(input, { | ||
onToken, | ||
openTag, | ||
closeTag, | ||
onlyAllowTags: options.onlyAllowTags, | ||
contextFreeTags: options.contextFreeTags, | ||
enableEscapeTags: options.enableEscapeTags | ||
}); | ||
// eslint-disable-next-line no-unused-vars | ||
tokenizer.tokenize(); | ||
return nodes.toArray(); | ||
}; | ||
exports.TagNode = TagNode; | ||
exports.default = parse; | ||
exports.parse = parse; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
})); |
@@ -1,1 +0,1 @@ | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).BbobParser={})}(this,function(t){"use strict";let e=t=>"object"==typeof t&&!!t.tag,r=t=>"string"==typeof t,n=(t,e,r)=>Object.keys(t).reduce(e,r),s=t=>e(t)?t.content.reduce((t,e)=>t+s(e),0):r(t)?t.length:0,i=(t,e)=>{t.content.push(e)},l=t=>t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/(javascript|data|vbscript):/gi,"$1%3A"),a=(t,e)=>{let r=typeof e,n={boolean:()=>e?""+t:"",number:()=>`${t}="${e}"`,string:()=>`${t}="${l(e)}"`,object:()=>`${t}="${l(JSON.stringify(e))}"`};return n[r]?n[r]():""},o=t=>null==t?"":n(t,(e,r)=>[...e,a(r,t[r])],[""]).join(" "),u=t=>n(t,(e,r)=>t[r]===r?t[r]:null,null),g=(t,e)=>{let r=u(e);if(r){let n=a(t,r),s={...e};delete s[r];let i=o(s);return`${n}${i}`}return`${t}${o(e)}`};class h{attr(t,e){return void 0!==e&&(this.attrs[t]=e),this.attrs[t]}append(t){return i(this,t)}get length(){return s(this)}toTagStart({openTag:t="[",closeTag:e="]"}={}){let r=g(this.tag,this.attrs);return`${t}${r}${e}`}toTagEnd({openTag:t="[",closeTag:e="]"}={}){return`${t}/${this.tag}${e}`}toTagNode(){return new h(this.tag.toLowerCase(),this.attrs,this.content)}toString({openTag:t="[",closeTag:e="]"}={}){let r=0===this.content.length,n=this.content.reduce((r,n)=>r+n.toString({openTag:t,closeTag:e}),""),s=this.toTagStart({openTag:t,closeTag:e});return r?s:`${s}${n}${this.toTagEnd({openTag:t,closeTag:e})}`}constructor(t,e,r){this.tag=t,this.attrs=e,this.content=Array.isArray(r)?r:[r]}}h.create=(t,e={},r=[])=>new h(t,e,r),h.isOf=(t,e)=>t.tag===e;let c="type",f="value",p="line",d=t=>t&&void 0!==t[f]?t[f]:"",b=t=>t&&t[p]||0,T=t=>t&&t.row||0,y=t=>!!t&&void 0!==t[c]&&(5===t[c]||6===t[c]||1===t[c]),N=t=>!!t&&void 0!==t[c]&&2===t[c],x=t=>47===d(t).charCodeAt(0),A=t=>!x(t),$=t=>!!t&&void 0!==t[c]&&3===t[c],k=t=>!!t&&void 0!==t[c]&&4===t[c],m=t=>{let e=d(t);return x(t)?e.slice(1):e},L=t=>d(t)+"]";class C{isEmpty(){return isNaN(this[c])}isText(){return y(this)}isTag(){return N(this)}isAttrName(){return $(this)}isAttrValue(){return k(this)}isStart(){return A(this)}isEnd(){return x(this)}getName(){return m(this)}getValue(){return d(this)}getLine(){return b(this)}getColumn(){return T(this)}toString(){return L(this)}constructor(t,e,r,n){this[c]=Number(t),this[f]=String(e),this[p]=Number(r),this.row=Number(n)}}function w(t,e){let r={pos:0,len:t.length},n=e=>{let{pos:n}=r,s=t.indexOf(e,n);return s>=0?t.substring(n,s):""},s=e=>t.indexOf(e,r.pos)>=0,i=()=>r.len>r.pos,l=(t=1,n)=>{r.pos+=t,e&&e.onSkip&&!n&&e.onSkip()},a=()=>t.substring(r.pos),o=(e=0)=>t.substring(r.pos,r.pos+e),u=()=>t[r.pos],g=()=>{let e=r.pos-1;return void 0!==t[e]?t[e]:null},h=()=>{let e=r.pos+1;return e<=t.length-1?t[e]:null},c=(e,n)=>{let s=0;if(i())for(s=r.pos;i()&&e(u());)l(1,n);return t.substring(s,r.pos)};this.skip=l,this.hasNext=i,this.getCurr=u,this.getRest=a,this.getNext=h,this.getPrev=g,this.isLast=()=>r.pos===r.len,this.includes=s,this.grabWhile=c,this.grabN=o,this.substrUntilChar=n}let S=(t,e)=>new w(t,e),v=(t,e)=>{for(;t.charAt(0)===e;)t=t.substring(1);for(;t.charAt(t.length-1)===e;)t=t.substring(0,t.length-1);return t},O=t=>t.replace('\\"','"');function E(t=[]){let e=()=>Array.isArray(t)&&t.length>0&&void 0!==t[t.length-1]?t[t.length-1]:null,r=()=>!!t.length&&t.pop(),n=e=>t.push(e);this.push=n,this.toArray=()=>t,this.getLast=e,this.flushLast=r}let W=(t=[])=>new E(t),V=(t,e,r=0,n=0)=>new C(t,e,r,n);function j(t,e={}){let r=0,n=0,s=-1,i=0,l=0,a="",o=Array(Math.floor(t.length)),u=e.openTag||"[",g=e.closeTag||"]",h=!!e.enableEscapeTags,c=e.contextFreeTags||[],f=e.onToken||(()=>{}),p=[g,u,'"',"\\"," "," ","=","\n","!"],d=[u," "," ","\n"],b=[" "," "],T=["="," "," "],y=t=>p.indexOf(t)>=0,N=t=>"\n"===t,x=t=>b.indexOf(t)>=0,A=t=>-1===d.indexOf(t),$=t=>T.indexOf(t)>=0,k=t=>t===u||t===g||"\\"===t,m=t=>"\\"===t,L=()=>{n++},C=t=>O(v(t,'"')),w=(t,e)=>{""!==a&&e&&(a=""),""===a&&c.includes(t)&&(a=t)},E=S(t,{onSkip:L});function W(t,e){let i=V(t,e,r,n);f(i),o[s+=1]=i}return{tokenize:function(){for(i=0;E.hasNext();)switch(i){case 1:i=function(){let t=E.getCurr(),e=E.getNext();E.skip();let r=E.substrUntilChar(g),n=0===r.length||r.indexOf(u)>=0;if(y(e)||n||E.isLast())return W(1,t),0;let s=-1===r.indexOf("="),i="/"===r[0];if(s||i){let l=E.grabWhile(t=>t!==g);return E.skip(),W(2,l),w(l,i),0}return 2}();break;case 2:i=function(){let t=E.grabWhile(t=>t!==g,!0),e=S(t,{onSkip:L}),r=e.includes(" ");for(l=0;e.hasNext();)l=function(t,e){if(1===l){let r=t=>!("="===t||x(t)),n=t.grabWhile(r),s=t.isLast(),i="="!==t.getCurr();return(t.skip(),s||i?W(4,C(n)):W(3,n),s)?0:i?1:2}if(2===l){let a=!1,o=r=>{let n=t.getPrev(),s=t.getNext(),i=x(r),l=x(s);return!!(a&&$(r))||('"'!==r||"\\"===n||!!(a=!a)||"="===s||!!l)&&(!!e||!1===i)},u=t.grabWhile(o);return(t.skip(),W(4,C(u)),t.isLast())?0:1}let g=e=>!("="===e||x(e)||t.isLast()),h=t.grabWhile(g);if(W(2,h),w(h),t.skip(),e)return 2;let c=t.includes("=");return c?1:2}(e,!r);return E.skip(),0}();break;default:i=function(){if(N(E.getCurr()))return W(6,E.getCurr()),E.skip(),n=0,r++,0;if(x(E.getCurr())){let t=E.grabWhile(x);return W(5,t),0}if(E.getCurr()===u){if(a){let e=u.length+1+a.length,s=`${u}/${a}`,i=E.grabN(e);if(i===s)return 1}else if(E.includes(g))return 1;return W(1,E.getCurr()),E.skip(),0}if(h){if(m(E.getCurr())){let l=E.getCurr(),o=E.getNext();return(E.skip(),k(o))?(E.skip(),W(1,o),0):(W(1,l),0)}let c=t=>A(t)&&!m(t),f=E.grabWhile(c);return W(1,f),0}let p=E.grabWhile(A);return W(1,p),0}()}return o.length=s+1,o},isTokenNested:function(e){let r=u+"/"+e.getValue();return t.indexOf(r)>-1}}}let z=(t,r={})=>{let n=r.openTag||"[",s=r.closeTag||"]",i=null,l=W(),a=W(),o=W(),u=W(),g=new Set,c=t=>{let e=t.getValue();return!g.has(e)&&i.isTokenNested&&i.isTokenNested(t)?(g.add(e),!0):g.has(e)},f=t=>Boolean(g.has(t)),p=t=>!r.onlyAllowTags||!r.onlyAllowTags.length||r.onlyAllowTags.indexOf(t)>=0,d=()=>{o.flushLast()&&u.flushLast()},b=()=>{let t=a.getLast();return t&&Array.isArray(t.content)?t.content:l.toArray()},T=t=>{let r=b();Array.isArray(r)&&(e(t)?p(t.tag)?r.push(t.toTagNode()):(r.push(t.toTagStart({openTag:n,closeTag:s})),t.content.length&&(t.content.forEach(t=>{r.push(t)}),r.push(t.toTagEnd({openTag:n,closeTag:s})))):r.push(t))},y=t=>{d();let e=h.create(t.getValue()),r=c(t);o.push(e),r?a.push(e):T(e)},N=t=>{d();let e=a.flushLast();if(e)T(e);else if("function"==typeof r.onError){let n=t.getValue(),s=t.getLine(),i=t.getColumn();r.onError({message:`Inconsistent tag '${n}' on line ${s} and column ${i}`,tagName:n,lineNumber:s,columnNumber:i})}},x=t=>{t.isStart()&&y(t),t.isEnd()&&N(t)},A=t=>{let e=o.getLast(),r=t.getValue(),n=f(t);if(e){if(t.isAttrName())u.push(r),e.attr(u.getLast(),"");else if(t.isAttrValue()){let s=u.getLast();s?(e.attr(s,r),u.flushLast()):e.attr(r,r)}else t.isText()?n?e.append(r):T(r):t.isTag()&&T(t.toString())}else t.isText()?T(r):t.isTag()&&T(t.toString())},$=t=>{t.isTag()?x(t):A(t)};return(i=(r.createTokenizer?r.createTokenizer:j)(t,{onToken:$,openTag:n,closeTag:s,onlyAllowTags:r.onlyAllowTags,contextFreeTags:r.contextFreeTags,enableEscapeTags:r.enableEscapeTags})).tokenize(),l.toArray()};t.TagNode=h,t.default=z,t.parse=z,Object.defineProperty(t,"__esModule",{value:!0})}); | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@bbob/plugin-helper")):"function"==typeof define&&define.amd?define(["exports","@bbob/plugin-helper"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).BbobParser={},e.pluginHelper)}(this,function(e,t){"use strict";let r="type",n="value",s="line",i=e=>e&&void 0!==e[n]?e[n]:"",l=e=>e&&e[s]||0,u=e=>e&&e.row||0,a=e=>!!e&&void 0!==e[r]&&(5===e[r]||6===e[r]||1===e[r]),o=e=>!!e&&void 0!==e[r]&&2===e[r],g=e=>i(e).charCodeAt(0)===t.SLASH.charCodeAt(0),h=e=>!g(e),f=e=>!!e&&void 0!==e[r]&&3===e[r],p=e=>!!e&&void 0!==e[r]&&4===e[r],A=e=>{let t=i(e);return g(e)?t.slice(1):t},c=e=>(t.OPEN_BRAKET,i(e)+t.CLOSE_BRAKET);class d{isEmpty(){return isNaN(this[r])}isText(){return a(this)}isTag(){return o(this)}isAttrName(){return f(this)}isAttrValue(){return p(this)}isStart(){return h(this)}isEnd(){return g(this)}getName(){return A(this)}getValue(){return i(this)}getLine(){return l(this)}getColumn(){return u(this)}toString(){return c(this)}constructor(e,t,i,l){this[r]=Number(e),this[n]=String(t),this[s]=Number(i),this.row=Number(l)}}function T(e,t){let r={pos:0,len:e.length},n=t=>{let{pos:n}=r,s=e.indexOf(t,n);return s>=0?e.substring(n,s):""},s=t=>e.indexOf(t,r.pos)>=0,i=()=>r.len>r.pos,l=(e=1,n)=>{r.pos+=e,t&&t.onSkip&&!n&&t.onSkip()},u=()=>e.substring(r.pos),a=(t=0)=>e.substring(r.pos,r.pos+t),o=()=>e[r.pos],g=()=>{let t=r.pos-1;return void 0!==e[t]?e[t]:null},h=()=>{let t=r.pos+1;return t<=e.length-1?e[t]:null},f=(t,n)=>{let s=0;if(i())for(s=r.pos;i()&&t(o());)l(1,n);return e.substring(s,r.pos)};this.skip=l,this.hasNext=i,this.getCurr=o,this.getRest=u,this.getNext=h,this.getPrev=g,this.isLast=()=>r.pos===r.len,this.includes=s,this.grabWhile=f,this.grabN=a,this.substrUntilChar=n}let b=(e,t)=>new T(e,t),E=(e,t)=>{for(;e.charAt(0)===t;)e=e.substring(1);for(;e.charAt(e.length-1)===t;)e=e.substring(0,e.length-1);return e},S=e=>e.replace(t.BACKSLASH+t.QUOTEMARK,t.QUOTEMARK);function N(e=[]){let t=()=>Array.isArray(e)&&e.length>0&&void 0!==e[e.length-1]?e[e.length-1]:null,r=()=>!!e.length&&e.pop(),n=t=>e.push(t);this.push=n,this.toArray=()=>e,this.getLast=t,this.flushLast=r}let L=(e=[])=>new N(e),C=(e,t,r=0,n=0)=>new d(e,t,r,n);function x(e,r={}){let n=0,s=0,i=-1,l=0,u=0,a="",o=Array(Math.floor(e.length)),g=r.openTag||t.OPEN_BRAKET,h=r.closeTag||t.CLOSE_BRAKET,f=!!r.enableEscapeTags,p=r.contextFreeTags||[],A=r.onToken||(()=>{}),c=[h,g,t.QUOTEMARK,t.BACKSLASH,t.SPACE,t.TAB,t.EQ,t.N,"!"],d=[g,t.SPACE,t.TAB,t.N],T=[t.SPACE,t.TAB],N=[t.EQ,t.SPACE,t.TAB],L=e=>c.indexOf(e)>=0,x=e=>e===t.N,y=e=>T.indexOf(e)>=0,k=e=>-1===d.indexOf(e),O=e=>N.indexOf(e)>=0,m=e=>e===g||e===h||e===t.BACKSLASH,B=e=>e===t.BACKSLASH,K=()=>{s++},P=e=>S(E(e,t.QUOTEMARK)),Q=(e,t)=>{""!==a&&t&&(a=""),""===a&&p.includes(e)&&(a=e)},w=b(e,{onSkip:K});function R(e,t){let r=C(e,t,n,s);A(r),o[i+=1]=r}return{tokenize:function(){for(l=0;w.hasNext();)switch(l){case 1:l=function(){let e=w.getCurr(),r=w.getNext();w.skip();let n=w.substrUntilChar(h),s=0===n.length||n.indexOf(g)>=0;if(L(r)||s||w.isLast())return R(1,e),0;let i=-1===n.indexOf(t.EQ),l=n[0]===t.SLASH;if(i||l){let u=w.grabWhile(e=>e!==h);return w.skip(),R(2,u),Q(u,l),0}return 2}();break;case 2:l=function(){let e=w.grabWhile(e=>e!==h,!0),r=b(e,{onSkip:K}),n=r.includes(t.SPACE);for(u=0;r.hasNext();)u=function(e,r){if(1===u){let n=e=>!(e===t.EQ||y(e)),s=e.grabWhile(n),i=e.isLast(),l=e.getCurr()!==t.EQ;return(e.skip(),i||l?R(4,P(s)):R(3,s),i)?0:l?1:2}if(2===u){let a=!1,o=n=>{let s=n===t.QUOTEMARK,i=e.getPrev(),l=e.getNext(),u=i===t.BACKSLASH,o=l===t.EQ,g=y(n),h=y(l);return!!(a&&O(n))||(!s||!!u||!!(a=!a)||!!o||!!h)&&(!!r||!1===g)},g=e.grabWhile(o);return(e.skip(),R(4,P(g)),e.isLast())?0:1}let h=r=>!(r===t.EQ||y(r)||e.isLast()),f=e.grabWhile(h);if(R(2,f),Q(f),e.skip(),r)return 2;let p=e.includes(t.EQ);return p?1:2}(r,!n);return w.skip(),0}();break;default:l=function(){if(x(w.getCurr()))return R(6,w.getCurr()),w.skip(),s=0,n++,0;if(y(w.getCurr())){let e=w.grabWhile(y);return R(5,e),0}if(w.getCurr()===g){if(a){let r=g.length+t.SLASH.length+a.length,i=`${g}${t.SLASH}${a}`,l=w.grabN(r);if(l===i)return 1}else if(w.includes(h))return 1;return R(1,w.getCurr()),w.skip(),0}if(f){if(B(w.getCurr())){let u=w.getCurr(),o=w.getNext();return(w.skip(),m(o))?(w.skip(),R(1,o),0):(R(1,u),0)}let p=e=>k(e)&&!B(e),A=w.grabWhile(p);return R(1,A),0}let c=w.grabWhile(k);return R(1,c),0}()}return o.length=i+1,o},isTokenNested:function(r){let n=g+t.SLASH+r.getValue();return e.indexOf(n)>-1}}}let y=(e,r={})=>{let n=r.openTag||t.OPEN_BRAKET,s=r.closeTag||t.CLOSE_BRAKET,i=null,l=L(),u=L(),a=L(),o=L(),g=new Set,h=e=>{let t=e.getValue();return!g.has(t)&&i.isTokenNested&&i.isTokenNested(e)?(g.add(t),!0):g.has(t)},f=e=>Boolean(g.has(e)),p=e=>!r.onlyAllowTags||!r.onlyAllowTags.length||r.onlyAllowTags.indexOf(e)>=0,A=()=>{a.flushLast()&&o.flushLast()},c=()=>{let e=u.getLast();return e&&Array.isArray(e.content)?e.content:l.toArray()},d=e=>{let r=c();Array.isArray(r)&&(t.isTagNode(e)?p(e.tag)?r.push(e.toTagNode()):(r.push(e.toTagStart({openTag:n,closeTag:s})),e.content.length&&(e.content.forEach(e=>{r.push(e)}),r.push(e.toTagEnd({openTag:n,closeTag:s})))):r.push(e))},T=e=>{A();let r=t.TagNode.create(e.getValue()),n=h(e);a.push(r),n?u.push(r):d(r)},b=e=>{A();let t=u.flushLast();if(t)d(t);else if("function"==typeof r.onError){let n=e.getValue(),s=e.getLine(),i=e.getColumn();r.onError({message:`Inconsistent tag '${n}' on line ${s} and column ${i}`,tagName:n,lineNumber:s,columnNumber:i})}},E=e=>{e.isStart()&&T(e),e.isEnd()&&b(e)},S=e=>{let t=a.getLast(),r=e.getValue(),n=f(e);if(t){if(e.isAttrName())o.push(r),t.attr(o.getLast(),"");else if(e.isAttrValue()){let s=o.getLast();s?(t.attr(s,r),o.flushLast()):t.attr(r,r)}else e.isText()?n?t.append(r):d(r):e.isTag()&&d(e.toString())}else e.isText()?d(r):e.isTag()&&d(e.toString())},N=e=>{e.isTag()?E(e):S(e)};return(i=(r.createTokenizer?r.createTokenizer:x)(e,{onToken:N,openTag:n,closeTag:s,onlyAllowTags:r.onlyAllowTags,contextFreeTags:r.contextFreeTags,enableEscapeTags:r.enableEscapeTags})).tokenize(),l.toArray()};Object.defineProperty(e,"TagNode",{enumerable:!0,get:function(){return t.TagNode}}),e.default=y,e.parse=y,Object.defineProperty(e,"__esModule",{value:!0})}); |
{ | ||
"name": "@bbob/parser", | ||
"version": "2.9.0", | ||
"version": "3.0.0", | ||
"description": "A BBCode to AST Parser part of @bbob", | ||
@@ -16,3 +16,3 @@ "keywords": [ | ||
"dependencies": { | ||
"@bbob/plugin-helper": "^2.9.0" | ||
"@bbob/plugin-helper": "^3.0.0" | ||
}, | ||
@@ -65,4 +65,3 @@ "main": "lib/index.js", | ||
"es" | ||
], | ||
"gitHead": "e5c741cb9a771ae5a7247ab73c2a2f90b8d83b28" | ||
] | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
138591
22
4018
1
+ Added@bbob/plugin-helper@3.0.2(transitive)
- Removed@bbob/plugin-helper@2.9.0(transitive)
Updated@bbob/plugin-helper@^3.0.0