206 lines
4.3 KiB
JavaScript
206 lines
4.3 KiB
JavaScript
/**
|
|
* Based on Kendo UI Core expression code <https://github.com/telerik/kendo-ui-core#license-information>
|
|
*/
|
|
'use strict'
|
|
|
|
function Cache(maxSize) {
|
|
this._maxSize = maxSize
|
|
this.clear()
|
|
}
|
|
Cache.prototype.clear = function() {
|
|
this._size = 0
|
|
this._values = {}
|
|
}
|
|
Cache.prototype.get = function(key) {
|
|
return this._values[key]
|
|
}
|
|
Cache.prototype.set = function(key, value) {
|
|
this._size >= this._maxSize && this.clear()
|
|
if (!this._values.hasOwnProperty(key)) {
|
|
this._size++
|
|
}
|
|
return this._values[key] = value
|
|
}
|
|
|
|
var SPLIT_REGEX = /[^.^\]^[]+|(?=\[\]|\.\.)/g,
|
|
DIGIT_REGEX = /^\d+$/,
|
|
LEAD_DIGIT_REGEX = /^\d/,
|
|
SPEC_CHAR_REGEX = /[~`!#$%\^&*+=\-\[\]\\';,/{}|\\":<>\?]/g,
|
|
CLEAN_QUOTES_REGEX = /^\s*(['"]?)(.*?)(\1)\s*$/,
|
|
MAX_CACHE_SIZE = 512
|
|
|
|
var contentSecurityPolicy = false,
|
|
pathCache = new Cache(MAX_CACHE_SIZE),
|
|
setCache = new Cache(MAX_CACHE_SIZE),
|
|
getCache = new Cache(MAX_CACHE_SIZE)
|
|
|
|
try {
|
|
new Function('')
|
|
} catch (error) {
|
|
contentSecurityPolicy = true
|
|
}
|
|
|
|
module.exports = {
|
|
Cache: Cache,
|
|
|
|
expr: expr,
|
|
|
|
split: split,
|
|
|
|
normalizePath: normalizePath,
|
|
|
|
setter: contentSecurityPolicy
|
|
? function(path) {
|
|
var parts = normalizePath(path)
|
|
return function(data, value) {
|
|
return setterFallback(parts, data, value)
|
|
}
|
|
}
|
|
: function(path) {
|
|
return setCache.get(path) || setCache.set(
|
|
path,
|
|
new Function(
|
|
'data, value',
|
|
expr(path, 'data') + ' = value'
|
|
)
|
|
)
|
|
},
|
|
|
|
getter: contentSecurityPolicy
|
|
? function(path, safe) {
|
|
var parts = normalizePath(path)
|
|
return function(data) {
|
|
return getterFallback(parts, safe, data)
|
|
}
|
|
}
|
|
: function(path, safe) {
|
|
var key = path + '_' + safe
|
|
return getCache.get(key) || getCache.set(
|
|
key,
|
|
new Function('data', 'return ' + expr(path, safe, 'data'))
|
|
)
|
|
},
|
|
|
|
join: function(segments) {
|
|
return segments.reduce(function(path, part) {
|
|
return (
|
|
path +
|
|
(isQuoted(part) || DIGIT_REGEX.test(part)
|
|
? '[' + part + ']'
|
|
: (path ? '.' : '') + part)
|
|
)
|
|
}, '')
|
|
},
|
|
|
|
forEach: function(path, cb, thisArg) {
|
|
forEach(split(path), cb, thisArg)
|
|
}
|
|
}
|
|
|
|
function setterFallback(parts, data, value) {
|
|
var index = 0,
|
|
len = parts.length
|
|
while (index < len - 1) {
|
|
data = data[parts[index++]]
|
|
}
|
|
data[parts[index]] = value
|
|
}
|
|
|
|
function getterFallback(parts, safe, data) {
|
|
var index = 0,
|
|
len = parts.length
|
|
while (index < len) {
|
|
if (data != null || !safe) {
|
|
data = data[parts[index++]]
|
|
} else {
|
|
return
|
|
}
|
|
}
|
|
return data
|
|
}
|
|
|
|
function normalizePath(path) {
|
|
return pathCache.get(path) || pathCache.set(
|
|
path,
|
|
split(path).map(function(part) {
|
|
return part.replace(CLEAN_QUOTES_REGEX, '$2')
|
|
})
|
|
)
|
|
}
|
|
|
|
function split(path) {
|
|
return path.match(SPLIT_REGEX)
|
|
}
|
|
|
|
function expr(expression, safe, param) {
|
|
expression = expression || ''
|
|
|
|
if (typeof safe === 'string') {
|
|
param = safe
|
|
safe = false
|
|
}
|
|
|
|
param = param || 'data'
|
|
|
|
if (expression && expression.charAt(0) !== '[') expression = '.' + expression
|
|
|
|
return safe ? makeSafe(expression, param) : param + expression
|
|
}
|
|
|
|
function forEach(parts, iter, thisArg) {
|
|
var len = parts.length,
|
|
part,
|
|
idx,
|
|
isArray,
|
|
isBracket
|
|
|
|
for (idx = 0; idx < len; idx++) {
|
|
part = parts[idx]
|
|
|
|
if (part) {
|
|
if (shouldBeQuoted(part)) {
|
|
part = '"' + part + '"'
|
|
}
|
|
|
|
isBracket = isQuoted(part)
|
|
isArray = !isBracket && /^\d+$/.test(part)
|
|
|
|
iter.call(thisArg, part, isBracket, isArray, idx, parts)
|
|
}
|
|
}
|
|
}
|
|
|
|
function isQuoted(str) {
|
|
return (
|
|
typeof str === 'string' && str && ["'", '"'].indexOf(str.charAt(0)) !== -1
|
|
)
|
|
}
|
|
|
|
function makeSafe(path, param) {
|
|
var result = param,
|
|
parts = split(path),
|
|
isLast
|
|
|
|
forEach(parts, function(part, isBracket, isArray, idx, parts) {
|
|
isLast = idx === parts.length - 1
|
|
|
|
part = isBracket || isArray ? '[' + part + ']' : '.' + part
|
|
|
|
result += part + (!isLast ? ' || {})' : ')')
|
|
})
|
|
|
|
return new Array(parts.length + 1).join('(') + result
|
|
}
|
|
|
|
function hasLeadingNumber(part) {
|
|
return part.match(LEAD_DIGIT_REGEX) && !part.match(DIGIT_REGEX)
|
|
}
|
|
|
|
function hasSpecialChars(part) {
|
|
return SPEC_CHAR_REGEX.test(part)
|
|
}
|
|
|
|
function shouldBeQuoted(part) {
|
|
return !isQuoted(part) && (hasLeadingNumber(part) || hasSpecialChars(part))
|
|
}
|