angular-cn/modules/@angular/router/src/url_serializer.ts

247 lines
6.8 KiB
TypeScript
Raw Normal View History

2016-05-21 17:35:55 -07:00
import { UrlTree, UrlSegment } from './url_tree';
import { PRIMARY_OUTLET } from './shared';
import { rootNode, TreeNode } from './utils/tree';
2016-05-21 17:35:55 -07:00
/**
* Defines a way to serialize/deserialize a url tree.
*/
export abstract class UrlSerializer {
/**
* Parse a url into a {@Link UrlTree}
*/
abstract parse(url: string): UrlTree;
/**
* Converts a {@Link UrlTree} into a url
*/
abstract serialize(tree: UrlTree): string;
}
/**
* A default implementation of the serialization.
*/
export class DefaultUrlSerializer implements UrlSerializer {
parse(url: string): UrlTree {
const p = new UrlParser(url);
return new UrlTree(p.parseRootSegment(), p.parseQueryParams(), p.parseFragment());
}
serialize(tree: UrlTree): string {
const node = serializeUrlTreeNode(rootNode(tree));
const query = serializeQueryParams(tree.queryParameters);
const fragment = tree.fragment !== null ? `#${tree.fragment}` : '';
return `${node}${query}${fragment}`;
}
}
function serializeUrlTreeNode(node: TreeNode<UrlSegment>): string {
return `${serializeSegment(node.value)}${serializeChildren(node)}`;
}
function serializeUrlTreeNodes(nodes: TreeNode<UrlSegment>[]): string {
const primary = serializeSegment(nodes[0].value);
const secondaryNodes = nodes.slice(1);
const secondary = secondaryNodes.length > 0 ? `(${secondaryNodes.map(serializeUrlTreeNode).join("//")})` : "";
const children = serializeChildren(nodes[0]);
return `${primary}${secondary}${children}`;
}
function serializeChildren(node: TreeNode<UrlSegment>): string {
if (node.children.length > 0) {
return `/${serializeUrlTreeNodes(node.children)}`;
} else {
return "";
}
}
export function serializeSegment(segment: UrlSegment): string {
const outlet = segment.outlet === PRIMARY_OUTLET ? '' : `${segment.outlet}:`;
return `${outlet}${segment.path}${serializeParams(segment.parameters)}`;
2016-05-21 17:35:55 -07:00
}
function serializeParams(params: {[key: string]: string}): string {
return pairs(params).map(p => `;${p.first}=${p.second}`).join("");
}
function serializeQueryParams(params: {[key: string]: string}): string {
const strs = pairs(params).map(p => `${p.first}=${p.second}`);
return strs.length > 0 ? `?${strs.join("&")}` : "";
}
class Pair<A,B> { constructor(public first:A, public second:B) {} }
function pairs<T>(obj: {[key: string]: T}):Pair<string,T>[] {
const res = [];
for (let prop in obj) {
if (obj.hasOwnProperty(prop)) {
res.push(new Pair<string, T>(prop, obj[prop]));
}
}
return res;
}
const SEGMENT_RE = /^[^\/\(\)\?;=&#]+/;
function matchUrlSegment(str: string): string {
SEGMENT_RE.lastIndex = 0;
var match = SEGMENT_RE.exec(str);
return match ? match[0] : '';
}
const QUERY_PARAM_VALUE_RE = /^[^\(\)\?;&#]+/;
function matchUrlQueryParamValue(str: string): string {
QUERY_PARAM_VALUE_RE.lastIndex = 0;
const match = QUERY_PARAM_VALUE_RE.exec(str);
return match ? match[0] : '';
}
class UrlParser {
constructor(private remaining: string) {}
peekStartsWith(str: string): boolean { return this.remaining.startsWith(str); }
capture(str: string): void {
if (!this.remaining.startsWith(str)) {
throw new Error(`Expected "${str}".`);
}
this.remaining = this.remaining.substring(str.length);
}
parseRootSegment(): TreeNode<UrlSegment> {
if (this.remaining == '' || this.remaining == '/') {
return new TreeNode<UrlSegment>(new UrlSegment('', {}, PRIMARY_OUTLET), []);
2016-05-21 17:35:55 -07:00
} else {
const segments = this.parseSegments(false);
return new TreeNode<UrlSegment>(new UrlSegment('', {}, PRIMARY_OUTLET), segments);
2016-05-21 17:35:55 -07:00
}
}
parseSegments(hasOutletName: boolean): TreeNode<UrlSegment>[] {
2016-05-21 17:35:55 -07:00
if (this.remaining.length == 0) {
return [];
}
if (this.peekStartsWith('/')) {
this.capture('/');
}
let path = matchUrlSegment(this.remaining);
2016-05-21 17:35:55 -07:00
this.capture(path);
let outletName;
if (hasOutletName) {
if (path.indexOf(":") === -1) {
throw new Error("Not outlet name is provided");
}
if (path.indexOf(":") > -1 && hasOutletName) {
let parts = path.split(":");
outletName = parts[0];
path = parts[1];
}
} else {
if (path.indexOf(":") > -1) {
throw new Error("Not outlet name is allowed");
}
outletName = PRIMARY_OUTLET;
}
let matrixParams: {[key: string]: any} = {};
2016-05-21 17:35:55 -07:00
if (this.peekStartsWith(';')) {
matrixParams = this.parseMatrixParams();
}
let secondary = [];
2016-05-21 17:35:55 -07:00
if (this.peekStartsWith('(')) {
secondary = this.parseSecondarySegments();
}
let children: TreeNode<UrlSegment>[] = [];
2016-05-21 17:35:55 -07:00
if (this.peekStartsWith('/') && !this.peekStartsWith('//')) {
this.capture('/');
children = this.parseSegments(false);
2016-05-21 17:35:55 -07:00
}
const segment = new UrlSegment(path, matrixParams, outletName);
const node = new TreeNode<UrlSegment>(segment, children);
2016-05-21 17:35:55 -07:00
return [node].concat(secondary);
}
parseQueryParams(): {[key: string]: any} {
var params: {[key: string]: any} = {};
if (this.peekStartsWith('?')) {
this.capture('?');
this.parseQueryParam(params);
while (this.remaining.length > 0 && this.peekStartsWith('&')) {
this.capture('&');
this.parseQueryParam(params);
}
}
return params;
}
parseFragment(): string | null {
if (this.peekStartsWith('#')) {
return this.remaining.substring(1);
} else {
return null;
}
}
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 (!key) {
return;
}
this.capture(key);
var value: any = "true";
if (this.peekStartsWith('=')) {
this.capture('=');
var valueMatch = matchUrlSegment(this.remaining);
if (valueMatch) {
value = valueMatch;
this.capture(value);
}
}
params[key] = value;
}
parseQueryParam(params: {[key: string]: any}): void {
var key = matchUrlSegment(this.remaining);
if (!key) {
return;
}
this.capture(key);
var value: any = "true";
if (this.peekStartsWith('=')) {
this.capture('=');
var valueMatch = matchUrlQueryParamValue(this.remaining);
if (valueMatch) {
value = valueMatch;
this.capture(value);
}
}
params[key] = value;
}
parseSecondarySegments(): TreeNode<UrlSegment>[] {
var segments = [];
this.capture('(');
while (!this.peekStartsWith(')') && this.remaining.length > 0) {
segments = segments.concat(this.parseSegments(true));
2016-05-21 17:35:55 -07:00
if (this.peekStartsWith('//')) {
this.capture('//');
}
}
this.capture(')');
return segments;
}
}