/** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import { PRIMARY_OUTLET } from './shared'; import { forEach, shallowEqual } from './utils/collection'; /** * @return {?} */ export function createEmptyUrlTree() { return new UrlTree(new UrlSegmentGroup([], {}), {}, null); } /** * @param {?} container * @param {?} containee * @param {?} exact * @return {?} */ export function containsTree(container, containee, exact) { if (exact) { return equalQueryParams(container.queryParams, containee.queryParams) && equalSegmentGroups(container.root, containee.root); } return containsQueryParams(container.queryParams, containee.queryParams) && containsSegmentGroup(container.root, containee.root); } /** * @param {?} container * @param {?} containee * @return {?} */ function equalQueryParams(container, containee) { return shallowEqual(container, containee); } /** * @param {?} container * @param {?} containee * @return {?} */ function equalSegmentGroups(container, containee) { if (!equalPath(container.segments, containee.segments)) return false; if (container.numberOfChildren !== containee.numberOfChildren) return false; for (var c in containee.children) { if (!container.children[c]) return false; if (!equalSegmentGroups(container.children[c], containee.children[c])) return false; } return true; } /** * @param {?} container * @param {?} containee * @return {?} */ function containsQueryParams(container, containee) { return Object.keys(containee).length <= Object.keys(container).length && Object.keys(containee).every(function (key) { return containee[key] === container[key]; }); } /** * @param {?} container * @param {?} containee * @return {?} */ function containsSegmentGroup(container, containee) { return containsSegmentGroupHelper(container, containee, containee.segments); } /** * @param {?} container * @param {?} containee * @param {?} containeePaths * @return {?} */ function containsSegmentGroupHelper(container, containee, containeePaths) { if (container.segments.length > containeePaths.length) { var /** @type {?} */ current = container.segments.slice(0, containeePaths.length); if (!equalPath(current, containeePaths)) return false; if (containee.hasChildren()) return false; return true; } else if (container.segments.length === containeePaths.length) { if (!equalPath(container.segments, containeePaths)) return false; for (var c in containee.children) { if (!container.children[c]) return false; if (!containsSegmentGroup(container.children[c], containee.children[c])) return false; } return true; } else { var /** @type {?} */ current = containeePaths.slice(0, container.segments.length); var /** @type {?} */ next = containeePaths.slice(container.segments.length); if (!equalPath(container.segments, current)) return false; if (!container.children[PRIMARY_OUTLET]) return false; return containsSegmentGroupHelper(container.children[PRIMARY_OUTLET], containee, next); } } /** * \@whatItDoes Represents the parsed URL. * * \@howToUse * * ``` * \@Component({templateUrl:'template.html'}) * class MyComponent { * constructor(router: Router) { * const tree: UrlTree = * router.parseUrl('/team/33/(user/victor//support:help)?debug=true#fragment'); * const f = tree.fragment; // return 'fragment' * const q = tree.queryParams; // returns {debug: 'true'} * const g: UrlSegmentGroup = tree.root.children[PRIMARY_OUTLET]; * const s: UrlSegment[] = g.segments; // returns 2 segments 'team' and '33' * g.children[PRIMARY_OUTLET].segments; // returns 2 segments 'user' and 'victor' * g.children['support'].segments; // return 1 segment 'help' * } * } * ``` * * \@description * * Since a router state is a tree, and the URL is nothing but a serialized state, the URL is a * serialized tree. * UrlTree is a data structure that provides a lot of affordances in dealing with URLs * * \@stable */ export var UrlTree = (function () { /** * \@internal * @param {?} root * @param {?} queryParams * @param {?} fragment */ function UrlTree(root, queryParams, fragment) { this.root = root; this.queryParams = queryParams; this.fragment = fragment; } /** * \@docsNotRequired * @return {?} */ UrlTree.prototype.toString = function () { return new DefaultUrlSerializer().serialize(this); }; return UrlTree; }()); function UrlTree_tsickle_Closure_declarations() { /** * The root segment group of the URL tree * @type {?} */ UrlTree.prototype.root; /** * The query params of the URL * @type {?} */ UrlTree.prototype.queryParams; /** * The fragment of the URL * @type {?} */ UrlTree.prototype.fragment; } /** * \@whatItDoes Represents the parsed URL segment group. * * See {\@link UrlTree} for more information. * * \@stable */ export var UrlSegmentGroup = (function () { /** * @param {?} segments * @param {?} children */ function UrlSegmentGroup(segments, children) { var _this = this; this.segments = segments; this.children = children; /** The parent node in the url tree */ this.parent = null; forEach(children, function (v, k) { return v.parent = _this; }); } /** * Wether the segment has child segments * @return {?} */ UrlSegmentGroup.prototype.hasChildren = function () { return this.numberOfChildren > 0; }; Object.defineProperty(UrlSegmentGroup.prototype, "numberOfChildren", { /** * Number of child segments * @return {?} */ get: function () { return Object.keys(this.children).length; }, enumerable: true, configurable: true }); /** * \@docsNotRequired * @return {?} */ UrlSegmentGroup.prototype.toString = function () { return serializePaths(this); }; return UrlSegmentGroup; }()); function UrlSegmentGroup_tsickle_Closure_declarations() { /** * \@internal * @type {?} */ UrlSegmentGroup.prototype._sourceSegment; /** * \@internal * @type {?} */ UrlSegmentGroup.prototype._segmentIndexShift; /** * The parent node in the url tree * @type {?} */ UrlSegmentGroup.prototype.parent; /** * The URL segments of this group. See {\@link UrlSegment} for more information * @type {?} */ UrlSegmentGroup.prototype.segments; /** * The list of children of this group * @type {?} */ UrlSegmentGroup.prototype.children; } /** * \@whatItDoes Represents a single URL segment. * * \@howToUse * * ``` * \@Component({templateUrl:'template.html'}) * class MyComponent { * constructor(router: Router) { * const tree: UrlTree = router.parseUrl('/team;id=33'); * const g: UrlSegmentGroup = tree.root.children[PRIMARY_OUTLET]; * const s: UrlSegment[] = g.segments; * s[0].path; // returns 'team' * s[0].parameters; // returns {id: 33} * } * } * ``` * * \@description * * A UrlSegment is a part of a URL between the two slashes. It contains a path and the matrix * parameters associated with the segment. * * \@stable */ export var UrlSegment = (function () { /** * @param {?} path * @param {?} parameters */ function UrlSegment(path, parameters) { this.path = path; this.parameters = parameters; } /** * \@docsNotRequired * @return {?} */ UrlSegment.prototype.toString = function () { return serializePath(this); }; return UrlSegment; }()); function UrlSegment_tsickle_Closure_declarations() { /** * The path part of a URL segment * @type {?} */ UrlSegment.prototype.path; /** * The matrix parameters associated with a segment * @type {?} */ UrlSegment.prototype.parameters; } /** * @param {?} a * @param {?} b * @return {?} */ export function equalSegments(a, b) { if (a.length !== b.length) return false; for (var /** @type {?} */ i = 0; i < a.length; ++i) { if (a[i].path !== b[i].path) return false; if (!shallowEqual(a[i].parameters, b[i].parameters)) return false; } return true; } /** * @param {?} a * @param {?} b * @return {?} */ export function equalPath(a, b) { if (a.length !== b.length) return false; for (var /** @type {?} */ i = 0; i < a.length; ++i) { if (a[i].path !== b[i].path) return false; } return true; } /** * @param {?} segment * @param {?} fn * @return {?} */ export function mapChildrenIntoArray(segment, fn) { var /** @type {?} */ res = []; forEach(segment.children, function (child, childOutlet) { if (childOutlet === PRIMARY_OUTLET) { res = res.concat(fn(child, childOutlet)); } }); forEach(segment.children, function (child, childOutlet) { if (childOutlet !== PRIMARY_OUTLET) { res = res.concat(fn(child, childOutlet)); } }); return res; } /** * \@whatItDoes Serializes and deserializes a URL string into a URL tree. * * \@description The url serialization strategy is customizable. You can * make all URLs case insensitive by providing a custom UrlSerializer. * * See {\@link DefaultUrlSerializer} for an example of a URL serializer. * * \@stable * @abstract */ export var UrlSerializer = (function () { function UrlSerializer() { } /** * Parse a url into a {\@link UrlTree} * @abstract * @param {?} url * @return {?} */ UrlSerializer.prototype.parse = function (url) { }; /** * Converts a {\@link UrlTree} into a url * @abstract * @param {?} tree * @return {?} */ UrlSerializer.prototype.serialize = function (tree) { }; return UrlSerializer; }()); /** * \@whatItDoes A default implementation of the {\@link UrlSerializer}. * * \@description * * Example URLs: * * ``` * /inbox/33(popup:compose) * /inbox/33;open=true/messages/44 * ``` * * DefaultUrlSerializer uses parentheses to serialize secondary segments (e.g., popup:compose), the * colon syntax to specify the outlet, and the ';parameter=value' syntax (e.g., open=true) to * specify route specific parameters. * * \@stable */ export var DefaultUrlSerializer = (function () { function DefaultUrlSerializer() { } /** * Parses a url into a {\@link UrlTree} * @param {?} url * @return {?} */ DefaultUrlSerializer.prototype.parse = function (url) { var /** @type {?} */ p = new UrlParser(url); return new UrlTree(p.parseRootSegment(), p.parseQueryParams(), p.parseFragment()); }; /** * Converts a {\@link UrlTree} into a url * @param {?} tree * @return {?} */ DefaultUrlSerializer.prototype.serialize = function (tree) { var /** @type {?} */ segment = "/" + serializeSegment(tree.root, true); var /** @type {?} */ query = serializeQueryParams(tree.queryParams); var /** @type {?} */ fragment = tree.fragment !== null && tree.fragment !== undefined ? "#" + encodeURI(tree.fragment) : ''; return "" + segment + query + fragment; }; return DefaultUrlSerializer; }()); /** * @param {?} segment * @return {?} */ export function serializePaths(segment) { return segment.segments.map(function (p) { return serializePath(p); }).join('/'); } /** * @param {?} segment * @param {?} root * @return {?} */ function serializeSegment(segment, root) { if (segment.hasChildren() && root) { var /** @type {?} */ primary = segment.children[PRIMARY_OUTLET] ? serializeSegment(segment.children[PRIMARY_OUTLET], false) : ''; var /** @type {?} */ children_1 = []; forEach(segment.children, function (v, k) { if (k !== PRIMARY_OUTLET) { children_1.push(k + ":" + serializeSegment(v, false)); } }); if (children_1.length > 0) { return primary + "(" + children_1.join('//') + ")"; } else { return "" + primary; } } else if (segment.hasChildren() && !root) { var /** @type {?} */ children = mapChildrenIntoArray(segment, function (v, k) { if (k === PRIMARY_OUTLET) { return [serializeSegment(segment.children[PRIMARY_OUTLET], false)]; } else { return [(k + ":" + serializeSegment(v, false))]; } }); return serializePaths(segment) + "/(" + children.join('//') + ")"; } else { return serializePaths(segment); } } /** * @param {?} s * @return {?} */ export function encode(s) { return encodeURIComponent(s); } /** * @param {?} s * @return {?} */ export function decode(s) { return decodeURIComponent(s); } /** * @param {?} path * @return {?} */ export function serializePath(path) { return "" + encode(path.path) + serializeParams(path.parameters); } /** * @param {?} params * @return {?} */ function serializeParams(params) { return pairs(params).map(function (p) { return (";" + encode(p.first) + "=" + encode(p.second)); }).join(''); } /** * @param {?} params * @return {?} */ function serializeQueryParams(params) { var /** @type {?} */ strParams = Object.keys(params).map(function (name) { var /** @type {?} */ value = params[name]; return Array.isArray(value) ? value.map(function (v) { return (encode(name) + "=" + encode(v)); }).join('&') : encode(name) + "=" + encode(value); }); return strParams.length ? "?" + strParams.join("&") : ''; } var Pair = (function () { /** * @param {?} first * @param {?} second */ function Pair(first, second) { this.first = first; this.second = second; } return Pair; }()); function Pair_tsickle_Closure_declarations() { /** @type {?} */ Pair.prototype.first; /** @type {?} */ Pair.prototype.second; } /** * @param {?} obj * @return {?} */ function pairs(obj) { var /** @type {?} */ res = []; for (var prop in obj) { if (obj.hasOwnProperty(prop)) { res.push(new Pair(prop, obj[prop])); } } return res; } var /** @type {?} */ SEGMENT_RE = /^[^\/()?;=&#]+/; /** * @param {?} str * @return {?} */ function matchSegments(str) { SEGMENT_RE.lastIndex = 0; var /** @type {?} */ match = str.match(SEGMENT_RE); return match ? match[0] : ''; } var /** @type {?} */ QUERY_PARAM_RE = /^[^=?&#]+/; /** * @param {?} str * @return {?} */ function matchQueryParams(str) { QUERY_PARAM_RE.lastIndex = 0; var /** @type {?} */ match = str.match(SEGMENT_RE); return match ? match[0] : ''; } var /** @type {?} */ QUERY_PARAM_VALUE_RE = /^[^?&#]+/; /** * @param {?} str * @return {?} */ function matchUrlQueryParamValue(str) { QUERY_PARAM_VALUE_RE.lastIndex = 0; var /** @type {?} */ match = str.match(QUERY_PARAM_VALUE_RE); return match ? match[0] : ''; } var UrlParser = (function () { /** * @param {?} url */ function UrlParser(url) { this.url = url; this.remaining = url; } /** * @param {?} str * @return {?} */ UrlParser.prototype.peekStartsWith = function (str) { return this.remaining.startsWith(str); }; /** * @param {?} str * @return {?} */ UrlParser.prototype.capture = function (str) { if (!this.remaining.startsWith(str)) { throw new Error("Expected \"" + str + "\"."); } this.remaining = this.remaining.substring(str.length); }; /** * @return {?} */ UrlParser.prototype.parseRootSegment = function () { if (this.remaining.startsWith('/')) { this.capture('/'); } if (this.remaining === '' || this.remaining.startsWith('?') || this.remaining.startsWith('#')) { return new UrlSegmentGroup([], {}); } return new UrlSegmentGroup([], this.parseChildren()); }; /** * @return {?} */ UrlParser.prototype.parseChildren = function () { if (this.remaining.length == 0) { return {}; } if (this.peekStartsWith('/')) { this.capture('/'); } var /** @type {?} */ paths = []; if (!this.peekStartsWith('(')) { paths.push(this.parseSegments()); } while (this.peekStartsWith('/') && !this.peekStartsWith('//') && !this.peekStartsWith('/(')) { this.capture('/'); paths.push(this.parseSegments()); } var /** @type {?} */ children = {}; if (this.peekStartsWith('/(')) { this.capture('/'); children = this.parseParens(true); } var /** @type {?} */ res = {}; if (this.peekStartsWith('(')) { res = this.parseParens(false); } if (paths.length > 0 || Object.keys(children).length > 0) { res[PRIMARY_OUTLET] = new UrlSegmentGroup(paths, children); } return res; }; /** * @return {?} */ UrlParser.prototype.parseSegments = function () { var /** @type {?} */ path = matchSegments(this.remaining); if (path === '' && this.peekStartsWith(';')) { throw new Error("Empty path url segment cannot have parameters: '" + this.remaining + "'."); } this.capture(path); var /** @type {?} */ matrixParams = {}; if (this.peekStartsWith(';')) { matrixParams = this.parseMatrixParams(); } return new UrlSegment(decode(path), matrixParams); }; /** * @return {?} */ UrlParser.prototype.parseQueryParams = function () { var /** @type {?} */ params = {}; if (this.peekStartsWith('?')) { this.capture('?'); this.parseQueryParam(params); while (this.remaining.length > 0 && this.peekStartsWith('&')) { this.capture('&'); this.parseQueryParam(params); } } return params; }; /** * @return {?} */ UrlParser.prototype.parseFragment = function () { if (this.peekStartsWith('#')) { return decodeURI(this.remaining.substring(1)); } return null; }; /** * @return {?} */ UrlParser.prototype.parseMatrixParams = function () { var /** @type {?} */ params = {}; while (this.remaining.length > 0 && this.peekStartsWith(';')) { this.capture(';'); this.parseParam(params); } return params; }; /** * @param {?} params * @return {?} */ UrlParser.prototype.parseParam = function (params) { var /** @type {?} */ key = matchSegments(this.remaining); if (!key) { return; } this.capture(key); var /** @type {?} */ value = ''; if (this.peekStartsWith('=')) { this.capture('='); var /** @type {?} */ valueMatch = matchSegments(this.remaining); if (valueMatch) { value = valueMatch; this.capture(value); } } params[decode(key)] = decode(value); }; /** * @param {?} params * @return {?} */ UrlParser.prototype.parseQueryParam = function (params) { var /** @type {?} */ key = matchQueryParams(this.remaining); if (!key) { return; } this.capture(key); var /** @type {?} */ value = ''; if (this.peekStartsWith('=')) { this.capture('='); var /** @type {?} */ valueMatch = matchUrlQueryParamValue(this.remaining); if (valueMatch) { value = valueMatch; this.capture(value); } } var /** @type {?} */ decodedKey = decode(key); var /** @type {?} */ decodedVal = decode(value); if (params.hasOwnProperty(decodedKey)) { // Append to existing values var /** @type {?} */ currentVal = params[decodedKey]; if (!Array.isArray(currentVal)) { currentVal = [currentVal]; params[decodedKey] = currentVal; } currentVal.push(decodedVal); } else { // Create a new value params[decodedKey] = decodedVal; } }; /** * @param {?} allowPrimary * @return {?} */ UrlParser.prototype.parseParens = function (allowPrimary) { var /** @type {?} */ segments = {}; this.capture('('); while (!this.peekStartsWith(')') && this.remaining.length > 0) { var /** @type {?} */ path = matchSegments(this.remaining); var /** @type {?} */ next = this.remaining[path.length]; // if is is not one of these characters, then the segment was unescaped // or the group was not closed if (next !== '/' && next !== ')' && next !== ';') { throw new Error("Cannot parse url '" + this.url + "'"); } var /** @type {?} */ outletName = void 0; if (path.indexOf(':') > -1) { outletName = path.substr(0, path.indexOf(':')); this.capture(outletName); this.capture(':'); } else if (allowPrimary) { outletName = PRIMARY_OUTLET; } var /** @type {?} */ children = this.parseChildren(); segments[outletName] = Object.keys(children).length === 1 ? children[PRIMARY_OUTLET] : new UrlSegmentGroup([], children); if (this.peekStartsWith('//')) { this.capture('//'); } } this.capture(')'); return segments; }; return UrlParser; }()); function UrlParser_tsickle_Closure_declarations() { /** @type {?} */ UrlParser.prototype.remaining; /** @type {?} */ UrlParser.prototype.url; } //# sourceMappingURL=url_tree.js.map