129 lines
3.7 KiB
JavaScript
Executable File
129 lines
3.7 KiB
JavaScript
Executable File
// Query String Utilities
|
|
// Taken from Jack
|
|
|
|
var DEFAULT_SEP = "&";
|
|
var DEFAULT_EQ = "=";
|
|
|
|
exports.unescape = function (str, decodeSpaces) {
|
|
return decodeURIComponent(decodeSpaces ? str.replace(/\+/g, " ") : str);
|
|
};
|
|
|
|
exports.escape = function (str) {
|
|
return encodeURIComponent(str);
|
|
};
|
|
|
|
exports.toQueryString = function (obj, sep, eq, name) {
|
|
sep = sep || DEFAULT_SEP;
|
|
eq = eq || DEFAULT_EQ;
|
|
if (isA(obj, null) || isA(obj, undefined)) {
|
|
return name ? encodeURIComponent(name) + eq : '';
|
|
}
|
|
if (isNumber(obj) || isString(obj)) {
|
|
return encodeURIComponent(name) + eq + encodeURIComponent(obj);
|
|
}
|
|
if (isA(obj, [])) {
|
|
var s = [];
|
|
name = name+'[]';
|
|
for (var i = 0, l = obj.length; i < l; i ++) {
|
|
s.push( exports.toQueryString(obj[i], sep, eq, name) );
|
|
}
|
|
return s.join(sep);
|
|
}
|
|
// now we know it's an object.
|
|
var s = [];
|
|
var begin = name ? name + '[' : '';
|
|
var end = name ? ']' : '';
|
|
for (var i in obj) if (obj.hasOwnProperty(i)) {
|
|
var n = begin + i + end;
|
|
s.push(exports.toQueryString(obj[i], sep, eq, n));
|
|
}
|
|
return s.join(sep);
|
|
};
|
|
|
|
exports.parseQuery = function(qs, sep, eq) {
|
|
return qs
|
|
.split(sep||DEFAULT_SEP)
|
|
.map(pieceParser(eq||DEFAULT_EQ))
|
|
.reduce(mergeParams);
|
|
};
|
|
|
|
// Parse a key=val string.
|
|
// These can get pretty hairy
|
|
// example flow:
|
|
// parse(foo[bar][][bla]=baz)
|
|
// return parse(foo[bar][][bla],"baz")
|
|
// return parse(foo[bar][], {bla : "baz"})
|
|
// return parse(foo[bar], [{bla:"baz"}])
|
|
// return parse(foo, {bar:[{bla:"baz"}]})
|
|
// return {foo:{bar:[{bla:"baz"}]}}
|
|
var pieceParser = function (eq) {
|
|
return function parsePiece (key, val) {
|
|
if (arguments.length !== 2) {
|
|
// key=val, called from the map/reduce
|
|
key = key.split(eq);
|
|
return parsePiece(
|
|
exports.unescape(key.shift(), true), exports.unescape(key.join(eq), true)
|
|
);
|
|
}
|
|
key = key.replace(/^\s+|\s+$/g, '');
|
|
if (isString(val)) val = val.replace(/^\s+|\s+$/g, '');
|
|
var sliced = /(.*)\[([^\]]*)\]$/.exec(key);
|
|
if (!sliced) {
|
|
var ret = {};
|
|
if (key) ret[key] = val;
|
|
return ret;
|
|
}
|
|
// ["foo[][bar][][baz]", "foo[][bar][]", "baz"]
|
|
var tail = sliced[2], head = sliced[1];
|
|
|
|
// array: key[]=val
|
|
if (!tail) return parsePiece(head, [val]);
|
|
|
|
// obj: key[subkey]=val
|
|
var ret = {};
|
|
ret[tail] = val;
|
|
return parsePiece(head, ret);
|
|
};
|
|
};
|
|
|
|
// the reducer function that merges each query piece together into one set of params
|
|
function mergeParams (params, addition) {
|
|
return (
|
|
// if it's uncontested, then just return the addition.
|
|
(!params) ? addition
|
|
// if the existing value is an array, then concat it.
|
|
: (isA(params, [])) ? params.concat(addition)
|
|
// if the existing value is not an array, arrayify it.
|
|
: (!isA(params, {}) || !isA(addition, {})) ? [params].concat(addition)
|
|
// else merge them as objects, which is a little more complex
|
|
: mergeObjects(params, addition)
|
|
)
|
|
};
|
|
|
|
// Merge two *objects* together. If this is called, we've already ruled
|
|
// out the simple cases, and need to do the for-in business.
|
|
function mergeObjects (params, addition) {
|
|
for (var i in addition) if (i && addition.hasOwnProperty(i)) {
|
|
params[i] = mergeParams(params[i], addition[i]);
|
|
}
|
|
return params;
|
|
};
|
|
|
|
// duck typing
|
|
function isA (thing, canon) {
|
|
return (
|
|
// truthiness. you can feel it in your gut.
|
|
(!thing === !canon)
|
|
// typeof is usually "object"
|
|
&& typeof(thing) === typeof(canon)
|
|
// check the constructor
|
|
&& Object.prototype.toString.call(thing) === Object.prototype.toString.call(canon)
|
|
);
|
|
};
|
|
function isNumber (thing) {
|
|
return typeof(thing) === "number" && isFinite(thing);
|
|
};
|
|
function isString (thing) {
|
|
return typeof(thing) === "string";
|
|
};
|