fix(router): add support for ../

This commit is contained in:
vsavkin
2016-05-02 14:47:59 -07:00
parent 908a102a87
commit 89704e0f93
6 changed files with 261 additions and 143 deletions

View File

@ -1,116 +1,184 @@
import {Tree, TreeNode, UrlSegment, RouteSegment, rootNode, UrlTree, RouteTree} from './segments';
import {isBlank, isPresent, isString, isStringMap} from './facade/lang';
import {BaseException} from './facade/exceptions';
import {ListWrapper} from './facade/collection';
export function link(segment: RouteSegment, routeTree: RouteTree, urlTree: UrlTree,
change: any[]): UrlTree {
if (change.length === 0) return urlTree;
export function link(segment: RouteSegment, routeTree: RouteTree, urlTree: UrlTree, commands: any[]): UrlTree {
if (commands.length === 0) return urlTree;
let startingNode;
let normalizedChange;
if (isString(change[0]) && change[0].startsWith("./")) {
normalizedChange = ["/", change[0].substring(2)].concat(change.slice(1));
startingNode = _findStartingNode(_findUrlSegment(segment, routeTree), rootNode(urlTree));
} else if (isString(change[0]) && change.length === 1 && change[0] == "/") {
normalizedChange = change;
startingNode = rootNode(urlTree);
} else if (isString(change[0]) && !change[0].startsWith("/")) {
normalizedChange = ["/"].concat(change);
startingNode = _findStartingNode(_findUrlSegment(segment, routeTree), rootNode(urlTree));
} else {
normalizedChange = ["/"].concat(change);
startingNode = rootNode(urlTree);
let normalizedCommands = _normalizeCommands(commands);
if (_navigateToRoot(normalizedCommands)) {
return new UrlTree(new TreeNode<UrlSegment>(urlTree.root, []));
}
let updated = _update(startingNode, normalizedChange);
let startingNode = _findStartingNode(normalizedCommands, urlTree, segment, routeTree);
let updated = normalizedCommands.commands.length > 0 ?
_updateMany(ListWrapper.clone(startingNode.children), normalizedCommands.commands) : [];
let newRoot = _constructNewTree(rootNode(urlTree), startingNode, updated);
return new UrlTree(newRoot);
}
function _findUrlSegment(segment: RouteSegment, routeTree: RouteTree): UrlSegment {
let s = segment;
let res = null;
while (isBlank(res)) {
res = ListWrapper.last(s.urlSegments);
s = routeTree.parent(s);
}
return res;
function _navigateToRoot(normalizedChange:_NormalizedNavigationCommands):boolean {
return normalizedChange.isAbsolute && normalizedChange.commands.length === 1 && normalizedChange.commands[0] == "/";
}
function _findStartingNode(segment: UrlSegment, node: TreeNode<UrlSegment>): TreeNode<UrlSegment> {
class _NormalizedNavigationCommands {
constructor(public isAbsolute: boolean,
public numberOfDoubleDots: number,
public commands: any[]) {}
}
function _normalizeCommands(commands: any[]): _NormalizedNavigationCommands {;''
if (isString(commands[0]) && commands.length === 1 && commands[0] == "/") {
return new _NormalizedNavigationCommands(true, 0, commands);
}
let numberOfDoubleDots = 0;
let isAbsolute = false;
let res = [];
for (let i = 0; i < commands.length; ++i) {
let c = commands[i];
if (!isString(c)) {
res.push(c);
continue;
}
let parts = c.split('/');
for (let j = 0; j < parts.length; ++j) {
let cc = parts[j];
// first exp is treated in a special way
if (i == 0) {
if (j == 0 && cc == ".") { // './a'
// skip it
} else if (j == 0 && cc == "") { // '/a'
isAbsolute = true;
} else if (cc == "..") { // '../a'
numberOfDoubleDots++;
} else if (cc != '') {
res.push(cc);
}
} else {
if (cc != ''){
res.push(cc);
}
}
}
}
return new _NormalizedNavigationCommands(isAbsolute, numberOfDoubleDots, res);
}
function _findUrlSegment(segment: RouteSegment, routeTree: RouteTree, urlTree: UrlTree, numberOfDoubleDots: number): UrlSegment {
let s = segment;
while (s.urlSegments.length === 0) {
s = routeTree.parent(s);
}
let urlSegment = ListWrapper.last(s.urlSegments);
let path = urlTree.pathFromRoot(urlSegment);
if (path.length <= numberOfDoubleDots) {
throw new BaseException("Invalid number of '../'");
}
return path[path.length - 1 - numberOfDoubleDots];
}
function _findStartingNode(normalizedChange:_NormalizedNavigationCommands, urlTree:UrlTree, segment:RouteSegment, routeTree:RouteTree):TreeNode<UrlSegment> {
if (normalizedChange.isAbsolute) {
return rootNode(urlTree);
} else {
let urlSegment = _findUrlSegment(segment, routeTree, urlTree, normalizedChange.numberOfDoubleDots);
return _findMatchingNode(urlSegment, rootNode(urlTree));
}
}
function _findMatchingNode(segment: UrlSegment, node: TreeNode<UrlSegment>): TreeNode<UrlSegment> {
if (node.value === segment) return node;
for (var c of node.children) {
let r = _findStartingNode(segment, c);
let r = _findMatchingNode(segment, c);
if (isPresent(r)) return r;
}
return null;
}
function _constructNewTree(node: TreeNode<UrlSegment>, original: TreeNode<UrlSegment>,
updated: TreeNode<UrlSegment>): TreeNode<UrlSegment> {
updated: TreeNode<UrlSegment>[]): TreeNode<UrlSegment> {
if (node === original) {
return new TreeNode<UrlSegment>(node.value, updated.children);
return new TreeNode<UrlSegment>(node.value, updated);
} else {
return new TreeNode<UrlSegment>(
node.value, node.children.map(c => _constructNewTree(c, original, updated)));
}
}
function _update(node: TreeNode<UrlSegment>, changes: any[]): TreeNode<UrlSegment> {
let rest = changes.slice(1);
let outlet = _outlet(changes);
let segment = _segment(changes);
if (isString(segment) && segment[0] == "/") segment = segment.substring(1);
function _update(node: TreeNode<UrlSegment>, commands: any[]): TreeNode<UrlSegment> {
let rest = commands.slice(1);
let next = rest.length === 0 ? null : rest[0];
let outlet = _outlet(commands);
let segment = _segment(commands);
// reach the end of the tree => create new tree nodes.
if (isBlank(node)) {
let urlSegment = new UrlSegment(segment, null, outlet);
if (isBlank(node) && !isStringMap(next)) {
let urlSegment = new UrlSegment(segment, {}, outlet);
let children = rest.length === 0 ? [] : [_update(null, rest)];
return new TreeNode<UrlSegment>(urlSegment, children);
// different outlet => preserve the subtree
} else if (isBlank(node) && isStringMap(next)) {
let urlSegment = new UrlSegment(segment, next, outlet);
return _recurse(urlSegment, node, rest.slice(1));
// different outlet => preserve the subtree
} else if (outlet != node.value.outlet) {
return node;
// same outlet => modify the subtree
} else {
let urlSegment = isStringMap(segment) ? new UrlSegment(null, segment, null) :
new UrlSegment(segment, null, outlet);
if (rest.length === 0) {
return new TreeNode<UrlSegment>(urlSegment, []);
}
// params command
} else if (isStringMap(segment)) {
let newSegment = new UrlSegment(node.value.segment, segment, node.value.outlet);
return _recurse(newSegment, node, rest);
return new TreeNode<UrlSegment>(urlSegment,
_updateMany(ListWrapper.clone(node.children), rest));
// next one is a params command
} else if (isStringMap(next)) {
let urlSegment = new UrlSegment(segment, next, outlet);
return _recurse(urlSegment, node, rest.slice(1));
// next one is not a params command
} else {
let urlSegment = new UrlSegment(segment, {}, outlet);
return _recurse(urlSegment, node, rest);
}
}
function _updateMany(nodes: TreeNode<UrlSegment>[], changes: any[]): TreeNode<UrlSegment>[] {
let outlet = _outlet(changes);
function _recurse(urlSegment: UrlSegment, node: TreeNode<UrlSegment>, rest: any[]): TreeNode<UrlSegment> {
if (rest.length === 0) {
return new TreeNode<UrlSegment>(urlSegment, []);
}
return new TreeNode<UrlSegment>(urlSegment, _updateMany(ListWrapper.clone(node.children), rest));
}
function _updateMany(nodes: TreeNode<UrlSegment>[], commands: any[]): TreeNode<UrlSegment>[] {
let outlet = _outlet(commands);
let nodesInRightOutlet = nodes.filter(c => c.value.outlet == outlet);
if (nodesInRightOutlet.length > 0) {
let nodeRightOutlet = nodesInRightOutlet[0]; // there can be only one
nodes[nodes.indexOf(nodeRightOutlet)] = _update(nodeRightOutlet, changes);
nodes[nodes.indexOf(nodeRightOutlet)] = _update(nodeRightOutlet, commands);
} else {
nodes.push(_update(null, changes));
nodes.push(_update(null, commands));
}
return nodes;
}
function _segment(changes: any[]): any {
if (!isString(changes[0])) return changes[0];
let parts = changes[0].toString().split(":");
return parts.length > 1 ? parts[1] : changes[0];
function _segment(commands: any[]): any {
if (!isString(commands[0])) return commands[0];
let parts = commands[0].toString().split(":");
return parts.length > 1 ? parts[1] : commands[0];
}
function _outlet(changes: any[]): string {
if (!isString(changes[0])) return null;
let parts = changes[0].toString().split(":");
function _outlet(commands: any[]): string {
if (!isString(commands[0])) return null;
let parts = commands[0].toString().split(":");
return parts.length > 1 ? parts[0] : null;
}