You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

387 lines
7.5 KiB

  1. /**
  2. * Object#toString() ref for stringify().
  3. */
  4. var toString = Object.prototype.toString;
  5. /**
  6. * Object#hasOwnProperty ref
  7. */
  8. var hasOwnProperty = Object.prototype.hasOwnProperty;
  9. /**
  10. * Array#indexOf shim.
  11. */
  12. var indexOf = typeof Array.prototype.indexOf === 'function'
  13. ? function(arr, el) { return arr.indexOf(el); }
  14. : function(arr, el) {
  15. for (var i = 0; i < arr.length; i++) {
  16. if (arr[i] === el) return i;
  17. }
  18. return -1;
  19. };
  20. /**
  21. * Array.isArray shim.
  22. */
  23. var isArray = Array.isArray || function(arr) {
  24. return toString.call(arr) == '[object Array]';
  25. };
  26. /**
  27. * Object.keys shim.
  28. */
  29. var objectKeys = Object.keys || function(obj) {
  30. var ret = [];
  31. for (var key in obj) ret.push(key);
  32. return ret;
  33. };
  34. /**
  35. * Array#forEach shim.
  36. */
  37. var forEach = typeof Array.prototype.forEach === 'function'
  38. ? function(arr, fn) { return arr.forEach(fn); }
  39. : function(arr, fn) {
  40. for (var i = 0; i < arr.length; i++) fn(arr[i]);
  41. };
  42. /**
  43. * Array#reduce shim.
  44. */
  45. var reduce = function(arr, fn, initial) {
  46. if (typeof arr.reduce === 'function') return arr.reduce(fn, initial);
  47. var res = initial;
  48. for (var i = 0; i < arr.length; i++) res = fn(res, arr[i]);
  49. return res;
  50. };
  51. /**
  52. * Create a nullary object if possible
  53. */
  54. function createObject() {
  55. return Object.create
  56. ? Object.create(null)
  57. : {};
  58. }
  59. /**
  60. * Cache non-integer test regexp.
  61. */
  62. var isint = /^[0-9]+$/;
  63. function promote(parent, key) {
  64. if (parent[key].length == 0) return parent[key] = createObject();
  65. var t = createObject();
  66. for (var i in parent[key]) {
  67. if (hasOwnProperty.call(parent[key], i)) {
  68. t[i] = parent[key][i];
  69. }
  70. }
  71. parent[key] = t;
  72. return t;
  73. }
  74. function parse(parts, parent, key, val) {
  75. var part = parts.shift();
  76. // end
  77. if (!part) {
  78. if (isArray(parent[key])) {
  79. parent[key].push(val);
  80. } else if ('object' == typeof parent[key]) {
  81. parent[key] = val;
  82. } else if ('undefined' == typeof parent[key]) {
  83. parent[key] = val;
  84. } else {
  85. parent[key] = [parent[key], val];
  86. }
  87. // array
  88. } else {
  89. var obj = parent[key] = parent[key] || [];
  90. if (']' == part) {
  91. if (isArray(obj)) {
  92. if ('' != val) obj.push(val);
  93. } else if ('object' == typeof obj) {
  94. obj[objectKeys(obj).length] = val;
  95. } else {
  96. obj = parent[key] = [parent[key], val];
  97. }
  98. // prop
  99. } else if (~indexOf(part, ']')) {
  100. part = part.substr(0, part.length - 1);
  101. if (!isint.test(part) && isArray(obj)) obj = promote(parent, key);
  102. parse(parts, obj, part, val);
  103. // key
  104. } else {
  105. if (!isint.test(part) && isArray(obj)) obj = promote(parent, key);
  106. parse(parts, obj, part, val);
  107. }
  108. }
  109. }
  110. /**
  111. * Merge parent key/val pair.
  112. */
  113. function merge(parent, key, val){
  114. if (~indexOf(key, ']')) {
  115. var parts = key.split('[')
  116. , len = parts.length
  117. , last = len - 1;
  118. parse(parts, parent, 'base', val);
  119. // optimize
  120. } else {
  121. if (!isint.test(key) && isArray(parent.base)) {
  122. var t = createObject();
  123. for (var k in parent.base) t[k] = parent.base[k];
  124. parent.base = t;
  125. }
  126. set(parent.base, key, val);
  127. }
  128. return parent;
  129. }
  130. /**
  131. * Compact sparse arrays.
  132. */
  133. function compact(obj) {
  134. if ('object' != typeof obj) return obj;
  135. if (isArray(obj)) {
  136. var ret = [];
  137. for (var i in obj) {
  138. if (hasOwnProperty.call(obj, i)) {
  139. ret.push(obj[i]);
  140. }
  141. }
  142. return ret;
  143. }
  144. for (var key in obj) {
  145. obj[key] = compact(obj[key]);
  146. }
  147. return obj;
  148. }
  149. /**
  150. * Restore Object.prototype.
  151. * see pull-request #58
  152. */
  153. function restoreProto(obj) {
  154. if (!Object.create) return obj;
  155. if (isArray(obj)) return obj;
  156. if (obj && 'object' != typeof obj) return obj;
  157. for (var key in obj) {
  158. if (hasOwnProperty.call(obj, key)) {
  159. obj[key] = restoreProto(obj[key]);
  160. }
  161. }
  162. obj.__proto__ = Object.prototype;
  163. return obj;
  164. }
  165. /**
  166. * Parse the given obj.
  167. */
  168. function parseObject(obj){
  169. var ret = { base: {} };
  170. forEach(objectKeys(obj), function(name){
  171. merge(ret, name, obj[name]);
  172. });
  173. return compact(ret.base);
  174. }
  175. /**
  176. * Parse the given str.
  177. */
  178. function parseString(str){
  179. var ret = reduce(String(str).split('&'), function(ret, pair){
  180. var eql = indexOf(pair, '=')
  181. , brace = lastBraceInKey(pair)
  182. , key = pair.substr(0, brace || eql)
  183. , val = pair.substr(brace || eql, pair.length)
  184. , val = val.substr(indexOf(val, '=') + 1, val.length);
  185. // ?foo
  186. if ('' == key) key = pair, val = '';
  187. if ('' == key) return ret;
  188. return merge(ret, decode(key), decode(val));
  189. }, { base: createObject() }).base;
  190. return restoreProto(compact(ret));
  191. }
  192. /**
  193. * Parse the given query `str` or `obj`, returning an object.
  194. *
  195. * @param {String} str | {Object} obj
  196. * @return {Object}
  197. * @api public
  198. */
  199. exports.parse = function(str){
  200. if (null == str || '' == str) return {};
  201. return 'object' == typeof str
  202. ? parseObject(str)
  203. : parseString(str);
  204. };
  205. /**
  206. * Turn the given `obj` into a query string
  207. *
  208. * @param {Object} obj
  209. * @return {String}
  210. * @api public
  211. */
  212. var stringify = exports.stringify = function(obj, prefix) {
  213. if (isArray(obj)) {
  214. return stringifyArray(obj, prefix);
  215. } else if ('[object Object]' == toString.call(obj)) {
  216. return stringifyObject(obj, prefix);
  217. } else if ('string' == typeof obj) {
  218. return stringifyString(obj, prefix);
  219. } else {
  220. return prefix + '=' + encodeURIComponent(String(obj));
  221. }
  222. };
  223. /**
  224. * Stringify the given `str`.
  225. *
  226. * @param {String} str
  227. * @param {String} prefix
  228. * @return {String}
  229. * @api private
  230. */
  231. function stringifyString(str, prefix) {
  232. if (!prefix) throw new TypeError('stringify expects an object');
  233. return prefix + '=' + encodeURIComponent(str);
  234. }
  235. /**
  236. * Stringify the given `arr`.
  237. *
  238. * @param {Array} arr
  239. * @param {String} prefix
  240. * @return {String}
  241. * @api private
  242. */
  243. function stringifyArray(arr, prefix) {
  244. var ret = [];
  245. if (!prefix) throw new TypeError('stringify expects an object');
  246. for (var i = 0; i < arr.length; i++) {
  247. ret.push(stringify(arr[i], prefix + '[' + i + ']'));
  248. }
  249. return ret.join('&');
  250. }
  251. /**
  252. * Stringify the given `obj`.
  253. *
  254. * @param {Object} obj
  255. * @param {String} prefix
  256. * @return {String}
  257. * @api private
  258. */
  259. function stringifyObject(obj, prefix) {
  260. var ret = []
  261. , keys = objectKeys(obj)
  262. , key;
  263. for (var i = 0, len = keys.length; i < len; ++i) {
  264. key = keys[i];
  265. if ('' == key) continue;
  266. if (null == obj[key]) {
  267. ret.push(encodeURIComponent(key) + '=');
  268. } else {
  269. ret.push(stringify(obj[key], prefix
  270. ? prefix + '[' + encodeURIComponent(key) + ']'
  271. : encodeURIComponent(key)));
  272. }
  273. }
  274. return ret.join('&');
  275. }
  276. /**
  277. * Set `obj`'s `key` to `val` respecting
  278. * the weird and wonderful syntax of a qs,
  279. * where "foo=bar&foo=baz" becomes an array.
  280. *
  281. * @param {Object} obj
  282. * @param {String} key
  283. * @param {String} val
  284. * @api private
  285. */
  286. function set(obj, key, val) {
  287. var v = obj[key];
  288. if (undefined === v) {
  289. obj[key] = val;
  290. } else if (isArray(v)) {
  291. v.push(val);
  292. } else {
  293. obj[key] = [v, val];
  294. }
  295. }
  296. /**
  297. * Locate last brace in `str` within the key.
  298. *
  299. * @param {String} str
  300. * @return {Number}
  301. * @api private
  302. */
  303. function lastBraceInKey(str) {
  304. var len = str.length
  305. , brace
  306. , c;
  307. for (var i = 0; i < len; ++i) {
  308. c = str[i];
  309. if (']' == c) brace = false;
  310. if ('[' == c) brace = true;
  311. if ('=' == c && !brace) return i;
  312. }
  313. }
  314. /**
  315. * Decode `str`.
  316. *
  317. * @param {String} str
  318. * @return {String}
  319. * @api private
  320. */
  321. function decode(str) {
  322. try {
  323. return decodeURIComponent(str.replace(/\+/g, ' '));
  324. } catch (err) {
  325. return str;
  326. }
  327. }