forked from nodejs/node
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add URL and QueryString modules, and tests for each.
Also, make a slight change from original on url-module to put the spacePattern into the function. On closer inspection, it turns out that the nonlocal-var cost is higher than the compiling-a-regexp cost. Also, documentation.
- Loading branch information
Showing
5 changed files
with
1,189 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
// Query String Utilities | ||
|
||
var QueryString = exports; | ||
|
||
QueryString.unescape = function (str, decodeSpaces) { | ||
return decodeURIComponent(decodeSpaces ? str.replace(/\+/g, " ") : str); | ||
}; | ||
|
||
QueryString.escape = function (str) { | ||
return encodeURIComponent(str); | ||
}; | ||
|
||
|
||
var stack = []; | ||
/** | ||
* <p>Converts an arbitrary value to a Query String representation.</p> | ||
* | ||
* <p>Objects with cyclical references will trigger an exception.</p> | ||
* | ||
* @method stringify | ||
* @param obj {Variant} any arbitrary value to convert to query string | ||
* @param sep {String} (optional) Character that should join param k=v pairs together. Default: "&" | ||
* @param eq {String} (optional) Character that should join keys to their values. Default: "=" | ||
* @param name {String} (optional) Name of the current key, for handling children recursively. | ||
* @static | ||
*/ | ||
QueryString.stringify = function (obj, sep, eq, name) { | ||
sep = sep || "&"; | ||
eq = eq || "="; | ||
if (isA(obj, null) || isA(obj, undefined) || typeof(obj) === 'function') { | ||
return name ? encodeURIComponent(name) + eq : ''; | ||
} | ||
|
||
if (isBool(obj)) obj = +obj; | ||
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( QueryString.stringify(obj[i], sep, eq, name) ); | ||
} | ||
return s.join(sep); | ||
} | ||
// now we know it's an object. | ||
|
||
// Check for cyclical references in nested objects | ||
for (var i = stack.length - 1; i >= 0; --i) if (stack[i] === obj) { | ||
throw new Error("querystring.stringify. Cyclical reference"); | ||
} | ||
|
||
stack.push(obj); | ||
|
||
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(QueryString.stringify(obj[i], sep, eq, n)); | ||
} | ||
|
||
stack.pop(); | ||
|
||
s = s.join(sep); | ||
if (!s && name) return name + "="; | ||
return s; | ||
}; | ||
|
||
QueryString.parseQuery = QueryString.parse = function (qs, sep, eq) { | ||
return qs | ||
.split(sep||"&") | ||
.map(pieceParser(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 trimmerPattern = /^\s+|\s+$/g, | ||
slicerPattern = /(.*)\[([^\]]*)\]$/; | ||
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( | ||
QueryString.unescape(key.shift(), true), | ||
QueryString.unescape(key.join(eq), true) | ||
); | ||
} | ||
key = key.replace(trimmerPattern, ''); | ||
if (isString(val)) { | ||
val = val.replace(trimmerPattern, ''); | ||
// convert numerals to numbers | ||
if (!isNaN(val)) { | ||
var numVal = +val; | ||
if (val === numVal.toString(10)) val = numVal; | ||
} | ||
} | ||
var sliced = slicerPattern.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, and either are not objects, 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 isBool (thing) { | ||
return ( | ||
typeof(thing) === "boolean" | ||
|| isA(thing, new Boolean(thing)) | ||
); | ||
}; | ||
function isNumber (thing) { | ||
return ( | ||
typeof(thing) === "number" | ||
|| isA(thing, new Number(thing)) | ||
) && isFinite(thing); | ||
}; | ||
function isString (thing) { | ||
return ( | ||
typeof(thing) === "string" | ||
|| isA(thing, new String(thing)) | ||
); | ||
}; |
Oops, something went wrong.