(function () { 'use strict'; var symbol = { param: '&', key: ':', keyValue: '=', array: '@', flagString: "'", flagNumber: 'n', flagBool: 'b', flagDate: 'd', flagNull: '_', flagUndefined: '' }; var toString = Object.prototype.toString; var isObject = function (value) { return value != null && typeof value === 'object'; }; var isArray = function (value) { return toString.call(value) === '[object Array]'; }; var notDefined = function (value) { return typeof value === 'undefined'; }; var encodeObject = function (x, prefix) { var result = '', name, sub, key; for (name in x) { if (x.hasOwnProperty(name)) { key = (prefix ? prefix + symbol.key : '') + encodeURIComponent(name); sub = encode(x[name], key); result += (result ? symbol.param : '') + (sub[0] ? '' : key + symbol.keyValue) + sub[1]; } } return result; }; var encodeArray = function (x, prefix) { var result = '', i, c, sub, key; for (i = 0, c = x.length; i < c; ++i) { key = (prefix ? prefix + symbol.key : '') + symbol.array + i; sub = encode(x[i], key); result += (result ? symbol.param : '') + (sub[0] ? '' : key + symbol.keyValue) + sub[1]; } return result; }; var setProp = function (target, path, value) { var data, raw, key, i, n, count; if (!isObject(target) && !isArray(target)) { throw new Error('setProp: target is not an object or array: ' + toString.call(data)); } data = target; for (i = 0, count = path.length; i < count; ++i) { n = i + 1; raw = path[i]; key = (symbol.array === raw[0]) ? 0|raw.substring(1) : decodeURIComponent(raw); if (n === count) { if (symbol.array === raw[0] ? isArray(data) : isObject(data)) { data[key] = value; } else { throw new Error('setProp: property "' + path.join(':') + '" has wrong type: ' + toString.call(data)); } } else { if (notDefined(data[key])) { data[key] = (symbol.array === path[n][0]) ? [] : {}; data = data[key]; } else if ((symbol.array === path[n][0]) ? isArray(data[key]) : isObject(data[key])) { data = data[key]; } else { throw new Error('setProp: property "' + path.slice(0, n).join(':') + '" has wrong type: ' + toString.call(data[key])); } } } return true; }; // todo: prevent cycle encoding function encode(x, prefix) { var complex = false, result, type; type = toString.call(x); switch (type) { case '[object String]': result = "'" + encodeURIComponent(x) + "'"; break; case '[object Number]': result = isFinite(x) ? '' + x : symbol.flagNumber + x; break; case '[object Boolean]': result = symbol.flagBool + (+x); break; case '[object Object]': complex = true; result = encodeObject(x, prefix); break; case '[object Array]': complex = true; result = encodeArray(x, prefix); break; case '[object Date]': result = symbol.flagDate + x; break; case '[object Null]': result = symbol.flagNull; break; case '[object Undefined]': result = symbol.flagUndefined; break; default: result = symbol.flagUndefined; break; } return [complex, result]; } function decode(s) { var result = {}, i, c, pos, parts, item, path, value; parts = ('' + s).split(symbol.param); for (i = 0, c = parts.length; i < c; ++i) { item = parts[i]; if (-1 !== (pos = item.indexOf(symbol.keyValue))) { path = item.substring(0, pos).split(symbol.key); value = decodeURIComponent(item.substring(pos + 1)); switch (value[0]) { case symbol.flagString: value = value.substring(1, value.length - 1); break; case symbol.flagNumber: value = +value.substring(1); break; case symbol.flagBool: value = !!(~~value.substring(1)); break; case symbol.flagDate: value = new Date(value.substring(1)); break; case symbol.flagNull: value = null; break; default: if (/^\d+$/.test(value)) { value = +value; } else { value = undefined; } break; } setProp(result, path, value); } } return result; } var query = { encode: function (x) { return encode(x, '')[1]; }, decode: function (s) { return decode(s); } }; angular.module('query', []) .factory('query', function () { return query; }); })();