import {StringMapWrapper} from '../src/facade/collection'; import {isPresent, isBlank, RegExpWrapper} from '../src/facade/lang'; import {BaseException} from '../src/facade/exceptions'; export function convertUrlParamsToArray(urlParams: {[key: string]: any}): string[] { var paramsArray = []; if (isBlank(urlParams)) { return []; } StringMapWrapper.forEach( urlParams, (value, key) => { paramsArray.push((value === true) ? key : key + '=' + value); }); return paramsArray; } // Convert an object of url parameters into a string that can be used in an URL export function serializeParams(urlParams: {[key: string]: any}, joiner = '&'): string { return convertUrlParamsToArray(urlParams).join(joiner); } /** * This class represents a parsed URL */ export class Url { constructor(public path: string, public child: Url = null, public auxiliary: Url[] = /*@ts2dart_const*/[], public params: {[key: string]: any} = /*@ts2dart_const*/ {}) {} toString(): string { return this.path + this._matrixParamsToString() + this._auxToString() + this._childString(); } segmentToString(): string { return this.path + this._matrixParamsToString(); } /** @internal */ _auxToString(): string { return this.auxiliary.length > 0 ? ('(' + this.auxiliary.map(sibling => sibling.toString()).join('//') + ')') : ''; } private _matrixParamsToString(): string { var paramString = serializeParams(this.params, ';'); if (paramString.length > 0) { return ';' + paramString; } return ''; } /** @internal */ _childString(): string { return isPresent(this.child) ? ('/' + this.child.toString()) : ''; } } export class RootUrl extends Url { constructor(path: string, child: Url = null, auxiliary: Url[] = /*@ts2dart_const*/[], params: {[key: string]: any} = null) { super(path, child, auxiliary, params); } toString(): string { return this.path + this._auxToString() + this._childString() + this._queryParamsToString(); } segmentToString(): string { return this.path + this._queryParamsToString(); } private _queryParamsToString(): string { if (isBlank(this.params)) { return ''; } return '?' + serializeParams(this.params); } } export function pathSegmentsToUrl(pathSegments: string[]): Url { var url = new Url(pathSegments[pathSegments.length - 1]); for (var i = pathSegments.length - 2; i >= 0; i -= 1) { url = new Url(pathSegments[i], url); } return url; } var SEGMENT_RE = RegExpWrapper.create('^[^\\/\\(\\)\\?;=&#]+'); function matchUrlSegment(str: string): string { var match = RegExpWrapper.firstMatch(SEGMENT_RE, str); return isPresent(match) ? match[0] : ''; } var QUERY_PARAM_VALUE_RE = RegExpWrapper.create('^[^\\(\\)\\?;&#]+'); function matchUrlQueryParamValue(str: string): string { var match = RegExpWrapper.firstMatch(QUERY_PARAM_VALUE_RE, str); return isPresent(match) ? match[0] : ''; } export class UrlParser { private _remaining: string; peekStartsWith(str: string): boolean { return this._remaining.startsWith(str); } capture(str: string): void { if (!this._remaining.startsWith(str)) { throw new BaseException(`Expected "${str}".`); } this._remaining = this._remaining.substring(str.length); } parse(url: string): Url { this._remaining = url; if (url == '' || url == '/') { return new Url(''); } return this.parseRoot(); } // segment + (aux segments) + (query params) parseRoot(): RootUrl { if (this.peekStartsWith('/')) { this.capture('/'); } var path = matchUrlSegment(this._remaining); this.capture(path); var aux: Url[] = []; if (this.peekStartsWith('(')) { aux = this.parseAuxiliaryRoutes(); } if (this.peekStartsWith(';')) { // TODO: should these params just be dropped? this.parseMatrixParams(); } var child = null; if (this.peekStartsWith('/') && !this.peekStartsWith('//')) { this.capture('/'); child = this.parseSegment(); } var queryParams: {[key: string]: any} = null; if (this.peekStartsWith('?')) { queryParams = this.parseQueryParams(); } return new RootUrl(path, child, aux, queryParams); } // segment + (matrix params) + (aux segments) parseSegment(): Url { if (this._remaining.length == 0) { return null; } if (this.peekStartsWith('/')) { this.capture('/'); } var path = matchUrlSegment(this._remaining); this.capture(path); var matrixParams: {[key: string]: any} = null; if (this.peekStartsWith(';')) { matrixParams = this.parseMatrixParams(); } var aux: Url[] = []; if (this.peekStartsWith('(')) { aux = this.parseAuxiliaryRoutes(); } var child: Url = null; if (this.peekStartsWith('/') && !this.peekStartsWith('//')) { this.capture('/'); child = this.parseSegment(); } return new Url(path, child, aux, matrixParams); } parseQueryParams(): {[key: string]: any} { var params: {[key: string]: any} = {}; this.capture('?'); this.parseQueryParam(params); while (this._remaining.length > 0 && this.peekStartsWith('&')) { this.capture('&'); this.parseQueryParam(params); } return params; } parseMatrixParams(): {[key: string]: any} { var params: {[key: string]: any} = {}; while (this._remaining.length > 0 && this.peekStartsWith(';')) { this.capture(';'); this.parseParam(params); } return params; } parseParam(params: {[key: string]: any}): void { var key = matchUrlSegment(this._remaining); if (isBlank(key)) { return; } this.capture(key); var value: any = true; if (this.peekStartsWith('=')) { this.capture('='); var valueMatch = matchUrlSegment(this._remaining); if (isPresent(valueMatch)) { value = valueMatch; this.capture(value); } } params[key] = value; } parseQueryParam(params: {[key: string]: any}): void { var key = matchUrlSegment(this._remaining); if (isBlank(key)) { return; } this.capture(key); var value: any = true; if (this.peekStartsWith('=')) { this.capture('='); var valueMatch = matchUrlQueryParamValue(this._remaining); if (isPresent(valueMatch)) { value = valueMatch; this.capture(value); } } params[key] = value; } parseAuxiliaryRoutes(): Url[] { var routes: Url[] = []; this.capture('('); while (!this.peekStartsWith(')') && this._remaining.length > 0) { routes.push(this.parseSegment()); if (this.peekStartsWith('//')) { this.capture('//'); } } this.capture(')'); return routes; } } export var parser = new UrlParser();