uri.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. /*
  2. * Utilities: A classic collection of JavaScript utilities
  3. * Copyright 2112 Matthew Eernisse ([email protected])
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. *
  17. */
  18. var uri
  19. , string = require('./string')
  20. , mixin = require('./core').mixin;
  21. /**
  22. @name uri
  23. @namespace uri
  24. */
  25. uri = new (function () {
  26. var _isArray = function (obj) {
  27. return obj &&
  28. typeof obj === 'object' &&
  29. typeof obj.length === 'number' &&
  30. typeof obj.splice === 'function' &&
  31. !(obj.propertyIsEnumerable('length'));
  32. };
  33. /**
  34. @name uri#getFileExtension
  35. @public
  36. @function
  37. @return {String} Returns the file extension for a given path
  38. @description Gets the file extension for a path and returns it
  39. @param {String} path The path to get the extension for
  40. */
  41. this.getFileExtension = function (path) {
  42. var match;
  43. if (path) {
  44. match = /.+\.(\w{2,4}$)/.exec(path);
  45. }
  46. return (match && match[1]) || '';
  47. };
  48. /**
  49. @name uri#paramify
  50. @public
  51. @function
  52. @return {String} Returns a querystring contains the given values
  53. @description Convert a JS Object to a querystring (key=val&key=val). Values in arrays
  54. will be added as multiple parameters
  55. @param {Object} obj An Object containing only scalars and arrays
  56. @param {Object} o The options to use for formatting
  57. @param {Boolean} [o.consolidate=false] take values from elements that can return
  58. multiple values (multi-select, checkbox groups) and collapse into a single,
  59. comman-delimited value.
  60. @param {Boolean} [o.includeEmpty=false] include keys in the string for all elements, even
  61. they have no value set (e.g., even if elemB has no value: elemA=foo&elemB=&elemC=bar).
  62. Note that some false-y values are always valid even without this option, [0, ''].
  63. This option extends coverage to [null, undefined, NaN]
  64. @param {Boolean} [o.snakeize=false] change param names from camelCase to snake_case.
  65. @param {Boolean} [o.escapeVals=false] escape the values for XML entities.
  66. @param {Boolean} [o.index=false] use numeric indices for arrays
  67. */
  68. this.paramify = function (obj, o) {
  69. var opts = o || {},
  70. _opts,
  71. str = '',
  72. key,
  73. val,
  74. isValid,
  75. itemArray,
  76. arr = [],
  77. arrVal,
  78. prefix = opts.prefix || '',
  79. self = this;
  80. function getParamName(key)
  81. {
  82. if (opts.prefix) {
  83. return prefix + '[' + key + ']';
  84. }
  85. else {
  86. return key;
  87. }
  88. }
  89. for (var p in obj) {
  90. if (Object.prototype.hasOwnProperty.call(obj, p)) {
  91. val = obj[p];
  92. // This keeps valid falsy values like false and 0
  93. // It's duplicated in the array block below. Could
  94. // put it in a function but don't want the overhead
  95. isValid = !( val === null || val === undefined ||
  96. (typeof val === 'number' && isNaN(val)) );
  97. key = opts.snakeize ? string.snakeize(p) : p;
  98. if (isValid) {
  99. // Multiple vals -- array
  100. if (_isArray(val) && val.length) {
  101. itemArray = [];
  102. for (var i = 0, ii = val.length; i < ii; i++) {
  103. arrVal = val[i];
  104. // This keeps valid falsy values like false and 0
  105. isValid = !( arrVal === null || arrVal === undefined ||
  106. (typeof arrVal === 'number' && isNaN(arrVal)) );
  107. // for index mode, which works recursive
  108. // objects and array must not be encoded
  109. if (opts.index && typeof arrVal === 'object') {
  110. itemArray[i] = arrVal;
  111. }
  112. else {
  113. itemArray[i] = isValid ? encodeURIComponent(arrVal) : '';
  114. if (opts.escapeVals) {
  115. itemArray[i] = string.escapeXML(itemArray[i]);
  116. }
  117. }
  118. }
  119. // Consolidation mode -- single value joined on comma
  120. if (opts.consolidate) {
  121. arr.push(getParamName(key) + '=' + itemArray.join(','));
  122. }
  123. // Indexed mode -- multiple, same-named params with numeric indices
  124. else if (opts.index) {
  125. // {foo: [1, 2, 3]} => 'foo[0]=1&foo[1]=2&foo[2]=3'
  126. itemArray.forEach(function(item, i) {
  127. // recursion of arrays
  128. if (_isArray(item) && item.length) {
  129. _opts = mixin(opts, {});
  130. item.forEach(function(_item, ii) {
  131. if (typeof _item === 'object') {
  132. _opts.prefix = getParamName(key) + '[' + i + '][' + ii + ']';
  133. arr.push(self.paramify(_item, _opts));
  134. }
  135. else {
  136. arr.push(getParamName(key) + '[' + i + '][' + ii + ']=' + _item);
  137. }
  138. });
  139. }
  140. // recursion of object in array
  141. else if (typeof item === 'object') {
  142. _opts = mixin(opts, {});
  143. _opts.prefix = getParamName(key) + '[' + i + ']';
  144. arr.push(self.paramify(item, _opts));
  145. }
  146. // primitive
  147. else {
  148. arr.push(getParamName(key) + '[' + i + ']=' + item);
  149. }
  150. });
  151. }
  152. // Normal mode -- multiple, same-named params with each val
  153. else {
  154. // {foo: [1, 2, 3]} => 'foo=1&foo=2&foo=3'
  155. // Add into results array, as this just ends up getting
  156. // joined on ampersand at the end anyhow
  157. arr.push(getParamName(key) + '=' + itemArray.join('&' + getParamName(key) + '='));
  158. }
  159. }
  160. // Object -- recursion
  161. else if (typeof val === 'object') {
  162. _opts = mixin(opts, {});
  163. _opts.prefix = getParamName(key);
  164. arr.push(this.paramify(val, _opts));
  165. }
  166. // Single val -- string
  167. else {
  168. if (opts.escapeVals) {
  169. val = string.escapeXML(val);
  170. }
  171. arr.push(getParamName(key) + '=' + encodeURIComponent(val));
  172. }
  173. str += '&';
  174. }
  175. else {
  176. if (opts.includeEmpty) { arr.push(getParamName(key) + '='); }
  177. }
  178. }
  179. }
  180. return arr.join('&');
  181. };
  182. /**
  183. @name uri#objectify
  184. @public
  185. @function
  186. @return {Object} JavaScript key/val object with the values from the querystring
  187. @description Convert the values in a query string (key=val&key=val) to an Object
  188. @param {String} str The querystring to convert to an object
  189. @param {Object} o The options to use for formatting
  190. @param {Boolean} [o.consolidate=true] Convert multiple instances of the same
  191. key into an array of values instead of overwriting
  192. */
  193. this.objectify = function (str, o) {
  194. var opts = o || {};
  195. var d = {};
  196. var consolidate = typeof opts.consolidate == 'undefined' ?
  197. true : opts.consolidate;
  198. if (str) {
  199. var arr = str.split('&');
  200. for (var i = 0; i < arr.length; i++) {
  201. var pair = arr[i].split('=');
  202. var name = pair[0];
  203. var val = decodeURIComponent(pair[1] || '');
  204. // "We've already got one!" -- arrayize if the flag
  205. // is set
  206. if (typeof d[name] != 'undefined' && consolidate) {
  207. if (typeof d[name] == 'string') {
  208. d[name] = [d[name]];
  209. }
  210. d[name].push(val);
  211. }
  212. // Otherwise just set the value
  213. else {
  214. d[name] = val;
  215. }
  216. }
  217. }
  218. return d;
  219. };
  220. })();
  221. module.exports = uri;