2025-04-19 23:12:19 -04:00

286 lines
7.1 KiB
JavaScript

'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, '~');
}