diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29..0000000 diff --git a/lib/uri/tests/index.html b/lib/uri/tests/index.html new file mode 100644 index 0000000..1f76ce3 --- /dev/null +++ b/lib/uri/tests/index.html @@ -0,0 +1,17 @@ + + + + + + + + + +

URI.js Test Suite

+

+
+

+
    + + \ No newline at end of file diff --git a/lib/uri/tests/qunit.css b/lib/uri/tests/qunit.css new file mode 100644 index 0000000..861c40d --- /dev/null +++ b/lib/uri/tests/qunit.css @@ -0,0 +1,118 @@ +ol#qunit-tests { + font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; + margin:0; + padding:0; + list-style-position:inside; + + font-size: smaller; +} +ol#qunit-tests li{ + padding:0.4em 0.5em 0.4em 2.5em; + border-bottom:1px solid #fff; + font-size:small; + list-style-position:inside; +} +ol#qunit-tests li ol{ + box-shadow: inset 0px 2px 13px #999; + -moz-box-shadow: inset 0px 2px 13px #999; + -webkit-box-shadow: inset 0px 2px 13px #999; + margin-top:0.5em; + margin-left:0; + padding:0.5em; + background-color:#fff; + border-radius:15px; + -moz-border-radius: 15px; + -webkit-border-radius: 15px; +} +ol#qunit-tests li li{ + border-bottom:none; + margin:0.5em; + background-color:#fff; + list-style-position: inside; + padding:0.4em 0.5em 0.4em 0.5em; +} + +ol#qunit-tests li li.pass{ + border-left:26px solid #C6E746; + background-color:#fff; + color:#5E740B; + } +ol#qunit-tests li li.fail{ + border-left:26px solid #EE5757; + background-color:#fff; + color:#710909; +} +ol#qunit-tests li.pass{ + background-color:#D2E0E6; + color:#528CE0; +} +ol#qunit-tests li.fail{ + background-color:#EE5757; + color:#000; +} +ol#qunit-tests li strong { + cursor:pointer; +} +h1#qunit-header{ + background-color:#0d3349; + margin:0; + padding:0.5em 0 0.5em 1em; + color:#fff; + font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; + border-top-right-radius:15px; + border-top-left-radius:15px; + -moz-border-radius-topright:15px; + -moz-border-radius-topleft:15px; + -webkit-border-top-right-radius:15px; + -webkit-border-top-left-radius:15px; + text-shadow: rgba(0, 0, 0, 0.5) 4px 4px 1px; +} +h2#qunit-banner{ + font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; + height:5px; + margin:0; + padding:0; +} +h2#qunit-banner.qunit-pass{ + background-color:#C6E746; +} +h2#qunit-banner.qunit-fail, #qunit-testrunner-toolbar { + background-color:#EE5757; +} +#qunit-testrunner-toolbar { + font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; + padding:0; + /*width:80%;*/ + padding:0em 0 0.5em 2em; + font-size: small; +} +h2#qunit-userAgent { + font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; + background-color:#2b81af; + margin:0; + padding:0; + color:#fff; + font-size: small; + padding:0.5em 0 0.5em 2.5em; + text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; +} +p#qunit-testresult{ + font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; + margin:0; + font-size: small; + color:#2b81af; + border-bottom-right-radius:15px; + border-bottom-left-radius:15px; + -moz-border-radius-bottomright:15px; + -moz-border-radius-bottomleft:15px; + -webkit-border-bottom-right-radius:15px; + -webkit-border-bottom-left-radius:15px; + background-color:#D2E0E6; + padding:0.5em 0.5em 0.5em 2.5em; +} +strong b.fail{ + color:#710909; + } +strong b.pass{ + color:#5E740B; + } \ No newline at end of file diff --git a/lib/uri/tests/qunit.js b/lib/uri/tests/qunit.js new file mode 100644 index 0000000..f050a08 --- /dev/null +++ b/lib/uri/tests/qunit.js @@ -0,0 +1,1042 @@ +/* + * QUnit - A JavaScript Unit Testing Framework + * + * http://docs.jquery.com/QUnit + * + * Copyright (c) 2009 John Resig, Jörn Zaefferer + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + */ + +(function(window) { + +var QUnit = { + + // Initialize the configuration options + init: function() { + config = { + stats: { all: 0, bad: 0 }, + moduleStats: { all: 0, bad: 0 }, + started: +new Date, + blocking: false, + autorun: false, + assertions: [], + filters: [], + queue: [] + }; + + var tests = id("qunit-tests"), + banner = id("qunit-banner"), + result = id("qunit-testresult"); + + if ( tests ) { + tests.innerHTML = ""; + } + + if ( banner ) { + banner.className = ""; + } + + if ( result ) { + result.parentNode.removeChild( result ); + } + }, + + // call on start of module test to prepend name to all tests + module: function(name, testEnvironment) { + config.currentModule = name; + + synchronize(function() { + if ( config.currentModule ) { + QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); + } + + config.currentModule = name; + config.moduleTestEnvironment = testEnvironment; + config.moduleStats = { all: 0, bad: 0 }; + + QUnit.moduleStart( name, testEnvironment ); + }); + }, + + asyncTest: function(testName, expected, callback) { + if ( arguments.length === 2 ) { + callback = expected; + expected = 0; + } + + QUnit.test(testName, expected, callback, true); + }, + + test: function(testName, expected, callback, async) { + var name = testName, testEnvironment, testEnvironmentArg; + + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + // is 2nd argument a testEnvironment? + if ( expected && typeof expected === 'object') { + testEnvironmentArg = expected; + expected = null; + } + + if ( config.currentModule ) { + name = config.currentModule + " module: " + name; + } + + if ( !validTest(name) ) { + return; + } + + synchronize(function() { + QUnit.testStart( testName ); + + testEnvironment = extend({ + setup: function() {}, + teardown: function() {} + }, config.moduleTestEnvironment); + if (testEnvironmentArg) { + extend(testEnvironment,testEnvironmentArg); + } + + // allow utility functions to access the current test environment + QUnit.current_testEnvironment = testEnvironment; + + config.assertions = []; + config.expected = expected; + + try { + if ( !config.pollution ) { + saveGlobal(); + } + + testEnvironment.setup.call(testEnvironment); + } catch(e) { + QUnit.ok( false, "Setup failed on " + name + ": " + e.message ); + } + + if ( async ) { + QUnit.stop(); + } + + try { + callback.call(testEnvironment); + } catch(e) { + fail("Test " + name + " died, exception and test follows", e, callback); + QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message ); + // else next test will carry the responsibility + saveGlobal(); + + // Restart the tests if they're blocking + if ( config.blocking ) { + start(); + } + } + }); + + synchronize(function() { + try { + checkPollution(); + testEnvironment.teardown.call(testEnvironment); + } catch(e) { + QUnit.ok( false, "Teardown failed on " + name + ": " + e.message ); + } + + try { + QUnit.reset(); + } catch(e) { + fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset); + } + + if ( config.expected && config.expected != config.assertions.length ) { + QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" ); + } + + var good = 0, bad = 0, + tests = id("qunit-tests"); + + config.stats.all += config.assertions.length; + config.moduleStats.all += config.assertions.length; + + if ( tests ) { + var ol = document.createElement("ol"); + ol.style.display = "none"; + + for ( var i = 0; i < config.assertions.length; i++ ) { + var assertion = config.assertions[i]; + + var li = document.createElement("li"); + li.className = assertion.result ? "pass" : "fail"; + li.appendChild(document.createTextNode(assertion.message || "(no message)")); + ol.appendChild( li ); + + if ( assertion.result ) { + good++; + } else { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + + var b = document.createElement("strong"); + b.innerHTML = name + " (" + bad + ", " + good + ", " + config.assertions.length + ")"; + + addEvent(b, "click", function() { + var next = b.nextSibling, display = next.style.display; + next.style.display = display === "none" ? "block" : "none"; + }); + + addEvent(b, "dblclick", function(e) { + var target = e && e.target ? e.target : window.event.srcElement; + if ( target.nodeName.toLowerCase() === "strong" ) { + var text = "", node = target.firstChild; + + while ( node.nodeType === 3 ) { + text += node.nodeValue; + node = node.nextSibling; + } + + text = text.replace(/(^\s*|\s*$)/g, ""); + + if ( window.location ) { + window.location.href = window.location.href.match(/^(.+?)(\?.*)?$/)[1] + "?" + encodeURIComponent(text); + } + } + }); + + var li = document.createElement("li"); + li.className = bad ? "fail" : "pass"; + li.appendChild( b ); + li.appendChild( ol ); + tests.appendChild( li ); + + if ( bad ) { + var toolbar = id("qunit-testrunner-toolbar"); + if ( toolbar ) { + toolbar.style.display = "block"; + id("qunit-filter-pass").disabled = null; + id("qunit-filter-missing").disabled = null; + } + } + + } else { + for ( var i = 0; i < config.assertions.length; i++ ) { + if ( !config.assertions[i].result ) { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + } + + QUnit.testDone( testName, bad, config.assertions.length ); + + if ( !window.setTimeout && !config.queue.length ) { + done(); + } + }); + + if ( window.setTimeout && !config.doneTimer ) { + config.doneTimer = window.setTimeout(function(){ + if ( !config.queue.length ) { + done(); + } else { + synchronize( done ); + } + }, 13); + } + }, + + /** + * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. + */ + expect: function(asserts) { + config.expected = asserts; + }, + + /** + * Asserts true. + * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); + */ + ok: function(a, msg) { + QUnit.log(a, msg); + + config.assertions.push({ + result: !!a, + message: msg + }); + }, + + /** + * Checks that the first two arguments are equal, with an optional message. + * Prints out both actual and expected values. + * + * Prefered to ok( actual == expected, message ) + * + * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); + * + * @param Object actual + * @param Object expected + * @param String message (optional) + */ + equal: function(actual, expected, message) { + push(expected == actual, actual, expected, message); + }, + + notEqual: function(actual, expected, message) { + push(expected != actual, actual, expected, message); + }, + + deepEqual: function(a, b, message) { + push(QUnit.equiv(a, b), a, b, message); + }, + + notDeepEqual: function(a, b, message) { + push(!QUnit.equiv(a, b), a, b, message); + }, + + strictEqual: function(actual, expected, message) { + push(expected === actual, actual, expected, message); + }, + + notStrictEqual: function(actual, expected, message) { + push(expected !== actual, actual, expected, message); + }, + + start: function() { + // A slight delay, to avoid any current callbacks + if ( window.setTimeout ) { + window.setTimeout(function() { + if ( config.timeout ) { + clearTimeout(config.timeout); + } + + config.blocking = false; + process(); + }, 13); + } else { + config.blocking = false; + process(); + } + }, + + stop: function(timeout) { + config.blocking = true; + + if ( timeout && window.setTimeout ) { + config.timeout = window.setTimeout(function() { + QUnit.ok( false, "Test timed out" ); + QUnit.start(); + }, timeout); + } + }, + + /** + * Resets the test setup. Useful for tests that modify the DOM. + */ + reset: function() { + if ( window.jQuery ) { + jQuery("#main").html( config.fixture ); + jQuery.event.global = {}; + jQuery.ajaxSettings = extend({}, config.ajaxSettings); + } + }, + + /** + * Trigger an event on an element. + * + * @example triggerEvent( document.body, "click" ); + * + * @param DOMElement elem + * @param String type + */ + triggerEvent: function( elem, type, event ) { + if ( document.createEvent ) { + event = document.createEvent("MouseEvents"); + event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, + 0, 0, 0, 0, 0, false, false, false, false, 0, null); + elem.dispatchEvent( event ); + + } else if ( elem.fireEvent ) { + elem.fireEvent("on"+type); + } + }, + + // Safe object type checking + is: function( type, obj ) { + return Object.prototype.toString.call( obj ) === "[object "+ type +"]"; + }, + + // Logging callbacks + done: function(failures, total) {}, + log: function(result, message) {}, + testStart: function(name) {}, + testDone: function(name, failures, total) {}, + moduleStart: function(name, testEnvironment) {}, + moduleDone: function(name, failures, total) {} +}; + +// Backwards compatibility, deprecated +QUnit.equals = QUnit.equal; +QUnit.same = QUnit.deepEqual; + +// Maintain internal state +var config = { + // The queue of tests to run + queue: [], + + // block until document ready + blocking: true +}; + +// Load paramaters +(function() { + var location = window.location || { search: "", protocol: "file:" }, + GETParams = location.search.slice(1).split('&'); + + for ( var i = 0; i < GETParams.length; i++ ) { + GETParams[i] = decodeURIComponent( GETParams[i] ); + if ( GETParams[i] === "noglobals" ) { + GETParams.splice( i, 1 ); + i--; + config.noglobals = true; + } else if ( GETParams[i].search('=') > -1 ) { + GETParams.splice( i, 1 ); + i--; + } + } + + // restrict modules/tests by get parameters + config.filters = GETParams; + + // Figure out if we're running the tests from a server or not + QUnit.isLocal = !!(location.protocol === 'file:'); +})(); + +// Expose the API as global variables, unless an 'exports' +// object exists, in that case we assume we're in CommonJS +if ( typeof exports === "undefined" || typeof require === "undefined" ) { + extend(window, QUnit); + window.QUnit = QUnit; +} else { + extend(exports, QUnit); + exports.QUnit = QUnit; +} + +if ( typeof document === "undefined" || document.readyState === "complete" ) { + config.autorun = true; +} + +addEvent(window, "load", function() { + // Initialize the config, saving the execution queue + var oldconfig = extend({}, config); + QUnit.init(); + extend(config, oldconfig); + + config.blocking = false; + + var userAgent = id("qunit-userAgent"); + if ( userAgent ) { + userAgent.innerHTML = navigator.userAgent; + } + + var toolbar = id("qunit-testrunner-toolbar"); + if ( toolbar ) { + toolbar.style.display = "none"; + + var filter = document.createElement("input"); + filter.type = "checkbox"; + filter.id = "qunit-filter-pass"; + filter.disabled = true; + addEvent( filter, "click", function() { + var li = document.getElementsByTagName("li"); + for ( var i = 0; i < li.length; i++ ) { + if ( li[i].className.indexOf("pass") > -1 ) { + li[i].style.display = filter.checked ? "none" : ""; + } + } + }); + toolbar.appendChild( filter ); + + var label = document.createElement("label"); + label.setAttribute("for", "qunit-filter-pass"); + label.innerHTML = "Hide passed tests"; + toolbar.appendChild( label ); + + var missing = document.createElement("input"); + missing.type = "checkbox"; + missing.id = "qunit-filter-missing"; + missing.disabled = true; + addEvent( missing, "click", function() { + var li = document.getElementsByTagName("li"); + for ( var i = 0; i < li.length; i++ ) { + if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) { + li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block"; + } + } + }); + toolbar.appendChild( missing ); + + label = document.createElement("label"); + label.setAttribute("for", "qunit-filter-missing"); + label.innerHTML = "Hide missing tests (untested code is broken code)"; + toolbar.appendChild( label ); + } + + var main = id('main'); + if ( main ) { + config.fixture = main.innerHTML; + } + + if ( window.jQuery ) { + config.ajaxSettings = window.jQuery.ajaxSettings; + } + + QUnit.start(); +}); + +function done() { + if ( config.doneTimer && window.clearTimeout ) { + window.clearTimeout( config.doneTimer ); + config.doneTimer = null; + } + + if ( config.queue.length ) { + config.doneTimer = window.setTimeout(function(){ + if ( !config.queue.length ) { + done(); + } else { + synchronize( done ); + } + }, 13); + + return; + } + + config.autorun = true; + + // Log the last module results + if ( config.currentModule ) { + QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); + } + + var banner = id("qunit-banner"), + tests = id("qunit-tests"), + html = ['Tests completed in ', + +new Date - config.started, ' milliseconds.
    ', + '', config.stats.all - config.stats.bad, ' tests of ', config.stats.all, ' passed, ', config.stats.bad,' failed.'].join(''); + + if ( banner ) { + banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); + } + + if ( tests ) { + var result = id("qunit-testresult"); + + if ( !result ) { + result = document.createElement("p"); + result.id = "qunit-testresult"; + result.className = "result"; + tests.parentNode.insertBefore( result, tests.nextSibling ); + } + + result.innerHTML = html; + } + + QUnit.done( config.stats.bad, config.stats.all ); +} + +function validTest( name ) { + var i = config.filters.length, + run = false; + + if ( !i ) { + return true; + } + + while ( i-- ) { + var filter = config.filters[i], + not = filter.charAt(0) == '!'; + + if ( not ) { + filter = filter.slice(1); + } + + if ( name.indexOf(filter) !== -1 ) { + return !not; + } + + if ( not ) { + run = true; + } + } + + return run; +} + +function push(result, actual, expected, message) { + message = message || (result ? "okay" : "failed"); + QUnit.ok( result, result ? message + ": " + expected : message + ", expected: " + QUnit.jsDump.parse(expected) + " result: " + QUnit.jsDump.parse(actual) ); +} + +function synchronize( callback ) { + config.queue.push( callback ); + + if ( config.autorun && !config.blocking ) { + process(); + } +} + +function process() { + while ( config.queue.length && !config.blocking ) { + config.queue.shift()(); + } +} + +function saveGlobal() { + config.pollution = []; + + if ( config.noglobals ) { + for ( var key in window ) { + config.pollution.push( key ); + } + } +} + +function checkPollution( name ) { + var old = config.pollution; + saveGlobal(); + + var newGlobals = diff( old, config.pollution ); + if ( newGlobals.length > 0 ) { + ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); + config.expected++; + } + + var deletedGlobals = diff( config.pollution, old ); + if ( deletedGlobals.length > 0 ) { + ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); + config.expected++; + } +} + +// returns a new Array with the elements that are in a but not in b +function diff( a, b ) { + var result = a.slice(); + for ( var i = 0; i < result.length; i++ ) { + for ( var j = 0; j < b.length; j++ ) { + if ( result[i] === b[j] ) { + result.splice(i, 1); + i--; + break; + } + } + } + return result; +} + +function fail(message, exception, callback) { + if ( typeof console !== "undefined" && console.error && console.warn ) { + console.error(message); + console.error(exception); + console.warn(callback.toString()); + + } else if ( window.opera && opera.postError ) { + opera.postError(message, exception, callback.toString); + } +} + +function extend(a, b) { + for ( var prop in b ) { + a[prop] = b[prop]; + } + + return a; +} + +function addEvent(elem, type, fn) { + if ( elem.addEventListener ) { + elem.addEventListener( type, fn, false ); + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, fn ); + } else { + fn(); + } +} + +function id(name) { + return !!(typeof document !== "undefined" && document && document.getElementById) && + document.getElementById( name ); +} + +// Test for equality any JavaScript type. +// Discussions and reference: http://philrathe.com/articles/equiv +// Test suites: http://philrathe.com/tests/equiv +// Author: Philippe Rathé +QUnit.equiv = function () { + + var innerEquiv; // the real equiv function + var callers = []; // stack to decide between skip/abort functions + + + // Determine what is o. + function hoozit(o) { + if (QUnit.is("String", o)) { + return "string"; + + } else if (QUnit.is("Boolean", o)) { + return "boolean"; + + } else if (QUnit.is("Number", o)) { + + if (isNaN(o)) { + return "nan"; + } else { + return "number"; + } + + } else if (typeof o === "undefined") { + return "undefined"; + + // consider: typeof null === object + } else if (o === null) { + return "null"; + + // consider: typeof [] === object + } else if (QUnit.is( "Array", o)) { + return "array"; + + // consider: typeof new Date() === object + } else if (QUnit.is( "Date", o)) { + return "date"; + + // consider: /./ instanceof Object; + // /./ instanceof RegExp; + // typeof /./ === "function"; // => false in IE and Opera, + // true in FF and Safari + } else if (QUnit.is( "RegExp", o)) { + return "regexp"; + + } else if (typeof o === "object") { + return "object"; + + } else if (QUnit.is( "Function", o)) { + return "function"; + } else { + return undefined; + } + } + + // Call the o related callback with the given arguments. + function bindCallbacks(o, callbacks, args) { + var prop = hoozit(o); + if (prop) { + if (hoozit(callbacks[prop]) === "function") { + return callbacks[prop].apply(callbacks, args); + } else { + return callbacks[prop]; // or undefined + } + } + } + + var callbacks = function () { + + // for string, boolean, number and null + function useStrictEquality(b, a) { + if (b instanceof a.constructor || a instanceof b.constructor) { + // to catch short annotaion VS 'new' annotation of a declaration + // e.g. var i = 1; + // var j = new Number(1); + return a == b; + } else { + return a === b; + } + } + + return { + "string": useStrictEquality, + "boolean": useStrictEquality, + "number": useStrictEquality, + "null": useStrictEquality, + "undefined": useStrictEquality, + + "nan": function (b) { + return isNaN(b); + }, + + "date": function (b, a) { + return hoozit(b) === "date" && a.valueOf() === b.valueOf(); + }, + + "regexp": function (b, a) { + return hoozit(b) === "regexp" && + a.source === b.source && // the regex itself + a.global === b.global && // and its modifers (gmi) ... + a.ignoreCase === b.ignoreCase && + a.multiline === b.multiline; + }, + + // - skip when the property is a method of an instance (OOP) + // - abort otherwise, + // initial === would have catch identical references anyway + "function": function () { + var caller = callers[callers.length - 1]; + return caller !== Object && + typeof caller !== "undefined"; + }, + + "array": function (b, a) { + var i; + var len; + + // b could be an object literal here + if ( ! (hoozit(b) === "array")) { + return false; + } + + len = a.length; + if (len !== b.length) { // safe and faster + return false; + } + for (i = 0; i < len; i++) { + if ( ! innerEquiv(a[i], b[i])) { + return false; + } + } + return true; + }, + + "object": function (b, a) { + var i; + var eq = true; // unless we can proove it + var aProperties = [], bProperties = []; // collection of strings + + // comparing constructors is more strict than using instanceof + if ( a.constructor !== b.constructor) { + return false; + } + + // stack constructor before traversing properties + callers.push(a.constructor); + + for (i in a) { // be strict: don't ensures hasOwnProperty and go deep + + aProperties.push(i); // collect a's properties + + if ( ! innerEquiv(a[i], b[i])) { + eq = false; + } + } + + callers.pop(); // unstack, we are done + + for (i in b) { + bProperties.push(i); // collect b's properties + } + + // Ensures identical properties name + return eq && innerEquiv(aProperties.sort(), bProperties.sort()); + } + }; + }(); + + innerEquiv = function () { // can take multiple arguments + var args = Array.prototype.slice.apply(arguments); + if (args.length < 2) { + return true; // end transition + } + + return (function (a, b) { + if (a === b) { + return true; // catch the most you can + } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || hoozit(a) !== hoozit(b)) { + return false; // don't lose time with error prone cases + } else { + return bindCallbacks(a, callbacks, [b, a]); + } + + // apply transition with (1..n) arguments + })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1)); + }; + + return innerEquiv; + +}(); + +/** + * jsDump + * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com + * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php) + * Date: 5/15/2008 + * @projectDescription Advanced and extensible data dumping for Javascript. + * @version 1.0.0 + * @author Ariel Flesler + * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} + */ +QUnit.jsDump = (function() { + function quote( str ) { + return '"' + str.toString().replace(/"/g, '\\"') + '"'; + }; + function literal( o ) { + return o + ''; + }; + function join( pre, arr, post ) { + var s = jsDump.separator(), + base = jsDump.indent(), + inner = jsDump.indent(1); + if ( arr.join ) + arr = arr.join( ',' + s + inner ); + if ( !arr ) + return pre + post; + return [ pre, inner + arr, base + post ].join(s); + }; + function array( arr ) { + var i = arr.length, ret = Array(i); + this.up(); + while ( i-- ) + ret[i] = this.parse( arr[i] ); + this.down(); + return join( '[', ret, ']' ); + }; + + var reName = /^function (\w+)/; + + var jsDump = { + parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance + var parser = this.parsers[ type || this.typeOf(obj) ]; + type = typeof parser; + + return type == 'function' ? parser.call( this, obj ) : + type == 'string' ? parser : + this.parsers.error; + }, + typeOf:function( obj ) { + var type; + if ( obj === null ) { + type = "null"; + } else if (typeof obj === "undefined") { + type = "undefined"; + } else if (QUnit.is("RegExp", obj)) { + type = "regexp"; + } else if (QUnit.is("Date", obj)) { + type = "date"; + } else if (QUnit.is("Function", obj)) { + type = "function"; + } else if (QUnit.is("Array", obj)) { + type = "array"; + } else if (QUnit.is("Window", obj) || QUnit.is("global", obj)) { + type = "window"; + } else if (QUnit.is("HTMLDocument", obj)) { + type = "document"; + } else if (QUnit.is("HTMLCollection", obj) || QUnit.is("NodeList", obj)) { + type = "nodelist"; + } else if (/^\[object HTML/.test(Object.prototype.toString.call( obj ))) { + type = "node"; + } else { + type = typeof obj; + } + return type; + }, + separator:function() { + return this.multiline ? this.HTML ? '
    ' : '\n' : this.HTML ? ' ' : ' '; + }, + indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing + if ( !this.multiline ) + return ''; + var chr = this.indentChar; + if ( this.HTML ) + chr = chr.replace(/\t/g,' ').replace(/ /g,' '); + return Array( this._depth_ + (extra||0) ).join(chr); + }, + up:function( a ) { + this._depth_ += a || 1; + }, + down:function( a ) { + this._depth_ -= a || 1; + }, + setParser:function( name, parser ) { + this.parsers[name] = parser; + }, + // The next 3 are exposed so you can use them + quote:quote, + literal:literal, + join:join, + // + _depth_: 1, + // This is the list of parsers, to modify them, use jsDump.setParser + parsers:{ + window: '[Window]', + document: '[Document]', + error:'[ERROR]', //when no parser is found, shouldn't happen + unknown: '[Unknown]', + 'null':'null', + undefined:'undefined', + 'function':function( fn ) { + var ret = 'function', + name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE + if ( name ) + ret += ' ' + name; + ret += '('; + + ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join(''); + return join( ret, this.parse(fn,'functionCode'), '}' ); + }, + array: array, + nodelist: array, + arguments: array, + object:function( map ) { + var ret = [ ]; + this.up(); + for ( var key in map ) + ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) ); + this.down(); + return join( '{', ret, '}' ); + }, + node:function( node ) { + var open = this.HTML ? '<' : '<', + close = this.HTML ? '>' : '>'; + + var tag = node.nodeName.toLowerCase(), + ret = open + tag; + + for ( var a in this.DOMAttrs ) { + var val = node[this.DOMAttrs[a]]; + if ( val ) + ret += ' ' + a + '=' + this.parse( val, 'attribute' ); + } + return ret + close + open + '/' + tag + close; + }, + functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function + var l = fn.length; + if ( !l ) return ''; + + var args = Array(l); + while ( l-- ) + args[l] = String.fromCharCode(97+l);//97 is 'a' + return ' ' + args.join(', ') + ' '; + }, + key:quote, //object calls it internally, the key part of an item in a map + functionCode:'[code]', //function calls it internally, it's the content of the function + attribute:quote, //node calls it internally, it's an html attribute value + string:quote, + date:quote, + regexp:literal, //regex + number:literal, + 'boolean':literal + }, + DOMAttrs:{//attributes to dump from nodes, name=>realName + id:'id', + name:'name', + 'class':'className' + }, + HTML:true,//if true, entities are escaped ( <, >, \t, space and \n ) + indentChar:' ',//indentation unit + multiline:true //if true, items in a collection, are separated by a \n, else just a space. + }; + + return jsDump; +})(); + +})(this); \ No newline at end of file diff --git a/lib/uri/tests/tests.js b/lib/uri/tests/tests.js new file mode 100644 index 0000000..c6d5162 --- /dev/null +++ b/lib/uri/tests/tests.js @@ -0,0 +1,210 @@ +// +// +// Tests +// +// + +test("Acquire URI", function () { + URI = require("./uri").URI; + ok(URI); +}); + +test("URI Parsing", function () { + var components; + + //scheme + components = URI.parse("uri:"); + equal(components.scheme, "uri", "scheme"); + equal(components.authority, undefined, "authority"); + equal(components.userinfo, undefined, "userinfo"); + equal(components.host, undefined, "host"); + equal(components.port, undefined, "port"); + equal(components.path, "", "path"); + equal(components.query, undefined, "query"); + equal(components.fragment, undefined, "fragment"); + + //userinfo + components = URI.parse("//@"); + equal(components.scheme, undefined, "scheme"); + equal(components.authority, "@", "authority"); + equal(components.userinfo, "", "userinfo"); + equal(components.host, "", "host"); + equal(components.port, undefined, "port"); + equal(components.path, "", "path"); + equal(components.query, undefined, "query"); + equal(components.fragment, undefined, "fragment"); + + //host + components = URI.parse("//"); + equal(components.scheme, undefined, "scheme"); + equal(components.authority, "", "authority"); + equal(components.userinfo, undefined, "userinfo"); + equal(components.host, "", "host"); + equal(components.port, undefined, "port"); + equal(components.path, "", "path"); + equal(components.query, undefined, "query"); + equal(components.fragment, undefined, "fragment"); + + //port + components = URI.parse("//:"); + equal(components.scheme, undefined, "scheme"); + equal(components.authority, ":", "authority"); + equal(components.userinfo, undefined, "userinfo"); + equal(components.host, "", "host"); + equal(components.port, "", "port"); + equal(components.path, "", "path"); + equal(components.query, undefined, "query"); + equal(components.fragment, undefined, "fragment"); + + //path + components = URI.parse(""); + equal(components.scheme, undefined, "scheme"); + equal(components.authority, undefined, "authority"); + equal(components.userinfo, undefined, "userinfo"); + equal(components.host, undefined, "host"); + equal(components.port, undefined, "port"); + equal(components.path, "", "path"); + equal(components.query, undefined, "query"); + equal(components.fragment, undefined, "fragment"); + + //query + components = URI.parse("?"); + equal(components.scheme, undefined, "scheme"); + equal(components.authority, undefined, "authority"); + equal(components.userinfo, undefined, "userinfo"); + equal(components.host, undefined, "host"); + equal(components.port, undefined, "port"); + equal(components.path, "", "path"); + equal(components.query, "", "query"); + equal(components.fragment, undefined, "fragment"); + + //fragment + components = URI.parse("#"); + equal(components.scheme, undefined, "scheme"); + equal(components.authority, undefined, "authority"); + equal(components.userinfo, undefined, "userinfo"); + equal(components.host, undefined, "host"); + equal(components.port, undefined, "port"); + equal(components.path, "", "path"); + equal(components.query, undefined, "query"); + equal(components.fragment, "", "fragment"); + + //all + components = URI.parse("uri://user:pass@example.com:123/one/two.three?q1=a1&q2=a2#body"); + equal(components.scheme, "uri", "scheme"); + equal(components.authority, "user:pass@example.com:123", "authority"); + equal(components.userinfo, "user:pass", "userinfo"); + equal(components.host, "example.com", "host"); + equal(components.port, 123, "port"); + equal(components.path, "/one/two.three", "path"); + equal(components.query, "q1=a1&q2=a2", "query"); + equal(components.fragment, "body", "fragment"); +}); + +test("URI Serialization", function () { + var components = { + scheme : "", + userinfo : "", + host : "", + port : 0, + path : "", + query : "", + fragment : "" + }; + equal(URI.serialize(components), "//@:0", "Empty Components"); + + //TODO: NEED MOAR TESTS!! +}); + +test("URI Resolving", function () { + //normal examples from RFC 3986 + var base = "http://a/b/c/d;p?q"; + equal(URI.resolve(base, "g:h"), "g:h", "g:h"); + equal(URI.resolve(base, "g:h"), "g:h", "g:h"); + equal(URI.resolve(base, "g"), "http://a/b/c/g", "g"); + equal(URI.resolve(base, "./g"), "http://a/b/c/g", "./g"); + equal(URI.resolve(base, "g/"), "http://a/b/c/g/", "g/"); + equal(URI.resolve(base, "/g"), "http://a/g", "/g"); + equal(URI.resolve(base, "//g"), "http://g", "//g"); + equal(URI.resolve(base, "?y"), "http://a/b/c/d;p?y", "?y"); + equal(URI.resolve(base, "g?y"), "http://a/b/c/g?y", "g?y"); + equal(URI.resolve(base, "#s"), "http://a/b/c/d;p?q#s", "#s"); + equal(URI.resolve(base, "g#s"), "http://a/b/c/g#s", "g#s"); + equal(URI.resolve(base, "g?y#s"), "http://a/b/c/g?y#s", "g?y#s"); + equal(URI.resolve(base, ";x"), "http://a/b/c/;x", ";x"); + equal(URI.resolve(base, "g;x"), "http://a/b/c/g;x", "g;x"); + equal(URI.resolve(base, "g;x?y#s"), "http://a/b/c/g;x?y#s", "g;x?y#s"); + equal(URI.resolve(base, ""), "http://a/b/c/d;p?q", ""); + equal(URI.resolve(base, "."), "http://a/b/c/", "."); + equal(URI.resolve(base, "./"), "http://a/b/c/", "./"); + equal(URI.resolve(base, ".."), "http://a/b/", ".."); + equal(URI.resolve(base, "../"), "http://a/b/", "../"); + equal(URI.resolve(base, "../g"), "http://a/b/g", "../g"); + equal(URI.resolve(base, "../.."), "http://a/", "../.."); + equal(URI.resolve(base, "../../"), "http://a/", "../../"); + equal(URI.resolve(base, "../../g"), "http://a/g", "../../g"); + + //abnormal examples from RFC 3986 + equal(URI.resolve(base, "../../../g"), "http://a/g", "../../../g"); + equal(URI.resolve(base, "../../../../g"), "http://a/g", "../../../../g"); + + equal(URI.resolve(base, "/./g"), "http://a/g", "/./g"); + equal(URI.resolve(base, "/../g"), "http://a/g", "/../g"); + equal(URI.resolve(base, "g."), "http://a/b/c/g.", "g."); + equal(URI.resolve(base, ".g"), "http://a/b/c/.g", ".g"); + equal(URI.resolve(base, "g.."), "http://a/b/c/g..", "g.."); + equal(URI.resolve(base, "..g"), "http://a/b/c/..g", "..g"); + + equal(URI.resolve(base, "./../g"), "http://a/b/g", "./../g"); + equal(URI.resolve(base, "./g/."), "http://a/b/c/g/", "./g/."); + equal(URI.resolve(base, "g/./h"), "http://a/b/c/g/h", "g/./h"); + equal(URI.resolve(base, "g/../h"), "http://a/b/c/h", "g/../h"); + equal(URI.resolve(base, "g;x=1/./y"), "http://a/b/c/g;x=1/y", "g;x=1/./y"); + equal(URI.resolve(base, "g;x=1/../y"), "http://a/b/c/y", "g;x=1/../y"); + + equal(URI.resolve(base, "g?y/./x"), "http://a/b/c/g?y/./x", "g?y/./x"); + equal(URI.resolve(base, "g?y/../x"), "http://a/b/c/g?y/../x", "g?y/../x"); + equal(URI.resolve(base, "g#s/./x"), "http://a/b/c/g#s/./x", "g#s/./x"); + equal(URI.resolve(base, "g#s/../x"), "http://a/b/c/g#s/../x", "g#s/../x"); + + equal(URI.resolve(base, "http:g"), "http:g", "http:g"); + equal(URI.resolve(base, "http:g", {tolerant:true}), "http://a/b/c/g", "http:g"); + +}); + +test("URI Equals", function () { + //test from RFC 3986 + equal(URI.equal("example://a/b/c/%7Bfoo%7D", "eXAMPLE://a/./b/../b/%63/%7bfoo%7d"), true); +}); + +test("Escape Component", function () { + var input = "", output = ""; + for (var d = 32; d < 128; ++d) { + input += String.fromCharCode(d); + if (String.fromCharCode(d).match(/[0-9A-Za-z\-\.\_\~\!\$\&\'\(\)\*\+\,\;\=]/)) { + output += String.fromCharCode(d); + } else { + output += "%" + d.toString(16).toUpperCase(); + } + } + input += "\u00c0\u30a2"; + output += "%C3%80%E3%82%A2"; + + equal(URI.escapeComponent(input), output); +}); + +test("Unescape Component", function () { + var input = "", output = ""; + for (var d = 32; d < 128; ++d) { + input += String.fromCharCode(d); + if (String.fromCharCode(d).match(/[0-9A-Za-z\-\.\_\~\!\$\&\'\(\)\*\+\,\;\=]/)) { + output += String.fromCharCode(d); + } else { + output += "%" + d.toString(16).toUpperCase(); + } + } + input += "\u00c0\u30a2"; + output += "%C3%80%E3%82%A2"; + + equal(URI.unescapeComponent(output), input); +}); \ No newline at end of file diff --git a/lib/uri/uri.js b/lib/uri/uri.js new file mode 100644 index 0000000..1d9e053 --- /dev/null +++ b/lib/uri/uri.js @@ -0,0 +1,706 @@ +/** + * URI.js + * + * @fileOverview An RFC 3986 compliant, scheme extendable URI parsing/validating/resolving library for JavaScript. + * @author Gary Court + * @version 1.0 + * @see http://github.com/garycourt/uri-js + */ + +/* + * Copyright 2010 Gary Court. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY GARY COURT ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of Gary Court. + */ + +/*jslint white: true, sub: true, onevar: true, undef: true, eqeqeq: true, newcap: true, immed: true, indent: 4 */ + +var exports = exports || this, + require = require || function () { + return exports; + }; + +(function () { + var Options, URIComponents, SchemeHandler, URI, + + mergeSet = function () { + var set = arguments[0], + x = 1, + nextSet = arguments[x]; + + while (nextSet) { + set = set.slice(0, -1) + nextSet.slice(1); + nextSet = arguments[++x]; + } + + return set; + }, + + subexp = function (str) { + return "(?:" + str + ")"; + }, + + ALPHA$$ = "[A-Za-z]", + CR$ = "[\\x0D]", + DIGIT$$ = "[0-9]", + DQUOTE$$ = "[\\x22]", + HEXDIG$$ = mergeSet(DIGIT$$, "[A-Fa-f]"), //case-insensitive + LF$$ = "[\\x0A]", + SP$$ = "[\\x20]", + PCT_ENCODED$ = subexp("%" + HEXDIG$$ + HEXDIG$$), + GEN_DELIMS$$ = "[\\:\\/\\?\\#\\[\\]\\@]", + SUB_DELIMS$$ = "[\\!\\$\\&\\'\\(\\)\\*\\+\\,\\;\\=]", + RESERVED$$ = mergeSet(GEN_DELIMS$$, SUB_DELIMS$$), + UNRESERVED$$ = mergeSet(ALPHA$$, DIGIT$$, "[\\-\\.\\_\\~]"), + SCHEME$ = subexp(ALPHA$$ + mergeSet(ALPHA$$, DIGIT$$, "[\\+\\-\\.]") + "*"), + USERINFO$ = subexp(subexp(PCT_ENCODED$ + "|" + mergeSet(UNRESERVED$$, SUB_DELIMS$$, "[\\:]")) + "*"), + DEC_OCTET$ = subexp(DIGIT$$ + "|" + subexp("[1-9]" + DIGIT$$) + "|" + subexp("1" + DIGIT$$ + DIGIT$$) + "|" + subexp("2[0-4]" + DIGIT$$) + "|" + subexp("25[0-5]")), + IPV4ADDRESS$ = subexp(DEC_OCTET$ + "." + DEC_OCTET$ + "." + DEC_OCTET$ + "." + DEC_OCTET$), + H16$ = subexp(HEXDIG$$ + "{1,4}"), + LS32$ = subexp(subexp(H16$ + "\\:" + H16$) + "|" + IPV4ADDRESS$), + IPV6ADDRESS$ = subexp(mergeSet(UNRESERVED$$, SUB_DELIMS$$, "[\\:]") + "+"), //FIXME + IPVFUTURE$ = subexp("v" + HEXDIG$$ + "+\\." + mergeSet(UNRESERVED$$, SUB_DELIMS$$, "[\\:]") + "+"), + IP_LITERAL$ = subexp("\\[" + subexp(IPV6ADDRESS$ + "|" + IPVFUTURE$) + "\\]"), + REG_NAME$ = subexp(subexp(PCT_ENCODED$ + "|" + mergeSet(UNRESERVED$$, SUB_DELIMS$$)) + "*"), + HOST$ = subexp(IP_LITERAL$ + "|" + IPV4ADDRESS$ + "|" + REG_NAME$), + PORT$ = subexp(DIGIT$$ + "*"), + AUTHORITY$ = subexp(subexp(USERINFO$ + "@") + "?" + HOST$ + subexp("\\:" + PORT$) + "?"), + PCHAR$ = subexp(PCT_ENCODED$ + "|" + mergeSet(UNRESERVED$$, SUB_DELIMS$$, "[\\:\\@]")), + SEGMENT$ = subexp(PCHAR$ + "*"), + SEGMENT_NZ$ = subexp(PCHAR$ + "+"), + SEGMENT_NZ_NC$ = subexp(subexp(PCT_ENCODED$ + "|" + mergeSet(UNRESERVED$$, SUB_DELIMS$$, "[\\@]")) + "+"), + PATH_ABEMPTY$ = subexp(subexp("\\/" + SEGMENT$) + "*"), + PATH_ABSOLUTE$ = subexp("\\/" + subexp(SEGMENT_NZ$ + PATH_ABEMPTY$) + "?"), //simplified + PATH_NOSCHEME$ = subexp(SEGMENT_NZ_NC$ + PATH_ABEMPTY$), //simplified + PATH_ROOTLESS$ = subexp(SEGMENT_NZ$ + PATH_ABEMPTY$), //simplified + PATH_EMPTY$ = subexp(""), //simplified + PATH$ = subexp(PATH_ABEMPTY$ + "|" + PATH_ABSOLUTE$ + "|" + PATH_NOSCHEME$ + "|" + PATH_ROOTLESS$ + "|" + PATH_EMPTY$), + QUERY$ = subexp(subexp(PCHAR$ + "|[\\/\\?]") + "*"), + FRAGMENT$ = subexp(subexp(PCHAR$ + "|[\\/\\?]") + "*"), + HIER_PART$ = subexp(subexp("\\/\\/" + AUTHORITY$ + PATH_ABEMPTY$) + "|" + PATH_ABSOLUTE$ + "|" + PATH_ROOTLESS$ + "|" + PATH_EMPTY$), + URI$ = subexp(SCHEME$ + "\\:" + HIER_PART$ + subexp("\\?" + QUERY$) + "?" + subexp("\\#" + FRAGMENT$) + "?"), + RELATIVE_PART$ = subexp(subexp("\\/\\/" + AUTHORITY$ + PATH_ABEMPTY$) + "|" + PATH_ABSOLUTE$ + "|" + PATH_NOSCHEME$ + "|" + PATH_EMPTY$), + RELATIVE_REF$ = subexp(RELATIVE_PART$ + subexp("\\?" + QUERY$) + "?" + subexp("\\#" + FRAGMENT$) + "?"), + URI_REFERENCE$ = subexp(URI$ + "|" + RELATIVE_REF$), + ABSOLUTE_URI$ = subexp(SCHEME$ + "\\:" + HIER_PART$ + subexp("\\?" + QUERY$) + "?"), + + URI_REF = new RegExp("^" + subexp("(" + URI$ + ")|(" + RELATIVE_REF$ + ")") + "$"), + GENERIC_REF = new RegExp("^(" + SCHEME$ + ")\\:" + subexp(subexp("\\/\\/(" + subexp("(" + USERINFO$ + ")@") + "?(" + HOST$ + ")" + subexp("\\:(" + PORT$ + ")") + "?)") + "?(" + PATH_ABEMPTY$ + "|" + PATH_ABSOLUTE$ + "|" + PATH_ROOTLESS$ + "|" + PATH_EMPTY$ + ")") + subexp("\\?(" + QUERY$ + ")") + "?" + subexp("\\#(" + FRAGMENT$ + ")") + "?$"), + RELATIVE_REF = new RegExp("^(){0}" + subexp(subexp("\\/\\/(" + subexp("(" + USERINFO$ + ")@") + "?(" + HOST$ + ")" + subexp("\\:(" + PORT$ + ")") + "?)") + "?(" + PATH_ABEMPTY$ + "|" + PATH_ABSOLUTE$ + "|" + PATH_NOSCHEME$ + "|" + PATH_EMPTY$ + ")") + subexp("\\?(" + QUERY$ + ")") + "?" + subexp("\\#(" + FRAGMENT$ + ")") + "?$"), + ABSOLUTE_REF = new RegExp("^(" + SCHEME$ + ")\\:" + subexp(subexp("\\/\\/(" + subexp("(" + USERINFO$ + ")@") + "?(" + HOST$ + ")" + subexp("\\:(" + PORT$ + ")") + "?)") + "?(" + PATH_ABEMPTY$ + "|" + PATH_ABSOLUTE$ + "|" + PATH_ROOTLESS$ + "|" + PATH_EMPTY$ + ")") + subexp("\\?(" + QUERY$ + ")") + "?$"), + SAMEDOC_REF = new RegExp("^" + subexp("\\#(" + FRAGMENT$ + ")") + "?$"), + AUTHORITY = new RegExp("^" + subexp("(" + USERINFO$ + ")@") + "?(" + HOST$ + ")" + subexp("\\:(" + PORT$ + ")") + "?$"), + + NOT_SCHEME = new RegExp(mergeSet("[^]", ALPHA$$, DIGIT$$, "[\\+\\-\\.]"), "g"), + NOT_USERINFO = new RegExp(mergeSet("[^\\%\\:]", UNRESERVED$$, SUB_DELIMS$$), "g"), + NOT_HOST = new RegExp(mergeSet("[^\\%]", UNRESERVED$$, SUB_DELIMS$$), "g"), + NOT_PATH = new RegExp(mergeSet("[^\\%\\/\\:\\@]", UNRESERVED$$, SUB_DELIMS$$), "g"), + NOT_PATH_NOSCHEME = new RegExp(mergeSet("[^\\%\\/\\@]", UNRESERVED$$, SUB_DELIMS$$), "g"), + NOT_QUERY = new RegExp(mergeSet("[^\\%]", UNRESERVED$$, SUB_DELIMS$$, "[\\:\\@\\/\\?]"), "g"), + NOT_FRAGMENT = NOT_QUERY, + ESCAPE = new RegExp(mergeSet("[^]", UNRESERVED$$, SUB_DELIMS$$), "g"), + UNRESERVED = new RegExp(UNRESERVED$$, "g"), + OTHER_CHARS = new RegExp(mergeSet("[^\\%]", UNRESERVED$$, RESERVED$$), "g"), + PCT_ENCODEDS = new RegExp(PCT_ENCODED$ + "+", "g"), + URI_PARSE = /^(?:([^:\/?#]+):)?(?:\/\/((?:([^\/?#@]*)@)?([^\/?#:]*)(?:\:(\d*))?))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?/i, + RDS1 = /^\.\.?\//, + RDS2 = /^\/\.(\/|$)/, + RDS3 = /^\/\.\.(\/|$)/, + RDS4 = /^\.\.?$/, + RDS5 = /^\/?.*?(?=\/|$)/, + NO_MATCH_IS_UNDEFINED = ("").match(/(){0}/)[1] === undefined, + + pctEncChar = function (chr) { + var c = chr.charCodeAt(0); + + if (c < 128) { + return "%" + c.toString(16).toUpperCase(); + } + else if ((c > 127) && (c < 2048)) { + return "%" + ((c >> 6) | 192).toString(16).toUpperCase() + "%" + ((c & 63) | 128).toString(16).toUpperCase(); + } + else { + return "%" + ((c >> 12) | 224).toString(16).toUpperCase() + "%" + (((c >> 6) & 63) | 128).toString(16).toUpperCase() + "%" + ((c & 63) | 128).toString(16).toUpperCase(); + } + }, + + pctDecUnreserved = function (str) { + var newStr = "", + i = 0, + c, s; + + while (i < str.length) { + c = parseInt(str.substr(i + 1, 2), 16); + + if (c < 128) { + s = String.fromCharCode(c); + if (s.match(UNRESERVED)) { + newStr += s; + } else { + newStr += str.substr(i, 3); + } + i += 3; + } + else if ((c > 191) && (c < 224)) { + newStr += str.substr(i, 6); + i += 6; + } + else { + newStr += str.substr(i, 9); + i += 9; + } + } + + return newStr; + }, + + pctDecChars = function (str) { + var newStr = "", + i = 0, + c, c2, c3; + + while (i < str.length) { + c = parseInt(str.substr(i + 1, 2), 16); + + if (c < 128) { + newStr += String.fromCharCode(c); + i += 3; + } + else if ((c > 191) && (c < 224)) { + c2 = parseInt(str.substr(i + 4, 2), 16); + newStr += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); + i += 6; + } + else { + c2 = parseInt(str.substr(i + 4, 2), 16); + c3 = parseInt(str.substr(i + 7, 2), 16); + newStr += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + i += 9; + } + } + + return newStr; + }, + + typeOf = function (o) { + return o === undefined ? "undefined" : (o === null ? "null" : Object.prototype.toString.call(o).split(" ").pop().split("]").shift().toLowerCase()); + }; + + /** + * @class + */ + + Options = function () {}; + + Options.prototype = { + /** + * @type Boolean + */ + + tolerant : undefined, + + /** + * @type String + */ + + scheme : undefined, + + /** + * @type String + * @enum "uri", "absolute", "relative", "same-document", "suffix" + */ + + reference : undefined + }; + + /** + * @class + */ + + URIComponents = function () { + this.errors = []; + }; + + URIComponents.prototype = { + /** + * @type String + */ + + scheme : undefined, + + /** + * @type String + */ + + authority : undefined, + + /** + * @type String + */ + + userinfo : undefined, + + /** + * @type String + */ + + host : undefined, + + /** + * @type Number + */ + + port : undefined, + + /** + * @type String + */ + + path : undefined, + + /** + * @type String + */ + + query : undefined, + + /** + * @type String + */ + + fragment : undefined, + + /** + * @type String + * @enum "uri", "absolute", "relative", "same-document" + */ + + reference : undefined, + + /** + * @type Array + */ + + errors : undefined + }; + + /** + * @class + */ + + SchemeHandler = function () {}; + + /** + * @param {URIComponents} components + * @param {Options} options + */ + + SchemeHandler.prototype.parse = function (components, options) {}; + + /** + * @param {URIComponents} components + * @param {Options} options + */ + + SchemeHandler.prototype.serialize = function (components, options) {}; + + /** + * @namespace + */ + + URI = {}; + + /** + * @namespace + */ + + URI.SCHEMES = {}; + + /** + * @param {String} uriString + * @param {Options} [options] + * @returns {URIComponents} + */ + + URI.parse = function (uriString, options) { + var matches, + components = new URIComponents(), + schemeHandler; + + uriString = uriString ? uriString.toString() : ""; + options = options || {}; + + if (options.reference === "suffix") { + uriString = (options.scheme ? options.scheme + ":" : "") + "//" + uriString; + } + + matches = uriString.match(URI_REF); + + if (matches) { + if (matches[1]) { + //generic URI + matches = uriString.match(GENERIC_REF); + } else { + //relative URI + matches = uriString.match(RELATIVE_REF); + } + } else { + if (!options.tolerant) { + components.errors.push("URI is not strictly valid."); + } + matches = uriString.match(URI_PARSE); + } + + if (matches) { + if (NO_MATCH_IS_UNDEFINED) { + //store each component + components.scheme = matches[1]; + components.authority = matches[2]; + components.userinfo = matches[3]; + components.host = matches[4]; + components.port = parseInt(matches[5], 10); + components.path = matches[6] || ""; + components.query = matches[7]; + components.fragment = matches[8]; + + //fix port number + if (isNaN(components.port)) { + components.port = matches[5]; + } + } else { //IE FIX for improper RegExp matching + //store each component + components.scheme = matches[1] || undefined; + components.authority = (uriString.indexOf("//") !== -1 ? matches[2] : undefined); + components.userinfo = (uriString.indexOf("@") !== -1 ? matches[3] : undefined); + components.host = (uriString.indexOf("//") !== -1 ? matches[4] : undefined); + components.port = parseInt(matches[5], 10); + components.path = matches[6] || ""; + components.query = (uriString.indexOf("?") !== -1 ? matches[7] : undefined); + components.fragment = (uriString.indexOf("#") !== -1 ? matches[8] : undefined); + + //fix port number + if (isNaN(components.port)) { + components.port = (uriString.match(/\/\/.*\:(?:\/|\?|\#|$)/) ? matches[4] : undefined); + } + } + + //determine reference type + if (!components.scheme && !components.authority && !components.path && !components.query) { + components.reference = "same-document"; + } else if (!components.scheme) { + components.reference = "relative"; + } else if (!components.fragment) { + components.reference = "absolute"; + } else { + components.reference = "uri"; + } + + //check for reference errors + if (options.reference && options.reference !== "suffix" && options.reference !== components.reference) { + components.errors.push("URI is not a " + options.reference + " reference."); + } + + //check if a handler for the scheme exists + schemeHandler = URI.SCHEMES[components.scheme || options.scheme]; + if (schemeHandler && schemeHandler.parse) { + //perform extra parsing + schemeHandler.parse(components, options); + } + } else { + components.errors.push("URI can not be parsed."); + } + + return components; + }; + + /** + * @private + * @param {URIComponents} components + * @returns {String} + */ + + URI._recomposeAuthority = function (components) { + var uriTokens = []; + + if (components.userinfo !== undefined || components.host !== undefined || typeof components.port === "number") { + if (components.userinfo !== undefined) { + uriTokens.push(components.userinfo.toString().replace(NOT_USERINFO, pctEncChar)); + uriTokens.push("@"); + } + if (components.host !== undefined) { + uriTokens.push(components.host.toString().toLowerCase().replace(NOT_HOST, pctEncChar)); + } + if (typeof components.port === "number") { + uriTokens.push(":"); + uriTokens.push(components.port.toString(10)); + } + } + + return uriTokens.length ? uriTokens.join("") : undefined; + }; + + /** + * @param {String} input + * @returns {String} + */ + + URI.removeDotSegments = function (input) { + var output = [], s; + + while (input.length) { + if (input.match(RDS1)) { + input = input.replace(RDS1, ""); + } else if (input.match(RDS2)) { + input = input.replace(RDS2, "/"); + } else if (input.match(RDS3)) { + input = input.replace(RDS3, "/"); + output.pop(); + } else if (input === "." || input === "..") { + input = ""; + } else { + s = input.match(RDS5)[0]; + input = input.slice(s.length); + output.push(s); + } + } + + return output.join(""); + }; + + /** + * @param {URIComponents} components + * @param {Options} options + * @returns {String} + */ + + URI.serialize = function (components, options) { + var uriTokens = [], + schemeHandler, + s; + options = options || {}; + + //check if a handler for the scheme exists + schemeHandler = URI.SCHEMES[components.scheme || options.scheme]; + if (schemeHandler && schemeHandler.serialize) { + //perform extra serialization + schemeHandler.serialize(components, options); + } + + if (options.reference !== "suffix" && components.scheme) { + uriTokens.push(components.scheme.toString().toLowerCase().replace(NOT_SCHEME, "")); + uriTokens.push(":"); + } + + components.authority = URI._recomposeAuthority(components); + if (components.authority !== undefined) { + if (options.reference !== "suffix") { + uriTokens.push("//"); + } + + uriTokens.push(components.authority); + + if (components.path && components.path.charAt(0) !== "/") { + uriTokens.push("/"); + } + } + + if (components.path) { + s = URI.removeDotSegments(components.path.toString().replace(/%2E/ig, ".")); + + if (components.scheme) { + s = s.replace(NOT_PATH, pctEncChar); + } else { + s = s.replace(NOT_PATH_NOSCHEME, pctEncChar); + } + + if (components.authority === undefined) { + s = s.replace(/^\/\//, "/%2F"); //don't allow the path to start with "//" + } + uriTokens.push(s); + } + + if (components.query) { + uriTokens.push("?"); + uriTokens.push(components.query.toString().replace(NOT_QUERY, pctEncChar)); + } + + if (components.fragment) { + uriTokens.push("#"); + uriTokens.push(components.fragment.toString().replace(NOT_FRAGMENT, pctEncChar)); + } + + return uriTokens + .join('') //merge tokens into a string + .replace(PCT_ENCODEDS, pctDecUnreserved) //undecode unreserved characters + //.replace(OTHER_CHARS, pctEncChar) //replace non-URI characters + .replace(/%[0-9A-Fa-f]{2}/g, function (str) { //uppercase percent encoded characters + return str.toUpperCase(); + }) + ; + }; + + /** + * @param {URIComponents} base + * @param {URIComponents} relative + * @param {Options} [options] + * @param {Boolean} [skipNormalization] + * @returns {URIComponents} + */ + + URI.resolveComponents = function (base, relative, options, skipNormalization) { + var target = new URIComponents(); + + if (!skipNormalization) { + base = URI.parse(URI.serialize(base, options), options); //normalize base components + relative = URI.parse(URI.serialize(relative, options), options); //normalize relative components + } + options = options || {}; + + if (!options.tolerant && relative.scheme) { + target.scheme = relative.scheme; + target.authority = relative.authority; + target.userinfo = relative.userinfo; + target.host = relative.host; + target.port = relative.port; + target.path = URI.removeDotSegments(relative.path); + target.query = relative.query; + } else { + if (relative.authority !== undefined) { + target.authority = relative.authority; + target.userinfo = relative.userinfo; + target.host = relative.host; + target.port = relative.port; + target.path = URI.removeDotSegments(relative.path); + target.query = relative.query; + } else { + if (!relative.path) { + target.path = base.path; + if (relative.query !== undefined) { + target.query = relative.query; + } else { + target.query = base.query; + } + } else { + if (relative.path.charAt(0) === "/") { + target.path = URI.removeDotSegments(relative.path); + } else { + if (base.authority !== undefined && !base.path) { + target.path = "/" + relative.path; + } else if (!base.path) { + target.path = relative.path; + } else { + target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative.path; + } + target.path = URI.removeDotSegments(target.path); + } + target.query = relative.query; + } + target.authority = base.authority; + target.userinfo = base.userinfo; + target.host = base.host; + target.port = base.port; + } + target.scheme = base.scheme; + } + + target.fragment = relative.fragment; + + return target; + }; + + /** + * @param {String} baseURI + * @param {String} relativeURI + * @param {Options} [options] + * @returns {String} + */ + + URI.resolve = function (baseURI, relativeURI, options) { + return URI.serialize(URI.resolveComponents(URI.parse(baseURI, options), URI.parse(relativeURI, options), options, true), options); + }; + + /** + * @param {String|URIComponents} uri + * @param {Options} options + * @returns {String|URIComponents} + */ + + URI.normalize = function (uri, options) { + if (typeof uri === "string") { + return URI.serialize(URI.parse(uri, options), options); + } else if (typeOf(uri) === "object") { + return URI.parse(URI.serialize(uri, options), options); + } + + return uri; + }; + + /** + * @param {String|URIComponents} uriA + * @param {String|URIComponents} uriB + * @param {Options} options + */ + + URI.equal = function (uriA, uriB, options) { + if (typeof uriA === "string") { + uriA = URI.serialize(URI.parse(uriA, options), options); + } else if (typeOf(uriA) === "object") { + uriA = URI.serialize(uriA, options); + } + + if (typeof uriB === "string") { + uriB = URI.serialize(URI.parse(uriB, options), options); + } else if (typeOf(uriB) === "object") { + uriB = URI.serialize(uriB, options); + } + + return uriA === uriB; + }; + + /** + * @param {String} str + * @returns {String} + */ + + URI.escapeComponent = function (str) { + return str && str.toString().replace(ESCAPE, pctEncChar); + }; + + /** + * @param {String} str + * @returns {String} + */ + + URI.unescapeComponent = function (str) { + return str && str.toString().replace(PCT_ENCODEDS, pctDecChars); + }; + + //export API + exports.Options = Options; + exports.URIComponents = URIComponents; + exports.SchemeHandler = SchemeHandler; + exports.URI = URI; + +}()); \ No newline at end of file