You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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))
}