page-query.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. (function(exports){
  2. /**
  3. * Library version.
  4. */
  5. exports.version = '0.4.2';
  6. /**
  7. * Object#toString() ref for stringify().
  8. */
  9. var toString = Object.prototype.toString;
  10. /**
  11. * Cache non-integer test regexp.
  12. */
  13. var isint = /^[0-9]+$/;
  14. function promote(parent, key) {
  15. if (parent[key].length == 0) return parent[key] = {};
  16. var t = {};
  17. for (var i in parent[key]) t[i] = parent[key][i];
  18. parent[key] = t;
  19. return t;
  20. }
  21. function parse(parts, parent, key, val) {
  22. var part = parts.shift();
  23. // end
  24. if (!part) {
  25. if (Array.isArray(parent[key])) {
  26. parent[key].push(val);
  27. } else if ('object' == typeof parent[key]) {
  28. parent[key] = val;
  29. } else if ('undefined' == typeof parent[key]) {
  30. parent[key] = val;
  31. } else {
  32. parent[key] = [parent[key], val];
  33. }
  34. // array
  35. } else {
  36. var obj = parent[key] = parent[key] || [];
  37. if (']' == part) {
  38. if (Array.isArray(obj)) {
  39. if ('' != val) obj.push(val);
  40. } else if ('object' == typeof obj) {
  41. obj[Object.keys(obj).length] = val;
  42. } else {
  43. obj = parent[key] = [parent[key], val];
  44. }
  45. // prop
  46. } else if (~part.indexOf(']')) {
  47. part = part.substr(0, part.length - 1);
  48. if (!isint.test(part) && Array.isArray(obj)) obj = promote(parent, key);
  49. parse(parts, obj, part, val);
  50. // key
  51. } else {
  52. if (!isint.test(part) && Array.isArray(obj)) obj = promote(parent, key);
  53. parse(parts, obj, part, val);
  54. }
  55. }
  56. }
  57. /**
  58. * Merge parent key/val pair.
  59. */
  60. function merge(parent, key, val){
  61. if (~key.indexOf(']')) {
  62. var parts = key.split('[')
  63. , len = parts.length
  64. , last = len - 1;
  65. parse(parts, parent, 'base', val);
  66. // optimize
  67. } else {
  68. if (!isint.test(key) && Array.isArray(parent.base)) {
  69. var t = {};
  70. for (var k in parent.base) t[k] = parent.base[k];
  71. parent.base = t;
  72. }
  73. set(parent.base, key, val);
  74. }
  75. return parent;
  76. }
  77. /**
  78. * Parse the given obj.
  79. */
  80. function parseObject(obj){
  81. var ret = { base: {} };
  82. Object.keys(obj).forEach(function(name){
  83. merge(ret, name, obj[name]);
  84. });
  85. return ret.base;
  86. }
  87. /**
  88. * Parse the given str.
  89. */
  90. function parseString(str){
  91. return String(str)
  92. .split('&')
  93. .reduce(function(ret, pair){
  94. try{
  95. pair = decodeURIComponent(pair.replace(/\+/g, ' '));
  96. } catch(e) {
  97. // ignore
  98. }
  99. var eql = pair.indexOf('=')
  100. , brace = lastBraceInKey(pair)
  101. , key = pair.substr(0, brace || eql)
  102. , val = pair.substr(brace || eql, pair.length)
  103. , val = val.substr(val.indexOf('=') + 1, val.length);
  104. // ?foo
  105. if ('' == key) key = pair, val = '';
  106. return merge(ret, key, val);
  107. }, { base: {} }).base;
  108. }
  109. /**
  110. * Parse the given query `str` or `obj`, returning an object.
  111. *
  112. * @param {String} str | {Object} obj
  113. * @return {Object}
  114. * @api public
  115. */
  116. exports.parse = function(str){
  117. if (null == str || '' == str) return {};
  118. return 'object' == typeof str
  119. ? parseObject(str)
  120. : parseString(str);
  121. };
  122. /**
  123. * Turn the given `obj` into a query string
  124. *
  125. * @param {Object} obj
  126. * @return {String}
  127. * @api public
  128. */
  129. var stringify = exports.stringify = function(obj, prefix) {
  130. if (Array.isArray(obj)) {
  131. return stringifyArray(obj, prefix);
  132. } else if ('[object Object]' == toString.call(obj)) {
  133. return stringifyObject(obj, prefix);
  134. } else if ('string' == typeof obj) {
  135. return stringifyString(obj, prefix);
  136. } else {
  137. return prefix + '=' + obj;
  138. }
  139. };
  140. /**
  141. * Stringify the given `str`.
  142. *
  143. * @param {String} str
  144. * @param {String} prefix
  145. * @return {String}
  146. * @api private
  147. */
  148. function stringifyString(str, prefix) {
  149. if (!prefix) throw new TypeError('stringify expects an object');
  150. return prefix + '=' + encodeURIComponent(str);
  151. }
  152. /**
  153. * Stringify the given `arr`.
  154. *
  155. * @param {Array} arr
  156. * @param {String} prefix
  157. * @return {String}
  158. * @api private
  159. */
  160. function stringifyArray(arr, prefix) {
  161. var ret = [];
  162. if (!prefix) throw new TypeError('stringify expects an object');
  163. for (var i = 0; i < arr.length; i++) {
  164. ret.push(stringify(arr[i], prefix + '['+i+']'));
  165. }
  166. return ret.join('&');
  167. }
  168. /**
  169. * Stringify the given `obj`.
  170. *
  171. * @param {Object} obj
  172. * @param {String} prefix
  173. * @return {String}
  174. * @api private
  175. */
  176. function stringifyObject(obj, prefix) {
  177. var ret = []
  178. , keys = Object.keys(obj)
  179. , key;
  180. for (var i = 0, len = keys.length; i < len; ++i) {
  181. key = keys[i];
  182. ret.push(stringify(obj[key], prefix
  183. ? prefix + '[' + encodeURIComponent(key) + ']'
  184. : encodeURIComponent(key)));
  185. }
  186. return ret.join('&');
  187. }
  188. /**
  189. * Set `obj`'s `key` to `val` respecting
  190. * the weird and wonderful syntax of a qs,
  191. * where "foo=bar&foo=baz" becomes an array.
  192. *
  193. * @param {Object} obj
  194. * @param {String} key
  195. * @param {String} val
  196. * @api private
  197. */
  198. function set(obj, key, val) {
  199. var v = obj[key];
  200. if (undefined === v) {
  201. obj[key] = val;
  202. } else if (Array.isArray(v)) {
  203. v.push(val);
  204. } else {
  205. obj[key] = [v, val];
  206. }
  207. }
  208. /**
  209. * Locate last brace in `str` within the key.
  210. *
  211. * @param {String} str
  212. * @return {Number}
  213. * @api private
  214. */
  215. function lastBraceInKey(str) {
  216. var len = str.length
  217. , brace
  218. , c;
  219. for (var i = 0; i < len; ++i) {
  220. c = str[i];
  221. if (']' == c) brace = false;
  222. if ('[' == c) brace = true;
  223. if ('=' == c && !brace) return i;
  224. }
  225. }
  226. })('undefined' == typeof exports ? qs = {} : exports);