http://www.mediawiki.org/wiki/Special:Code/MediaWiki/93781
Revision: 93781 Author: neilk Date: 2011-08-02 20:33:27 +0000 (Tue, 02 Aug 2011) Log Message: ----------- moving URI library to main resources Modified Paths: -------------- trunk/extensions/UploadWizard/UploadWizardHooks.php trunk/phase3/resources/Resources.php Added Paths: ----------- trunk/phase3/resources/mediawiki/mediawiki.uri.js trunk/phase3/tests/jasmine/SpecRunner.html trunk/phase3/tests/jasmine/lib/jasmine-1.0.1/ trunk/phase3/tests/jasmine/spec/mediawiki.uri.spec.js Removed Paths: ------------- trunk/extensions/UploadWizard/resources/mw.Uri.js trunk/extensions/UploadWizard/test/jasmine/lib/jasmine-1.0.1/ trunk/extensions/UploadWizard/test/jasmine/spec/mw.Uri.spec.js Modified: trunk/extensions/UploadWizard/UploadWizardHooks.php =================================================================== --- trunk/extensions/UploadWizard/UploadWizardHooks.php 2011-08-02 20:25:51 UTC (rev 93780) +++ trunk/extensions/UploadWizard/UploadWizardHooks.php 2011-08-02 20:33:27 UTC (rev 93781) @@ -24,6 +24,7 @@ 'jquery.suggestions', 'jquery.ui.widget', 'mediawiki.language', + 'mediawiki.uri', 'mediawiki.util', 'mediawiki.libs.jpegmeta', 'ext.uploadwizard.mediawiki.language.parser', @@ -47,7 +48,6 @@ 'resources/mw.Log.js', 'resources/mw.Utilities.js', 'resources/mw.UtilitiesTime.js', - 'resources/mw.Uri.js', 'resources/mw.Api.js', 'resources/mw.Api.edit.js', 'resources/mw.Api.category.js', Deleted: trunk/extensions/UploadWizard/resources/mw.Uri.js =================================================================== --- trunk/extensions/UploadWizard/resources/mw.Uri.js 2011-08-02 20:25:51 UTC (rev 93780) +++ trunk/extensions/UploadWizard/resources/mw.Uri.js 2011-08-02 20:33:27 UTC (rev 93781) @@ -1,257 +0,0 @@ -/** - * Library for simple URI parsing and manipulation. Requires jQuery. - * - * Do not expect full RFC 3986 compliance. Intended to be minimal, but featureful. - * The use cases we have in mind are constructing 'next page' or 'previous page' URLs, - * detecting whether we need to use cross-domain proxies for an API, constructing simple - * URL-based API calls, etc. - * - * Intended to compress very well if you use a JS-parsing minifier. - * - * Dependencies: mw, mw.Utilities, jQuery - * - * Example: - * - * var uri = new mw.uri( 'http://foo.com/mysite/mypage.php?quux=2' ); - * - * if ( uri.host == 'foo.com' ) { - * uri.host = 'www.foo.com'; - * uri.extend( { bar: 1 } ); - * - * $( 'a#id1' ).setAttr( 'href', uri ); - * // anchor with id 'id1' now links to http://www.foo.com/mysite/mypage.php?bar=1&quux=2 - * - * $( 'a#id2' ).setAttr( 'href', uri.clone().extend( { bar: 3, pif: 'paf' } ) ); - * // anchor with id 'id2' now links to http://www.foo.com/mysite/mypage.php?bar=3&quux=2&pif=paf - * } - * - * Parsing here is regex based, so may not work on all URIs, but is good enough for most. - * - * Given a URI like - * 'http://usr:p...@www.test.com:81/dir/dir.2/index.htm?q1=0&&test1&test2=&test3=value+%28escaped%29&r=1&r=2#top': - * The returned object will have the following properties: - * - * protocol 'http' - * user 'usr' - * password 'pwd' - * host 'www.test.com' - * port '81' - * path '/dir/dir.2/index.htm' - * query { - * q1: 0, - * test1: null, - * test2: '', - * test3: 'value (escaped)' - * r: [1, 2] - * } - * fragment 'top' - * - * n.b. 'password' is not technically allowed for HTTP URIs, but it is possible with other sorts of URIs. - * - * You can modify the properties directly. Then use the toString() method to extract the full URI string again. - * - * parsing based on parseUri 1.2.2 (c) Steven Levithan <stevenlevithan.com> MIT License - * http://stevenlevithan.com/demo/parseuri/js/ - * - */ - -( function( mw, $ ) { - /** - * Constructs URI object. Throws error if arguments are illegal/impossible, or otherwise don't parse. - * @constructor - * @param {!Object|String} URI string, or an Object with appropriate properties (especially another URI object to clone). Object must have non-blank 'protocol', 'host', and 'path' properties. - * @param {Boolean} strict mode (when parsing a string) - */ - mw.uri = function( uri, strictMode ) { - strictMode = !!strictMode; - if ( !mw.isEmpty( uri ) ) { - if ( typeof uri === 'string' ) { - this._parse( uri, strictMode ); - } else if ( typeof uri === 'object' ) { - var _this = this; - $.each( this._properties, function( i, property ) { - _this[property] = uri[property]; - } ); - if ( !mw.isDefined( this.query ) ) { - this.query = {}; - } - } - } - if ( !( this.protocol && this.host && this.path ) ) { - throw new Error( "bad constructor arguments" ); - } - }; - - /** - * Standard encodeURIComponent, with extra stuff to make all browsers work similarly and more compliant with RFC 3986 - * Similar to rawurlencode from PHP and our JS library mw.util.rawurlencode, but we also replace space with a + - * @param {String} string - * @return {String} encoded for URI - */ - mw.uri.encode = function( s ) { - return encodeURIComponent( s ) - .replace( /!/g, '%21').replace( /'/g, '%27').replace( /\(/g, '%28') - .replace( /\)/g, '%29').replace( /\*/g, '%2A') - .replace( /%20/g, '+' ); - }; - - /** - * Standard decodeURIComponent, with '+' to space - * @param {String} string encoded for URI - * @return {String} decoded string - */ - mw.uri.decode = function( s ) { - return decodeURIComponent( s ).replace( /\+/g, ' ' ); - }; - - /** - * Function that's useful when constructing the URI string -- we frequently encounter the pattern of - * having to add something to the URI as we go, but only if it's present, and to include a character before or after if so. - * @param {String} to prepend, if value not empty - * @param {String} value to include, if not empty - * @param {String} to append, if value not empty - * @param {Boolean} raw -- if true, do not URI encode - * @return {String} - */ - function _cat( pre, val, post, raw ) { - return mw.isEmpty( val ) ? '' : pre + ( raw ? val : mw.uri.encode( val ) ) + post; - } - - mw.uri.prototype = { - - // regular expressions to parse many common URIs. - // @private - _parser: { - strict: /^(?:([^:\/?#]+):)?(?:\/\/(?:(?:([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)?((?:[^?#\/]*\/)*[^?#]*)(?:\?([^#]*))?(?:#(.*))?/, - loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?(?:(?:([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?((?:\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?[^?#\/]*)(?:\?([^#]*))?(?:#(.*))?/ - }, - - /* the order here matches the order of captured matches in the above parser regexes */ - // @private - _properties: [ - "protocol", // http - "user", // usr - "password", // pwd - "host", // www.test.com - "port", // 81 - "path", // /dir/dir.2/index.htm - "query", // q1=0&&test1&test2=value (will become { q1: 0, test1: '', test2: 'value' } ) - "fragment" // top - ], - - /** - * Parse a string and set our properties accordingly. - * @param {String} URI - * @param {Boolean} strictness - * @return {Boolean} success - */ - _parse: function( str, strictMode ) { - var matches = this._parser[ strictMode ? "strict" : "loose" ].exec( str ); - var uri = this; - $.each( uri._properties, function( i, property ) { - uri[ property ] = matches[ i+1 ]; - } ); - - // uri.query starts out as the query string; we will parse it into key-val pairs then make - // that object the "query" property. - // we overwrite query in uri way to make cloning easier, it can use the same list of properties. - q = {}; - // using replace to iterate over a string - if ( uri.query ) { - uri.query.replace( /(?:^|&)([^&=]*)(?:(=)([^&]*))?/g, function ($0, $1, $2, $3) { - if ( $1 ) { - var k = mw.uri.decode( $1 ); - var v = ( $2 === '' || typeof $2 === 'undefined' ) ? null : mw.uri.decode( $3 ); - if ( typeof q[ k ] === 'string' ) { - q[ k ] = [ q[ k ] ]; - } - if ( typeof q[ k ] === 'object' ) { - q[ k ].push( v ); - } else { - q[ k ] = v; - } - } - } ); - } - this.query = q; - }, - - /** - * Returns user and password portion of a URI. - * @return {String} - */ - getUserInfo: function() { - return _cat( '', this.user, _cat( ':', this.password, '' ) ); - }, - - /** - * Gets host and port portion of a URI. - * @return {String} - */ - getHostPort: function() { - return this.host + _cat( ':', this.port, '' ); - }, - - /** - * Returns the userInfo and host and port portion of the URI. - * In most real-world URLs, this is simply the hostname, but it is more general. - * @return {String} - */ - getAuthority: function() { - return _cat( '', this.getUserInfo(), '@' ) + this.getHostPort(); - }, - - /** - * Returns the query arguments of the URL, encoded into a string - * Does not preserve the order of arguments passed into the URI. Does handle escaping. - * @return {String} - */ - getQueryString: function() { - var args = []; - var _this = this; - $.each( this.query, function( key, val ) { - var k = mw.uri.encode( key ); - var vals = val === null ? [ null ] : $.makeArray( val ); - $.each( vals, function( i, v ) { - args.push( k + ( v === null ? '' : '=' + mw.uri.encode( v ) ) ); - } ); - } ); - return args.join( '&' ); - }, - - /** - * Returns everything after the authority section of the URI - * @return {String} - */ - getRelativePath: function() { - return this.path + _cat( '?', this.getQueryString(), '', true ) + _cat( '#', this.fragment, '' ); - }, - - /** - * Gets the entire URI string. May not be precisely the same as input due to order of query arguments. - * @return {String} the URI string - */ - toString: function() { - return this.protocol + '://' + this.getAuthority() + this.getRelativePath(); - }, - - /** - * Clone this URI - * @return {Object} new URI object with same properties - */ - clone: function() { - return new mw.uri( this ); - }, - - /** - * Extend the query -- supply query parameters to override or add to ours - * @param {Object} query parameters in key-val form to override or add - * @return {Object} this URI object - */ - extend: function( parameters ) { - $.extend( this.query, parameters ); - return this; - } - }; - -} )( window.mediaWiki, jQuery ); Deleted: trunk/extensions/UploadWizard/test/jasmine/spec/mw.Uri.spec.js =================================================================== --- trunk/extensions/UploadWizard/test/jasmine/spec/mw.Uri.spec.js 2011-08-02 20:25:51 UTC (rev 93780) +++ trunk/extensions/UploadWizard/test/jasmine/spec/mw.Uri.spec.js 2011-08-02 20:33:27 UTC (rev 93781) @@ -1,274 +0,0 @@ -( function( mw ) { - - describe( "mw.uri", function() { - - describe( "should work well in loose and strict mode", function() { - - function basicTests( strict ) { - - describe( "should parse a simple HTTP URI correctly", function() { - - var uriString = 'http://www.ietf.org/rfc/rfc2396.txt'; - var uri; - if ( strict ) { - uri = new mw.uri( uriString, strict ); - } else { - uri = new mw.uri( uriString ); - } - - it( "should have basic object properties", function() { - expect( uri.protocol ).toEqual( 'http' ); - expect( uri.host ).toEqual( 'www.ietf.org' ); - expect( uri.port ).not.toBeDefined(); - expect( uri.path ).toEqual( '/rfc/rfc2396.txt' ); - expect( uri.query ).toEqual( {} ); - expect( uri.fragment ).not.toBeDefined(); - } ); - - describe( "should construct composite components of URI on request", function() { - it( "should have empty userinfo", function() { - expect( uri.getUserInfo() ).toEqual( '' ); - } ); - - it( "should have authority equal to host", function() { - expect( uri.getAuthority() ).toEqual( 'www.ietf.org' ); - } ); - - it( "should have hostport equal to host", function() { - expect( uri.getHostPort() ).toEqual( 'www.ietf.org' ); - } ); - - it( "should have empty string as query string", function() { - expect( uri.getQueryString() ).toEqual( '' ); - } ); - - it( "should have path as relative path", function() { - expect( uri.getRelativePath() ).toEqual( '/rfc/rfc2396.txt' ); - } ); - - it( "should return a uri string equivalent to original", function() { - expect( uri.toString() ).toEqual( uriString ); - } ); - } ); - } ); - } - - describe( "should work in loose mode", function() { - basicTests( false ); - } ); - - describe( "should work in strict mode", function() { - basicTests( true ); - } ); - - } ); - - it( "should parse a simple ftp URI correctly with user and password", function() { - var uri = new mw.uri( 'ftp://usr:pwd@192.0.2.16/' ); - expect( uri.protocol ).toEqual( 'ftp' ); - expect( uri.user ).toEqual( 'usr' ); - expect( uri.password ).toEqual( 'pwd' ); - expect( uri.host ).toEqual( '192.0.2.16' ); - expect( uri.port ).not.toBeDefined(); - expect( uri.path ).toEqual( '/' ); - expect( uri.query ).toEqual( {} ); - expect( uri.fragment ).not.toBeDefined(); - } ); - - it( "should parse a simple querystring", function() { - var uri = new mw.uri( 'http://www.google.com/?q=uri' ); - expect( uri.protocol ).toEqual( 'http' ); - expect( uri.host ).toEqual( 'www.google.com' ); - expect( uri.port ).not.toBeDefined(); - expect( uri.path ).toEqual( '/' ); - expect( uri.query ).toBeDefined(); - expect( uri.query ).toEqual( { q: 'uri' } ); - expect( uri.fragment ).not.toBeDefined(); - expect( uri.getQueryString() ).toEqual( 'q=uri' ); - } ); - - describe( "should handle multiple value query args", function() { - var uri = new mw.uri( 'http://www.sample.com/dir/?m=foo&m=bar&n=1' ); - it ( "should parse with multiple values", function() { - expect( uri.query.m.length ).toEqual( 2 ); - expect( uri.query.m[0] ).toEqual( 'foo' ); - expect( uri.query.m[1] ).toEqual( 'bar' ); - expect( uri.query.n ).toEqual( '1' ); - } ); - it ( "should accept multiple values", function() { - uri.query.n = [ "x", "y", "z" ]; - expect( uri.toString() ).toContain( 'm=foo&m=bar' ); - expect( uri.toString() ).toContain( 'n=x&n=y&n=z' ); - expect( uri.toString().length ).toEqual( 'http://www.sample.com/dir/?m=foo&m=bar&n=x&n=y&n=z'.length ); - } ); - it ( "should be okay with removing values", function() { - uri.query.m.splice( 0, 1 ); - delete uri.query.n; - expect( uri.toString() ).toEqual( 'http://www.sample.com/dir/?m=bar' ); - uri.query.m.splice( 0, 1 ); - expect( uri.toString() ).toEqual( 'http://www.sample.com/dir/' ); - } ); - } ); - - describe( "should deal with an all-dressed URI with everything", function() { - var uri = new mw.uri( 'http://a...@www.sample.com:81/dir/dir.2/index.htm?q1=0&&test1&test2=value+%28escaped%29#top' ); - - it( "should have basic object properties", function() { - expect( uri.protocol ).toEqual( 'http' ); - expect( uri.user ).toEqual( 'auth' ); - expect( uri.password ).not.toBeDefined(); - expect( uri.host ).toEqual( 'www.sample.com' ); - expect( uri.port ).toEqual( '81' ); - expect( uri.path ).toEqual( '/dir/dir.2/index.htm' ); - expect( uri.query ).toEqual( { q1: '0', test1: null, test2: 'value (escaped)' } ); - expect( uri.fragment ).toEqual( 'top' ); - } ); - - describe( "should construct composite components of URI on request", function() { - it( "should have userinfo", function() { - expect( uri.getUserInfo() ).toEqual( 'auth' ); - } ); - - it( "should have authority equal to auth@hostport", function() { - expect( uri.getAuthority() ).toEqual( 'a...@www.sample.com:81' ); - } ); - - it( "should have hostport equal to host:port", function() { - expect( uri.getHostPort() ).toEqual( 'www.sample.com:81' ); - } ); - - it( "should have query string which contains all components", function() { - var queryString = uri.getQueryString(); - expect( queryString ).toContain( 'q1=0' ); - expect( queryString ).toContain( 'test1' ); - expect( queryString ).not.toContain( 'test1=' ); - expect( queryString ).toContain( 'test2=value+%28escaped%29' ); - } ); - - it( "should have path as relative path", function() { - expect( uri.getRelativePath() ).toContain( uri.path ); - expect( uri.getRelativePath() ).toContain( uri.getQueryString() ); - expect( uri.getRelativePath() ).toContain( uri.fragment ); - } ); - - } ); - } ); - - describe( "should be able to clone itself", function() { - var original = new mw.uri( 'http://en.wiki.local/w/api.php?action=query&foo=bar' ); - var clone = original.clone(); - - it( "should make clones equivalent", function() { - expect( original ).toEqual( clone ); - expect( original.toString() ).toEqual( clone.toString() ); - } ); - - it( "should be able to manipulate clones independently", function() { - // but they are still different objects - expect( original ).not.toBe( clone ); - // and can diverge - clone.host = 'fr.wiki.local'; - expect( original.host ).not.toEqual( clone.host ); - expect( original.toString() ).not.toEqual( clone.toString() ); - } ); - } ); - - describe( "should be able to construct URL from object", function() { - it ( "should construct given basic arguments", function() { - var uri = new mw.uri( { protocol: 'http', host: 'www.foo.local', path: '/this' } ); - expect( uri.toString() ).toEqual( 'http://www.foo.local/this' ); - } ); - - it ( "should construct given more complex arguments", function() { - var uri = new mw.uri( { - protocol: 'http', - host: 'www.foo.local', - path: '/this', - query: { hi: 'there' }, - fragment: 'blah' - } ); - expect( uri.toString() ).toEqual( 'http://www.foo.local/this?hi=there#blah' ); - } ); - - it ( "should fail to construct without required properties", function() { - expect( function() { - var uri = new mw.uri( { protocol: 'http', host: 'www.foo.local' } ); - } ).toThrow( "bad constructor arguments" ); - } ); - } ); - - describe( "should be able to manipulate properties", function() { - var uri; - - beforeEach( function() { - uri = new mw.uri( 'http://en.wiki.local/w/api.php' ); - } ); - - it( "can add a fragment", function() { - uri.fragment = 'frag'; - expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php#frag' ); - } ); - - it( "can change host and port", function() { - uri.host = 'fr.wiki.local'; - uri.port = '8080'; - expect( uri.toString() ).toEqual( 'http://fr.wiki.local:8080/w/api.php' ); - } ); - - it ( "can add query arguments", function() { - uri.query.foo = 'bar'; - expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php?foo=bar' ); - } ); - - it ( "can extend query arguments", function() { - uri.query.foo = 'bar'; - expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php?foo=bar' ); - uri.extend( { foo: 'quux', pif: 'paf' } ); - expect( uri.toString() ).toContain( 'foo=quux' ); - expect( uri.toString() ).not.toContain( 'foo=bar' ); - expect( uri.toString() ).toContain( 'pif=paf' ); - } ); - - it ( "can remove query arguments", function() { - uri.query.foo = 'bar'; - expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php?foo=bar' ); - delete( uri.query.foo ); - expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php' ); - } ); - - } ); - - it( "should throw error on no arguments to constructor", function() { - expect( function() { - uri = new mw.uri(); - } ).toThrow( "bad constructor arguments" ); - } ); - - it( "should throw error on empty string as argument to constructor", function() { - expect( function() { - uri = new mw.uri( '' ); - } ).toThrow( "bad constructor arguments" ); - } ); - - it( "should throw error on non-URI as argument to constructor", function() { - expect( function() { - uri = new mw.uri( 'glaswegian penguins' ); - } ).toThrow( "bad constructor arguments" ); - } ); - - it( "should throw error on improper URI as argument to constructor", function() { - expect( function() { - uri = new mw.uri( 'http:/foo.com' ); - } ).toThrow( "bad constructor arguments" ); - } ); - - it( "should throw error on URI without protocol as argument to constructor", function() { - expect( function() { - uri = new mw.uri( 'foo.com/bar/baz' ); - } ).toThrow( "bad constructor arguments" ); - } ); - - - } ); - -} )( mediaWiki ); Modified: trunk/phase3/resources/Resources.php =================================================================== --- trunk/phase3/resources/Resources.php 2011-08-02 20:25:51 UTC (rev 93780) +++ trunk/phase3/resources/Resources.php 2011-08-02 20:33:27 UTC (rev 93781) @@ -576,6 +576,15 @@ 'jquery.mwPrototypes', ), ), + 'mediawiki.uri' => array( + 'scripts' => 'resources/mediawiki/mediawiki.uri.js', + ), + 'mediawiki.page.mwsuggest' => array( + 'scripts' => 'resources/mediawiki.page/mediawiki.page.mwsuggest.js', + 'dependencies' => array( + 'jquery.ui.autocomplete', + ), + ), 'mediawiki.page.ajaxCategories' => array( 'scripts' => 'resources/mediawiki.page/mediawiki.page.ajaxCategories.js', 'styles' => 'resources/mediawiki.page/mediawiki.page.ajaxCategories.css', Copied: trunk/phase3/resources/mediawiki/mediawiki.uri.js (from rev 93693, trunk/extensions/UploadWizard/resources/mw.Uri.js) =================================================================== --- trunk/phase3/resources/mediawiki/mediawiki.uri.js (rev 0) +++ trunk/phase3/resources/mediawiki/mediawiki.uri.js 2011-08-02 20:33:27 UTC (rev 93781) @@ -0,0 +1,257 @@ +/** + * Library for simple URI parsing and manipulation. Requires jQuery. + * + * Do not expect full RFC 3986 compliance. Intended to be minimal, but featureful. + * The use cases we have in mind are constructing 'next page' or 'previous page' URLs, + * detecting whether we need to use cross-domain proxies for an API, constructing simple + * URL-based API calls, etc. + * + * Intended to compress very well if you use a JS-parsing minifier. + * + * Dependencies: mw, mw.Utilities, jQuery + * + * Example: + * + * var uri = new mw.uri( 'http://foo.com/mysite/mypage.php?quux=2' ); + * + * if ( uri.host == 'foo.com' ) { + * uri.host = 'www.foo.com'; + * uri.extend( { bar: 1 } ); + * + * $( 'a#id1' ).setAttr( 'href', uri ); + * // anchor with id 'id1' now links to http://www.foo.com/mysite/mypage.php?bar=1&quux=2 + * + * $( 'a#id2' ).setAttr( 'href', uri.clone().extend( { bar: 3, pif: 'paf' } ) ); + * // anchor with id 'id2' now links to http://www.foo.com/mysite/mypage.php?bar=3&quux=2&pif=paf + * } + * + * Parsing here is regex based, so may not work on all URIs, but is good enough for most. + * + * Given a URI like + * 'http://usr:p...@www.test.com:81/dir/dir.2/index.htm?q1=0&&test1&test2=&test3=value+%28escaped%29&r=1&r=2#top': + * The returned object will have the following properties: + * + * protocol 'http' + * user 'usr' + * password 'pwd' + * host 'www.test.com' + * port '81' + * path '/dir/dir.2/index.htm' + * query { + * q1: 0, + * test1: null, + * test2: '', + * test3: 'value (escaped)' + * r: [1, 2] + * } + * fragment 'top' + * + * n.b. 'password' is not technically allowed for HTTP URIs, but it is possible with other sorts of URIs. + * + * You can modify the properties directly. Then use the toString() method to extract the full URI string again. + * + * parsing based on parseUri 1.2.2 (c) Steven Levithan <stevenlevithan.com> MIT License + * http://stevenlevithan.com/demo/parseuri/js/ + * + */ + +( function( mw, $ ) { + /** + * Constructs URI object. Throws error if arguments are illegal/impossible, or otherwise don't parse. + * @constructor + * @param {!Object|String} URI string, or an Object with appropriate properties (especially another URI object to clone). Object must have non-blank 'protocol', 'host', and 'path' properties. + * @param {Boolean} strict mode (when parsing a string) + */ + mw.uri = function( uri, strictMode ) { + strictMode = !!strictMode; + if ( !mw.isEmpty( uri ) ) { + if ( typeof uri === 'string' ) { + this._parse( uri, strictMode ); + } else if ( typeof uri === 'object' ) { + var _this = this; + $.each( this._properties, function( i, property ) { + _this[property] = uri[property]; + } ); + if ( !mw.isDefined( this.query ) ) { + this.query = {}; + } + } + } + if ( !( this.protocol && this.host && this.path ) ) { + throw new Error( "bad constructor arguments" ); + } + }; + + /** + * Standard encodeURIComponent, with extra stuff to make all browsers work similarly and more compliant with RFC 3986 + * Similar to rawurlencode from PHP and our JS library mw.util.rawurlencode, but we also replace space with a + + * @param {String} string + * @return {String} encoded for URI + */ + mw.uri.encode = function( s ) { + return encodeURIComponent( s ) + .replace( /!/g, '%21').replace( /'/g, '%27').replace( /\(/g, '%28') + .replace( /\)/g, '%29').replace( /\*/g, '%2A') + .replace( /%20/g, '+' ); + }; + + /** + * Standard decodeURIComponent, with '+' to space + * @param {String} string encoded for URI + * @return {String} decoded string + */ + mw.uri.decode = function( s ) { + return decodeURIComponent( s ).replace( /\+/g, ' ' ); + }; + + /** + * Function that's useful when constructing the URI string -- we frequently encounter the pattern of + * having to add something to the URI as we go, but only if it's present, and to include a character before or after if so. + * @param {String} to prepend, if value not empty + * @param {String} value to include, if not empty + * @param {String} to append, if value not empty + * @param {Boolean} raw -- if true, do not URI encode + * @return {String} + */ + function _cat( pre, val, post, raw ) { + return mw.isEmpty( val ) ? '' : pre + ( raw ? val : mw.uri.encode( val ) ) + post; + } + + mw.uri.prototype = { + + // regular expressions to parse many common URIs. + // @private + _parser: { + strict: /^(?:([^:\/?#]+):)?(?:\/\/(?:(?:([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)?((?:[^?#\/]*\/)*[^?#]*)(?:\?([^#]*))?(?:#(.*))?/, + loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?(?:(?:([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?((?:\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?[^?#\/]*)(?:\?([^#]*))?(?:#(.*))?/ + }, + + /* the order here matches the order of captured matches in the above parser regexes */ + // @private + _properties: [ + "protocol", // http + "user", // usr + "password", // pwd + "host", // www.test.com + "port", // 81 + "path", // /dir/dir.2/index.htm + "query", // q1=0&&test1&test2=value (will become { q1: 0, test1: '', test2: 'value' } ) + "fragment" // top + ], + + /** + * Parse a string and set our properties accordingly. + * @param {String} URI + * @param {Boolean} strictness + * @return {Boolean} success + */ + _parse: function( str, strictMode ) { + var matches = this._parser[ strictMode ? "strict" : "loose" ].exec( str ); + var uri = this; + $.each( uri._properties, function( i, property ) { + uri[ property ] = matches[ i+1 ]; + } ); + + // uri.query starts out as the query string; we will parse it into key-val pairs then make + // that object the "query" property. + // we overwrite query in uri way to make cloning easier, it can use the same list of properties. + q = {}; + // using replace to iterate over a string + if ( uri.query ) { + uri.query.replace( /(?:^|&)([^&=]*)(?:(=)([^&]*))?/g, function ($0, $1, $2, $3) { + if ( $1 ) { + var k = mw.uri.decode( $1 ); + var v = ( $2 === '' || typeof $2 === 'undefined' ) ? null : mw.uri.decode( $3 ); + if ( typeof q[ k ] === 'string' ) { + q[ k ] = [ q[ k ] ]; + } + if ( typeof q[ k ] === 'object' ) { + q[ k ].push( v ); + } else { + q[ k ] = v; + } + } + } ); + } + this.query = q; + }, + + /** + * Returns user and password portion of a URI. + * @return {String} + */ + getUserInfo: function() { + return _cat( '', this.user, _cat( ':', this.password, '' ) ); + }, + + /** + * Gets host and port portion of a URI. + * @return {String} + */ + getHostPort: function() { + return this.host + _cat( ':', this.port, '' ); + }, + + /** + * Returns the userInfo and host and port portion of the URI. + * In most real-world URLs, this is simply the hostname, but it is more general. + * @return {String} + */ + getAuthority: function() { + return _cat( '', this.getUserInfo(), '@' ) + this.getHostPort(); + }, + + /** + * Returns the query arguments of the URL, encoded into a string + * Does not preserve the order of arguments passed into the URI. Does handle escaping. + * @return {String} + */ + getQueryString: function() { + var args = []; + var _this = this; + $.each( this.query, function( key, val ) { + var k = mw.uri.encode( key ); + var vals = val === null ? [ null ] : $.makeArray( val ); + $.each( vals, function( i, v ) { + args.push( k + ( v === null ? '' : '=' + mw.uri.encode( v ) ) ); + } ); + } ); + return args.join( '&' ); + }, + + /** + * Returns everything after the authority section of the URI + * @return {String} + */ + getRelativePath: function() { + return this.path + _cat( '?', this.getQueryString(), '', true ) + _cat( '#', this.fragment, '' ); + }, + + /** + * Gets the entire URI string. May not be precisely the same as input due to order of query arguments. + * @return {String} the URI string + */ + toString: function() { + return this.protocol + '://' + this.getAuthority() + this.getRelativePath(); + }, + + /** + * Clone this URI + * @return {Object} new URI object with same properties + */ + clone: function() { + return new mw.uri( this ); + }, + + /** + * Extend the query -- supply query parameters to override or add to ours + * @param {Object} query parameters in key-val form to override or add + * @return {Object} this URI object + */ + extend: function( parameters ) { + $.extend( this.query, parameters ); + return this; + } + }; + +} )( window.mediaWiki, jQuery ); Added: trunk/phase3/tests/jasmine/SpecRunner.html =================================================================== --- trunk/phase3/tests/jasmine/SpecRunner.html (rev 0) +++ trunk/phase3/tests/jasmine/SpecRunner.html 2011-08-02 20:33:27 UTC (rev 93781) @@ -0,0 +1,27 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" + "http://www.w3.org/TR/html4/loose.dtd"> +<html> +<head> + <title>Jasmine Test Runner</title> + <link rel="stylesheet" type="text/css" href="lib/jasmine-1.0.1/jasmine.css"> + <script type="text/javascript" src="lib/jasmine-1.0.1/jasmine.js"></script> + <script type="text/javascript" src="lib/jasmine-1.0.1/jasmine-html.js"></script> + + <!-- include source files here... --> + <script type="text/javascript" src="../../load.php?debug=true&lang=en&modules=jquery%7Cmediawiki&only=scripts&skin=vector"></script> + + <script type="text/javascript" src="../../resources/mediawiki/mediawiki.uri.js"></script> + + <!-- include spec files here... --> + <script type="text/javascript" src="spec/mediawiki.uri.spec.js"></script> + +</head> +<body> +<script type="text/javascript"> + jasmine.getEnv().addReporter(new jasmine.TrivialReporter()); + jasmine.getEnv().execute(); +</script> + +</body> +</html> + Copied: trunk/phase3/tests/jasmine/spec/mediawiki.uri.spec.js (from rev 93693, trunk/extensions/UploadWizard/test/jasmine/spec/mw.Uri.spec.js) =================================================================== --- trunk/phase3/tests/jasmine/spec/mediawiki.uri.spec.js (rev 0) +++ trunk/phase3/tests/jasmine/spec/mediawiki.uri.spec.js 2011-08-02 20:33:27 UTC (rev 93781) @@ -0,0 +1,274 @@ +( function( mw ) { + + describe( "mw.uri", function() { + + describe( "should work well in loose and strict mode", function() { + + function basicTests( strict ) { + + describe( "should parse a simple HTTP URI correctly", function() { + + var uriString = 'http://www.ietf.org/rfc/rfc2396.txt'; + var uri; + if ( strict ) { + uri = new mw.uri( uriString, strict ); + } else { + uri = new mw.uri( uriString ); + } + + it( "should have basic object properties", function() { + expect( uri.protocol ).toEqual( 'http' ); + expect( uri.host ).toEqual( 'www.ietf.org' ); + expect( uri.port ).not.toBeDefined(); + expect( uri.path ).toEqual( '/rfc/rfc2396.txt' ); + expect( uri.query ).toEqual( {} ); + expect( uri.fragment ).not.toBeDefined(); + } ); + + describe( "should construct composite components of URI on request", function() { + it( "should have empty userinfo", function() { + expect( uri.getUserInfo() ).toEqual( '' ); + } ); + + it( "should have authority equal to host", function() { + expect( uri.getAuthority() ).toEqual( 'www.ietf.org' ); + } ); + + it( "should have hostport equal to host", function() { + expect( uri.getHostPort() ).toEqual( 'www.ietf.org' ); + } ); + + it( "should have empty string as query string", function() { + expect( uri.getQueryString() ).toEqual( '' ); + } ); + + it( "should have path as relative path", function() { + expect( uri.getRelativePath() ).toEqual( '/rfc/rfc2396.txt' ); + } ); + + it( "should return a uri string equivalent to original", function() { + expect( uri.toString() ).toEqual( uriString ); + } ); + } ); + } ); + } + + describe( "should work in loose mode", function() { + basicTests( false ); + } ); + + describe( "should work in strict mode", function() { + basicTests( true ); + } ); + + } ); + + it( "should parse a simple ftp URI correctly with user and password", function() { + var uri = new mw.uri( 'ftp://usr:pwd@192.0.2.16/' ); + expect( uri.protocol ).toEqual( 'ftp' ); + expect( uri.user ).toEqual( 'usr' ); + expect( uri.password ).toEqual( 'pwd' ); + expect( uri.host ).toEqual( '192.0.2.16' ); + expect( uri.port ).not.toBeDefined(); + expect( uri.path ).toEqual( '/' ); + expect( uri.query ).toEqual( {} ); + expect( uri.fragment ).not.toBeDefined(); + } ); + + it( "should parse a simple querystring", function() { + var uri = new mw.uri( 'http://www.google.com/?q=uri' ); + expect( uri.protocol ).toEqual( 'http' ); + expect( uri.host ).toEqual( 'www.google.com' ); + expect( uri.port ).not.toBeDefined(); + expect( uri.path ).toEqual( '/' ); + expect( uri.query ).toBeDefined(); + expect( uri.query ).toEqual( { q: 'uri' } ); + expect( uri.fragment ).not.toBeDefined(); + expect( uri.getQueryString() ).toEqual( 'q=uri' ); + } ); + + describe( "should handle multiple value query args", function() { + var uri = new mw.uri( 'http://www.sample.com/dir/?m=foo&m=bar&n=1' ); + it ( "should parse with multiple values", function() { + expect( uri.query.m.length ).toEqual( 2 ); + expect( uri.query.m[0] ).toEqual( 'foo' ); + expect( uri.query.m[1] ).toEqual( 'bar' ); + expect( uri.query.n ).toEqual( '1' ); + } ); + it ( "should accept multiple values", function() { + uri.query.n = [ "x", "y", "z" ]; + expect( uri.toString() ).toContain( 'm=foo&m=bar' ); + expect( uri.toString() ).toContain( 'n=x&n=y&n=z' ); + expect( uri.toString().length ).toEqual( 'http://www.sample.com/dir/?m=foo&m=bar&n=x&n=y&n=z'.length ); + } ); + it ( "should be okay with removing values", function() { + uri.query.m.splice( 0, 1 ); + delete uri.query.n; + expect( uri.toString() ).toEqual( 'http://www.sample.com/dir/?m=bar' ); + uri.query.m.splice( 0, 1 ); + expect( uri.toString() ).toEqual( 'http://www.sample.com/dir/' ); + } ); + } ); + + describe( "should deal with an all-dressed URI with everything", function() { + var uri = new mw.uri( 'http://a...@www.sample.com:81/dir/dir.2/index.htm?q1=0&&test1&test2=value+%28escaped%29#top' ); + + it( "should have basic object properties", function() { + expect( uri.protocol ).toEqual( 'http' ); + expect( uri.user ).toEqual( 'auth' ); + expect( uri.password ).not.toBeDefined(); + expect( uri.host ).toEqual( 'www.sample.com' ); + expect( uri.port ).toEqual( '81' ); + expect( uri.path ).toEqual( '/dir/dir.2/index.htm' ); + expect( uri.query ).toEqual( { q1: '0', test1: null, test2: 'value (escaped)' } ); + expect( uri.fragment ).toEqual( 'top' ); + } ); + + describe( "should construct composite components of URI on request", function() { + it( "should have userinfo", function() { + expect( uri.getUserInfo() ).toEqual( 'auth' ); + } ); + + it( "should have authority equal to auth@hostport", function() { + expect( uri.getAuthority() ).toEqual( 'a...@www.sample.com:81' ); + } ); + + it( "should have hostport equal to host:port", function() { + expect( uri.getHostPort() ).toEqual( 'www.sample.com:81' ); + } ); + + it( "should have query string which contains all components", function() { + var queryString = uri.getQueryString(); + expect( queryString ).toContain( 'q1=0' ); + expect( queryString ).toContain( 'test1' ); + expect( queryString ).not.toContain( 'test1=' ); + expect( queryString ).toContain( 'test2=value+%28escaped%29' ); + } ); + + it( "should have path as relative path", function() { + expect( uri.getRelativePath() ).toContain( uri.path ); + expect( uri.getRelativePath() ).toContain( uri.getQueryString() ); + expect( uri.getRelativePath() ).toContain( uri.fragment ); + } ); + + } ); + } ); + + describe( "should be able to clone itself", function() { + var original = new mw.uri( 'http://en.wiki.local/w/api.php?action=query&foo=bar' ); + var clone = original.clone(); + + it( "should make clones equivalent", function() { + expect( original ).toEqual( clone ); + expect( original.toString() ).toEqual( clone.toString() ); + } ); + + it( "should be able to manipulate clones independently", function() { + // but they are still different objects + expect( original ).not.toBe( clone ); + // and can diverge + clone.host = 'fr.wiki.local'; + expect( original.host ).not.toEqual( clone.host ); + expect( original.toString() ).not.toEqual( clone.toString() ); + } ); + } ); + + describe( "should be able to construct URL from object", function() { + it ( "should construct given basic arguments", function() { + var uri = new mw.uri( { protocol: 'http', host: 'www.foo.local', path: '/this' } ); + expect( uri.toString() ).toEqual( 'http://www.foo.local/this' ); + } ); + + it ( "should construct given more complex arguments", function() { + var uri = new mw.uri( { + protocol: 'http', + host: 'www.foo.local', + path: '/this', + query: { hi: 'there' }, + fragment: 'blah' + } ); + expect( uri.toString() ).toEqual( 'http://www.foo.local/this?hi=there#blah' ); + } ); + + it ( "should fail to construct without required properties", function() { + expect( function() { + var uri = new mw.uri( { protocol: 'http', host: 'www.foo.local' } ); + } ).toThrow( "bad constructor arguments" ); + } ); + } ); + + describe( "should be able to manipulate properties", function() { + var uri; + + beforeEach( function() { + uri = new mw.uri( 'http://en.wiki.local/w/api.php' ); + } ); + + it( "can add a fragment", function() { + uri.fragment = 'frag'; + expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php#frag' ); + } ); + + it( "can change host and port", function() { + uri.host = 'fr.wiki.local'; + uri.port = '8080'; + expect( uri.toString() ).toEqual( 'http://fr.wiki.local:8080/w/api.php' ); + } ); + + it ( "can add query arguments", function() { + uri.query.foo = 'bar'; + expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php?foo=bar' ); + } ); + + it ( "can extend query arguments", function() { + uri.query.foo = 'bar'; + expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php?foo=bar' ); + uri.extend( { foo: 'quux', pif: 'paf' } ); + expect( uri.toString() ).toContain( 'foo=quux' ); + expect( uri.toString() ).not.toContain( 'foo=bar' ); + expect( uri.toString() ).toContain( 'pif=paf' ); + } ); + + it ( "can remove query arguments", function() { + uri.query.foo = 'bar'; + expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php?foo=bar' ); + delete( uri.query.foo ); + expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php' ); + } ); + + } ); + + it( "should throw error on no arguments to constructor", function() { + expect( function() { + uri = new mw.uri(); + } ).toThrow( "bad constructor arguments" ); + } ); + + it( "should throw error on empty string as argument to constructor", function() { + expect( function() { + uri = new mw.uri( '' ); + } ).toThrow( "bad constructor arguments" ); + } ); + + it( "should throw error on non-URI as argument to constructor", function() { + expect( function() { + uri = new mw.uri( 'glaswegian penguins' ); + } ).toThrow( "bad constructor arguments" ); + } ); + + it( "should throw error on improper URI as argument to constructor", function() { + expect( function() { + uri = new mw.uri( 'http:/foo.com' ); + } ).toThrow( "bad constructor arguments" ); + } ); + + it( "should throw error on URI without protocol as argument to constructor", function() { + expect( function() { + uri = new mw.uri( 'foo.com/bar/baz' ); + } ).toThrow( "bad constructor arguments" ); + } ); + + + } ); + +} )( mediaWiki ); _______________________________________________ MediaWiki-CVS mailing list MediaWiki-CVS@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs