| /** |
| * Object#toString() ref for stringify(). |
| */ |
| |
| var toString = Object.prototype.toString; |
| |
| /** |
| * Object#hasOwnProperty ref |
| */ |
| |
| var hasOwnProperty = Object.prototype.hasOwnProperty; |
| |
| /** |
| * Array#indexOf shim. |
| */ |
| |
| var indexOf = typeof Array.prototype.indexOf === 'function' |
| ? function(arr, el) { return arr.indexOf(el); } |
| : function(arr, el) { |
| for (var i = 0; i < arr.length; i++) { |
| if (arr[i] === el) return i; |
| } |
| return -1; |
| }; |
| |
| /** |
| * Array.isArray shim. |
| */ |
| |
| var isArray = Array.isArray || function(arr) { |
| return toString.call(arr) == '[object Array]'; |
| }; |
| |
| /** |
| * Object.keys shim. |
| */ |
| |
| var objectKeys = Object.keys || function(obj) { |
| var ret = []; |
| for (var key in obj) { |
| if (obj.hasOwnProperty(key)) { |
| ret.push(key); |
| } |
| } |
| return ret; |
| }; |
| |
| /** |
| * Array#forEach shim. |
| */ |
| |
| var forEach = typeof Array.prototype.forEach === 'function' |
| ? function(arr, fn) { return arr.forEach(fn); } |
| : function(arr, fn) { |
| for (var i = 0; i < arr.length; i++) fn(arr[i]); |
| }; |
| |
| /** |
| * Array#reduce shim. |
| */ |
| |
| var reduce = function(arr, fn, initial) { |
| if (typeof arr.reduce === 'function') return arr.reduce(fn, initial); |
| var res = initial; |
| for (var i = 0; i < arr.length; i++) res = fn(res, arr[i]); |
| return res; |
| }; |
| |
| /** |
| * Cache non-integer test regexp. |
| */ |
| |
| var isint = /^[0-9]+$/; |
| |
| function promote(parent, key) { |
| if (parent[key].length == 0) return parent[key] = {} |
| var t = {}; |
| for (var i in parent[key]) { |
| if (hasOwnProperty.call(parent[key], i)) { |
| t[i] = parent[key][i]; |
| } |
| } |
| parent[key] = t; |
| return t; |
| } |
| |
| function parse(parts, parent, key, val) { |
| var part = parts.shift(); |
| |
| // illegal |
| if (Object.getOwnPropertyDescriptor(Object.prototype, key)) return; |
| |
| // end |
| if (!part) { |
| if (isArray(parent[key])) { |
| parent[key].push(val); |
| } else if ('object' == typeof parent[key]) { |
| parent[key] = val; |
| } else if ('undefined' == typeof parent[key]) { |
| parent[key] = val; |
| } else { |
| parent[key] = [parent[key], val]; |
| } |
| // array |
| } else { |
| var obj = parent[key] = parent[key] || []; |
| if (']' == part) { |
| if (isArray(obj)) { |
| if ('' != val) obj.push(val); |
| } else if ('object' == typeof obj) { |
| obj[objectKeys(obj).length] = val; |
| } else { |
| obj = parent[key] = [parent[key], val]; |
| } |
| // prop |
| } else if (~indexOf(part, ']')) { |
| part = part.substr(0, part.length - 1); |
| if (!isint.test(part) && isArray(obj)) obj = promote(parent, key); |
| parse(parts, obj, part, val); |
| // key |
| } else { |
| if (!isint.test(part) && isArray(obj)) obj = promote(parent, key); |
| parse(parts, obj, part, val); |
| } |
| } |
| } |
| |
| /** |
| * Merge parent key/val pair. |
| */ |
| |
| function merge(parent, key, val){ |
| if (~indexOf(key, ']')) { |
| var parts = key.split('[') |
| , len = parts.length |
| , last = len - 1; |
| parse(parts, parent, 'base', val); |
| // optimize |
| } else { |
| if (!isint.test(key) && isArray(parent.base)) { |
| var t = {}; |
| for (var k in parent.base) t[k] = parent.base[k]; |
| parent.base = t; |
| } |
| set(parent.base, key, val); |
| } |
| |
| return parent; |
| } |
| |
| /** |
| * Compact sparse arrays. |
| */ |
| |
| function compact(obj) { |
| if ('object' != typeof obj) return obj; |
| |
| if (isArray(obj)) { |
| var ret = []; |
| |
| for (var i in obj) { |
| if (hasOwnProperty.call(obj, i)) { |
| ret.push(obj[i]); |
| } |
| } |
| |
| return ret; |
| } |
| |
| for (var key in obj) { |
| obj[key] = compact(obj[key]); |
| } |
| |
| return obj; |
| } |
| |
| /** |
| * Parse the given obj. |
| */ |
| |
| function parseObject(obj){ |
| var ret = { base: {} }; |
| |
| forEach(objectKeys(obj), function(name){ |
| merge(ret, name, obj[name]); |
| }); |
| |
| return compact(ret.base); |
| } |
| |
| /** |
| * Parse the given str. |
| */ |
| |
| function parseString(str){ |
| var ret = reduce(String(str).split('&'), function(ret, pair){ |
| var eql = indexOf(pair, '=') |
| , brace = lastBraceInKey(pair) |
| , key = pair.substr(0, brace || eql) |
| , val = pair.substr(brace || eql, pair.length) |
| , val = val.substr(indexOf(val, '=') + 1, val.length); |
| |
| // ?foo |
| if ('' == key) key = pair, val = ''; |
| if ('' == key) return ret; |
| |
| return merge(ret, decode(key), decode(val)); |
| }, { base: {} }).base; |
| |
| return compact(ret); |
| } |
| |
| /** |
| * Parse the given query `str` or `obj`, returning an object. |
| * |
| * @param {String} str | {Object} obj |
| * @return {Object} |
| * @api public |
| */ |
| |
| exports.parse = function(str){ |
| if (null == str || '' == str) return {}; |
| return 'object' == typeof str |
| ? parseObject(str) |
| : parseString(str); |
| }; |
| |
| /** |
| * Turn the given `obj` into a query string |
| * |
| * @param {Object} obj |
| * @return {String} |
| * @api public |
| */ |
| |
| var stringify = exports.stringify = function(obj, prefix) { |
| if (isArray(obj)) { |
| return stringifyArray(obj, prefix); |
| } else if ('[object Object]' == toString.call(obj)) { |
| return stringifyObject(obj, prefix); |
| } else if ('string' == typeof obj) { |
| return stringifyString(obj, prefix); |
| } else { |
| return prefix + '=' + encodeURIComponent(String(obj)); |
| } |
| }; |
| |
| /** |
| * Stringify the given `str`. |
| * |
| * @param {String} str |
| * @param {String} prefix |
| * @return {String} |
| * @api private |
| */ |
| |
| function stringifyString(str, prefix) { |
| if (!prefix) throw new TypeError('stringify expects an object'); |
| return prefix + '=' + encodeURIComponent(str); |
| } |
| |
| /** |
| * Stringify the given `arr`. |
| * |
| * @param {Array} arr |
| * @param {String} prefix |
| * @return {String} |
| * @api private |
| */ |
| |
| function stringifyArray(arr, prefix) { |
| var ret = []; |
| if (!prefix) throw new TypeError('stringify expects an object'); |
| for (var i = 0; i < arr.length; i++) { |
| ret.push(stringify(arr[i], prefix + '[' + i + ']')); |
| } |
| return ret.join('&'); |
| } |
| |
| /** |
| * Stringify the given `obj`. |
| * |
| * @param {Object} obj |
| * @param {String} prefix |
| * @return {String} |
| * @api private |
| */ |
| |
| function stringifyObject(obj, prefix) { |
| var ret = [] |
| , keys = objectKeys(obj) |
| , key; |
| |
| for (var i = 0, len = keys.length; i < len; ++i) { |
| key = keys[i]; |
| if ('' == key) continue; |
| if (null == obj[key]) { |
| ret.push(encodeURIComponent(key) + '='); |
| } else { |
| ret.push(stringify(obj[key], prefix |
| ? prefix + '[' + encodeURIComponent(key) + ']' |
| : encodeURIComponent(key))); |
| } |
| } |
| |
| return ret.join('&'); |
| } |
| |
| /** |
| * Set `obj`'s `key` to `val` respecting |
| * the weird and wonderful syntax of a qs, |
| * where "foo=bar&foo=baz" becomes an array. |
| * |
| * @param {Object} obj |
| * @param {String} key |
| * @param {String} val |
| * @api private |
| */ |
| |
| function set(obj, key, val) { |
| var v = obj[key]; |
| if (Object.getOwnPropertyDescriptor(Object.prototype, key)) return; |
| if (undefined === v) { |
| obj[key] = val; |
| } else if (isArray(v)) { |
| v.push(val); |
| } else { |
| obj[key] = [v, val]; |
| } |
| } |
| |
| /** |
| * Locate last brace in `str` within the key. |
| * |
| * @param {String} str |
| * @return {Number} |
| * @api private |
| */ |
| |
| function lastBraceInKey(str) { |
| var len = str.length |
| , brace |
| , c; |
| for (var i = 0; i < len; ++i) { |
| c = str[i]; |
| if (']' == c) brace = false; |
| if ('[' == c) brace = true; |
| if ('=' == c && !brace) return i; |
| } |
| } |
| |
| /** |
| * Decode `str`. |
| * |
| * @param {String} str |
| * @return {String} |
| * @api private |
| */ |
| |
| function decode(str) { |
| try { |
| return decodeURIComponent(str.replace(/\+/g, ' ')); |
| } catch (err) { |
| return str; |
| } |
| } |