| |
| /*! |
| * querystring |
| * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> |
| * MIT Licensed |
| */ |
| |
| /** |
| * Library version. |
| */ |
| |
| exports.version = '0.4.2'; |
| |
| /** |
| * Object#toString() ref for stringify(). |
| */ |
| |
| var toString = Object.prototype.toString; |
| |
| /** |
| * 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]) t[i] = parent[key][i]; |
| parent[key] = t; |
| return t; |
| } |
| |
| function parse(parts, parent, key, val) { |
| var part = parts.shift(); |
| // end |
| if (!part) { |
| if (Array.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 (Array.isArray(obj)) { |
| if ('' != val) obj.push(val); |
| } else if ('object' == typeof obj) { |
| obj[Object.keys(obj).length] = val; |
| } else { |
| obj = parent[key] = [parent[key], val]; |
| } |
| // prop |
| } else if (~part.indexOf(']')) { |
| part = part.substr(0, part.length - 1); |
| if (!isint.test(part) && Array.isArray(obj)) obj = promote(parent, key); |
| parse(parts, obj, part, val); |
| // key |
| } else { |
| if (!isint.test(part) && Array.isArray(obj)) obj = promote(parent, key); |
| parse(parts, obj, part, val); |
| } |
| } |
| } |
| |
| /** |
| * Merge parent key/val pair. |
| */ |
| |
| function merge(parent, key, val){ |
| if (~key.indexOf(']')) { |
| var parts = key.split('[') |
| , len = parts.length |
| , last = len - 1; |
| parse(parts, parent, 'base', val); |
| // optimize |
| } else { |
| if (!isint.test(key) && Array.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; |
| } |
| |
| /** |
| * Parse the given obj. |
| */ |
| |
| function parseObject(obj){ |
| var ret = { base: {} }; |
| Object.keys(obj).forEach(function(name){ |
| merge(ret, name, obj[name]); |
| }); |
| return ret.base; |
| } |
| |
| /** |
| * Parse the given str. |
| */ |
| |
| function parseString(str){ |
| return String(str) |
| .split('&') |
| .reduce(function(ret, pair){ |
| try{ |
| pair = decodeURIComponent(pair.replace(/\+/g, ' ')); |
| } catch(e) { |
| // ignore |
| } |
| |
| var eql = pair.indexOf('=') |
| , brace = lastBraceInKey(pair) |
| , key = pair.substr(0, brace || eql) |
| , val = pair.substr(brace || eql, pair.length) |
| , val = val.substr(val.indexOf('=') + 1, val.length); |
| |
| // ?foo |
| if ('' == key) key = pair, val = ''; |
| |
| return merge(ret, key, val); |
| }, { base: {} }).base; |
| } |
| |
| /** |
| * 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 (Array.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 + '=' + 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 + '[]')); |
| } |
| return ret.join('&'); |
| } |
| |
| /** |
| * Stringify the given `obj`. |
| * |
| * @param {Object} obj |
| * @param {String} prefix |
| * @return {String} |
| * @api private |
| */ |
| |
| function stringifyObject(obj, prefix) { |
| var ret = [] |
| , keys = Object.keys(obj) |
| , key; |
| |
| for (var i = 0, len = keys.length; i < len; ++i) { |
| key = keys[i]; |
| 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 (undefined === v) { |
| obj[key] = val; |
| } else if (Array.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; |
| } |
| } |