'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, cleanUpCode: cleanUpCode, finalCleanUpCode: finalCleanUpCode, schemaHasRules: schemaHasRules, schemaHasRulesExcept: schemaHasRulesExcept, 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, 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 + ')'; default: return 'typeof ' + data + EQUAL + '"' + dataType + '"'; } } function checkDataTypes(dataTypes, data) { switch (dataTypes.length) { case 1: return checkDataType(dataTypes[0], data, 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, 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'); } var EMPTY_ELSE = /else\s*{\s*}/g , EMPTY_IF_NO_ELSE = /if\s*\([^)]+\)\s*\{\s*\}(?!\s*else)/g , EMPTY_IF_WITH_ELSE = /if\s*\(([^)]+)\)\s*\{\s*\}\s*else(?!\s*if)/g; function cleanUpCode(out) { return out.replace(EMPTY_ELSE, '') .replace(EMPTY_IF_NO_ELSE, '') .replace(EMPTY_IF_WITH_ELSE, 'if (!($1))'); } var ERRORS_REGEXP = /[^v.]errors/g , REMOVE_ERRORS = /var errors = 0;|var vErrors = null;|validate.errors = vErrors;/g , REMOVE_ERRORS_ASYNC = /var errors = 0;|var vErrors = null;/g , RETURN_VALID = 'return errors === 0;' , RETURN_TRUE = 'validate.errors = null; return true;' , RETURN_ASYNC = /if \(errors === 0\) return data;\s*else throw new ValidationError\(vErrors\);/ , RETURN_DATA_ASYNC = 'return data;' , ROOTDATA_REGEXP = /[^A-Za-z_$]rootData[^A-Za-z0-9_$]/g , REMOVE_ROOTDATA = /if \(rootData === undefined\) rootData = data;/; function finalCleanUpCode(out, async) { var matches = out.match(ERRORS_REGEXP); if (matches && matches.length == 2) { out = async ? out.replace(REMOVE_ERRORS_ASYNC, '') .replace(RETURN_ASYNC, RETURN_DATA_ASYNC) : out.replace(REMOVE_ERRORS, '') .replace(RETURN_VALID, RETURN_TRUE); } matches = out.match(ROOTDATA_REGEXP); if (!matches || matches.length !== 3) return out; return out.replace(REMOVE_ROOTDATA, ''); } 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 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, ''); } 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, '~'); }