src/uri.js
author Laurens Holst <laurens.hg@grauw.nl>
Thu May 20 13:48:31 2010 +0200 (3 months ago)
changeset 269 d518030b550b
parent 268 ca096724a56d
permissions -rw-r--r--
uri: Move exports to the end of the file.
     1 /**
     2  * @fileOverview
     3  * 
     4  * Grauw URI utilities
     5  * 
     6  * See: http://hg.grauw.nl/grauw-lib/file/tip/src/uri.js
     7  * 
     8  * @author Laurens Holst (http://www.grauw.nl/)
     9  * 
    10  *   Copyright 2010 Laurens Holst
    11  * 
    12  *   Licensed under the Apache License, Version 2.0 (the "License");
    13  *   you may not use this file except in compliance with the License.
    14  *   You may obtain a copy of the License at
    15  * 
    16  *       http://www.apache.org/licenses/LICENSE-2.0
    17  * 
    18  *   Unless required by applicable law or agreed to in writing, software
    19  *   distributed under the License is distributed on an "AS IS" BASIS,
    20  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    21  *   See the License for the specific language governing permissions and
    22  *   limitations under the License.
    23  * 
    24  */
    25 gl.module('gl.uri', [], function() {
    26 	
    27 	/**
    28 	 * Constructs a URI object.
    29 	 * @constructor
    30 	 * @class Implementation of URI parsing and base URI resolving algorithm in RFC 3986.
    31 	 * @param {string|URI} uri A string or URI object to create the object from.
    32 	 */
    33 	var URI = function(vURI) {
    34 		if (vURI instanceof URI) {	// copy constructor
    35 			this.scheme = vURI.scheme;
    36 			this.authority = vURI.authority;
    37 			this.path = vURI.path;
    38 			this.query = vURI.query;
    39 			this.fragment = vURI.fragment;
    40 		} else if (vURI) { // vURI is URI string or cast to string
    41 			var c = oParseRegex.exec(vURI);
    42 			this.scheme = c[1];
    43 			this.authority = c[2];
    44 			this.path = c[3];
    45 			this.query = c[4];
    46 			this.fragment = c[5];
    47 		}
    48 	};
    49 	
    50 	// Initial values on the prototype
    51 	URI.prototype.scheme    = null;
    52 	URI.prototype.authority = null;
    53 	URI.prototype.path      = '';
    54 	URI.prototype.query     = null;
    55 	URI.prototype.fragment  = null;
    56 	
    57 	// Regular expression from RFC 2396 appendix B
    58 	var oParseRegex = new RegExp('^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(?:\\?([^#]*))?(?:#(.*))?');
    59 	
    60 	/**
    61 	 * Resolves a relative URI against an absolute base URI.
    62 	 * Convenience method.
    63 	 * @param {String} uri the relative URI to resolve
    64 	 * @param {String} baseURI the base URI (must be absolute) to resolve against
    65 	 */
    66 	URI.resolve = function(sURI, sBaseURI) {
    67 		var oURI = hCache[sURI] || (hCache[sURI] = new URI(sURI));
    68 		var oBaseURI = hCache[sBaseURI] || (hCache[sBaseURI] = new URI(sBaseURI));
    69 		return oURI.resolve(oBaseURI).toString();
    70 	};
    71 	
    72 	var hCache = {};
    73 	
    74 	/**
    75 	 * Tests whether the URI is an absolute URI.
    76 	 * See RFC 3986 section 4.3.
    77 	 */
    78 	URI.prototype.isAbsolute = function() {
    79 		return this.scheme && !this.fragment;
    80 	};
    81 	
    82 	///**
    83 	//* Extensive validation of the URI against the ABNF in RFC 3986
    84 	//*/
    85 	//URI.prototype.validate
    86 	
    87 	/**
    88 	 * Tests whether the URI is a same-document reference.
    89 	 * See RFC 3986 section 4.4.
    90 	 * 
    91 	 * To perform more thorough comparison, you can normalise the URI objects.
    92 	 */
    93 	URI.prototype.isSameDocumentAs = function(oURI) {
    94 		return oURI.scheme == this.scheme &&
    95 		    oURI.authority == this.authority &&
    96 		         oURI.path == this.path &&
    97 		        oURI.query == this.query;
    98 	};
    99 	
   100 	/**
   101 	 * Simple String Comparison of two URIs.
   102 	 * See RFC 3986 section 6.2.1.
   103 	 * 
   104 	 * To perform more thorough comparison, you can normalise the URI objects.
   105 	 */
   106 	URI.prototype.equals = function(oURI) {
   107 		return this.isSameDocumentAs(oURI) && oURI.fragment == this.fragment;
   108 	};
   109 	
   110 	/**
   111 	 * Normalizes the URI using syntax-based normalization.
   112 	 * This includes case normalization, percent-encoding normalization and path segment normalization.
   113 	 * XXX: Percent-encoding normalization does not escape characters that need to be escaped.
   114 	 *      (Although that would not be a valid URI in the first place. See validate().)
   115 	 * See RFC 3986 section 6.2.2.
   116 	 */
   117 	URI.prototype.normalize = function() {
   118 		this.removeDotSegments();
   119 		if (this.scheme)
   120 			this.scheme = this.scheme.toLowerCase();
   121 		if (this.authority)
   122 			this.authority = this.authority.replace(oAuthorityRegex, fAuthority).
   123 									replace(oCaseRegex, fCase);
   124 		if (this.path)
   125 			this.path = this.path.replace(oCaseRegex, fCase);
   126 		if (this.query)
   127 			this.query = this.query.replace(oCaseRegex, fCase);
   128 		if (this.fragment)
   129 			this.fragment = this.fragment.replace(oCaseRegex, fCase);
   130 	};
   131 	
   132 	var oCaseRegex = /%[0-9a-z]{2}/gi;
   133 	var oPercentRegex = /[a-zA-Z0-9\-\._~]/;
   134 	var oAuthorityRegex = /(.*@)?([^@:]*)(:.*)?/;
   135 	
   136 	function fCase(sStr) {
   137 		var sDec = unescape(sStr);
   138 		return oPercentRegex.test(sDec) ? sDec : sStr.toUpperCase();
   139 	}
   140 	
   141 	function fAuthority(sStr, p1, p2, p3) {
   142 		return (p1 || '') + p2.toLowerCase() + (p3 || '');
   143 	}
   144 	
   145 	/**
   146 	 * Resolve a relative URI (this) against a base URI.
   147 	 * The base URI must be an absolute URI.
   148 	 * See RFC 3986 section 5.2
   149 	 */
   150 	URI.prototype.resolve = function(oBaseURI) {
   151 		var oURI = new URI();
   152 		if (this.scheme) {
   153 			oURI.scheme = this.scheme;
   154 			oURI.authority = this.authority;
   155 			oURI.path = this.path;
   156 			oURI.query = this.query;
   157 		} else {
   158 			oURI.scheme = oBaseURI.scheme;
   159 			if (this.authority) {
   160 				oURI.authority = this.authority;
   161 				oURI.path = this.path;
   162 				oURI.query = this.query;
   163 			} else {
   164 				oURI.authority = oBaseURI.authority;
   165 				if (this.path == '') {
   166 					oURI.path = oBaseURI.path;
   167 					oURI.query = this.query || oBaseURI.query;
   168 				} else {
   169 					if (this.path.charAt(0) == '/') {
   170 						oURI.path = this.path;
   171 						oURI.removeDotSegments();
   172 					} else {
   173 						if (oBaseURI.authority && oBaseURI.path == '') {
   174 							oURI.path = '/' + this.path;
   175 						} else {
   176 							oURI.path = oBaseURI.path.substring(0, oBaseURI.path.lastIndexOf('/') + 1) + this.path;
   177 						}
   178 						oURI.removeDotSegments();
   179 					}
   180 					oURI.query = this.query;
   181 				}
   182 			}
   183 		}
   184 		oURI.fragment = this.fragment;
   185 		return oURI;
   186 	};
   187 	
   188 	/**
   189 	 * Remove dot segments from path.
   190 	 * See RFC 3986 section 5.2.4
   191 	 */
   192 	URI.prototype.removeDotSegments = function() {
   193 		var aInput = this.path.split('/'),
   194 			aOutput = [],
   195 			sSegment,
   196 			bAbsPath = aInput[0] == '';
   197 		if (bAbsPath)
   198 			aInput.shift();
   199 		var sFirst = aInput[0] == '' ? aInput.shift() : null;
   200 		while (aInput.length) {
   201 			sSegment = aInput.shift();
   202 			if (sSegment == '..') {
   203 				aOutput.pop();
   204 			} else if (sSegment != '.') {
   205 				aOutput.push(sSegment);
   206 			}
   207 		}
   208 		if (sSegment == '.' || sSegment == '..')
   209 			aOutput.push('');
   210 		if (bAbsPath)
   211 			aOutput.unshift('');
   212 		this.path = aOutput.join('/');
   213 	};
   214 	
   215 	/**
   216 	 * Serialises the URI to a string.
   217 	 */
   218 	URI.prototype.toString = function() {
   219 		var sResult = '';
   220 		if (this.scheme)
   221 			sResult += this.scheme + ':';
   222 		if (this.authority)
   223 			sResult += '//' + this.authority;
   224 		sResult += this.path;
   225 		if (this.query)
   226 			sResult += '?' + this.query;
   227 		if (this.fragment)
   228 			sResult += '#' + this.fragment;
   229 		return sResult;
   230 	};
   231 	
   232 	
   233 	
   234 	
   235 	// exports
   236 	this.URI = URI;
   237 
   238 });