123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342 |
- // Copyright 2013-2014 Kevin Cox
- /*******************************************************************************
- * *
- * This software is provided 'as-is', without any express or implied *
- * warranty. In no event will the authors be held liable for any damages *
- * arising from the use of this software. *
- * *
- * Permission is granted to anyone to use this software for any purpose, *
- * including commercial applications, and to alter it and redistribute it *
- * freely, subject to the following restrictions: *
- * *
- * 1. The origin of this software must not be misrepresented; you must not *
- * claim that you wrote the original software. If you use this software in *
- * a product, an acknowledgment in the product documentation would be *
- * appreciated but is not required. *
- * *
- * 2. Altered source versions must be plainly marked as such, and must not be *
- * misrepresented as being the original software. *
- * *
- * 3. This notice may not be removed or altered from any source distribution. *
- * *
- *******************************************************************************/
- +function(){
- "use strict";
- var array = /\[([^\[]*)\]$/;
- /// URL Regex.
- /**
- * This regex splits the URL into parts. The capture groups catch the important
- * bits.
- *
- * Each section is optional, so to work on any part find the correct top level
- * `(...)?` and mess around with it.
- */
- var regex = /^(?:([a-z]*):)?(?:\/\/)?(?:([^:@]*)(?::([^@]*))?@)?([0-9a-z-._]+)?(?::([0-9]*))?(\/[^?#]*)?(?:\?([^#]*))?(?:#(.*))?$/i;
- // 1 - scheme 2 - user 3 = pass 4 - host 5 - port 6 - path 7 - query 8 - hash
- var noslash = ["mailto","bitcoin"];
- var self = {
- /** Parse a query string.
- *
- * This function parses a query string (sometimes called the search
- * string). It takes a query string and returns a map of the results.
- *
- * Keys are considered to be everything up to the first '=' and values are
- * everything afterwords. Since URL-decoding is done after parsing, keys
- * and values can have any values, however, '=' have to be encoded in keys
- * while '?' and '&' have to be encoded anywhere (as they delimit the
- * kv-pairs).
- *
- * Keys and values will always be strings, except if there is a key with no
- * '=' in which case it will be considered a flag and will be set to true.
- * Later values will override earlier values.
- *
- * Array keys are also supported. By default keys in the form of `name[i]`
- * will be returned like that as strings. However, if you set the `array`
- * flag in the options object they will be parsed into arrays. Note that
- * although the object returned is an `Array` object all keys will be
- * written to it. This means that if you have a key such as `k[forEach]`
- * it will overwrite the `forEach` function on that array. Also note that
- * string properties always take precedence over array properties,
- * irrespective of where they are in the query string.
- *
- * url.get("array[1]=test&array[foo]=bar",{array:true}).array[1] === "test"
- * url.get("array[1]=test&array[foo]=bar",{array:true}).array.foo === "bar"
- * url.get("array=notanarray&array[0]=1",{array:true}).array === "notanarray"
- *
- * If array parsing is enabled keys in the form of `name[]` will
- * automatically be given the next available index. Note that this can be
- * overwritten with later values in the query string. For this reason is
- * is best not to mix the two formats, although it is safe (and often
- * useful) to add an automatic index argument to the end of a query string.
- *
- * url.get("a[]=0&a[]=1&a[0]=2", {array:true}) -> {a:["2","1"]};
- * url.get("a[0]=0&a[1]=1&a[]=2", {array:true}) -> {a:["0","1","2"]};
- *
- * @param{string} q The query string (the part after the '?').
- * @param{{full:boolean,array:boolean}=} opt Options.
- *
- * - full: If set `q` will be treated as a full url and `q` will be built.
- * by calling #parse to retrieve the query portion.
- * - array: If set keys in the form of `key[i]` will be treated
- * as arrays/maps.
- *
- * @return{!Object.<string, string|Array>} The parsed result.
- */
- "get": function(q, opt){
- q = q || "";
- if ( typeof opt == "undefined" ) opt = {};
- if ( typeof opt["full"] == "undefined" ) opt["full"] = false;
- if ( typeof opt["array"] == "undefined" ) opt["array"] = false;
- if ( opt["full"] === true )
- {
- q = self["parse"](q, {"get":false})["query"] || "";
- }
- var o = {};
- var c = q.split("&");
- for (var i = 0; i < c.length; i++)
- {
- if (!c[i].length) continue;
- var d = c[i].indexOf("=");
- var k = c[i], v = true;
- if ( d >= 0 )
- {
- k = c[i].substr(0, d);
- v = c[i].substr(d+1);
- v = decodeURIComponent(v);
- }
- if (opt["array"])
- {
- var inds = [];
- var ind;
- var curo = o;
- var curk = k;
- while (ind = curk.match(array)) // Array!
- {
- curk = curk.substr(0, ind.index);
- inds.unshift(decodeURIComponent(ind[1]));
- }
- curk = decodeURIComponent(curk);
- if (inds.some(function(i)
- {
- if ( typeof curo[curk] == "undefined" ) curo[curk] = [];
- if (!Array.isArray(curo[curk]))
- {
- //console.log("url.get: Array property "+curk+" already exists as string!");
- return true;
- }
- curo = curo[curk];
- if ( i === "" ) i = curo.length;
- curk = i;
- })) continue;
- curo[curk] = v;
- continue;
- }
- k = decodeURIComponent(k);
- //typeof o[k] == "undefined" || console.log("Property "+k+" already exists!");
- o[k] = v;
- }
- return o;
- },
- /** Build a get query from an object.
- *
- * This constructs a query string from the kv pairs in `data`. Calling
- * #get on the string returned should return an object identical to the one
- * passed in except all non-boolean scalar types become strings and all
- * object types become arrays (non-integer keys are still present, see
- * #get's documentation for more details).
- *
- * This always uses array syntax for describing arrays. If you want to
- * serialize them differently (like having the value be a JSON array and
- * have a plain key) you will need to do that before passing it in.
- *
- * All keys and values are supported (binary data anyone?) as they are
- * properly URL-encoded and #get properly decodes.
- *
- * @param{Object} data The kv pairs.
- * @param{string} prefix The properly encoded array key to put the
- * properties. Mainly intended for internal use.
- * @return{string} A URL-safe string.
- */
- "buildget": function(data, prefix){
- var itms = [];
- for ( var k in data )
- {
- var ek = encodeURIComponent(k);
- if ( typeof prefix != "undefined" )
- ek = prefix+"["+ek+"]";
- var v = data[k];
- switch (typeof v)
- {
- case 'boolean':
- if(v) itms.push(ek);
- break;
- case 'number':
- v = v.toString();
- case 'string':
- itms.push(ek+"="+encodeURIComponent(v));
- break;
- case 'object':
- itms.push(self["buildget"](v, ek));
- break;
- }
- }
- return itms.join("&");
- },
- /** Parse a URL
- *
- * This breaks up a URL into components. It attempts to be very liberal
- * and returns the best result in most cases. This means that you can
- * often pass in part of a URL and get correct categories back. Notably,
- * this works for emails and Jabber IDs, as well as adding a '?' to the
- * beginning of a string will parse the whole thing as a query string. If
- * an item is not found the property will be undefined. In some cases an
- * empty string will be returned if the surrounding syntax but the actual
- * value is empty (example: "://example.com" will give a empty string for
- * scheme.) Notably the host name will always be set to something.
- *
- * Returned properties.
- *
- * - **scheme:** The url scheme. (ex: "mailto" or "https")
- * - **user:** The username.
- * - **pass:** The password.
- * - **host:** The hostname. (ex: "localhost", "123.456.7.8" or "example.com")
- * - **port:** The port, as a number. (ex: 1337)
- * - **path:** The path. (ex: "/" or "/about.html")
- * - **query:** "The query string. (ex: "foo=bar&v=17&format=json")
- * - **get:** The query string parsed with get. If `opt.get` is `false` this
- * will be absent
- * - **hash:** The value after the hash. (ex: "myanchor")
- * be undefined even if `query` is set.
- *
- * @param{string} url The URL to parse.
- * @param{{get:Object}=} opt Options:
- *
- * - get: An options argument to be passed to #get or false to not call #get.
- * **DO NOT** set `full`.
- *
- * @return{!Object} An object with the parsed values.
- */
- "parse": function(url, opt) {
- if ( typeof opt == "undefined" ) opt = {};
- var md = url.match(regex) || [];
- var r = {
- "url": url,
- "scheme": md[1],
- "user": md[2],
- "pass": md[3],
- "host": md[4],
- "port": md[5] && +md[5],
- "path": md[6],
- "query": md[7],
- "hash": md[8],
- };
- if ( opt.get !== false )
- r["get"] = r["query"] && self["get"](r["query"], opt.get);
- return r;
- },
- /** Build a URL from components.
- *
- * This pieces together a url from the properties of the passed in object.
- * In general passing the result of `parse()` should return the URL. There
- * may differences in the get string as the keys and values might be more
- * encoded then they were originally were. However, calling `get()` on the
- * two values should yield the same result.
- *
- * Here is how the parameters are used.
- *
- * - url: Used only if no other values are provided. If that is the case
- * `url` will be returned verbatim.
- * - scheme: Used if defined.
- * - user: Used if defined.
- * - pass: Used if defined.
- * - host: Used if defined.
- * - path: Used if defined.
- * - query: Used only if `get` is not provided and non-empty.
- * - get: Used if non-empty. Passed to #buildget and the result is used
- * as the query string.
- * - hash: Used if defined.
- *
- * These are the options that are valid on the options object.
- *
- * - useemptyget: If truthy, a question mark will be appended for empty get
- * strings. This notably makes `build()` and `parse()` fully symmetric.
- *
- * @param{Object} data The pieces of the URL.
- * @param{Object} opt Options for building the url.
- * @return{string} The URL.
- */
- "build": function(data, opt){
- opt = opt || {};
- var r = "";
- if ( typeof data["scheme"] != "undefined" )
- {
- r += data["scheme"];
- r += (noslash.indexOf(data["scheme"])>=0)?":":"://";
- }
- if ( typeof data["user"] != "undefined" )
- {
- r += data["user"];
- if ( typeof data["pass"] == "undefined" )
- {
- r += "@";
- }
- }
- if ( typeof data["pass"] != "undefined" ) r += ":" + data["pass"] + "@";
- if ( typeof data["host"] != "undefined" ) r += data["host"];
- if ( typeof data["port"] != "undefined" ) r += ":" + data["port"];
- if ( typeof data["path"] != "undefined" ) r += data["path"];
- if (opt["useemptyget"])
- {
- if ( typeof data["get"] != "undefined" ) r += "?" + self["buildget"](data["get"]);
- else if ( typeof data["query"] != "undefined" ) r += "?" + data["query"];
- }
- else
- {
- // If .get use it. If .get leads to empty, use .query.
- var q = data["get"] && self["buildget"](data["get"]) || data["query"];
- if (q) r += "?" + q;
- }
- if ( typeof data["hash"] != "undefined" ) r += "#" + data["hash"];
- return r || data["url"] || "";
- },
- };
- if ( typeof define != "undefined" && define["amd"] ) define(self);
- else if ( typeof module != "undefined" ) module['exports'] = self;
- else window["url"] = self;
- }();
|