2016-06-23 12:47:54 -04:00
|
|
|
/**
|
|
|
|
* @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
|
|
|
|
*/
|
|
|
|
|
2016-06-08 14:13:41 -04:00
|
|
|
import {ActivatedRoute} from './router_state';
|
|
|
|
import {PRIMARY_OUTLET, Params} from './shared';
|
2016-07-25 15:15:07 -04:00
|
|
|
import {UrlSegment, UrlSegmentGroup, UrlTree} from './url_tree';
|
2016-06-08 14:13:41 -04:00
|
|
|
import {forEach, shallowEqual} from './utils/collection';
|
|
|
|
|
|
|
|
export function createUrlTree(
|
2016-06-15 12:14:41 -04:00
|
|
|
route: ActivatedRoute, urlTree: UrlTree, commands: any[], queryParams: Params,
|
|
|
|
fragment: string): UrlTree {
|
2016-05-26 19:51:19 -04:00
|
|
|
if (commands.length === 0) {
|
2016-06-14 17:55:59 -04:00
|
|
|
return tree(urlTree.root, urlTree.root, urlTree, queryParams, fragment);
|
2016-05-26 19:51:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
const normalizedCommands = normalizeCommands(commands);
|
2016-07-08 13:56:36 -04:00
|
|
|
validateCommands(normalizedCommands);
|
|
|
|
|
2016-05-26 19:51:19 -04:00
|
|
|
if (navigateToRoot(normalizedCommands)) {
|
2016-07-25 15:15:07 -04:00
|
|
|
return tree(urlTree.root, new UrlSegmentGroup([], {}), urlTree, queryParams, fragment);
|
2016-05-26 19:51:19 -04:00
|
|
|
}
|
|
|
|
|
2016-06-14 17:55:59 -04:00
|
|
|
const startingPosition = findStartingPosition(normalizedCommands, urlTree, route);
|
2016-07-25 15:15:07 -04:00
|
|
|
const segmentGroup = startingPosition.processChildren ?
|
|
|
|
updateSegmentGroupChildren(
|
|
|
|
startingPosition.segmentGroup, startingPosition.index, normalizedCommands.commands) :
|
|
|
|
updateSegmentGroup(
|
|
|
|
startingPosition.segmentGroup, startingPosition.index, normalizedCommands.commands);
|
|
|
|
return tree(startingPosition.segmentGroup, segmentGroup, urlTree, queryParams, fragment);
|
2016-05-26 19:51:19 -04:00
|
|
|
}
|
|
|
|
|
2016-07-08 13:56:36 -04:00
|
|
|
function validateCommands(n: NormalizedNavigationCommands): void {
|
2016-08-07 21:34:12 -04:00
|
|
|
if (n.isAbsolute && n.commands.length > 0 && isMatrixParams(n.commands[0])) {
|
2016-07-08 13:56:36 -04:00
|
|
|
throw new Error('Root segment cannot have matrix parameters');
|
|
|
|
}
|
2016-08-07 22:04:57 -04:00
|
|
|
|
|
|
|
const c = n.commands.filter(c => typeof c === 'object' && c.outlets !== undefined);
|
|
|
|
if (c.length > 0 && c[0] !== n.commands[n.commands.length - 1]) {
|
|
|
|
throw new Error('{outlets:{}} has to be the last command');
|
|
|
|
}
|
2016-07-08 13:56:36 -04:00
|
|
|
}
|
|
|
|
|
2016-08-07 21:34:12 -04:00
|
|
|
function isMatrixParams(command: any): boolean {
|
|
|
|
return typeof command === 'object' && command.outlets === undefined &&
|
|
|
|
command.segmentPath === undefined;
|
|
|
|
}
|
|
|
|
|
2016-06-08 14:13:41 -04:00
|
|
|
function tree(
|
2016-07-25 15:15:07 -04:00
|
|
|
oldSegmentGroup: UrlSegmentGroup, newSegmentGroup: UrlSegmentGroup, urlTree: UrlTree,
|
|
|
|
queryParams: Params, fragment: string): UrlTree {
|
|
|
|
if (urlTree.root === oldSegmentGroup) {
|
|
|
|
return new UrlTree(newSegmentGroup, stringify(queryParams), fragment);
|
2016-06-14 17:55:59 -04:00
|
|
|
} else {
|
2016-07-20 17:30:04 -04:00
|
|
|
return new UrlTree(
|
2016-07-25 15:15:07 -04:00
|
|
|
replaceSegment(urlTree.root, oldSegmentGroup, newSegmentGroup), stringify(queryParams),
|
|
|
|
fragment);
|
2016-06-14 17:55:59 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function replaceSegment(
|
2016-07-25 15:15:07 -04:00
|
|
|
current: UrlSegmentGroup, oldSegment: UrlSegmentGroup,
|
|
|
|
newSegment: UrlSegmentGroup): UrlSegmentGroup {
|
|
|
|
const children: {[key: string]: UrlSegmentGroup} = {};
|
|
|
|
forEach(current.children, (c: UrlSegmentGroup, outletName: string) => {
|
2016-06-14 17:55:59 -04:00
|
|
|
if (c === oldSegment) {
|
2016-06-15 19:45:19 -04:00
|
|
|
children[outletName] = newSegment;
|
2016-06-14 17:55:59 -04:00
|
|
|
} else {
|
2016-06-15 19:45:19 -04:00
|
|
|
children[outletName] = replaceSegment(c, oldSegment, newSegment);
|
2016-06-14 17:55:59 -04:00
|
|
|
}
|
|
|
|
});
|
2016-07-25 15:15:07 -04:00
|
|
|
return new UrlSegmentGroup(current.segments, children);
|
2016-05-26 19:51:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
function navigateToRoot(normalizedChange: NormalizedNavigationCommands): boolean {
|
|
|
|
return normalizedChange.isAbsolute && normalizedChange.commands.length === 1 &&
|
2016-06-08 14:13:41 -04:00
|
|
|
normalizedChange.commands[0] == '/';
|
2016-05-26 19:51:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
class NormalizedNavigationCommands {
|
2016-06-08 14:13:41 -04:00
|
|
|
constructor(
|
|
|
|
public isAbsolute: boolean, public numberOfDoubleDots: number, public commands: any[]) {}
|
2016-05-26 19:51:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
function normalizeCommands(commands: any[]): NormalizedNavigationCommands {
|
2016-06-08 14:13:41 -04:00
|
|
|
if ((typeof commands[0] === 'string') && commands.length === 1 && commands[0] == '/') {
|
2016-05-26 19:51:19 -04:00
|
|
|
return new NormalizedNavigationCommands(true, 0, commands);
|
|
|
|
}
|
|
|
|
|
|
|
|
let numberOfDoubleDots = 0;
|
|
|
|
let isAbsolute = false;
|
2016-06-15 19:45:19 -04:00
|
|
|
const res: any[] = [];
|
2016-05-26 19:51:19 -04:00
|
|
|
|
|
|
|
for (let i = 0; i < commands.length; ++i) {
|
|
|
|
const c = commands[i];
|
|
|
|
|
2016-07-12 12:49:55 -04:00
|
|
|
if (typeof c === 'object' && c.outlets !== undefined) {
|
|
|
|
const r: {[k: string]: any} = {};
|
|
|
|
forEach(c.outlets, (commands: any, name: string) => {
|
|
|
|
if (typeof commands === 'string') {
|
2016-07-22 16:25:48 -04:00
|
|
|
r[name] = commands.split('/');
|
2016-07-12 12:49:55 -04:00
|
|
|
} else {
|
2016-07-22 16:25:48 -04:00
|
|
|
r[name] = commands;
|
2016-07-12 12:49:55 -04:00
|
|
|
}
|
|
|
|
});
|
|
|
|
res.push({outlets: r});
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-08-04 20:19:23 -04:00
|
|
|
if (typeof c === 'object' && c.segmentPath !== undefined) {
|
|
|
|
res.push(c.segmentPath);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-06-08 14:13:41 -04:00
|
|
|
if (!(typeof c === 'string')) {
|
2016-05-26 19:51:19 -04:00
|
|
|
res.push(c);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-08-04 20:19:23 -04:00
|
|
|
if (i === 0) {
|
|
|
|
const parts = c.split('/');
|
|
|
|
for (let j = 0; j < parts.length; ++j) {
|
|
|
|
let cc = parts[j];
|
2016-05-26 19:51:19 -04:00
|
|
|
|
2016-06-08 14:13:41 -04:00
|
|
|
if (j == 0 && cc == '.') { // './a'
|
2016-05-26 19:51:19 -04:00
|
|
|
// skip it
|
2016-06-08 14:13:41 -04:00
|
|
|
} else if (j == 0 && cc == '') { // '/a'
|
2016-05-26 19:51:19 -04:00
|
|
|
isAbsolute = true;
|
2016-06-08 14:13:41 -04:00
|
|
|
} else if (cc == '..') { // '../a'
|
2016-05-26 19:51:19 -04:00
|
|
|
numberOfDoubleDots++;
|
|
|
|
} else if (cc != '') {
|
|
|
|
res.push(cc);
|
|
|
|
}
|
|
|
|
}
|
2016-08-04 20:19:23 -04:00
|
|
|
} else {
|
|
|
|
res.push(c);
|
2016-05-26 19:51:19 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return new NormalizedNavigationCommands(isAbsolute, numberOfDoubleDots, res);
|
|
|
|
}
|
|
|
|
|
2016-06-14 17:55:59 -04:00
|
|
|
class Position {
|
2016-07-25 15:15:07 -04:00
|
|
|
constructor(
|
|
|
|
public segmentGroup: UrlSegmentGroup, public processChildren: boolean, public index: number) {
|
|
|
|
}
|
2016-06-14 17:55:59 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
function findStartingPosition(
|
2016-06-08 14:13:41 -04:00
|
|
|
normalizedChange: NormalizedNavigationCommands, urlTree: UrlTree,
|
2016-06-14 17:55:59 -04:00
|
|
|
route: ActivatedRoute): Position {
|
2016-05-26 19:51:19 -04:00
|
|
|
if (normalizedChange.isAbsolute) {
|
2016-06-14 17:55:59 -04:00
|
|
|
return new Position(urlTree.root, true, 0);
|
|
|
|
} else if (route.snapshot._lastPathIndex === -1) {
|
|
|
|
return new Position(route.snapshot._urlSegment, true, 0);
|
2016-05-26 19:51:19 -04:00
|
|
|
} else {
|
2016-08-09 20:03:17 -04:00
|
|
|
const modifier = isMatrixParams(normalizedChange.commands[0]) ? 0 : 1;
|
|
|
|
const index = route.snapshot._lastPathIndex + modifier;
|
|
|
|
return createPositionApplyingDoubleDots(
|
|
|
|
route.snapshot._urlSegment, index, normalizedChange.numberOfDoubleDots);
|
2016-05-26 19:51:19 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-09 20:03:17 -04:00
|
|
|
function createPositionApplyingDoubleDots(
|
|
|
|
group: UrlSegmentGroup, index: number, numberOfDoubleDots: number): Position {
|
|
|
|
let g = group;
|
|
|
|
let ci = index;
|
|
|
|
let dd = numberOfDoubleDots;
|
|
|
|
while (dd > ci) {
|
|
|
|
dd -= ci;
|
|
|
|
g = g.parent;
|
|
|
|
if (!g) {
|
|
|
|
throw new Error('Invalid number of \'../\'');
|
|
|
|
}
|
|
|
|
ci = g.segments.length;
|
|
|
|
}
|
|
|
|
return new Position(g, false, ci - dd);
|
|
|
|
}
|
|
|
|
|
2016-06-14 17:55:59 -04:00
|
|
|
function getPath(command: any): any {
|
2016-07-12 12:49:55 -04:00
|
|
|
return `${command}`;
|
2016-05-26 19:51:19 -04:00
|
|
|
}
|
|
|
|
|
2016-07-12 12:49:55 -04:00
|
|
|
function getOutlets(commands: any[]): {[k: string]: any[]} {
|
|
|
|
if (!(typeof commands[0] === 'object')) return {[PRIMARY_OUTLET]: commands};
|
|
|
|
if (commands[0].outlets === undefined) return {[PRIMARY_OUTLET]: commands};
|
|
|
|
return commands[0].outlets;
|
2016-05-26 19:51:19 -04:00
|
|
|
}
|
|
|
|
|
2016-07-25 15:15:07 -04:00
|
|
|
function updateSegmentGroup(
|
|
|
|
segmentGroup: UrlSegmentGroup, startIndex: number, commands: any[]): UrlSegmentGroup {
|
|
|
|
if (!segmentGroup) {
|
|
|
|
segmentGroup = new UrlSegmentGroup([], {});
|
2016-06-14 17:55:59 -04:00
|
|
|
}
|
2016-07-25 15:15:07 -04:00
|
|
|
if (segmentGroup.segments.length === 0 && segmentGroup.hasChildren()) {
|
|
|
|
return updateSegmentGroupChildren(segmentGroup, startIndex, commands);
|
2016-06-14 17:55:59 -04:00
|
|
|
}
|
2016-08-09 20:03:17 -04:00
|
|
|
|
2016-07-25 15:15:07 -04:00
|
|
|
const m = prefixedWith(segmentGroup, startIndex, commands);
|
2016-06-14 17:55:59 -04:00
|
|
|
const slicedCommands = commands.slice(m.lastIndex);
|
|
|
|
|
|
|
|
if (m.match && slicedCommands.length === 0) {
|
2016-07-25 15:15:07 -04:00
|
|
|
return new UrlSegmentGroup(segmentGroup.segments, {});
|
|
|
|
} else if (m.match && !segmentGroup.hasChildren()) {
|
|
|
|
return createNewSegmentGroup(segmentGroup, startIndex, commands);
|
2016-06-14 17:55:59 -04:00
|
|
|
} else if (m.match) {
|
2016-07-25 15:15:07 -04:00
|
|
|
return updateSegmentGroupChildren(segmentGroup, 0, slicedCommands);
|
2016-05-26 19:51:19 -04:00
|
|
|
} else {
|
2016-07-25 15:15:07 -04:00
|
|
|
return createNewSegmentGroup(segmentGroup, startIndex, commands);
|
2016-05-26 19:51:19 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-25 15:15:07 -04:00
|
|
|
function updateSegmentGroupChildren(
|
|
|
|
segmentGroup: UrlSegmentGroup, startIndex: number, commands: any[]): UrlSegmentGroup {
|
2016-06-14 17:55:59 -04:00
|
|
|
if (commands.length === 0) {
|
2016-07-25 15:15:07 -04:00
|
|
|
return new UrlSegmentGroup(segmentGroup.segments, {});
|
2016-05-26 19:51:19 -04:00
|
|
|
} else {
|
2016-07-12 12:49:55 -04:00
|
|
|
const outlets = getOutlets(commands);
|
2016-07-25 15:15:07 -04:00
|
|
|
const children: {[key: string]: UrlSegmentGroup} = {};
|
2016-07-12 12:49:55 -04:00
|
|
|
|
|
|
|
forEach(outlets, (commands: any, outlet: string) => {
|
|
|
|
if (commands !== null) {
|
2016-07-25 15:15:07 -04:00
|
|
|
children[outlet] = updateSegmentGroup(segmentGroup.children[outlet], startIndex, commands);
|
2016-07-12 12:49:55 -04:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2016-07-25 15:15:07 -04:00
|
|
|
forEach(segmentGroup.children, (child: UrlSegmentGroup, childOutlet: string) => {
|
2016-07-12 12:49:55 -04:00
|
|
|
if (outlets[childOutlet] === undefined) {
|
2016-06-14 17:55:59 -04:00
|
|
|
children[childOutlet] = child;
|
|
|
|
}
|
|
|
|
});
|
2016-07-25 15:15:07 -04:00
|
|
|
return new UrlSegmentGroup(segmentGroup.segments, children);
|
2016-05-26 19:51:19 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-25 15:15:07 -04:00
|
|
|
function prefixedWith(segmentGroup: UrlSegmentGroup, startIndex: number, commands: any[]) {
|
2016-06-14 17:55:59 -04:00
|
|
|
let currentCommandIndex = 0;
|
|
|
|
let currentPathIndex = startIndex;
|
|
|
|
|
|
|
|
const noMatch = {match: false, lastIndex: 0};
|
2016-07-25 15:15:07 -04:00
|
|
|
while (currentPathIndex < segmentGroup.segments.length) {
|
2016-06-14 17:55:59 -04:00
|
|
|
if (currentCommandIndex >= commands.length) return noMatch;
|
2016-07-25 15:15:07 -04:00
|
|
|
const path = segmentGroup.segments[currentPathIndex];
|
2016-06-14 17:55:59 -04:00
|
|
|
const curr = getPath(commands[currentCommandIndex]);
|
|
|
|
const next =
|
|
|
|
currentCommandIndex < commands.length - 1 ? commands[currentCommandIndex + 1] : null;
|
|
|
|
|
2016-07-12 12:49:55 -04:00
|
|
|
if (curr && next && (typeof next === 'object') && next.outlets === undefined) {
|
2016-06-14 17:55:59 -04:00
|
|
|
if (!compare(curr, next, path)) return noMatch;
|
|
|
|
currentCommandIndex += 2;
|
|
|
|
} else {
|
|
|
|
if (!compare(curr, {}, path)) return noMatch;
|
|
|
|
currentCommandIndex++;
|
|
|
|
}
|
|
|
|
currentPathIndex++;
|
|
|
|
}
|
2016-05-26 19:51:19 -04:00
|
|
|
|
2016-06-14 18:20:43 -04:00
|
|
|
return {match: true, lastIndex: currentCommandIndex};
|
2016-05-26 19:51:19 -04:00
|
|
|
}
|
|
|
|
|
2016-07-25 15:15:07 -04:00
|
|
|
function createNewSegmentGroup(
|
|
|
|
segmentGroup: UrlSegmentGroup, startIndex: number, commands: any[]): UrlSegmentGroup {
|
|
|
|
const paths = segmentGroup.segments.slice(0, startIndex);
|
2016-08-07 22:04:57 -04:00
|
|
|
|
2016-06-14 17:55:59 -04:00
|
|
|
let i = 0;
|
|
|
|
while (i < commands.length) {
|
2016-08-07 22:04:57 -04:00
|
|
|
if (typeof commands[i] === 'object' && commands[i].outlets !== undefined) {
|
|
|
|
const children = createNewSegmentChldren(commands[i].outlets);
|
|
|
|
return new UrlSegmentGroup(paths, children);
|
|
|
|
}
|
|
|
|
|
2016-06-16 17:45:16 -04:00
|
|
|
// if we start with an object literal, we need to reuse the path part from the segment
|
2016-08-09 20:03:17 -04:00
|
|
|
if (i === 0 && isMatrixParams(commands[0])) {
|
2016-07-25 15:15:07 -04:00
|
|
|
const p = segmentGroup.segments[startIndex];
|
|
|
|
paths.push(new UrlSegment(p.path, commands[0]));
|
2016-06-14 17:55:59 -04:00
|
|
|
i++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const curr = getPath(commands[i]);
|
|
|
|
const next = (i < commands.length - 1) ? commands[i + 1] : null;
|
2016-08-09 20:03:17 -04:00
|
|
|
if (curr && next && isMatrixParams(next)) {
|
2016-07-25 15:15:07 -04:00
|
|
|
paths.push(new UrlSegment(curr, stringify(next)));
|
2016-06-14 17:55:59 -04:00
|
|
|
i += 2;
|
|
|
|
} else {
|
2016-07-25 15:15:07 -04:00
|
|
|
paths.push(new UrlSegment(curr, {}));
|
2016-06-14 17:55:59 -04:00
|
|
|
i++;
|
|
|
|
}
|
2016-05-26 19:51:19 -04:00
|
|
|
}
|
2016-07-25 15:15:07 -04:00
|
|
|
return new UrlSegmentGroup(paths, {});
|
2016-05-26 19:51:19 -04:00
|
|
|
}
|
|
|
|
|
2016-08-07 22:04:57 -04:00
|
|
|
function createNewSegmentChldren(outlets: {[name: string]: any}): any {
|
|
|
|
const children: {[key: string]: UrlSegmentGroup} = {};
|
|
|
|
forEach(outlets, (commands: any, outlet: string) => {
|
|
|
|
if (commands !== null) {
|
|
|
|
children[outlet] = createNewSegmentGroup(new UrlSegmentGroup([], {}), 0, commands);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return children;
|
|
|
|
}
|
|
|
|
|
2016-05-26 19:51:19 -04:00
|
|
|
function stringify(params: {[key: string]: any}): {[key: string]: string} {
|
2016-06-15 12:14:41 -04:00
|
|
|
const res: {[key: string]: string} = {};
|
2016-06-15 19:45:19 -04:00
|
|
|
forEach(params, (v: any, k: string) => res[k] = `${v}`);
|
2016-05-26 19:51:19 -04:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2016-07-25 15:15:07 -04:00
|
|
|
function compare(path: string, params: {[key: string]: any}, segment: UrlSegment): boolean {
|
|
|
|
return path == segment.path && shallowEqual(params, segment.parameters);
|
2016-05-26 19:51:19 -04:00
|
|
|
}
|