UNPKG

11.8 kBJavaScriptView Raw
1"use strict";
2var collection_1 = require('../../facade/collection');
3var exceptions_1 = require('../../facade/exceptions');
4var lang_1 = require('../../facade/lang');
5var url_parser_1 = require('../../url_parser');
6var utils_1 = require('../../utils');
7var route_path_1 = require('./route_path');
8/**
9 * Identified by a `...` URL segment. This indicates that the
10 * Route will continue to be matched by child `Router`s.
11 */
12var ContinuationPathSegment = (function () {
13 function ContinuationPathSegment() {
14 this.name = '';
15 this.specificity = '';
16 this.hash = '...';
17 }
18 ContinuationPathSegment.prototype.generate = function (params) { return ''; };
19 ContinuationPathSegment.prototype.match = function (path) { return true; };
20 return ContinuationPathSegment;
21}());
22/**
23 * Identified by a string not starting with a `:` or `*`.
24 * Only matches the URL segments that equal the segment path
25 */
26var StaticPathSegment = (function () {
27 function StaticPathSegment(path) {
28 this.path = path;
29 this.name = '';
30 this.specificity = '2';
31 this.hash = path;
32 }
33 StaticPathSegment.prototype.match = function (path) { return path == this.path; };
34 StaticPathSegment.prototype.generate = function (params) { return this.path; };
35 return StaticPathSegment;
36}());
37/**
38 * Identified by a string starting with `:`. Indicates a segment
39 * that can contain a value that will be extracted and provided to
40 * a matching `Instruction`.
41 */
42var DynamicPathSegment = (function () {
43 function DynamicPathSegment(name) {
44 this.name = name;
45 this.specificity = '1';
46 this.hash = ':';
47 }
48 DynamicPathSegment.prototype.match = function (path) { return path.length > 0; };
49 DynamicPathSegment.prototype.generate = function (params) {
50 if (!collection_1.StringMapWrapper.contains(params.map, this.name)) {
51 throw new exceptions_1.BaseException("Route generator for '" + this.name + "' was not included in parameters passed.");
52 }
53 return encodeDynamicSegment(utils_1.normalizeString(params.get(this.name)));
54 };
55 DynamicPathSegment.paramMatcher = /^:([^\/]+)$/g;
56 return DynamicPathSegment;
57}());
58/**
59 * Identified by a string starting with `*` Indicates that all the following
60 * segments match this route and that the value of these segments should
61 * be provided to a matching `Instruction`.
62 */
63var StarPathSegment = (function () {
64 function StarPathSegment(name) {
65 this.name = name;
66 this.specificity = '0';
67 this.hash = '*';
68 }
69 StarPathSegment.prototype.match = function (path) { return true; };
70 StarPathSegment.prototype.generate = function (params) { return utils_1.normalizeString(params.get(this.name)); };
71 StarPathSegment.wildcardMatcher = /^\*([^\/]+)$/g;
72 return StarPathSegment;
73}());
74/**
75 * Parses a URL string using a given matcher DSL, and generates URLs from param maps
76 */
77var ParamRoutePath = (function () {
78 /**
79 * Takes a string representing the matcher DSL
80 */
81 function ParamRoutePath(routePath) {
82 this.routePath = routePath;
83 this.terminal = true;
84 this._assertValidPath(routePath);
85 this._parsePathString(routePath);
86 this.specificity = this._calculateSpecificity();
87 this.hash = this._calculateHash();
88 var lastSegment = this._segments[this._segments.length - 1];
89 this.terminal = !(lastSegment instanceof ContinuationPathSegment);
90 }
91 ParamRoutePath.prototype.matchUrl = function (url) {
92 var nextUrlSegment = url;
93 var currentUrlSegment;
94 var positionalParams = {};
95 var captured = [];
96 for (var i = 0; i < this._segments.length; i += 1) {
97 var pathSegment = this._segments[i];
98 if (pathSegment instanceof ContinuationPathSegment) {
99 break;
100 }
101 currentUrlSegment = nextUrlSegment;
102 if (lang_1.isPresent(currentUrlSegment)) {
103 // the star segment consumes all of the remaining URL, including matrix params
104 if (pathSegment instanceof StarPathSegment) {
105 positionalParams[pathSegment.name] =
106 currentUrlSegment.toString();
107 captured.push(currentUrlSegment.toString());
108 nextUrlSegment = null;
109 break;
110 }
111 captured.push(currentUrlSegment.path);
112 if (pathSegment instanceof DynamicPathSegment) {
113 positionalParams[pathSegment.name] =
114 decodeDynamicSegment(currentUrlSegment.path);
115 }
116 else if (!pathSegment.match(currentUrlSegment.path)) {
117 return null;
118 }
119 nextUrlSegment = currentUrlSegment.child;
120 }
121 else if (!pathSegment.match('')) {
122 return null;
123 }
124 }
125 if (this.terminal && lang_1.isPresent(nextUrlSegment)) {
126 return null;
127 }
128 var urlPath = captured.join('/');
129 var auxiliary = [];
130 var urlParams = [];
131 var allParams = positionalParams;
132 if (lang_1.isPresent(currentUrlSegment)) {
133 // If this is the root component, read query params. Otherwise, read matrix params.
134 var paramsSegment = url instanceof url_parser_1.RootUrl ? url : currentUrlSegment;
135 if (lang_1.isPresent(paramsSegment.params)) {
136 allParams = collection_1.StringMapWrapper.merge(paramsSegment.params, positionalParams);
137 urlParams = url_parser_1.convertUrlParamsToArray(paramsSegment.params);
138 }
139 else {
140 allParams = positionalParams;
141 }
142 auxiliary = currentUrlSegment.auxiliary;
143 }
144 return new route_path_1.MatchedUrl(urlPath, urlParams, allParams, auxiliary, nextUrlSegment);
145 };
146 ParamRoutePath.prototype.generateUrl = function (params) {
147 var paramTokens = new utils_1.TouchMap(params);
148 var path = [];
149 for (var i = 0; i < this._segments.length; i++) {
150 var segment = this._segments[i];
151 if (!(segment instanceof ContinuationPathSegment)) {
152 path.push(segment.generate(paramTokens));
153 }
154 }
155 var urlPath = path.join('/');
156 var nonPositionalParams = paramTokens.getUnused();
157 var urlParams = nonPositionalParams;
158 return new route_path_1.GeneratedUrl(urlPath, urlParams);
159 };
160 ParamRoutePath.prototype.toString = function () { return this.routePath; };
161 ParamRoutePath.prototype._parsePathString = function (routePath) {
162 // normalize route as not starting with a "/". Recognition will
163 // also normalize.
164 if (routePath.startsWith('/')) {
165 routePath = routePath.substring(1);
166 }
167 var segmentStrings = routePath.split('/');
168 this._segments = [];
169 var limit = segmentStrings.length - 1;
170 for (var i = 0; i <= limit; i++) {
171 var segment = segmentStrings[i], match;
172 if (lang_1.isPresent(match = lang_1.RegExpWrapper.firstMatch(DynamicPathSegment.paramMatcher, segment))) {
173 this._segments.push(new DynamicPathSegment(match[1]));
174 }
175 else if (lang_1.isPresent(match = lang_1.RegExpWrapper.firstMatch(StarPathSegment.wildcardMatcher, segment))) {
176 this._segments.push(new StarPathSegment(match[1]));
177 }
178 else if (segment == '...') {
179 if (i < limit) {
180 throw new exceptions_1.BaseException("Unexpected \"...\" before the end of the path for \"" + routePath + "\".");
181 }
182 this._segments.push(new ContinuationPathSegment());
183 }
184 else {
185 this._segments.push(new StaticPathSegment(segment));
186 }
187 }
188 };
189 ParamRoutePath.prototype._calculateSpecificity = function () {
190 // The "specificity" of a path is used to determine which route is used when multiple routes
191 // match
192 // a URL. Static segments (like "/foo") are the most specific, followed by dynamic segments
193 // (like
194 // "/:id"). Star segments add no specificity. Segments at the start of the path are more
195 // specific
196 // than proceeding ones.
197 //
198 // The code below uses place values to combine the different types of segments into a single
199 // string that we can sort later. Each static segment is marked as a specificity of "2," each
200 // dynamic segment is worth "1" specificity, and stars are worth "0" specificity.
201 var i /** TODO #9100 */, length = this._segments.length, specificity;
202 if (length == 0) {
203 // a single slash (or "empty segment" is as specific as a static segment
204 specificity += '2';
205 }
206 else {
207 specificity = '';
208 for (i = 0; i < length; i++) {
209 specificity += this._segments[i].specificity;
210 }
211 }
212 return specificity;
213 };
214 ParamRoutePath.prototype._calculateHash = function () {
215 // this function is used to determine whether a route config path like `/foo/:id` collides with
216 // `/foo/:name`
217 var i /** TODO #9100 */, length = this._segments.length;
218 var hashParts = [];
219 for (i = 0; i < length; i++) {
220 hashParts.push(this._segments[i].hash);
221 }
222 return hashParts.join('/');
223 };
224 ParamRoutePath.prototype._assertValidPath = function (path) {
225 if (lang_1.StringWrapper.contains(path, '#')) {
226 throw new exceptions_1.BaseException("Path \"" + path + "\" should not include \"#\". Use \"HashLocationStrategy\" instead.");
227 }
228 var illegalCharacter = lang_1.RegExpWrapper.firstMatch(ParamRoutePath.RESERVED_CHARS, path);
229 if (lang_1.isPresent(illegalCharacter)) {
230 throw new exceptions_1.BaseException("Path \"" + path + "\" contains \"" + illegalCharacter[0] + "\" which is not allowed in a route config.");
231 }
232 };
233 ParamRoutePath.RESERVED_CHARS = lang_1.RegExpWrapper.create('//|\\(|\\)|;|\\?|=');
234 return ParamRoutePath;
235}());
236exports.ParamRoutePath = ParamRoutePath;
237var REGEXP_PERCENT = /%/g;
238var REGEXP_SLASH = /\//g;
239var REGEXP_OPEN_PARENT = /\(/g;
240var REGEXP_CLOSE_PARENT = /\)/g;
241var REGEXP_SEMICOLON = /;/g;
242function encodeDynamicSegment(value) {
243 if (lang_1.isBlank(value)) {
244 return null;
245 }
246 value = lang_1.StringWrapper.replaceAll(value, REGEXP_PERCENT, '%25');
247 value = lang_1.StringWrapper.replaceAll(value, REGEXP_SLASH, '%2F');
248 value = lang_1.StringWrapper.replaceAll(value, REGEXP_OPEN_PARENT, '%28');
249 value = lang_1.StringWrapper.replaceAll(value, REGEXP_CLOSE_PARENT, '%29');
250 value = lang_1.StringWrapper.replaceAll(value, REGEXP_SEMICOLON, '%3B');
251 return value;
252}
253var REGEXP_ENC_SEMICOLON = /%3B/ig;
254var REGEXP_ENC_CLOSE_PARENT = /%29/ig;
255var REGEXP_ENC_OPEN_PARENT = /%28/ig;
256var REGEXP_ENC_SLASH = /%2F/ig;
257var REGEXP_ENC_PERCENT = /%25/ig;
258function decodeDynamicSegment(value) {
259 if (lang_1.isBlank(value)) {
260 return null;
261 }
262 value = lang_1.StringWrapper.replaceAll(value, REGEXP_ENC_SEMICOLON, ';');
263 value = lang_1.StringWrapper.replaceAll(value, REGEXP_ENC_CLOSE_PARENT, ')');
264 value = lang_1.StringWrapper.replaceAll(value, REGEXP_ENC_OPEN_PARENT, '(');
265 value = lang_1.StringWrapper.replaceAll(value, REGEXP_ENC_SLASH, '/');
266 value = lang_1.StringWrapper.replaceAll(value, REGEXP_ENC_PERCENT, '%');
267 return value;
268}
269//# sourceMappingURL=param_route_path.js.map
\No newline at end of file