@bbob/parser
Advanced tools
Comparing version 3.0.2 to 4.0.0
1680
dist/index.js
(function (global, factory) { | ||
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'; | ||
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'; | ||
// 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 N = '\n'; | ||
const TAB = '\t'; | ||
const EQ = '='; | ||
const QUOTEMARK = '"'; | ||
const SPACE = ' '; | ||
const OPEN_BRAKET = '['; | ||
const CLOSE_BRAKET = ']'; | ||
const SLASH = '/'; | ||
const BACKSLASH = '\\'; | ||
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); | ||
function isTagNode(el) { | ||
return typeof el === 'object' && el !== null && 'tag' in el; | ||
} | ||
function isStringNode(el) { | ||
return typeof el === 'string'; | ||
} | ||
function keysReduce(obj, reduce, def) { | ||
const keys = Object.keys(obj); | ||
return keys.reduce((acc, key)=>reduce(acc, key, obj), def); | ||
} | ||
function getNodeLength(node) { | ||
if (isTagNode(node) && Array.isArray(node.content)) { | ||
return node.content.reduce((count, contentNode)=>{ | ||
return count + getNodeLength(contentNode); | ||
}, 0); | ||
} | ||
if (isStringNode(node)) { | ||
return String(node).length; | ||
} | ||
return 0; | ||
} | ||
function appendToNode(node, value) { | ||
if (Array.isArray(node.content)) { | ||
node.content.push(value); | ||
} | ||
} | ||
/** | ||
* Replaces " to &qquot; | ||
* @param {string} value | ||
*/ function escapeAttrValue(value) { | ||
return 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'); | ||
} | ||
/** | ||
* Accept name and value and return valid html5 attribute string | ||
*/ function attrValue(name, value) { | ||
// in case of performance | ||
switch(typeof value){ | ||
case 'boolean': | ||
return value ? `${name}` : ''; | ||
case 'number': | ||
return `${name}="${value}"`; | ||
case 'string': | ||
return `${name}="${escapeAttrValue(value)}"`; | ||
case 'object': | ||
return `${name}="${escapeAttrValue(JSON.stringify(value))}"`; | ||
default: | ||
return ''; | ||
} | ||
} | ||
/** | ||
* Transforms attrs to html params string | ||
* @example | ||
* attrsToString({ 'foo': true, 'bar': bar' }) => 'foo="true" bar="bar"' | ||
*/ function attrsToString(values) { | ||
// To avoid some malformed attributes | ||
if (values == null) { | ||
return ''; | ||
} | ||
return keysReduce(values, (arr, key, obj)=>[ | ||
...arr, | ||
attrValue(key, obj[key]) | ||
], [ | ||
'' | ||
]).join(' '); | ||
} | ||
/** | ||
* Gets value from | ||
* @example | ||
* getUniqAttr({ 'foo': true, 'bar': bar' }) => 'bar' | ||
*/ function getUniqAttr(attrs) { | ||
return keysReduce(attrs || {}, (res, key, obj)=>obj[key] === key ? obj[key] : null, null); | ||
} | ||
// 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 | ||
}; | ||
} | ||
const getTagAttrs = (tag, params)=>{ | ||
const uniqAttr = getUniqAttr(params); | ||
if (uniqAttr) { | ||
const tagAttr = attrValue(tag, uniqAttr); | ||
const attrs = { | ||
...params | ||
}; | ||
delete attrs[String(uniqAttr)]; | ||
const attrsStr = attrsToString(attrs); | ||
return `${tagAttr}${attrsStr}`; | ||
} | ||
return `${tag}${attrsToString(params)}`; | ||
}; | ||
const renderContent = (content, openTag, closeTag)=>{ | ||
const toString = (node)=>{ | ||
if (isTagNode(node)) { | ||
return node.toString({ | ||
openTag, | ||
closeTag | ||
}); | ||
} | ||
return String(node); | ||
}; | ||
if (Array.isArray(content)) { | ||
return content.reduce((r, node)=>{ | ||
if (node !== null) { | ||
return r + toString(node); | ||
} | ||
return r; | ||
}, ''); | ||
} | ||
if (content) { | ||
return toString(content); | ||
} | ||
return null; | ||
}; | ||
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 content = this.content ? renderContent(this.content, openTag, closeTag) : ''; | ||
const tagStart = this.toTagStart({ | ||
openTag, | ||
closeTag | ||
}); | ||
if (this.content === null || Array.isArray(this.content) && this.content.length === 0) { | ||
return tagStart; | ||
} | ||
return `${tagStart}${content}${this.toTagEnd({ | ||
openTag, | ||
closeTag | ||
})}`; | ||
} | ||
static create(tag, attrs = {}, content = null) { | ||
return new TagNode(tag, attrs, content); | ||
} | ||
static isOf(node, type) { | ||
return node.tag === type; | ||
} | ||
constructor(tag, attrs, content){ | ||
this.tag = tag; | ||
this.attrs = attrs; | ||
this.content = content; | ||
} | ||
} | ||
/** | ||
* @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<string|TagNode>} | ||
*/ const parse = (input, opts = {})=>{ | ||
const options = opts; | ||
const openTag = options.openTag || pluginHelper.OPEN_BRAKET; | ||
const closeTag = options.closeTag || pluginHelper.CLOSE_BRAKET; | ||
const onlyAllowTags = (options.onlyAllowTags || []).filter(Boolean).map((tag)=>tag.toLowerCase()); | ||
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 | ||
* @type Set<string> | ||
*/ const nestedTagsMap = new Set(); | ||
/** | ||
* @param {Token} 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); | ||
}; | ||
/** | ||
* @private | ||
* @param {string} tagName | ||
* @returns {boolean} | ||
*/ const isTagNested = (tagName)=>Boolean(nestedTagsMap.has(tagName)); | ||
/** | ||
* @private | ||
* @param {string} value | ||
* @return {boolean} | ||
*/ const isAllowedTag = (value)=>{ | ||
if (onlyAllowTags.length) { | ||
return onlyAllowTags.indexOf(value.toLowerCase()) >= 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 | ||
* @param {boolean} isNested | ||
*/ const appendNodeAsString = (node, isNested = true)=>{ | ||
const items = getNodes(); | ||
if (Array.isArray(items)) { | ||
items.push(node.toTagStart({ | ||
openTag, | ||
closeTag | ||
})); | ||
if (node.content.length) { | ||
node.content.forEach((item)=>{ | ||
items.push(item); | ||
}); | ||
if (isNested) { | ||
items.push(node.toTagEnd({ | ||
openTag, | ||
closeTag | ||
})); | ||
} | ||
} | ||
} | ||
}; | ||
/** | ||
* @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 { | ||
appendNodeAsString(node); | ||
} | ||
} 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(); | ||
// 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 | ||
const lastNestedNode = nestedNodes.flushLast(); | ||
if (lastNestedNode && isTagNested(lastNestedNode.tag)) { | ||
appendNodeAsString(lastNestedNode, false); | ||
} | ||
return nodes.toArray(); | ||
}; | ||
// type, value, line, row, | ||
const TOKEN_TYPE_ID = 't'; // 0; | ||
const TOKEN_VALUE_ID = 'v'; // 1; | ||
const TOKEN_COLUMN_ID = 'r'; // 2; | ||
const TOKEN_LINE_ID = 'l'; // 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'; | ||
const getTokenValue = (token)=>{ | ||
if (token && typeof token[TOKEN_VALUE_ID] !== 'undefined') { | ||
return token[TOKEN_VALUE_ID]; | ||
} | ||
return ''; | ||
}; | ||
const getTokenLine = (token)=>token && token[TOKEN_LINE_ID] || 0; | ||
const getTokenColumn = (token)=>token && token[TOKEN_COLUMN_ID] || 0; | ||
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; | ||
}; | ||
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; | ||
}; | ||
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 tokenToText = (token)=>{ | ||
let text = OPEN_BRAKET; | ||
text += getTokenValue(token); | ||
text += CLOSE_BRAKET; | ||
return text; | ||
}; | ||
/** | ||
* @export | ||
* @class Token | ||
*/ class Token { | ||
get type() { | ||
return this[TOKEN_TYPE_ID]; | ||
} | ||
isEmpty() { | ||
return this[TOKEN_TYPE_ID] === 0 || 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 tokenToText(this); | ||
} | ||
constructor(type, value, row = 0, col = 0){ | ||
this[TOKEN_LINE_ID] = row; | ||
this[TOKEN_COLUMN_ID] = col; | ||
this[TOKEN_TYPE_ID] = type || 0; | ||
this[TOKEN_VALUE_ID] = String(value); | ||
} | ||
} | ||
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; | ||
Object.defineProperty(exports, 'TagNode', { | ||
enumerable: true, | ||
get: function () { return pluginHelper.TagNode; } | ||
}); | ||
exports.default = parse; | ||
exports.parse = parse; | ||
class CharGrabber { | ||
skip(num = 1, silent) { | ||
this.c.pos += num; | ||
if (this.o && this.o.onSkip && !silent) { | ||
this.o.onSkip(); | ||
} | ||
} | ||
hasNext() { | ||
return this.c.len > this.c.pos; | ||
} | ||
getCurr() { | ||
if (typeof this.s[this.c.pos] === 'undefined') { | ||
return ''; | ||
} | ||
return this.s[this.c.pos]; | ||
} | ||
getRest() { | ||
return this.s.substring(this.c.pos); | ||
} | ||
getNext() { | ||
const nextPos = this.c.pos + 1; | ||
return nextPos <= this.s.length - 1 ? this.s[nextPos] : null; | ||
} | ||
getPrev() { | ||
const prevPos = this.c.pos - 1; | ||
if (typeof this.s[prevPos] === 'undefined') { | ||
return null; | ||
} | ||
return this.s[prevPos]; | ||
} | ||
isLast() { | ||
return this.c.pos === this.c.len; | ||
} | ||
includes(val) { | ||
return this.s.indexOf(val, this.c.pos) >= 0; | ||
} | ||
grabWhile(condition, silent) { | ||
let start = 0; | ||
if (this.hasNext()) { | ||
start = this.c.pos; | ||
while(this.hasNext() && condition(this.getCurr())){ | ||
this.skip(1, silent); | ||
} | ||
} | ||
return this.s.substring(start, this.c.pos); | ||
} | ||
grabN(num = 0) { | ||
return this.s.substring(this.c.pos, this.c.pos + num); | ||
} | ||
/** | ||
* Grabs rest of string until it find a char | ||
*/ substrUntilChar(char) { | ||
const { pos } = this.c; | ||
const idx = this.s.indexOf(char, pos); | ||
return idx >= 0 ? this.s.substring(pos, idx) : ''; | ||
} | ||
constructor(source, options = {}){ | ||
this.s = source; | ||
this.c = { | ||
pos: 0, | ||
len: source.length | ||
}; | ||
this.o = options; | ||
} | ||
} | ||
/** | ||
* Creates a grabber wrapper for source string, that helps to iterate over string char by char | ||
*/ const createCharGrabber = (source, options)=>new CharGrabber(source, options); | ||
/** | ||
* Trims string from start and end by char | ||
* @example | ||
* trimChar('*hello*', '*') ==> 'hello' | ||
*/ 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 " | ||
*/ const unquote = (str)=>str.replace(BACKSLASH + QUOTEMARK, QUOTEMARK); | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
// for cases <!-- --> | ||
const EM = '!'; | ||
function createTokenOfType(type, value, r = 0, cl = 0) { | ||
return new Token(type, value, r, cl); | ||
} | ||
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; | ||
const WHITESPACES = [ | ||
SPACE, | ||
TAB | ||
]; | ||
const SPECIAL_CHARS = [ | ||
EQ, | ||
SPACE, | ||
TAB | ||
]; | ||
const isWhiteSpace = (char)=>WHITESPACES.indexOf(char) >= 0; | ||
const isEscapeChar = (char)=>char === BACKSLASH; | ||
const isSpecialChar = (char)=>SPECIAL_CHARS.indexOf(char) >= 0; | ||
const isNewLine = (char)=>char === N; | ||
const unq = (val)=>unquote(trimChar(val, QUOTEMARK)); | ||
function createLexer(buffer, options = {}) { | ||
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 || []).filter(Boolean).map((tag)=>tag.toLowerCase()); | ||
const nestedMap = new Map(); | ||
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 isCharReserved = (char)=>RESERVED_CHARS.indexOf(char) >= 0; | ||
const isCharToken = (char)=>NOT_CHAR_TOKENS.indexOf(char) === -1; | ||
const isEscapableChar = (char)=>char === openTag || char === closeTag || char === BACKSLASH; | ||
const onSkip = ()=>{ | ||
col++; | ||
}; | ||
const checkContextFreeMode = (name, isClosingTag)=>{ | ||
if (contextFreeTag !== '' && isClosingTag) { | ||
contextFreeTag = ''; | ||
} | ||
if (contextFreeTag === '' && contextFreeTags.includes(name.toLowerCase())) { | ||
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 = createTokenOfType(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 = nextChar && isWhiteSpace(nextChar); | ||
if (stateSpecial && isSpecialChar(char)) { | ||
return true; | ||
} | ||
if (isQM && !isPrevSLASH) { | ||
stateSpecial = !stateSpecial; | ||
if (!stateSpecial && !(isNextEQ || isNextWS)) { | ||
return false; | ||
} | ||
} | ||
if (!isSingleValueTag) { | ||
return !isWS; | ||
// return (isEQ || isWS) === false; | ||
} | ||
return true; | ||
}; | ||
const name = tagChars.grabWhile(validAttrValue); | ||
tagChars.skip(); | ||
emitToken(TYPE_ATTR_VALUE, unq(name)); | ||
if (tagChars.isLast()) { | ||
return TAG_STATE_NAME; | ||
} | ||
return TAG_STATE_ATTR; | ||
} | ||
const validName = (char)=>!(char === EQ || isWhiteSpace(char) || tagChars.isLast()); | ||
const name = tagChars.grabWhile(validName); | ||
emitToken(TYPE_TAG, name); | ||
checkContextFreeMode(name); | ||
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 (nextChar && 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 (nextChar && 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 word = chars.grabWhile(isChar); | ||
emitToken(TYPE_WORD, word); | ||
return STATE_WORD; | ||
} | ||
const word = chars.grabWhile(isCharToken); | ||
emitToken(TYPE_WORD, word); | ||
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(); | ||
if (nestedMap.has(value)) { | ||
return !!nestedMap.get(value); | ||
} else { | ||
const status = buffer.indexOf(value) > -1; | ||
nestedMap.set(value, status); | ||
return status; | ||
} | ||
} | ||
return { | ||
tokenize, | ||
isTokenNested | ||
}; | ||
} | ||
class NodeList { | ||
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; | ||
} | ||
flush() { | ||
return this.n.length ? this.n.pop() : false; | ||
} | ||
push(value) { | ||
this.n.push(value); | ||
} | ||
toArray() { | ||
return this.n; | ||
} | ||
constructor(){ | ||
this.n = []; | ||
} | ||
} | ||
const createList = ()=>new NodeList(); | ||
function parse(input, opts = {}) { | ||
const options = opts; | ||
const openTag = options.openTag || OPEN_BRAKET; | ||
const closeTag = options.closeTag || CLOSE_BRAKET; | ||
const onlyAllowTags = (options.onlyAllowTags || []).filter(Boolean).map((tag)=>tag.toLowerCase()); | ||
let tokenizer = null; | ||
/** | ||
* Result AST of nodes | ||
* @private | ||
* @type {NodeList} | ||
*/ const nodes = createList(); | ||
/** | ||
* Temp buffer of nodes that's nested to another node | ||
* @private | ||
*/ 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(); | ||
function isTokenNested(token) { | ||
const value = token.getValue(); | ||
const { isTokenNested } = tokenizer || {}; | ||
if (!nestedTagsMap.has(value) && isTokenNested && isTokenNested(token)) { | ||
nestedTagsMap.add(value); | ||
return true; | ||
} | ||
return nestedTagsMap.has(value); | ||
} | ||
/** | ||
* @private | ||
*/ function isTagNested(tagName) { | ||
return Boolean(nestedTagsMap.has(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() { | ||
const lastNestedNode = nestedNodes.last(); | ||
if (lastNestedNode && isTagNode(lastNestedNode)) { | ||
return lastNestedNode.content; | ||
} | ||
return nodes.toArray(); | ||
} | ||
/** | ||
* @private | ||
*/ function appendNodeAsString(nodes, node, isNested = true) { | ||
if (Array.isArray(nodes) && typeof node !== "undefined") { | ||
nodes.push(node.toTagStart({ | ||
openTag, | ||
closeTag | ||
})); | ||
if (Array.isArray(node.content) && node.content.length) { | ||
node.content.forEach((item)=>{ | ||
nodes.push(item); | ||
}); | ||
if (isNested) { | ||
nodes.push(node.toTagEnd({ | ||
openTag, | ||
closeTag | ||
})); | ||
} | ||
} | ||
} | ||
} | ||
/** | ||
* @private | ||
*/ function appendNodes(nodes, node) { | ||
if (Array.isArray(nodes) && typeof node !== "undefined") { | ||
if (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(); | ||
const tagNode = TagNode.create(token.getValue(), {}, []); | ||
const isNested = isTokenNested(token); | ||
tagNodes.push(tagNode); | ||
if (isNested) { | ||
nestedNodes.push(tagNode); | ||
} else { | ||
const nodes = getNodes(); | ||
appendNodes(nodes, tagNode); | ||
} | ||
} | ||
/** | ||
* @private | ||
* @param {Token} token | ||
*/ function handleTagEnd(token) { | ||
flushTagNodes(); | ||
const lastNestedNode = nestedNodes.flush(); | ||
if (lastNestedNode) { | ||
const nodes = getNodes(); | ||
appendNodes(nodes, lastNestedNode); | ||
} else if (typeof options.onError === "function") { | ||
const tag = token.getValue(); | ||
const line = token.getLine(); | ||
const 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} | ||
*/ const activeTagNode = tagNodes.last(); | ||
const tokenValue = token.getValue(); | ||
const isNested = isTagNested(token.toString()); | ||
const nodes = getNodes(); | ||
if (activeTagNode !== null) { | ||
if (token.isAttrName()) { | ||
tagNodesAttrName.push(tokenValue); | ||
const attrName = tagNodesAttrName.last(); | ||
if (attrName) { | ||
activeTagNode.attr(attrName, ""); | ||
} | ||
} else if (token.isAttrValue()) { | ||
const attrName = tagNodesAttrName.last(); | ||
if (attrName) { | ||
activeTagNode.attr(attrName, 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()); | ||
} | ||
} 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()); | ||
} | ||
} | ||
/** | ||
* @private | ||
* @param {Token} token | ||
*/ function onToken(token) { | ||
if (token.isTag()) { | ||
handleTag(token); | ||
} else { | ||
handleNode(token); | ||
} | ||
} | ||
const lexer = opts.createTokenizer ? opts.createTokenizer : createLexer; | ||
tokenizer = lexer(input, { | ||
onToken, | ||
openTag, | ||
closeTag, | ||
onlyAllowTags: options.onlyAllowTags, | ||
contextFreeTags: options.contextFreeTags, | ||
enableEscapeTags: options.enableEscapeTags | ||
}); | ||
// eslint-disable-next-line no-unused-vars | ||
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 | ||
const lastNestedNode = nestedNodes.flush(); | ||
if (lastNestedNode !== null && lastNestedNode && isTagNode(lastNestedNode) && isTagNested(lastNestedNode.tag)) { | ||
appendNodeAsString(getNodes(), lastNestedNode, false); | ||
} | ||
return nodes.toArray(); | ||
} | ||
exports.TagNode = TagNode; | ||
exports.createLexer = createLexer; | ||
exports.createTokenOfType = createTokenOfType; | ||
exports.default = parse; | ||
exports.parse = parse; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
})); |
@@ -1,1 +0,1 @@ | ||
!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,a=e=>e&&e.row||0,u=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 u(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 a(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()},a=()=>e.substring(r.pos),u=(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=a,this.getNext=h,this.getPrev=g,this.isLast=()=>r.pos===r.len,this.includes=s,this.grabWhile=f,this.grabN=u,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,a=0,u="",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)=>{""!==u&&t&&(u=""),""===u&&p.includes(e)&&(u=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 a=w.grabWhile(e=>e!==h);return w.skip(),R(2,a),Q(a,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(a=0;r.hasNext();)a=function(e,r){if(1===a){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===a){let u=!1,o=n=>{let s=n===t.QUOTEMARK,i=e.getPrev(),l=e.getNext(),a=i===t.BACKSLASH,o=l===t.EQ,g=y(n),h=y(l);return!!(u&&O(n))||(!s||!!a||!!(u=!u)||!!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(u){let r=g.length+t.SLASH.length+u.length,i=`${g}${t.SLASH}${u}`,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 a=w.getCurr(),o=w.getNext();return(w.skip(),m(o))?(w.skip(),R(1,o),0):(R(1,a),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=(r.onlyAllowTags||[]).filter(Boolean).map(e=>e.toLowerCase()),l=null,a=L(),u=L(),o=L(),g=L(),h=new Set,f=e=>{let t=e.getValue();return!h.has(t)&&l.isTokenNested&&l.isTokenNested(e)?(h.add(t),!0):h.has(t)},p=e=>Boolean(h.has(e)),A=e=>!i.length||i.indexOf(e.toLowerCase())>=0,c=()=>{o.flushLast()&&g.flushLast()},d=()=>{let e=u.getLast();return e&&Array.isArray(e.content)?e.content:a.toArray()},T=(e,t=!0)=>{let r=d();Array.isArray(r)&&(r.push(e.toTagStart({openTag:n,closeTag:s})),e.content.length&&(e.content.forEach(e=>{r.push(e)}),t&&r.push(e.toTagEnd({openTag:n,closeTag:s}))))},b=e=>{let r=d();Array.isArray(r)&&(t.isTagNode(e)?A(e.tag)?r.push(e.toTagNode()):T(e):r.push(e))},E=e=>{c();let r=t.TagNode.create(e.getValue()),n=f(e);o.push(r),n?u.push(r):b(r)},S=e=>{c();let t=u.flushLast();if(t)b(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})}},N=e=>{e.isStart()&&E(e),e.isEnd()&&S(e)},C=e=>{let t=o.getLast(),r=e.getValue(),n=p(e);if(t){if(e.isAttrName())g.push(r),t.attr(g.getLast(),"");else if(e.isAttrValue()){let s=g.getLast();s?(t.attr(s,r),g.flushLast()):t.attr(r,r)}else e.isText()?n?t.append(r):b(r):e.isTag()&&b(e.toString())}else e.isText()?b(r):e.isTag()&&b(e.toString())},y=e=>{e.isTag()?N(e):C(e)};(l=(r.createTokenizer?r.createTokenizer:x)(e,{onToken:y,openTag:n,closeTag:s,onlyAllowTags:r.onlyAllowTags,contextFreeTags:r.contextFreeTags,enableEscapeTags:r.enableEscapeTags})).tokenize();let k=u.flushLast();return k&&p(k.tag)&&T(k,!1),a.toArray()};Object.defineProperty(e,"TagNode",{enumerable:!0,get:function(){return t.TagNode}}),e.default=y,e.parse=y,Object.defineProperty(e,"__esModule",{value:!0})}); | ||
!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";function e(t){return"object"==typeof t&&null!==t&&"tag"in t}function r(t,e,r){return Object.keys(t).reduce((r,n)=>e(r,n,t),r)}function n(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/(javascript|data|vbscript):/gi,"$1%3A")}function s(t,e){switch(typeof e){case"boolean":return e?""+t:"";case"number":return`${t}="${e}"`;case"string":return`${t}="${n(e)}"`;case"object":return`${t}="${n(JSON.stringify(e))}"`;default:return""}}function i(t){return null==t?"":r(t,(t,e,r)=>[...t,s(e,r[e])],[""]).join(" ")}let u=(t,e)=>{let n=r(e||{},(t,e,r)=>r[e]===e?r[e]:null,null);if(n){let r=s(t,n),u={...e};delete u[n+""];let a=i(u);return`${r}${a}`}return`${t}${i(e)}`},a=(t,r,n)=>{let s=t=>e(t)?t.toString({openTag:r,closeTag:n}):t+"";return Array.isArray(t)?t.reduce((t,e)=>null!==e?t+s(e):t,""):t?s(t):null};class l{attr(t,e){return void 0!==e&&(this.attrs[t]=e),this.attrs[t]}append(t){Array.isArray(this.content)&&this.content.push(t)}get length(){return function t(r){return e(r)&&Array.isArray(r.content)?r.content.reduce((e,r)=>e+t(r),0):"string"==typeof r?(r+"").length:0}(this)}toTagStart({openTag:t="[",closeTag:e="]"}={}){let r=u(this.tag,this.attrs);return`${t}${r}${e}`}toTagEnd({openTag:t="[",closeTag:e="]"}={}){return`${t}/${this.tag}${e}`}toTagNode(){return new l(this.tag.toLowerCase(),this.attrs,this.content)}toString({openTag:t="[",closeTag:e="]"}={}){let r=this.content?a(this.content,t,e):"",n=this.toTagStart({openTag:t,closeTag:e});return null===this.content||Array.isArray(this.content)&&0===this.content.length?n:`${n}${r}${this.toTagEnd({openTag:t,closeTag:e})}`}static create(t,e={},r=null){return new l(t,e,r)}static isOf(t,e){return t.tag===e}constructor(t,e,r){this.tag=t,this.attrs=e,this.content=r}}let o=t=>t&&void 0!==t.v?t.v:"",h=t=>t&&t.l||0,c=t=>t&&t.r||0,g=t=>!!t&&void 0!==t.t&&(5===t.t||6===t.t||1===t.t),f=t=>!!t&&void 0!==t.t&&2===t.t,p=t=>47===o(t).charCodeAt(0),d=t=>!p(t),b=t=>!!t&&void 0!==t.t&&3===t.t,T=t=>!!t&&void 0!==t.t&&4===t.t,y=t=>{let e=o(t);return p(t)?e.slice(1):e},A=t=>"["+o(t)+"]";class x{get type(){return this.t}isEmpty(){return 0===this.t||isNaN(this.t)}isText(){return g(this)}isTag(){return f(this)}isAttrName(){return b(this)}isAttrValue(){return T(this)}isStart(){return d(this)}isEnd(){return p(this)}getName(){return y(this)}getValue(){return o(this)}getLine(){return h(this)}getColumn(){return c(this)}toString(){return A(this)}constructor(t,e,r=0,n=0){this.l=r,this.r=n,this.t=t||0,this.v=e+""}}class k{skip(t=1,e){this.c.pos+=t,this.o&&this.o.onSkip&&!e&&this.o.onSkip()}hasNext(){return this.c.len>this.c.pos}getCurr(){return void 0===this.s[this.c.pos]?"":this.s[this.c.pos]}getRest(){return this.s.substring(this.c.pos)}getNext(){let t=this.c.pos+1;return t<=this.s.length-1?this.s[t]:null}getPrev(){let t=this.c.pos-1;return void 0===this.s[t]?null:this.s[t]}isLast(){return this.c.pos===this.c.len}includes(t){return this.s.indexOf(t,this.c.pos)>=0}grabWhile(t,e){let r=0;if(this.hasNext())for(r=this.c.pos;this.hasNext()&&t(this.getCurr());)this.skip(1,e);return this.s.substring(r,this.c.pos)}grabN(t=0){return this.s.substring(this.c.pos,this.c.pos+t)}substrUntilChar(t){let{pos:e}=this.c,r=this.s.indexOf(t,e);return r>=0?this.s.substring(e,r):""}constructor(t,e={}){this.s=t,this.c={pos:0,len:t.length},this.o=e}}let N=(t,e)=>new k(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},$=t=>t.replace('\\"','"');function C(t,e,r=0,n=0){return new x(t,e,r,n)}let w=[" "," "],m=["="," "," "],O=t=>w.indexOf(t)>=0,S=t=>"\\"===t,L=t=>m.indexOf(t)>=0,E=t=>"\n"===t,W=t=>$(v(t,'"'));function V(t,e={}){let r=0,n=0,s=-1,i=0,u=0,a="",l=Array(Math.floor(t.length)),o=e.openTag||"[",h=e.closeTag||"]",c=!!e.enableEscapeTags,g=(e.contextFreeTags||[]).filter(Boolean).map(t=>t.toLowerCase()),f=new Map,p=e.onToken||(()=>{}),d=[h,o,'"',"\\"," "," ","=","\n","!"],b=[o," "," ","\n"],T=t=>d.indexOf(t)>=0,y=t=>-1===b.indexOf(t),A=t=>t===o||t===h||"\\"===t,x=()=>{n++},k=(t,e)=>{""!==a&&e&&(a=""),""===a&&g.includes(t.toLowerCase())&&(a=t)},v=N(t,{onSkip:x});function $(t,e){let i=C(t,e,r,n);p(i),l[s+=1]=i}return{tokenize:function(){for(i=0;v.hasNext();)switch(i){case 1:i=function(){let t=v.getCurr(),e=v.getNext();v.skip();let r=v.substrUntilChar(h),n=0===r.length||r.indexOf(o)>=0;if(e&&T(e)||n||v.isLast())return $(1,t),0;let s=-1===r.indexOf("="),i="/"===r[0];if(s||i){let t=v.grabWhile(t=>t!==h);return v.skip(),$(2,t),k(t,i),0}return 2}();break;case 2:i=function(){let t=N(v.grabWhile(t=>t!==h,!0),{onSkip:x}),e=t.includes(" ");for(u=0;t.hasNext();)u=function(t,e){if(1===u){let e=t.grabWhile(t=>!("="===t||O(t))),r=t.isLast(),n="="!==t.getCurr();return(t.skip(),r||n?$(4,W(e)):$(3,e),r)?0:n?1:2}if(2===u){let r=!1,n=t.grabWhile(n=>{let s='"'===n,i=t.getPrev(),u=t.getNext(),a="="===u,l=O(n),o=u&&O(u);return!!(r&&L(n))||(!s||"\\"===i||!!(r=!r)||!!a||!!o)&&(!!e||!l)});return(t.skip(),$(4,W(n)),t.isLast())?0:1}let r=t.grabWhile(e=>!("="===e||O(e)||t.isLast()));return($(2,r),k(r),t.skip(),e)?2:t.includes("=")?1:2}(t,!e);return v.skip(),0}();break;default:i=function(){if(E(v.getCurr()))return $(6,v.getCurr()),v.skip(),n=0,r++,0;if(O(v.getCurr()))return $(5,v.grabWhile(O)),0;if(v.getCurr()===o){if(a){let t=o.length+1+a.length,e=`${o}/${a}`;if(v.grabN(t)===e)return 1}else if(v.includes(h))return 1;return $(1,v.getCurr()),v.skip(),0}if(c){if(S(v.getCurr())){let t=v.getCurr(),e=v.getNext();return(v.skip(),e&&A(e))?(v.skip(),$(1,e)):$(1,t),0}return $(1,v.grabWhile(t=>y(t)&&!S(t))),0}return $(1,v.grabWhile(y)),0}()}return l.length=s+1,l},isTokenNested:function(e){let r=o+"/"+e.getValue();if(f.has(r))return!!f.get(r);{let e=t.indexOf(r)>-1;return f.set(r,e),e}}}}class j{last(){return Array.isArray(this.n)&&this.n.length>0&&void 0!==this.n[this.n.length-1]?this.n[this.n.length-1]:null}flush(){return!!this.n.length&&this.n.pop()}push(t){this.n.push(t)}toArray(){return this.n}constructor(){this.n=[]}}let z=()=>new j;function P(t,r={}){var n;let s=r.openTag||"[",i=r.closeTag||"]",u=(r.onlyAllowTags||[]).filter(Boolean).map(t=>t.toLowerCase()),a=null,o=z(),h=z(),c=z(),g=z(),f=new Set;function p(){c.flush()&&g.flush()}function d(){let t=h.last();return t&&e(t)?t.content:o.toArray()}function b(t,e,r=!0){Array.isArray(t)&&void 0!==e&&(t.push(e.toTagStart({openTag:s,closeTag:i})),Array.isArray(e.content)&&e.content.length&&(e.content.forEach(e=>{t.push(e)}),r&&t.push(e.toTagEnd({openTag:s,closeTag:i}))))}function T(t,r){if(Array.isArray(t)&&void 0!==r){if(e(r)){var n;(n=r.tag,!u.length||u.indexOf(n.toLowerCase())>=0)?t.push(r.toTagNode()):b(t,r)}else t.push(r)}}(a=(r.createTokenizer?r.createTokenizer:V)(t,{onToken:function(t){t.isTag()?(t.isStart()&&function(t){p();let e=l.create(t.getValue(),{},[]),r=function(t){let e=t.getValue(),{isTokenNested:r}=a||{};return!f.has(e)&&r&&r(t)?(f.add(e),!0):f.has(e)}(t);c.push(e),r?h.push(e):T(d(),e)}(t),t.isEnd()&&function(t){p();let e=h.flush();if(e)T(d(),e);else if("function"==typeof r.onError){let e=t.getValue(),n=t.getLine(),s=t.getColumn();r.onError({tagName:e,lineNumber:n,columnNumber:s})}}(t)):!function(t){var e;let r=c.last(),n=t.getValue(),s=(e=t.toString(),!!f.has(e)),i=d();if(null!==r){if(t.isAttrName()){g.push(n);let t=g.last();t&&r.attr(t,"")}else if(t.isAttrValue()){let t=g.last();t?(r.attr(t,n),g.flush()):r.attr(n,n)}else t.isText()?s?r.append(n):T(i,n):t.isTag()&&T(i,t.toString())}else t.isText()?T(i,n):t.isTag()&&T(i,t.toString())}(t)},openTag:s,closeTag:i,onlyAllowTags:r.onlyAllowTags,contextFreeTags:r.contextFreeTags,enableEscapeTags:r.enableEscapeTags})).tokenize();let y=h.flush();return null!==y&&y&&e(y)&&(n=y.tag,f.has(n))&&b(d(),y,!1),o.toArray()}t.TagNode=l,t.createLexer=V,t.createTokenOfType=C,t.default=P,t.parse=P,Object.defineProperty(t,"__esModule",{value:!0})}); |
export { TagNode } from '@bbob/plugin-helper'; | ||
export { default, parse } from './parse'; | ||
export { default } from './parse'; | ||
export * from './parse'; | ||
export * from './lexer'; |
108
es/lexer.js
@@ -6,28 +6,26 @@ /* eslint-disable no-plusplus,no-param-reassign */ import { OPEN_BRAKET, CLOSE_BRAKET, QUOTEMARK, BACKSLASH, SLASH, SPACE, TAB, EQ, N } from '@bbob/plugin-helper'; | ||
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; | ||
export function createTokenOfType(type, value, r = 0, cl = 0) { | ||
return new Token(type, value, r, cl); | ||
} | ||
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; | ||
const WHITESPACES = [ | ||
SPACE, | ||
TAB | ||
]; | ||
const SPECIAL_CHARS = [ | ||
EQ, | ||
SPACE, | ||
TAB | ||
]; | ||
const isWhiteSpace = (char)=>WHITESPACES.indexOf(char) >= 0; | ||
const isEscapeChar = (char)=>char === BACKSLASH; | ||
const isSpecialChar = (char)=>SPECIAL_CHARS.indexOf(char) >= 0; | ||
const isNewLine = (char)=>char === N; | ||
const unq = (val)=>unquote(trimChar(val, QUOTEMARK)); | ||
export function createLexer(buffer, options = {}) { | ||
let row = 0; | ||
@@ -43,3 +41,4 @@ let col = 0; | ||
const escapeTags = !!options.enableEscapeTags; | ||
const contextFreeTags = options.contextFreeTags || []; | ||
const contextFreeTags = (options.contextFreeTags || []).filter(Boolean).map((tag)=>tag.toLowerCase()); | ||
const nestedMap = new Map(); | ||
const onToken = options.onToken || (()=>{}); | ||
@@ -63,22 +62,8 @@ const RESERVED_CHARS = [ | ||
]; | ||
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)=>{ | ||
@@ -88,3 +73,3 @@ if (contextFreeTag !== '' && isClosingTag) { | ||
} | ||
if (contextFreeTag === '' && contextFreeTags.includes(name)) { | ||
if (contextFreeTag === '' && contextFreeTags.includes(name.toLowerCase())) { | ||
contextFreeTag = name; | ||
@@ -101,3 +86,3 @@ } | ||
*/ function emitToken(type, value) { | ||
const token = createToken(type, value, row, col); | ||
const token = createTokenOfType(type, value, row, col); | ||
onToken(token); | ||
@@ -138,3 +123,3 @@ tokenIndex += 1; | ||
// const isPrevWS = isWhiteSpace(prevChar); | ||
const isNextWS = isWhiteSpace(nextChar); | ||
const isNextWS = nextChar && isWhiteSpace(nextChar); | ||
if (stateSpecial && isSpecialChar(char)) { | ||
@@ -150,3 +135,3 @@ return true; | ||
if (!isSingleValueTag) { | ||
return isWS === false; | ||
return !isWS; | ||
// return (isEQ || isWS) === false; | ||
@@ -156,5 +141,5 @@ } | ||
}; | ||
const name1 = tagChars.grabWhile(validAttrValue); | ||
const name = tagChars.grabWhile(validAttrValue); | ||
tagChars.skip(); | ||
emitToken(TYPE_ATTR_VALUE, unq(name1)); | ||
emitToken(TYPE_ATTR_VALUE, unq(name)); | ||
if (tagChars.isLast()) { | ||
@@ -166,5 +151,5 @@ return TAG_STATE_NAME; | ||
const validName = (char)=>!(char === EQ || isWhiteSpace(char) || tagChars.isLast()); | ||
const name2 = tagChars.grabWhile(validName); | ||
emitToken(TYPE_TAG, name2); | ||
checkContextFreeMode(name2); | ||
const name = tagChars.grabWhile(validName); | ||
emitToken(TYPE_TAG, name); | ||
checkContextFreeMode(name); | ||
tagChars.skip(); | ||
@@ -185,3 +170,3 @@ // in cases when we has [url=someval]GET[/url] and we dont need to parse all | ||
const hasInvalidChars = substr.length === 0 || substr.indexOf(openTag) >= 0; | ||
if (isCharReserved(nextChar) || hasInvalidChars || chars.isLast()) { | ||
if (nextChar && isCharReserved(nextChar) || hasInvalidChars || chars.isLast()) { | ||
emitToken(TYPE_WORD, currChar); | ||
@@ -251,3 +236,3 @@ return STATE_WORD; | ||
chars.skip(); // skip the \ without emitting anything | ||
if (isEscapableChar(nextChar)) { | ||
if (nextChar && isEscapableChar(nextChar)) { | ||
chars.skip(); // skip past the [, ] or \ as well | ||
@@ -261,8 +246,8 @@ emitToken(TYPE_WORD, nextChar); | ||
const isChar = (char)=>isCharToken(char) && !isEscapeChar(char); | ||
const word1 = chars.grabWhile(isChar); | ||
emitToken(TYPE_WORD, word1); | ||
const word = chars.grabWhile(isChar); | ||
emitToken(TYPE_WORD, word); | ||
return STATE_WORD; | ||
} | ||
const word2 = chars.grabWhile(isCharToken); | ||
emitToken(TYPE_WORD, word2); | ||
const word = chars.grabWhile(isCharToken); | ||
emitToken(TYPE_WORD, word); | ||
return STATE_WORD; | ||
@@ -291,4 +276,9 @@ } | ||
const value = openTag + SLASH + token.getValue(); | ||
// potential bottleneck | ||
return buffer.indexOf(value) > -1; | ||
if (nestedMap.has(value)) { | ||
return !!nestedMap.get(value); | ||
} else { | ||
const status = buffer.indexOf(value) > -1; | ||
nestedMap.set(value, status); | ||
return status; | ||
} | ||
} | ||
@@ -300,3 +290,1 @@ return { | ||
} | ||
export const createTokenOfType = createToken; | ||
export { createLexer }; |
190
es/parse.js
@@ -1,16 +0,25 @@ | ||
import { TagNode, CLOSE_BRAKET, OPEN_BRAKET, isTagNode } from '@bbob/plugin-helper'; | ||
import { createLexer } from './lexer'; | ||
import { createList } from './utils'; | ||
/** | ||
* @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<string|TagNode>} | ||
*/ const parse = (input, opts = {})=>{ | ||
import { CLOSE_BRAKET, OPEN_BRAKET, TagNode, isTagNode } from "@bbob/plugin-helper"; | ||
import { createLexer } from "./lexer"; | ||
class NodeList { | ||
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; | ||
} | ||
flush() { | ||
return this.n.length ? this.n.pop() : false; | ||
} | ||
push(value) { | ||
this.n.push(value); | ||
} | ||
toArray() { | ||
return this.n; | ||
} | ||
constructor(){ | ||
this.n = []; | ||
} | ||
} | ||
const createList = ()=>new NodeList(); | ||
function parse(input, opts = {}) { | ||
const options = opts; | ||
@@ -29,3 +38,2 @@ const openTag = options.openTag || OPEN_BRAKET; | ||
* @private | ||
* @type {NodeList} | ||
*/ const nestedNodes = createList(); | ||
@@ -44,10 +52,7 @@ /** | ||
* Cache for nested tags checks | ||
* @type Set<string> | ||
*/ const nestedTagsMap = new Set(); | ||
/** | ||
* @param {Token} token | ||
* @returns {boolean} | ||
*/ const isTokenNested = (token)=>{ | ||
function isTokenNested(token) { | ||
const value = token.getValue(); | ||
if (!nestedTagsMap.has(value) && tokenizer.isTokenNested && tokenizer.isTokenNested(token)) { | ||
const { isTokenNested } = tokenizer || {}; | ||
if (!nestedTagsMap.has(value) && isTokenNested && isTokenNested(token)) { | ||
nestedTagsMap.add(value); | ||
@@ -57,13 +62,11 @@ return true; | ||
return nestedTagsMap.has(value); | ||
}; | ||
} | ||
/** | ||
* @private | ||
* @param {string} tagName | ||
* @returns {boolean} | ||
*/ const isTagNested = (tagName)=>Boolean(nestedTagsMap.has(tagName)); | ||
*/ function isTagNested(tagName) { | ||
return Boolean(nestedTagsMap.has(tagName)); | ||
} | ||
/** | ||
* @private | ||
* @param {string} value | ||
* @return {boolean} | ||
*/ const isAllowedTag = (value)=>{ | ||
*/ function isAllowedTag(value) { | ||
if (onlyAllowTags.length) { | ||
@@ -73,39 +76,34 @@ return onlyAllowTags.indexOf(value.toLowerCase()) >= 0; | ||
return true; | ||
}; | ||
} | ||
/** | ||
* Flushes temp tag nodes and its attributes buffers | ||
* @private | ||
* @return {Array} | ||
*/ const flushTagNodes = ()=>{ | ||
if (tagNodes.flushLast()) { | ||
tagNodesAttrName.flushLast(); | ||
*/ function flushTagNodes() { | ||
if (tagNodes.flush()) { | ||
tagNodesAttrName.flush(); | ||
} | ||
}; | ||
} | ||
/** | ||
* @private | ||
* @return {Array} | ||
*/ const getNodes = ()=>{ | ||
const lastNestedNode = nestedNodes.getLast(); | ||
if (lastNestedNode && Array.isArray(lastNestedNode.content)) { | ||
*/ function getNodes() { | ||
const lastNestedNode = nestedNodes.last(); | ||
if (lastNestedNode && isTagNode(lastNestedNode)) { | ||
return lastNestedNode.content; | ||
} | ||
return nodes.toArray(); | ||
}; | ||
} | ||
/** | ||
* @private | ||
* @param {string|TagNode} node | ||
* @param {boolean} isNested | ||
*/ const appendNodeAsString = (node, isNested = true)=>{ | ||
const items = getNodes(); | ||
if (Array.isArray(items)) { | ||
items.push(node.toTagStart({ | ||
*/ function appendNodeAsString(nodes, node, isNested = true) { | ||
if (Array.isArray(nodes) && typeof node !== "undefined") { | ||
nodes.push(node.toTagStart({ | ||
openTag, | ||
closeTag | ||
})); | ||
if (node.content.length) { | ||
if (Array.isArray(node.content) && node.content.length) { | ||
node.content.forEach((item)=>{ | ||
items.push(item); | ||
nodes.push(item); | ||
}); | ||
if (isNested) { | ||
items.push(node.toTagEnd({ | ||
nodes.push(node.toTagEnd({ | ||
openTag, | ||
@@ -117,26 +115,24 @@ closeTag | ||
} | ||
}; | ||
} | ||
/** | ||
* @private | ||
* @param {string|TagNode} node | ||
*/ const appendNodes = (node)=>{ | ||
const items = getNodes(); | ||
if (Array.isArray(items)) { | ||
*/ function appendNodes(nodes, node) { | ||
if (Array.isArray(nodes) && typeof node !== "undefined") { | ||
if (isTagNode(node)) { | ||
if (isAllowedTag(node.tag)) { | ||
items.push(node.toTagNode()); | ||
nodes.push(node.toTagNode()); | ||
} else { | ||
appendNodeAsString(node); | ||
appendNodeAsString(nodes, node); | ||
} | ||
} else { | ||
items.push(node); | ||
nodes.push(node); | ||
} | ||
} | ||
}; | ||
} | ||
/** | ||
* @private | ||
* @param {Token} token | ||
*/ const handleTagStart = (token)=>{ | ||
*/ function handleTagStart(token) { | ||
flushTagNodes(); | ||
const tagNode = TagNode.create(token.getValue()); | ||
const tagNode = TagNode.create(token.getValue(), {}, []); | ||
const isNested = isTokenNested(token); | ||
@@ -147,14 +143,16 @@ tagNodes.push(tagNode); | ||
} else { | ||
appendNodes(tagNode, token); | ||
const nodes = getNodes(); | ||
appendNodes(nodes, tagNode); | ||
} | ||
}; | ||
} | ||
/** | ||
* @private | ||
* @param {Token} token | ||
*/ const handleTagEnd = (token)=>{ | ||
*/ function handleTagEnd(token) { | ||
flushTagNodes(); | ||
const lastNestedNode = nestedNodes.flushLast(); | ||
const lastNestedNode = nestedNodes.flush(); | ||
if (lastNestedNode) { | ||
appendNodes(lastNestedNode, token); | ||
} else if (typeof options.onError === 'function') { | ||
const nodes = getNodes(); | ||
appendNodes(nodes, lastNestedNode); | ||
} else if (typeof options.onError === "function") { | ||
const tag = token.getValue(); | ||
@@ -164,3 +162,2 @@ const line = token.getLine(); | ||
options.onError({ | ||
message: `Inconsistent tag '${tag}' on line ${line} and column ${column}`, | ||
tagName: tag, | ||
@@ -171,7 +168,7 @@ lineNumber: line, | ||
} | ||
}; | ||
} | ||
/** | ||
* @private | ||
* @param {Token} token | ||
*/ const handleTag = (token)=>{ | ||
*/ function handleTag(token) { | ||
// [tag] | ||
@@ -185,45 +182,49 @@ if (token.isStart()) { | ||
} | ||
}; | ||
} | ||
/** | ||
* @private | ||
* @param {Token} token | ||
*/ const handleNode = (token)=>{ | ||
*/ function handleNode(token) { | ||
/** | ||
* @type {TagNode} | ||
*/ const lastTagNode = tagNodes.getLast(); | ||
*/ const activeTagNode = tagNodes.last(); | ||
const tokenValue = token.getValue(); | ||
const isNested = isTagNested(token); | ||
if (lastTagNode) { | ||
const isNested = isTagNested(token.toString()); | ||
const nodes = getNodes(); | ||
if (activeTagNode !== null) { | ||
if (token.isAttrName()) { | ||
tagNodesAttrName.push(tokenValue); | ||
lastTagNode.attr(tagNodesAttrName.getLast(), ''); | ||
const attrName = tagNodesAttrName.last(); | ||
if (attrName) { | ||
activeTagNode.attr(attrName, ""); | ||
} | ||
} else if (token.isAttrValue()) { | ||
const attrName = tagNodesAttrName.getLast(); | ||
const attrName = tagNodesAttrName.last(); | ||
if (attrName) { | ||
lastTagNode.attr(attrName, tokenValue); | ||
tagNodesAttrName.flushLast(); | ||
activeTagNode.attr(attrName, tokenValue); | ||
tagNodesAttrName.flush(); | ||
} else { | ||
lastTagNode.attr(tokenValue, tokenValue); | ||
activeTagNode.attr(tokenValue, tokenValue); | ||
} | ||
} else if (token.isText()) { | ||
if (isNested) { | ||
lastTagNode.append(tokenValue); | ||
activeTagNode.append(tokenValue); | ||
} else { | ||
appendNodes(tokenValue); | ||
appendNodes(nodes, tokenValue); | ||
} | ||
} else if (token.isTag()) { | ||
// if tag is not allowed, just past it as is | ||
appendNodes(token.toString()); | ||
// if tag is not allowed, just pass it as is | ||
appendNodes(nodes, token.toString()); | ||
} | ||
} else if (token.isText()) { | ||
appendNodes(tokenValue); | ||
appendNodes(nodes, tokenValue); | ||
} else if (token.isTag()) { | ||
// if tag is not allowed, just past it as is | ||
appendNodes(token.toString()); | ||
// if tag is not allowed, just pass it as is | ||
appendNodes(nodes, token.toString()); | ||
} | ||
}; | ||
} | ||
/** | ||
* @private | ||
* @param {Token} token | ||
*/ const onToken = (token)=>{ | ||
*/ function onToken(token) { | ||
if (token.isTag()) { | ||
@@ -234,4 +235,5 @@ handleTag(token); | ||
} | ||
}; | ||
tokenizer = (opts.createTokenizer ? opts.createTokenizer : createLexer)(input, { | ||
} | ||
const lexer = opts.createTokenizer ? opts.createTokenizer : createLexer; | ||
tokenizer = lexer(input, { | ||
onToken, | ||
@@ -249,9 +251,9 @@ openTag, | ||
// so we need to flush nested content to nodes array | ||
const lastNestedNode = nestedNodes.flushLast(); | ||
if (lastNestedNode && isTagNested(lastNestedNode.tag)) { | ||
appendNodeAsString(lastNestedNode, false); | ||
const lastNestedNode = nestedNodes.flush(); | ||
if (lastNestedNode !== null && lastNestedNode && isTagNode(lastNestedNode) && isTagNested(lastNestedNode.tag)) { | ||
appendNodeAsString(getNodes(), lastNestedNode, false); | ||
} | ||
return nodes.toArray(); | ||
}; | ||
} | ||
export { parse }; | ||
export default parse; |
import { OPEN_BRAKET, CLOSE_BRAKET, SLASH } from '@bbob/plugin-helper'; | ||
// 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_ID = 't'; // 0; | ||
const TOKEN_VALUE_ID = 'v'; // 1; | ||
const TOKEN_COLUMN_ID = 'r'; // 2; | ||
const TOKEN_LINE_ID = 'l'; // 3; | ||
const TOKEN_TYPE_WORD = 1; // 'word'; | ||
@@ -13,6 +13,3 @@ const TOKEN_TYPE_TAG = 2; // 'tag'; | ||
const TOKEN_TYPE_NEW_LINE = 6; // 'new-line'; | ||
/** | ||
* @param {Token} token | ||
* @returns {string} | ||
*/ const getTokenValue = (token)=>{ | ||
const getTokenValue = (token)=>{ | ||
if (token && typeof token[TOKEN_VALUE_ID] !== 'undefined') { | ||
@@ -23,11 +20,5 @@ return token[TOKEN_VALUE_ID]; | ||
}; | ||
/** | ||
* @param {Token}token | ||
* @returns {number} | ||
*/ const getTokenLine = (token)=>token && token[TOKEN_LINE_ID] || 0; | ||
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)=>{ | ||
const isTextToken = (token)=>{ | ||
if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') { | ||
@@ -38,6 +29,3 @@ return token[TOKEN_TYPE_ID] === TOKEN_TYPE_SPACE || token[TOKEN_TYPE_ID] === TOKEN_TYPE_NEW_LINE || token[TOKEN_TYPE_ID] === TOKEN_TYPE_WORD; | ||
}; | ||
/** | ||
* @param {Token} token | ||
* @returns {boolean} | ||
*/ const isTagToken = (token)=>{ | ||
const isTagToken = (token)=>{ | ||
if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') { | ||
@@ -56,6 +44,3 @@ return token[TOKEN_TYPE_ID] === TOKEN_TYPE_TAG; | ||
}; | ||
/** | ||
* @param {Token} token | ||
* @returns {boolean} | ||
*/ const isAttrValueToken = (token)=>{ | ||
const isAttrValueToken = (token)=>{ | ||
if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') { | ||
@@ -70,3 +55,3 @@ return token[TOKEN_TYPE_ID] === TOKEN_TYPE_ATTR_VALUE; | ||
}; | ||
const convertTagToText = (token)=>{ | ||
const tokenToText = (token)=>{ | ||
let text = OPEN_BRAKET; | ||
@@ -77,6 +62,11 @@ text += getTokenValue(token); | ||
}; | ||
class Token { | ||
/** | ||
* @export | ||
* @class Token | ||
*/ class Token { | ||
get type() { | ||
return this[TOKEN_TYPE_ID]; | ||
} | ||
isEmpty() { | ||
// eslint-disable-next-line no-restricted-globals | ||
return isNaN(this[TOKEN_TYPE_ID]); | ||
return this[TOKEN_TYPE_ID] === 0 || isNaN(this[TOKEN_TYPE_ID]); | ||
} | ||
@@ -114,14 +104,9 @@ isText() { | ||
toString() { | ||
return convertTagToText(this); | ||
return tokenToText(this); | ||
} | ||
/** | ||
* @param {String} type | ||
* @param {String} value | ||
* @param line | ||
* @param row | ||
*/ constructor(type, value, line, row){ | ||
this[TOKEN_TYPE_ID] = Number(type); | ||
constructor(type, value, row = 0, col = 0){ | ||
this[TOKEN_LINE_ID] = row; | ||
this[TOKEN_COLUMN_ID] = col; | ||
this[TOKEN_TYPE_ID] = type || 0; | ||
this[TOKEN_VALUE_ID] = String(value); | ||
this[TOKEN_LINE_ID] = Number(line); | ||
this[TOKEN_COLUMN_ID] = Number(row); | ||
} | ||
@@ -128,0 +113,0 @@ } |
155
es/utils.js
import { QUOTEMARK, BACKSLASH } from '@bbob/plugin-helper'; | ||
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(); | ||
export class CharGrabber { | ||
skip(num = 1, silent) { | ||
this.c.pos += num; | ||
if (this.o && this.o.onSkip && !silent) { | ||
this.o.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)=>{ | ||
} | ||
hasNext() { | ||
return this.c.len > this.c.pos; | ||
} | ||
getCurr() { | ||
if (typeof this.s[this.c.pos] === 'undefined') { | ||
return ''; | ||
} | ||
return this.s[this.c.pos]; | ||
} | ||
getRest() { | ||
return this.s.substring(this.c.pos); | ||
} | ||
getNext() { | ||
const nextPos = this.c.pos + 1; | ||
return nextPos <= this.s.length - 1 ? this.s[nextPos] : null; | ||
} | ||
getPrev() { | ||
const prevPos = this.c.pos - 1; | ||
if (typeof this.s[prevPos] === 'undefined') { | ||
return null; | ||
} | ||
return this.s[prevPos]; | ||
} | ||
isLast() { | ||
return this.c.pos === this.c.len; | ||
} | ||
includes(val) { | ||
return this.s.indexOf(val, this.c.pos) >= 0; | ||
} | ||
grabWhile(condition, silent) { | ||
let start = 0; | ||
if (hasNext()) { | ||
start = cursor.pos; | ||
while(hasNext() && cond(curr())){ | ||
skip(1, silent); | ||
if (this.hasNext()) { | ||
start = this.c.pos; | ||
while(this.hasNext() && condition(this.getCurr())){ | ||
this.skip(1, silent); | ||
} | ||
} | ||
return source.substring(start, cursor.pos); | ||
}; | ||
return this.s.substring(start, this.c.pos); | ||
} | ||
grabN(num = 0) { | ||
return this.s.substring(this.c.pos, this.c.pos + num); | ||
} | ||
/** | ||
* @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; | ||
*/ substrUntilChar(char) { | ||
const { pos } = this.c; | ||
const idx = this.s.indexOf(char, pos); | ||
return idx >= 0 ? this.s.substring(pos, idx) : ''; | ||
} | ||
constructor(source, options = {}){ | ||
this.s = source; | ||
this.c = { | ||
pos: 0, | ||
len: source.length | ||
}; | ||
this.o = options; | ||
} | ||
} | ||
/** | ||
* 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 | ||
*/ export const createCharGrabber = (source, options)=>new CharGrabber(source, options); | ||
@@ -92,5 +74,2 @@ /** | ||
* trimChar('*hello*', '*') ==> 'hello' | ||
* @param {String} str | ||
* @param {String} charToRemove | ||
* @returns {String} | ||
*/ export const trimChar = (str, charToRemove)=>{ | ||
@@ -109,20 +88,2 @@ while(str.charAt(0) === charToRemove){ | ||
* Unquotes \" to " | ||
* @param str | ||
* @return {String} | ||
*/ export 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} | ||
*/ export const createList = (values = [])=>new NodeList(values); |
@@ -13,51 +13,28 @@ "use strict"; | ||
TagNode: function() { | ||
return _pluginHelper.TagNode; | ||
return _pluginhelper.TagNode; | ||
}, | ||
default: function() { | ||
return _parse.default; | ||
}, | ||
parse: function() { | ||
return _parse.parse; | ||
} | ||
}); | ||
var _pluginHelper = require("@bbob/plugin-helper"); | ||
var _parse = /*#__PURE__*/ _interopRequireWildcard(require("./parse")); | ||
function _getRequireWildcardCache(nodeInterop) { | ||
if (typeof WeakMap !== "function") return null; | ||
var cacheBabelInterop = new WeakMap(); | ||
var cacheNodeInterop = new WeakMap(); | ||
return (_getRequireWildcardCache = function(nodeInterop) { | ||
return nodeInterop ? cacheNodeInterop : cacheBabelInterop; | ||
})(nodeInterop); | ||
} | ||
function _interopRequireWildcard(obj, nodeInterop) { | ||
if (!nodeInterop && obj && obj.__esModule) { | ||
return obj; | ||
} | ||
if (obj === null || typeof obj !== "object" && typeof obj !== "function") { | ||
return { | ||
default: obj | ||
}; | ||
} | ||
var cache = _getRequireWildcardCache(nodeInterop); | ||
if (cache && cache.has(obj)) { | ||
return cache.get(obj); | ||
} | ||
var newObj = {}; | ||
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; | ||
for(var key in obj){ | ||
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { | ||
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; | ||
if (desc && (desc.get || desc.set)) { | ||
Object.defineProperty(newObj, key, desc); | ||
} else { | ||
newObj[key] = obj[key]; | ||
} | ||
var _pluginhelper = require("@bbob/plugin-helper"); | ||
var _parse = /*#__PURE__*/ _interop_require_default(_export_star(require("./parse"), exports)); | ||
_export_star(require("./lexer"), exports); | ||
function _export_star(from, to) { | ||
Object.keys(from).forEach(function(k) { | ||
if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) { | ||
Object.defineProperty(to, k, { | ||
enumerable: true, | ||
get: function() { | ||
return from[k]; | ||
} | ||
}); | ||
} | ||
} | ||
newObj.default = obj; | ||
if (cache) { | ||
cache.set(obj, newObj); | ||
} | ||
return newObj; | ||
}); | ||
return from; | ||
} | ||
function _interop_require_default(obj) { | ||
return obj && obj.__esModule ? obj : { | ||
default: obj | ||
}; | ||
} |
299
lib/lexer.js
@@ -12,40 +12,107 @@ /* eslint-disable no-plusplus,no-param-reassign */ "use strict"; | ||
_export(exports, { | ||
createLexer: function() { | ||
return createLexer; | ||
}, | ||
createTokenOfType: function() { | ||
return createTokenOfType; | ||
}, | ||
createLexer: function() { | ||
return createLexer; | ||
} | ||
}); | ||
var _pluginHelper = require("@bbob/plugin-helper"); | ||
var _token = require("./Token"); | ||
var _pluginhelper = require("@bbob/plugin-helper"); | ||
var _Token = require("./Token"); | ||
var _utils = require("./utils"); | ||
// for cases <!-- --> | ||
var EM = "!"; | ||
/** | ||
* Creates a Token entity class | ||
* @param {Number} type | ||
* @param {String} value | ||
* @param {Number} r line number | ||
* @param {Number} cl char number in line | ||
*/ var createToken = function(type, value, r, cl) { | ||
function createTokenOfType(type, value, r, cl) { | ||
if (r === void 0) r = 0; | ||
if (cl === void 0) cl = 0; | ||
return new _token.Token(type, value, r, cl); | ||
return new _Token.Token(type, value, r, cl); | ||
} | ||
var STATE_WORD = 0; | ||
var STATE_TAG = 1; | ||
var STATE_TAG_ATTRS = 2; | ||
var TAG_STATE_NAME = 0; | ||
var TAG_STATE_ATTR = 1; | ||
var TAG_STATE_VALUE = 2; | ||
var WHITESPACES = [ | ||
_pluginhelper.SPACE, | ||
_pluginhelper.TAB | ||
]; | ||
var SPECIAL_CHARS = [ | ||
_pluginhelper.EQ, | ||
_pluginhelper.SPACE, | ||
_pluginhelper.TAB | ||
]; | ||
var isWhiteSpace = function(char) { | ||
return WHITESPACES.indexOf(char) >= 0; | ||
}; | ||
/** | ||
* @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) { | ||
var isEscapeChar = function(char) { | ||
return char === _pluginhelper.BACKSLASH; | ||
}; | ||
var isSpecialChar = function(char) { | ||
return SPECIAL_CHARS.indexOf(char) >= 0; | ||
}; | ||
var isNewLine = function(char) { | ||
return char === _pluginhelper.N; | ||
}; | ||
var unq = function(val) { | ||
return (0, _utils.unquote)((0, _utils.trimChar)(val, _pluginhelper.QUOTEMARK)); | ||
}; | ||
function createLexer(buffer, options) { | ||
if (options === void 0) options = {}; | ||
var emitToken = /** | ||
var row = 0; | ||
var col = 0; | ||
var tokenIndex = -1; | ||
var stateMode = STATE_WORD; | ||
var tagMode = TAG_STATE_NAME; | ||
var contextFreeTag = ""; | ||
var tokens = new Array(Math.floor(buffer.length)); | ||
var openTag = options.openTag || _pluginhelper.OPEN_BRAKET; | ||
var closeTag = options.closeTag || _pluginhelper.CLOSE_BRAKET; | ||
var escapeTags = !!options.enableEscapeTags; | ||
var contextFreeTags = (options.contextFreeTags || []).filter(Boolean).map(function(tag) { | ||
return tag.toLowerCase(); | ||
}); | ||
var nestedMap = new Map(); | ||
var onToken = options.onToken || function() {}; | ||
var RESERVED_CHARS = [ | ||
closeTag, | ||
openTag, | ||
_pluginhelper.QUOTEMARK, | ||
_pluginhelper.BACKSLASH, | ||
_pluginhelper.SPACE, | ||
_pluginhelper.TAB, | ||
_pluginhelper.EQ, | ||
_pluginhelper.N, | ||
EM | ||
]; | ||
var NOT_CHAR_TOKENS = [ | ||
openTag, | ||
_pluginhelper.SPACE, | ||
_pluginhelper.TAB, | ||
_pluginhelper.N | ||
]; | ||
var isCharReserved = function(char) { | ||
return RESERVED_CHARS.indexOf(char) >= 0; | ||
}; | ||
var isCharToken = function(char) { | ||
return NOT_CHAR_TOKENS.indexOf(char) === -1; | ||
}; | ||
var isEscapableChar = function(char) { | ||
return char === openTag || char === closeTag || char === _pluginhelper.BACKSLASH; | ||
}; | ||
var onSkip = function() { | ||
col++; | ||
}; | ||
var checkContextFreeMode = function(name, isClosingTag) { | ||
if (contextFreeTag !== "" && isClosingTag) { | ||
contextFreeTag = ""; | ||
} | ||
if (contextFreeTag === "" && contextFreeTags.includes(name.toLowerCase())) { | ||
contextFreeTag = name; | ||
} | ||
}; | ||
var chars = (0, _utils.createCharGrabber)(buffer, { | ||
onSkip: onSkip | ||
}); | ||
/** | ||
* Emits newly created token to subscriber | ||
@@ -55,20 +122,20 @@ * @param {Number} type | ||
*/ function emitToken(type, value) { | ||
var token = createToken(type, value, row, col); | ||
var token = createTokenOfType(type, value, row, col); | ||
onToken(token); | ||
tokenIndex += 1; | ||
tokens[tokenIndex] = token; | ||
}; | ||
var nextTagState = function nextTagState(tagChars, isSingleValueTag) { | ||
} | ||
function nextTagState(tagChars, isSingleValueTag) { | ||
if (tagMode === TAG_STATE_ATTR) { | ||
var validAttrName = function(char) { | ||
return !(char === _pluginHelper.EQ || isWhiteSpace(char)); | ||
return !(char === _pluginhelper.EQ || isWhiteSpace(char)); | ||
}; | ||
var name = tagChars.grabWhile(validAttrName); | ||
var isEnd = tagChars.isLast(); | ||
var isValue = tagChars.getCurr() !== _pluginHelper.EQ; | ||
var isValue = tagChars.getCurr() !== _pluginhelper.EQ; | ||
tagChars.skip(); | ||
if (isEnd || isValue) { | ||
emitToken(_token.TYPE_ATTR_VALUE, unq(name)); | ||
emitToken(_Token.TYPE_ATTR_VALUE, unq(name)); | ||
} else { | ||
emitToken(_token.TYPE_ATTR_NAME, name); | ||
emitToken(_Token.TYPE_ATTR_NAME, name); | ||
} | ||
@@ -87,10 +154,10 @@ if (isEnd) { | ||
// const isEQ = char === EQ; | ||
var isQM = char === _pluginHelper.QUOTEMARK; | ||
var isQM = char === _pluginhelper.QUOTEMARK; | ||
var prevChar = tagChars.getPrev(); | ||
var nextChar = tagChars.getNext(); | ||
var isPrevSLASH = prevChar === _pluginHelper.BACKSLASH; | ||
var isNextEQ = nextChar === _pluginHelper.EQ; | ||
var isPrevSLASH = prevChar === _pluginhelper.BACKSLASH; | ||
var isNextEQ = nextChar === _pluginhelper.EQ; | ||
var isWS = isWhiteSpace(char); | ||
// const isPrevWS = isWhiteSpace(prevChar); | ||
var isNextWS = isWhiteSpace(nextChar); | ||
var isNextWS = nextChar && isWhiteSpace(nextChar); | ||
if (stateSpecial && isSpecialChar(char)) { | ||
@@ -106,3 +173,3 @@ return true; | ||
if (!isSingleValueTag) { | ||
return isWS === false; | ||
return !isWS; | ||
// return (isEQ || isWS) === false; | ||
@@ -114,3 +181,3 @@ } | ||
tagChars.skip(); | ||
emitToken(_token.TYPE_ATTR_VALUE, unq(name1)); | ||
emitToken(_Token.TYPE_ATTR_VALUE, unq(name1)); | ||
if (tagChars.isLast()) { | ||
@@ -122,6 +189,6 @@ return TAG_STATE_NAME; | ||
var validName = function(char) { | ||
return !(char === _pluginHelper.EQ || isWhiteSpace(char) || tagChars.isLast()); | ||
return !(char === _pluginhelper.EQ || isWhiteSpace(char) || tagChars.isLast()); | ||
}; | ||
var name2 = tagChars.grabWhile(validName); | ||
emitToken(_token.TYPE_TAG, name2); | ||
emitToken(_Token.TYPE_TAG, name2); | ||
checkContextFreeMode(name2); | ||
@@ -133,6 +200,6 @@ tagChars.skip(); | ||
} | ||
var hasEQ = tagChars.includes(_pluginHelper.EQ); | ||
var hasEQ = tagChars.includes(_pluginhelper.EQ); | ||
return hasEQ ? TAG_STATE_ATTR : TAG_STATE_VALUE; | ||
}; | ||
var stateTag = function stateTag() { | ||
} | ||
function stateTag() { | ||
var currChar = chars.getCurr(); | ||
@@ -144,10 +211,10 @@ var nextChar = chars.getNext(); | ||
var hasInvalidChars = substr.length === 0 || substr.indexOf(openTag) >= 0; | ||
if (isCharReserved(nextChar) || hasInvalidChars || chars.isLast()) { | ||
emitToken(_token.TYPE_WORD, currChar); | ||
if (nextChar && isCharReserved(nextChar) || hasInvalidChars || chars.isLast()) { | ||
emitToken(_Token.TYPE_WORD, currChar); | ||
return STATE_WORD; | ||
} | ||
// [myTag ] | ||
var isNoAttrsInTag = substr.indexOf(_pluginHelper.EQ) === -1; | ||
var isNoAttrsInTag = substr.indexOf(_pluginhelper.EQ) === -1; | ||
// [/myTag] | ||
var isClosingTag = substr[0] === _pluginHelper.SLASH; | ||
var isClosingTag = substr[0] === _pluginhelper.SLASH; | ||
if (isNoAttrsInTag || isClosingTag) { | ||
@@ -158,3 +225,3 @@ var name = chars.grabWhile(function(char) { | ||
chars.skip(); // skip closeTag | ||
emitToken(_token.TYPE_TAG, name); | ||
emitToken(_Token.TYPE_TAG, name); | ||
checkContextFreeMode(name, isClosingTag); | ||
@@ -164,4 +231,4 @@ return STATE_WORD; | ||
return STATE_TAG_ATTRS; | ||
}; | ||
var stateAttrs = function stateAttrs() { | ||
} | ||
function stateAttrs() { | ||
var silent = true; | ||
@@ -174,3 +241,3 @@ var tagStr = chars.grabWhile(function(char) { | ||
}); | ||
var hasSpace = tagGrabber.includes(_pluginHelper.SPACE); | ||
var hasSpace = tagGrabber.includes(_pluginhelper.SPACE); | ||
tagMode = TAG_STATE_NAME; | ||
@@ -182,6 +249,6 @@ while(tagGrabber.hasNext()){ | ||
return STATE_WORD; | ||
}; | ||
var stateWord = function stateWord() { | ||
} | ||
function stateWord() { | ||
if (isNewLine(chars.getCurr())) { | ||
emitToken(_token.TYPE_NEW_LINE, chars.getCurr()); | ||
emitToken(_Token.TYPE_NEW_LINE, chars.getCurr()); | ||
chars.skip(); | ||
@@ -194,3 +261,3 @@ col = 0; | ||
var word = chars.grabWhile(isWhiteSpace); | ||
emitToken(_token.TYPE_SPACE, word); | ||
emitToken(_Token.TYPE_SPACE, word); | ||
return STATE_WORD; | ||
@@ -200,4 +267,4 @@ } | ||
if (contextFreeTag) { | ||
var fullTagLen = openTag.length + _pluginHelper.SLASH.length + contextFreeTag.length; | ||
var fullTagName = "" + openTag + _pluginHelper.SLASH + contextFreeTag; | ||
var fullTagLen = openTag.length + _pluginhelper.SLASH.length + contextFreeTag.length; | ||
var fullTagName = "" + openTag + _pluginhelper.SLASH + contextFreeTag; | ||
var foundTag = chars.grabN(fullTagLen); | ||
@@ -211,3 +278,3 @@ var isEndContextFreeMode = foundTag === fullTagName; | ||
} | ||
emitToken(_token.TYPE_WORD, chars.getCurr()); | ||
emitToken(_Token.TYPE_WORD, chars.getCurr()); | ||
chars.skip(); | ||
@@ -221,8 +288,8 @@ return STATE_WORD; | ||
chars.skip(); // skip the \ without emitting anything | ||
if (isEscapableChar(nextChar)) { | ||
if (nextChar && isEscapableChar(nextChar)) { | ||
chars.skip(); // skip past the [, ] or \ as well | ||
emitToken(_token.TYPE_WORD, nextChar); | ||
emitToken(_Token.TYPE_WORD, nextChar); | ||
return STATE_WORD; | ||
} | ||
emitToken(_token.TYPE_WORD, currChar); | ||
emitToken(_Token.TYPE_WORD, currChar); | ||
return STATE_WORD; | ||
@@ -234,10 +301,10 @@ } | ||
var word1 = chars.grabWhile(isChar); | ||
emitToken(_token.TYPE_WORD, word1); | ||
emitToken(_Token.TYPE_WORD, word1); | ||
return STATE_WORD; | ||
} | ||
var word2 = chars.grabWhile(isCharToken); | ||
emitToken(_token.TYPE_WORD, word2); | ||
emitToken(_Token.TYPE_WORD, word2); | ||
return STATE_WORD; | ||
}; | ||
var tokenize = function tokenize() { | ||
} | ||
function tokenize() { | ||
stateMode = STATE_WORD; | ||
@@ -260,90 +327,13 @@ while(chars.hasNext()){ | ||
return tokens; | ||
}; | ||
var isTokenNested = function isTokenNested(token) { | ||
var value = openTag + _pluginHelper.SLASH + token.getValue(); | ||
// potential bottleneck | ||
return buffer.indexOf(value) > -1; | ||
}; | ||
var STATE_WORD = 0; | ||
var STATE_TAG = 1; | ||
var STATE_TAG_ATTRS = 2; | ||
var TAG_STATE_NAME = 0; | ||
var TAG_STATE_ATTR = 1; | ||
var TAG_STATE_VALUE = 2; | ||
var row = 0; | ||
var col = 0; | ||
var tokenIndex = -1; | ||
var stateMode = STATE_WORD; | ||
var tagMode = TAG_STATE_NAME; | ||
var contextFreeTag = ""; | ||
var tokens = new Array(Math.floor(buffer.length)); | ||
var openTag = options.openTag || _pluginHelper.OPEN_BRAKET; | ||
var closeTag = options.closeTag || _pluginHelper.CLOSE_BRAKET; | ||
var escapeTags = !!options.enableEscapeTags; | ||
var contextFreeTags = options.contextFreeTags || []; | ||
var onToken = options.onToken || function() {}; | ||
var RESERVED_CHARS = [ | ||
closeTag, | ||
openTag, | ||
_pluginHelper.QUOTEMARK, | ||
_pluginHelper.BACKSLASH, | ||
_pluginHelper.SPACE, | ||
_pluginHelper.TAB, | ||
_pluginHelper.EQ, | ||
_pluginHelper.N, | ||
EM | ||
]; | ||
var NOT_CHAR_TOKENS = [ | ||
openTag, | ||
_pluginHelper.SPACE, | ||
_pluginHelper.TAB, | ||
_pluginHelper.N | ||
]; | ||
var WHITESPACES = [ | ||
_pluginHelper.SPACE, | ||
_pluginHelper.TAB | ||
]; | ||
var SPECIAL_CHARS = [ | ||
_pluginHelper.EQ, | ||
_pluginHelper.SPACE, | ||
_pluginHelper.TAB | ||
]; | ||
var isCharReserved = function(char) { | ||
return RESERVED_CHARS.indexOf(char) >= 0; | ||
}; | ||
var isNewLine = function(char) { | ||
return char === _pluginHelper.N; | ||
}; | ||
var isWhiteSpace = function(char) { | ||
return WHITESPACES.indexOf(char) >= 0; | ||
}; | ||
var isCharToken = function(char) { | ||
return NOT_CHAR_TOKENS.indexOf(char) === -1; | ||
}; | ||
var isSpecialChar = function(char) { | ||
return SPECIAL_CHARS.indexOf(char) >= 0; | ||
}; | ||
var isEscapableChar = function(char) { | ||
return char === openTag || char === closeTag || char === _pluginHelper.BACKSLASH; | ||
}; | ||
var isEscapeChar = function(char) { | ||
return char === _pluginHelper.BACKSLASH; | ||
}; | ||
var onSkip = function() { | ||
col++; | ||
}; | ||
var unq = function(val) { | ||
return (0, _utils.unquote)((0, _utils.trimChar)(val, _pluginHelper.QUOTEMARK)); | ||
}; | ||
var checkContextFreeMode = function(name, isClosingTag) { | ||
if (contextFreeTag !== "" && isClosingTag) { | ||
contextFreeTag = ""; | ||
} | ||
function isTokenNested(token) { | ||
var value = openTag + _pluginhelper.SLASH + token.getValue(); | ||
if (nestedMap.has(value)) { | ||
return !!nestedMap.get(value); | ||
} else { | ||
var status = buffer.indexOf(value) > -1; | ||
nestedMap.set(value, status); | ||
return status; | ||
} | ||
if (contextFreeTag === "" && contextFreeTags.includes(name)) { | ||
contextFreeTag = name; | ||
} | ||
}; | ||
var chars = (0, _utils.createCharGrabber)(buffer, { | ||
onSkip: onSkip | ||
}); | ||
} | ||
return { | ||
@@ -354,2 +344,1 @@ tokenize: tokenize, | ||
} | ||
var createTokenOfType = createToken; |
213
lib/parse.js
@@ -12,28 +12,42 @@ "use strict"; | ||
_export(exports, { | ||
default: function() { | ||
return _default; | ||
}, | ||
parse: function() { | ||
return parse; | ||
}, | ||
default: function() { | ||
return _default; | ||
} | ||
}); | ||
var _pluginHelper = require("@bbob/plugin-helper"); | ||
var _pluginhelper = require("@bbob/plugin-helper"); | ||
var _lexer = require("./lexer"); | ||
var _utils = require("./utils"); | ||
/** | ||
* @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<string|TagNode>} | ||
*/ var parse = function(input, opts) { | ||
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 openTag = options.openTag || _pluginhelper.OPEN_BRAKET; | ||
var closeTag = options.closeTag || _pluginhelper.CLOSE_BRAKET; | ||
var onlyAllowTags = (options.onlyAllowTags || []).filter(Boolean).map(function(tag) { | ||
@@ -47,8 +61,7 @@ return tag.toLowerCase(); | ||
* @type {NodeList} | ||
*/ var nodes = (0, _utils.createList)(); | ||
*/ var nodes = createList(); | ||
/** | ||
* Temp buffer of nodes that's nested to another node | ||
* @private | ||
* @type {NodeList} | ||
*/ var nestedNodes = (0, _utils.createList)(); | ||
*/ var nestedNodes = createList(); | ||
/** | ||
@@ -58,3 +71,3 @@ * Temp buffer of nodes [tag..]...[/tag] | ||
* @type {NodeList} | ||
*/ var tagNodes = (0, _utils.createList)(); | ||
*/ var tagNodes = createList(); | ||
/** | ||
@@ -64,13 +77,10 @@ * Temp buffer of tag attributes | ||
* @type {NodeList} | ||
*/ var tagNodesAttrName = (0, _utils.createList)(); | ||
*/ var tagNodesAttrName = createList(); | ||
/** | ||
* Cache for nested tags checks | ||
* @type Set<string> | ||
*/ var nestedTagsMap = new Set(); | ||
/** | ||
* @param {Token} token | ||
* @returns {boolean} | ||
*/ var isTokenNested = function(token) { | ||
function isTokenNested(token) { | ||
var value = token.getValue(); | ||
if (!nestedTagsMap.has(value) && tokenizer.isTokenNested && tokenizer.isTokenNested(token)) { | ||
var isTokenNested = (tokenizer || {}).isTokenNested; | ||
if (!nestedTagsMap.has(value) && isTokenNested && isTokenNested(token)) { | ||
nestedTagsMap.add(value); | ||
@@ -80,15 +90,11 @@ return true; | ||
return nestedTagsMap.has(value); | ||
}; | ||
} | ||
/** | ||
* @private | ||
* @param {string} tagName | ||
* @returns {boolean} | ||
*/ var isTagNested = function(tagName) { | ||
*/ function isTagNested(tagName) { | ||
return Boolean(nestedTagsMap.has(tagName)); | ||
}; | ||
} | ||
/** | ||
* @private | ||
* @param {string} value | ||
* @return {boolean} | ||
*/ var isAllowedTag = function(value) { | ||
*/ function isAllowedTag(value) { | ||
if (onlyAllowTags.length) { | ||
@@ -98,40 +104,35 @@ return onlyAllowTags.indexOf(value.toLowerCase()) >= 0; | ||
return true; | ||
}; | ||
} | ||
/** | ||
* Flushes temp tag nodes and its attributes buffers | ||
* @private | ||
* @return {Array} | ||
*/ var flushTagNodes = function() { | ||
if (tagNodes.flushLast()) { | ||
tagNodesAttrName.flushLast(); | ||
*/ function flushTagNodes() { | ||
if (tagNodes.flush()) { | ||
tagNodesAttrName.flush(); | ||
} | ||
}; | ||
} | ||
/** | ||
* @private | ||
* @return {Array} | ||
*/ var getNodes = function() { | ||
var lastNestedNode = nestedNodes.getLast(); | ||
if (lastNestedNode && Array.isArray(lastNestedNode.content)) { | ||
*/ function getNodes() { | ||
var lastNestedNode = nestedNodes.last(); | ||
if (lastNestedNode && (0, _pluginhelper.isTagNode)(lastNestedNode)) { | ||
return lastNestedNode.content; | ||
} | ||
return nodes.toArray(); | ||
}; | ||
} | ||
/** | ||
* @private | ||
* @param {string|TagNode} node | ||
* @param {boolean} isNested | ||
*/ var appendNodeAsString = function(node, isNested) { | ||
*/ function appendNodeAsString(nodes, node, isNested) { | ||
if (isNested === void 0) isNested = true; | ||
var items = getNodes(); | ||
if (Array.isArray(items)) { | ||
items.push(node.toTagStart({ | ||
if (Array.isArray(nodes) && typeof node !== "undefined") { | ||
nodes.push(node.toTagStart({ | ||
openTag: openTag, | ||
closeTag: closeTag | ||
})); | ||
if (node.content.length) { | ||
if (Array.isArray(node.content) && node.content.length) { | ||
node.content.forEach(function(item) { | ||
items.push(item); | ||
nodes.push(item); | ||
}); | ||
if (isNested) { | ||
items.push(node.toTagEnd({ | ||
nodes.push(node.toTagEnd({ | ||
openTag: openTag, | ||
@@ -143,26 +144,24 @@ closeTag: closeTag | ||
} | ||
}; | ||
} | ||
/** | ||
* @private | ||
* @param {string|TagNode} node | ||
*/ var appendNodes = function(node) { | ||
var items = getNodes(); | ||
if (Array.isArray(items)) { | ||
if ((0, _pluginHelper.isTagNode)(node)) { | ||
*/ function appendNodes(nodes, node) { | ||
if (Array.isArray(nodes) && typeof node !== "undefined") { | ||
if ((0, _pluginhelper.isTagNode)(node)) { | ||
if (isAllowedTag(node.tag)) { | ||
items.push(node.toTagNode()); | ||
nodes.push(node.toTagNode()); | ||
} else { | ||
appendNodeAsString(node); | ||
appendNodeAsString(nodes, node); | ||
} | ||
} else { | ||
items.push(node); | ||
nodes.push(node); | ||
} | ||
} | ||
}; | ||
} | ||
/** | ||
* @private | ||
* @param {Token} token | ||
*/ var handleTagStart = function(token) { | ||
*/ function handleTagStart(token) { | ||
flushTagNodes(); | ||
var tagNode = _pluginHelper.TagNode.create(token.getValue()); | ||
var tagNode = _pluginhelper.TagNode.create(token.getValue(), {}, []); | ||
var isNested = isTokenNested(token); | ||
@@ -173,13 +172,15 @@ tagNodes.push(tagNode); | ||
} else { | ||
appendNodes(tagNode, token); | ||
var nodes = getNodes(); | ||
appendNodes(nodes, tagNode); | ||
} | ||
}; | ||
} | ||
/** | ||
* @private | ||
* @param {Token} token | ||
*/ var handleTagEnd = function(token) { | ||
*/ function handleTagEnd(token) { | ||
flushTagNodes(); | ||
var lastNestedNode = nestedNodes.flushLast(); | ||
var lastNestedNode = nestedNodes.flush(); | ||
if (lastNestedNode) { | ||
appendNodes(lastNestedNode, token); | ||
var nodes = getNodes(); | ||
appendNodes(nodes, lastNestedNode); | ||
} else if (typeof options.onError === "function") { | ||
@@ -190,3 +191,2 @@ var tag = token.getValue(); | ||
options.onError({ | ||
message: "Inconsistent tag '" + tag + "' on line " + line + " and column " + column, | ||
tagName: tag, | ||
@@ -197,7 +197,7 @@ lineNumber: line, | ||
} | ||
}; | ||
} | ||
/** | ||
* @private | ||
* @param {Token} token | ||
*/ var handleTag = function(token) { | ||
*/ function handleTag(token) { | ||
// [tag] | ||
@@ -211,45 +211,49 @@ if (token.isStart()) { | ||
} | ||
}; | ||
} | ||
/** | ||
* @private | ||
* @param {Token} token | ||
*/ var handleNode = function(token) { | ||
*/ function handleNode(token) { | ||
/** | ||
* @type {TagNode} | ||
*/ var lastTagNode = tagNodes.getLast(); | ||
*/ var activeTagNode = tagNodes.last(); | ||
var tokenValue = token.getValue(); | ||
var isNested = isTagNested(token); | ||
if (lastTagNode) { | ||
var isNested = isTagNested(token.toString()); | ||
var nodes = getNodes(); | ||
if (activeTagNode !== null) { | ||
if (token.isAttrName()) { | ||
tagNodesAttrName.push(tokenValue); | ||
lastTagNode.attr(tagNodesAttrName.getLast(), ""); | ||
var attrName = tagNodesAttrName.last(); | ||
if (attrName) { | ||
activeTagNode.attr(attrName, ""); | ||
} | ||
} else if (token.isAttrValue()) { | ||
var attrName = tagNodesAttrName.getLast(); | ||
if (attrName) { | ||
lastTagNode.attr(attrName, tokenValue); | ||
tagNodesAttrName.flushLast(); | ||
var attrName1 = tagNodesAttrName.last(); | ||
if (attrName1) { | ||
activeTagNode.attr(attrName1, tokenValue); | ||
tagNodesAttrName.flush(); | ||
} else { | ||
lastTagNode.attr(tokenValue, tokenValue); | ||
activeTagNode.attr(tokenValue, tokenValue); | ||
} | ||
} else if (token.isText()) { | ||
if (isNested) { | ||
lastTagNode.append(tokenValue); | ||
activeTagNode.append(tokenValue); | ||
} else { | ||
appendNodes(tokenValue); | ||
appendNodes(nodes, tokenValue); | ||
} | ||
} else if (token.isTag()) { | ||
// if tag is not allowed, just past it as is | ||
appendNodes(token.toString()); | ||
// if tag is not allowed, just pass it as is | ||
appendNodes(nodes, token.toString()); | ||
} | ||
} else if (token.isText()) { | ||
appendNodes(tokenValue); | ||
appendNodes(nodes, tokenValue); | ||
} else if (token.isTag()) { | ||
// if tag is not allowed, just past it as is | ||
appendNodes(token.toString()); | ||
// if tag is not allowed, just pass it as is | ||
appendNodes(nodes, token.toString()); | ||
} | ||
}; | ||
} | ||
/** | ||
* @private | ||
* @param {Token} token | ||
*/ var onToken = function(token) { | ||
*/ function onToken(token) { | ||
if (token.isTag()) { | ||
@@ -260,4 +264,5 @@ handleTag(token); | ||
} | ||
}; | ||
tokenizer = (opts.createTokenizer ? opts.createTokenizer : _lexer.createLexer)(input, { | ||
} | ||
var lexer = opts.createTokenizer ? opts.createTokenizer : _lexer.createLexer; | ||
tokenizer = lexer(input, { | ||
onToken: onToken, | ||
@@ -275,8 +280,8 @@ openTag: openTag, | ||
// so we need to flush nested content to nodes array | ||
var lastNestedNode = nestedNodes.flushLast(); | ||
if (lastNestedNode && isTagNested(lastNestedNode.tag)) { | ||
appendNodeAsString(lastNestedNode, false); | ||
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; |
117
lib/Token.js
@@ -12,20 +12,8 @@ "use strict"; | ||
_export(exports, { | ||
TYPE_ID: function() { | ||
return TYPE_ID; | ||
COLUMN_ID: function() { | ||
return COLUMN_ID; | ||
}, | ||
VALUE_ID: function() { | ||
return VALUE_ID; | ||
}, | ||
LINE_ID: function() { | ||
return LINE_ID; | ||
}, | ||
COLUMN_ID: function() { | ||
return COLUMN_ID; | ||
}, | ||
TYPE_WORD: function() { | ||
return TYPE_WORD; | ||
}, | ||
TYPE_TAG: function() { | ||
return TYPE_TAG; | ||
}, | ||
TYPE_ATTR_NAME: function() { | ||
@@ -37,4 +25,4 @@ return TYPE_ATTR_NAME; | ||
}, | ||
TYPE_SPACE: function() { | ||
return TYPE_SPACE; | ||
TYPE_ID: function() { | ||
return TYPE_ID; | ||
}, | ||
@@ -44,5 +32,17 @@ TYPE_NEW_LINE: function() { | ||
}, | ||
TYPE_SPACE: function() { | ||
return TYPE_SPACE; | ||
}, | ||
TYPE_TAG: function() { | ||
return TYPE_TAG; | ||
}, | ||
TYPE_WORD: function() { | ||
return TYPE_WORD; | ||
}, | ||
Token: function() { | ||
return Token; | ||
}, | ||
VALUE_ID: function() { | ||
return VALUE_ID; | ||
}, | ||
default: function() { | ||
@@ -52,8 +52,22 @@ return _default; | ||
}); | ||
var _pluginHelper = require("@bbob/plugin-helper"); | ||
var _pluginhelper = require("@bbob/plugin-helper"); | ||
function _defineProperties(target, props) { | ||
for(var i = 0; i < props.length; i++){ | ||
var descriptor = props[i]; | ||
descriptor.enumerable = descriptor.enumerable || false; | ||
descriptor.configurable = true; | ||
if ("value" in descriptor) descriptor.writable = true; | ||
Object.defineProperty(target, descriptor.key, descriptor); | ||
} | ||
} | ||
function _create_class(Constructor, protoProps, staticProps) { | ||
if (protoProps) _defineProperties(Constructor.prototype, protoProps); | ||
if (staticProps) _defineProperties(Constructor, staticProps); | ||
return Constructor; | ||
} | ||
// type, value, line, row, | ||
var TOKEN_TYPE_ID = "type"; // 0; | ||
var TOKEN_VALUE_ID = "value"; // 1; | ||
var TOKEN_COLUMN_ID = "row"; // 2; | ||
var TOKEN_LINE_ID = "line"; // 3; | ||
var TOKEN_TYPE_ID = "t"; // 0; | ||
var TOKEN_VALUE_ID = "v"; // 1; | ||
var TOKEN_COLUMN_ID = "r"; // 2; | ||
var TOKEN_LINE_ID = "l"; // 3; | ||
var TOKEN_TYPE_WORD = 1; // 'word'; | ||
@@ -65,6 +79,3 @@ var TOKEN_TYPE_TAG = 2; // 'tag'; | ||
var TOKEN_TYPE_NEW_LINE = 6; // 'new-line'; | ||
/** | ||
* @param {Token} token | ||
* @returns {string} | ||
*/ var getTokenValue = function(token) { | ||
var getTokenValue = function(token) { | ||
if (token && typeof token[TOKEN_VALUE_ID] !== "undefined") { | ||
@@ -75,6 +86,3 @@ return token[TOKEN_VALUE_ID]; | ||
}; | ||
/** | ||
* @param {Token}token | ||
* @returns {number} | ||
*/ var getTokenLine = function(token) { | ||
var getTokenLine = function(token) { | ||
return token && token[TOKEN_LINE_ID] || 0; | ||
@@ -85,6 +93,3 @@ }; | ||
}; | ||
/** | ||
* @param {Token} token | ||
* @returns {boolean} | ||
*/ var isTextToken = function(token) { | ||
var isTextToken = function(token) { | ||
if (token && typeof token[TOKEN_TYPE_ID] !== "undefined") { | ||
@@ -95,6 +100,3 @@ return token[TOKEN_TYPE_ID] === TOKEN_TYPE_SPACE || token[TOKEN_TYPE_ID] === TOKEN_TYPE_NEW_LINE || token[TOKEN_TYPE_ID] === TOKEN_TYPE_WORD; | ||
}; | ||
/** | ||
* @param {Token} token | ||
* @returns {boolean} | ||
*/ var isTagToken = function(token) { | ||
var isTagToken = function(token) { | ||
if (token && typeof token[TOKEN_TYPE_ID] !== "undefined") { | ||
@@ -106,3 +108,3 @@ return token[TOKEN_TYPE_ID] === TOKEN_TYPE_TAG; | ||
var isTagEnd = function(token) { | ||
return getTokenValue(token).charCodeAt(0) === _pluginHelper.SLASH.charCodeAt(0); | ||
return getTokenValue(token).charCodeAt(0) === _pluginhelper.SLASH.charCodeAt(0); | ||
}; | ||
@@ -118,6 +120,3 @@ var isTagStart = function(token) { | ||
}; | ||
/** | ||
* @param {Token} token | ||
* @returns {boolean} | ||
*/ var isAttrValueToken = function(token) { | ||
var isAttrValueToken = function(token) { | ||
if (token && typeof token[TOKEN_TYPE_ID] !== "undefined") { | ||
@@ -132,20 +131,24 @@ return token[TOKEN_TYPE_ID] === TOKEN_TYPE_ATTR_VALUE; | ||
}; | ||
var convertTagToText = function(token) { | ||
var text = _pluginHelper.OPEN_BRAKET; | ||
var tokenToText = function(token) { | ||
var text = _pluginhelper.OPEN_BRAKET; | ||
text += getTokenValue(token); | ||
text += _pluginHelper.CLOSE_BRAKET; | ||
text += _pluginhelper.CLOSE_BRAKET; | ||
return text; | ||
}; | ||
var Token = /*#__PURE__*/ function() { | ||
/** | ||
* @export | ||
* @class Token | ||
*/ var Token = /*#__PURE__*/ function() { | ||
"use strict"; | ||
function Token(type, value, line, row) { | ||
this[TOKEN_TYPE_ID] = Number(type); | ||
function Token(type, value, row, col) { | ||
if (row === void 0) row = 0; | ||
if (col === void 0) col = 0; | ||
this[TOKEN_LINE_ID] = row; | ||
this[TOKEN_COLUMN_ID] = col; | ||
this[TOKEN_TYPE_ID] = type || 0; | ||
this[TOKEN_VALUE_ID] = String(value); | ||
this[TOKEN_LINE_ID] = Number(line); | ||
this[TOKEN_COLUMN_ID] = Number(row); | ||
} | ||
var _proto = Token.prototype; | ||
_proto.isEmpty = function isEmpty() { | ||
// eslint-disable-next-line no-restricted-globals | ||
return isNaN(this[TOKEN_TYPE_ID]); | ||
return this[TOKEN_TYPE_ID] === 0 || isNaN(this[TOKEN_TYPE_ID]); | ||
}; | ||
@@ -183,4 +186,12 @@ _proto.isText = function isText() { | ||
_proto.toString = function toString() { | ||
return convertTagToText(this); | ||
return tokenToText(this); | ||
}; | ||
_create_class(Token, [ | ||
{ | ||
key: "type", | ||
get: function get() { | ||
return this[TOKEN_TYPE_ID]; | ||
} | ||
} | ||
]); | ||
return Token; | ||
@@ -187,0 +198,0 @@ }(); |
168
lib/utils.js
@@ -12,2 +12,5 @@ "use strict"; | ||
_export(exports, { | ||
CharGrabber: function() { | ||
return CharGrabber; | ||
}, | ||
createCharGrabber: function() { | ||
@@ -21,101 +24,76 @@ return createCharGrabber; | ||
return unquote; | ||
}, | ||
createList: function() { | ||
return createList; | ||
} | ||
}); | ||
var _pluginHelper = require("@bbob/plugin-helper"); | ||
function CharGrabber(source, options) { | ||
var cursor = { | ||
pos: 0, | ||
len: source.length | ||
var _pluginhelper = require("@bbob/plugin-helper"); | ||
var CharGrabber = /*#__PURE__*/ function() { | ||
"use strict"; | ||
function CharGrabber(source, options) { | ||
if (options === void 0) options = {}; | ||
this.s = source; | ||
this.c = { | ||
pos: 0, | ||
len: source.length | ||
}; | ||
this.o = options; | ||
} | ||
var _proto = CharGrabber.prototype; | ||
_proto.skip = function skip(num, silent) { | ||
if (num === void 0) num = 1; | ||
this.c.pos += num; | ||
if (this.o && this.o.onSkip && !silent) { | ||
this.o.onSkip(); | ||
} | ||
}; | ||
var substrUntilChar = function(char) { | ||
var pos = cursor.pos; | ||
var idx = source.indexOf(char, pos); | ||
return idx >= 0 ? source.substring(pos, idx) : ""; | ||
_proto.hasNext = function hasNext() { | ||
return this.c.len > this.c.pos; | ||
}; | ||
var includes = function(val) { | ||
return source.indexOf(val, cursor.pos) >= 0; | ||
_proto.getCurr = function getCurr() { | ||
if (typeof this.s[this.c.pos] === "undefined") { | ||
return ""; | ||
} | ||
return this.s[this.c.pos]; | ||
}; | ||
var hasNext = function() { | ||
return cursor.len > cursor.pos; | ||
_proto.getRest = function getRest() { | ||
return this.s.substring(this.c.pos); | ||
}; | ||
var isLast = function() { | ||
return cursor.pos === cursor.len; | ||
_proto.getNext = function getNext() { | ||
var nextPos = this.c.pos + 1; | ||
return nextPos <= this.s.length - 1 ? this.s[nextPos] : null; | ||
}; | ||
var skip = function(num, silent) { | ||
if (num === void 0) num = 1; | ||
cursor.pos += num; | ||
if (options && options.onSkip && !silent) { | ||
options.onSkip(); | ||
_proto.getPrev = function getPrev() { | ||
var prevPos = this.c.pos - 1; | ||
if (typeof this.s[prevPos] === "undefined") { | ||
return null; | ||
} | ||
return this.s[prevPos]; | ||
}; | ||
var rest = function() { | ||
return source.substring(cursor.pos); | ||
_proto.isLast = function isLast() { | ||
return this.c.pos === this.c.len; | ||
}; | ||
var grabN = function(num) { | ||
if (num === void 0) num = 0; | ||
return source.substring(cursor.pos, cursor.pos + num); | ||
_proto.includes = function includes(val) { | ||
return this.s.indexOf(val, this.c.pos) >= 0; | ||
}; | ||
var curr = function() { | ||
return source[cursor.pos]; | ||
}; | ||
var prev = function() { | ||
var prevPos = cursor.pos - 1; | ||
return typeof source[prevPos] !== "undefined" ? source[prevPos] : null; | ||
}; | ||
var next = function() { | ||
var nextPos = cursor.pos + 1; | ||
return nextPos <= source.length - 1 ? source[nextPos] : null; | ||
}; | ||
var grabWhile = function(cond, silent) { | ||
_proto.grabWhile = function grabWhile(condition, silent) { | ||
var start = 0; | ||
if (hasNext()) { | ||
start = cursor.pos; | ||
while(hasNext() && cond(curr())){ | ||
skip(1, silent); | ||
if (this.hasNext()) { | ||
start = this.c.pos; | ||
while(this.hasNext() && condition(this.getCurr())){ | ||
this.skip(1, silent); | ||
} | ||
} | ||
return source.substring(start, cursor.pos); | ||
return this.s.substring(start, this.c.pos); | ||
}; | ||
_proto.grabN = function grabN(num) { | ||
if (num === void 0) num = 0; | ||
return this.s.substring(this.c.pos, this.c.pos + num); | ||
}; | ||
/** | ||
* @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; | ||
} | ||
*/ _proto.substrUntilChar = function substrUntilChar(char) { | ||
var pos = this.c.pos; | ||
var idx = this.s.indexOf(char, pos); | ||
return idx >= 0 ? this.s.substring(pos, idx) : ""; | ||
}; | ||
return CharGrabber; | ||
}(); | ||
var createCharGrabber = function(source, options) { | ||
@@ -136,27 +114,3 @@ return new CharGrabber(source, options); | ||
var unquote = function(str) { | ||
return str.replace(_pluginHelper.BACKSLASH + _pluginHelper.QUOTEMARK, _pluginHelper.QUOTEMARK); | ||
return str.replace(_pluginhelper.BACKSLASH + _pluginhelper.QUOTEMARK, _pluginhelper.QUOTEMARK); | ||
}; | ||
function NodeList(values) { | ||
if (values === void 0) values = []; | ||
var nodes = values; | ||
var getLast = function() { | ||
return Array.isArray(nodes) && nodes.length > 0 && typeof nodes[nodes.length - 1] !== "undefined" ? nodes[nodes.length - 1] : null; | ||
}; | ||
var flushLast = function() { | ||
return nodes.length ? nodes.pop() : false; | ||
}; | ||
var push = function(value) { | ||
return nodes.push(value); | ||
}; | ||
var toArray = function() { | ||
return nodes; | ||
}; | ||
this.push = push; | ||
this.toArray = toArray; | ||
this.getLast = getLast; | ||
this.flushLast = flushLast; | ||
} | ||
var createList = function(values) { | ||
if (values === void 0) values = []; | ||
return new NodeList(values); | ||
}; |
{ | ||
"name": "@bbob/parser", | ||
"version": "3.0.2", | ||
"version": "4.0.0", | ||
"description": "A BBCode to AST Parser part of @bbob", | ||
@@ -15,4 +15,12 @@ "keywords": [ | ||
], | ||
"files": [ | ||
"dist", | ||
"lib", | ||
"src", | ||
"es", | ||
"types" | ||
], | ||
"dependencies": { | ||
"@bbob/plugin-helper": "^3.0.2" | ||
"@bbob/plugin-helper": "*", | ||
"@bbob/types": "*" | ||
}, | ||
@@ -24,2 +32,40 @@ "main": "lib/index.js", | ||
"browserName": "BbobParser", | ||
"types": "types/index.d.ts", | ||
"exports": { | ||
".": { | ||
"types": "./types/index.d.ts", | ||
"import": "./es/index.js", | ||
"require": "./lib/index.js", | ||
"browser": "./dist/index.min.js", | ||
"umd": "./dist/index.min.js" | ||
}, | ||
"./lexer": { | ||
"types": "./types/lexer.d.ts", | ||
"import": "./es/lexer.js", | ||
"require": "./lib/lexer.js", | ||
"browser": "./dist/index.min.js", | ||
"umd": "./dist/index.min.js" | ||
}, | ||
"./parse": { | ||
"types": "./types/parse.d.ts", | ||
"import": "./es/parse.js", | ||
"require": "./lib/parse.js", | ||
"browser": "./dist/index.min.js", | ||
"umd": "./dist/index.min.js" | ||
}, | ||
"./Token": { | ||
"types": "./types/Token.d.ts", | ||
"import": "./es/Token.js", | ||
"require": "./lib/Token.js", | ||
"browser": "./dist/index.min.js", | ||
"umd": "./dist/index.min.js" | ||
}, | ||
"./utils": { | ||
"types": "./types/utils.d.ts", | ||
"import": "./es/utils.js", | ||
"require": "./lib/utils.js", | ||
"browser": "./dist/index.min.js", | ||
"umd": "./dist/index.min.js" | ||
} | ||
}, | ||
"homepage": "https://github.com/JiLiZART/bbob", | ||
@@ -35,17 +81,6 @@ "author": "Nikolay Kostyurin <jilizart@gmail.com>", | ||
}, | ||
"scripts": { | ||
"build:commonjs": "../../scripts/pkg-task build-commonjs", | ||
"build:es": "../../scripts/pkg-task build-es", | ||
"build:umd": "../../scripts/pkg-task build-umd", | ||
"build": "npm run build:commonjs && npm run build:es && npm run build:umd", | ||
"test": "../../scripts/pkg-task test", | ||
"cover": "../../scripts/pkg-task cover", | ||
"lint": "../../scripts/pkg-task lint", | ||
"size": "../../scripts/pkg-task size", | ||
"bundlesize": "../../scripts/pkg-task bundlesize", | ||
"prepublishOnly": "npm run build" | ||
}, | ||
"size-limit": [ | ||
{ | ||
"path": "lib/index.js" | ||
"path": "./dist/index.min.js", | ||
"size": "3 KB" | ||
} | ||
@@ -62,8 +97,14 @@ ], | ||
}, | ||
"files": [ | ||
"dist", | ||
"lib", | ||
"src", | ||
"es" | ||
] | ||
} | ||
"scripts": { | ||
"build:commonjs": "pkg-task", | ||
"build:es": "pkg-task", | ||
"build:umd": "pkg-task", | ||
"build": "pkg-task", | ||
"test": "pkg-task", | ||
"cover": "pkg-task", | ||
"lint": "pkg-task", | ||
"size": "pkg-task", | ||
"bundlesize": "pkg-task", | ||
"types": "pkg-task" | ||
} | ||
} |
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
Wildcard dependency
QualityPackage has a dependency with a floating version range. This can cause issues if the dependency publishes a new major version.
Found 2 instances in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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
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
153202
31
0
2
3479
2
1
+ Added@bbob/types@*
+ Added@bbob/plugin-helper@4.1.1(transitive)
+ Added@bbob/types@4.1.1(transitive)
- Removed@bbob/plugin-helper@3.0.2(transitive)
Updated@bbob/plugin-helper@*