You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
239 lines
6.8 KiB
239 lines
6.8 KiB
'use strict'; |
|
|
|
|
|
module.exports = { |
|
copy: copy, |
|
checkDataType: checkDataType, |
|
checkDataTypes: checkDataTypes, |
|
coerceToTypes: coerceToTypes, |
|
toHash: toHash, |
|
getProperty: getProperty, |
|
escapeQuotes: escapeQuotes, |
|
equal: require('fast-deep-equal'), |
|
ucs2length: require('./ucs2length'), |
|
varOccurences: varOccurences, |
|
varReplace: varReplace, |
|
schemaHasRules: schemaHasRules, |
|
schemaHasRulesExcept: schemaHasRulesExcept, |
|
schemaUnknownRules: schemaUnknownRules, |
|
toQuotedString: toQuotedString, |
|
getPathExpr: getPathExpr, |
|
getPath: getPath, |
|
getData: getData, |
|
unescapeFragment: unescapeFragment, |
|
unescapeJsonPointer: unescapeJsonPointer, |
|
escapeFragment: escapeFragment, |
|
escapeJsonPointer: escapeJsonPointer |
|
}; |
|
|
|
|
|
function copy(o, to) { |
|
to = to || {}; |
|
for (var key in o) to[key] = o[key]; |
|
return to; |
|
} |
|
|
|
|
|
function checkDataType(dataType, data, strictNumbers, negate) { |
|
var EQUAL = negate ? ' !== ' : ' === ' |
|
, AND = negate ? ' || ' : ' && ' |
|
, OK = negate ? '!' : '' |
|
, NOT = negate ? '' : '!'; |
|
switch (dataType) { |
|
case 'null': return data + EQUAL + 'null'; |
|
case 'array': return OK + 'Array.isArray(' + data + ')'; |
|
case 'object': return '(' + OK + data + AND + |
|
'typeof ' + data + EQUAL + '"object"' + AND + |
|
NOT + 'Array.isArray(' + data + '))'; |
|
case 'integer': return '(typeof ' + data + EQUAL + '"number"' + AND + |
|
NOT + '(' + data + ' % 1)' + |
|
AND + data + EQUAL + data + |
|
(strictNumbers ? (AND + OK + 'isFinite(' + data + ')') : '') + ')'; |
|
case 'number': return '(typeof ' + data + EQUAL + '"' + dataType + '"' + |
|
(strictNumbers ? (AND + OK + 'isFinite(' + data + ')') : '') + ')'; |
|
default: return 'typeof ' + data + EQUAL + '"' + dataType + '"'; |
|
} |
|
} |
|
|
|
|
|
function checkDataTypes(dataTypes, data, strictNumbers) { |
|
switch (dataTypes.length) { |
|
case 1: return checkDataType(dataTypes[0], data, strictNumbers, true); |
|
default: |
|
var code = ''; |
|
var types = toHash(dataTypes); |
|
if (types.array && types.object) { |
|
code = types.null ? '(': '(!' + data + ' || '; |
|
code += 'typeof ' + data + ' !== "object")'; |
|
delete types.null; |
|
delete types.array; |
|
delete types.object; |
|
} |
|
if (types.number) delete types.integer; |
|
for (var t in types) |
|
code += (code ? ' && ' : '' ) + checkDataType(t, data, strictNumbers, true); |
|
|
|
return code; |
|
} |
|
} |
|
|
|
|
|
var COERCE_TO_TYPES = toHash([ 'string', 'number', 'integer', 'boolean', 'null' ]); |
|
function coerceToTypes(optionCoerceTypes, dataTypes) { |
|
if (Array.isArray(dataTypes)) { |
|
var types = []; |
|
for (var i=0; i<dataTypes.length; i++) { |
|
var t = dataTypes[i]; |
|
if (COERCE_TO_TYPES[t]) types[types.length] = t; |
|
else if (optionCoerceTypes === 'array' && t === 'array') types[types.length] = t; |
|
} |
|
if (types.length) return types; |
|
} else if (COERCE_TO_TYPES[dataTypes]) { |
|
return [dataTypes]; |
|
} else if (optionCoerceTypes === 'array' && dataTypes === 'array') { |
|
return ['array']; |
|
} |
|
} |
|
|
|
|
|
function toHash(arr) { |
|
var hash = {}; |
|
for (var i=0; i<arr.length; i++) hash[arr[i]] = true; |
|
return hash; |
|
} |
|
|
|
|
|
var IDENTIFIER = /^[a-z$_][a-z$_0-9]*$/i; |
|
var SINGLE_QUOTE = /'|\\/g; |
|
function getProperty(key) { |
|
return typeof key == 'number' |
|
? '[' + key + ']' |
|
: IDENTIFIER.test(key) |
|
? '.' + key |
|
: "['" + escapeQuotes(key) + "']"; |
|
} |
|
|
|
|
|
function escapeQuotes(str) { |
|
return str.replace(SINGLE_QUOTE, '\\$&') |
|
.replace(/\n/g, '\\n') |
|
.replace(/\r/g, '\\r') |
|
.replace(/\f/g, '\\f') |
|
.replace(/\t/g, '\\t'); |
|
} |
|
|
|
|
|
function varOccurences(str, dataVar) { |
|
dataVar += '[^0-9]'; |
|
var matches = str.match(new RegExp(dataVar, 'g')); |
|
return matches ? matches.length : 0; |
|
} |
|
|
|
|
|
function varReplace(str, dataVar, expr) { |
|
dataVar += '([^0-9])'; |
|
expr = expr.replace(/\$/g, '$$$$'); |
|
return str.replace(new RegExp(dataVar, 'g'), expr + '$1'); |
|
} |
|
|
|
|
|
function schemaHasRules(schema, rules) { |
|
if (typeof schema == 'boolean') return !schema; |
|
for (var key in schema) if (rules[key]) return true; |
|
} |
|
|
|
|
|
function schemaHasRulesExcept(schema, rules, exceptKeyword) { |
|
if (typeof schema == 'boolean') return !schema && exceptKeyword != 'not'; |
|
for (var key in schema) if (key != exceptKeyword && rules[key]) return true; |
|
} |
|
|
|
|
|
function schemaUnknownRules(schema, rules) { |
|
if (typeof schema == 'boolean') return; |
|
for (var key in schema) if (!rules[key]) return key; |
|
} |
|
|
|
|
|
function toQuotedString(str) { |
|
return '\'' + escapeQuotes(str) + '\''; |
|
} |
|
|
|
|
|
function getPathExpr(currentPath, expr, jsonPointers, isNumber) { |
|
var path = jsonPointers // false by default |
|
? '\'/\' + ' + expr + (isNumber ? '' : '.replace(/~/g, \'~0\').replace(/\\//g, \'~1\')') |
|
: (isNumber ? '\'[\' + ' + expr + ' + \']\'' : '\'[\\\'\' + ' + expr + ' + \'\\\']\''); |
|
return joinPaths(currentPath, path); |
|
} |
|
|
|
|
|
function getPath(currentPath, prop, jsonPointers) { |
|
var path = jsonPointers // false by default |
|
? toQuotedString('/' + escapeJsonPointer(prop)) |
|
: toQuotedString(getProperty(prop)); |
|
return joinPaths(currentPath, path); |
|
} |
|
|
|
|
|
var JSON_POINTER = /^\/(?:[^~]|~0|~1)*$/; |
|
var RELATIVE_JSON_POINTER = /^([0-9]+)(#|\/(?:[^~]|~0|~1)*)?$/; |
|
function getData($data, lvl, paths) { |
|
var up, jsonPointer, data, matches; |
|
if ($data === '') return 'rootData'; |
|
if ($data[0] == '/') { |
|
if (!JSON_POINTER.test($data)) throw new Error('Invalid JSON-pointer: ' + $data); |
|
jsonPointer = $data; |
|
data = 'rootData'; |
|
} else { |
|
matches = $data.match(RELATIVE_JSON_POINTER); |
|
if (!matches) throw new Error('Invalid JSON-pointer: ' + $data); |
|
up = +matches[1]; |
|
jsonPointer = matches[2]; |
|
if (jsonPointer == '#') { |
|
if (up >= lvl) throw new Error('Cannot access property/index ' + up + ' levels up, current level is ' + lvl); |
|
return paths[lvl - up]; |
|
} |
|
|
|
if (up > lvl) throw new Error('Cannot access data ' + up + ' levels up, current level is ' + lvl); |
|
data = 'data' + ((lvl - up) || ''); |
|
if (!jsonPointer) return data; |
|
} |
|
|
|
var expr = data; |
|
var segments = jsonPointer.split('/'); |
|
for (var i=0; i<segments.length; i++) { |
|
var segment = segments[i]; |
|
if (segment) { |
|
data += getProperty(unescapeJsonPointer(segment)); |
|
expr += ' && ' + data; |
|
} |
|
} |
|
return expr; |
|
} |
|
|
|
|
|
function joinPaths (a, b) { |
|
if (a == '""') return b; |
|
return (a + ' + ' + b).replace(/([^\\])' \+ '/g, '$1'); |
|
} |
|
|
|
|
|
function unescapeFragment(str) { |
|
return unescapeJsonPointer(decodeURIComponent(str)); |
|
} |
|
|
|
|
|
function escapeFragment(str) { |
|
return encodeURIComponent(escapeJsonPointer(str)); |
|
} |
|
|
|
|
|
function escapeJsonPointer(str) { |
|
return str.replace(/~/g, '~0').replace(/\//g, '~1'); |
|
} |
|
|
|
|
|
function unescapeJsonPointer(str) { |
|
return str.replace(/~1/g, '/').replace(/~0/g, '~'); |
|
}
|
|
|