Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(14)

Side by Side Diff: third_party/pkg/route_hierarchical/lib/url_pattern.dart

Issue 124053002: Adding Angular and dependent packages for testing (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 library route.url_pattern;
6
7 import 'url_matcher.dart';
8
9 // From the PatternCharacter rule here:
10 // http://ecma-international.org/ecma-262/5.1/#sec-15.10
11 // removed '( and ')' since we'll never escape them when not in a group
12 final _specialChars = new RegExp(r'[\^\$\.\|\+\[\]\{\}]');
13
14 UrlPattern urlPattern(String p) => new UrlPattern(p);
15
16 /**
17 * A pattern, similar to a [RegExp], that is designed to match against URL
18 * paths, easily return groups of a matched path, and produce paths from a list
19 * of arguments - this is they are "reversible".
20 *
21 * `UrlPattern`s also allow for handling plain paths and URLs with a fragment in
22 * a uniform way so that they can be used for client side routing on browsers
23 * that support `window.history.pushState` as well as legacy browsers.
24 *
25 * The differences from a plain [RegExp]:
26 * * All non-literals must be in a group. Everything outside of a groups is
27 * considered a literal and special regex characters are escaped.
28 * * There can only be one match, and it must match the entire string. `^` and
29 * `$` are automatically added to the beginning and end of the pattern,
30 * respectively.
31 * * The pattern must be un-ambiguous, eg `(.*)(.*)` is not allowed at the
32 * top-level.
33 * * The hash character (#) matches both '#' and '/', and it is only allowed
34 * once per pattern. Hashes are not allowed inside groups.
35 *
36 * With those differences, `UrlPatterns` become much more useful for routing
37 * URLs and constructing them, both on the client and server. The best practice
38 * is to define your application's set of URLs in a shared library.
39 *
40 * urls.dart:
41 *
42 * library urls;
43 *
44 * final articleUrl = new UrlPattern(r'/articles/(\d+)');
45 *
46 * server.dart:
47 *
48 * import 'urls.dart';
49 * import 'package:route/server.dart';
50 *
51 * main() {
52 * var server = new HttpServer();
53 * server.addRequestHandler(matchesUrl(articleUrl), serveArticle);
54 * }
55 *
56 * serveArcticle(req, res) {
57 * var articleId = articleUrl.parse(req.path)[0];
58 * // ...
59 * }
60 *
61 * Use with older browsers
62 * -----------------------
63 *
64 * Since '#' matches both '#' and '/' it can be used in as a path separator
65 * between the "static" portion of your URL and the "dynamic" portion. The
66 * dynamic portion would be the part that change when a user navigates to new
67 * data that's loaded dynamically rather than loading a new page.
68 *
69 * In newer browsers that support `History.pushState()` an entire new path can
70 * be pushed into the location bar without reloading the page. In older browsers
71 * only the fragment can be changed without reloading the page. By matching both
72 * characters, and by producing either, we can use pushState in newer browsers,
73 * but fall back to fragments when necessary.
74 *
75 * Examples:
76 *
77 * var pattern = new UrlPattern(r'/app#profile/(\d+)');
78 * pattern.matches('/app/profile/1234'); // true
79 * pattern.matches('/app#profile/1234'); // true
80 * pattern.reverse([1234], useFragment: true); // /app#profile/1234
81 * pattern.reverse([1234], useFragment: false); // /app/profile/1234
82 */
83 class UrlPattern implements UrlMatcher, Pattern {
84 final String pattern;
85 RegExp _regex;
86 bool _hasFragment;
87 RegExp _baseRegex;
88
89 UrlPattern(this.pattern) {
90 _parse(pattern);
91 }
92
93 RegExp get regex => _regex;
94
95 String reverse(Iterable args, {bool useFragment: false}) {
96 var sb = new StringBuffer();
97 var chars = pattern.split('');
98 var argsIter = args.iterator;
99
100 int depth = 0;
101 int groupCount = 0;
102 bool escaped = false;
103
104 for (int i = 0; i < chars.length; i++) {
105 var c = chars[i];
106 if (c == '\\' && escaped == false) {
107 escaped = true;
108 } else {
109 if (c == '(') {
110 if (escaped && depth == 0) {
111 sb.write(c);
112 }
113 if (!escaped) depth++;
114 } else if (c == ')') {
115 if (escaped && depth == 0) {
116 sb.write(c);
117 } else if (!escaped) {
118 if (depth == 0) throw new ArgumentError('unmatched parentheses');
119 depth--;
120 if (depth == 0) {
121 // append the nth arg
122 if (argsIter.moveNext()) {
123 sb.write(argsIter.current.toString());
124 } else {
125 throw new ArgumentError('more groups than args');
126 }
127 }
128 }
129 } else if (depth == 0) {
130 if (c == '#' && !useFragment) {
131 sb.write('/');
132 } else {
133 sb.write(c);
134 }
135 }
136 escaped = false;
137 }
138 }
139 if (depth > 0) {
140 throw new ArgumentError('unclosed group');
141 }
142 return sb.toString();
143 }
144
145 /**
146 * Parses a URL path, or path + fragment, and returns the group matches.
147 * Throws [ArgumentError] if this pattern does not match [path].
148 */
149 List<String> parse(String path) {
150 var match = regex.firstMatch(path);
151 if (match == null) {
152 throw new ArgumentError('no match for $path');
153 }
154 var result = <String>[];
155 for (int i = 1; i <= match.groupCount; i++) {
156 result.add(match[i]);
157 }
158 return result;
159 }
160
161 UrlMatch match(String url) {
162 var matches = allMatches(url);
163 if (matches.isEmpty) {
164 return null;
165 }
166 var match = matches.first;
167 var tail = url.substring(match.group(0).length);
168 Map parameters = new Map();
169 for (var i = 0; i < match.groupCount; i++) {
170 parameters[i] = match.group(i + 1);
171 }
172 return new UrlMatch(match.group(0), tail, parameters);
173 }
174
175 /**
176 * Returns true if this pattern matches [path].
177 */
178 bool matches(String str) => _matches(regex, str);
179
180 // TODO(justinfagnani): file bug for similar method to be added to Pattern
181 bool _matches(Pattern p, String str) {
182 var iter = p.allMatches(str).iterator;
183 if (iter.moveNext()) {
184 var match = iter.current;
185 return (match.start == 0) && (match.end == str.length)
186 && (!iter.moveNext());
187 }
188 return false;
189 }
190
191 /**
192 * Returns true if the path portion of the pattern, the part before the
193 * fragment, matches [str]. If there is no fragment in the pattern, this is
194 * equivalent to calling [matches].
195 *
196 * This method is most useful on a server that is serving the HTML of a
197 * single page app. Clients that don't support pushState will not send the
198 * fragment to the server, so the server will have to handle just the path
199 * part.
200 */
201 bool matchesNonFragment(String str) {
202 if (!_hasFragment) {
203 return matches(str);
204 } else {
205 return _matches(_baseRegex, str);
206 }
207 }
208
209 Iterable<Match> allMatches(String str) {
210 return regex.allMatches(str);
211 }
212
213 bool operator ==(other) =>
214 (other is UrlPattern) && (other.pattern == pattern);
215
216 int get hashCode => pattern.hashCode;
217
218 String toString() => pattern.toString();
219
220 _parse(String pattern) {
221 var sb = new StringBuffer();
222 int depth = 0;
223 int lastGroupEnd = -2;
224 bool escaped = false;
225
226 sb.write('^');
227 var chars = pattern.split('');
228 for (var i = 0; i < chars.length; i++) {
229 var c = chars[i];
230
231 if (depth == 0) {
232 // outside of groups, transform the pattern to matches the literal
233 if (c == r'\') {
234 if (escaped) {
235 sb.write(r'\\');
236 }
237 escaped = !escaped;
238 } else {
239 if (_specialChars.hasMatch(c)) {
240 sb.write('\\$c');
241 } else if (c == '(') {
242 if (escaped) {
243 sb.write(r'\(');
244 } else {
245 sb.write('(');
246 if (lastGroupEnd == i - 1) {
247 throw new ArgumentError('ambiguous adjecent top-level groups');
248 }
249 depth = 1;
250 }
251 } else if (c == ')') {
252 if (escaped) {
253 sb.write(r'\)');
254 } else {
255 throw new ArgumentError('unmatched parenthesis');
256 }
257 } else if (c == '#') {
258 _setBasePattern(sb.toString());
259 sb.write('[/#]');
260 } else {
261 sb.write(c);
262 }
263 escaped = false;
264 }
265 } else {
266 // in a group, don't modify the pattern, but track escaping and depth
267 if (c == '(' && !escaped) {
268 depth++;
269 } else if (c == ')' && !escaped) {
270 depth--;
271 if (depth < 0) throw new ArgumentError('unmatched parenthesis');
272 if (depth == 0) {
273 lastGroupEnd = i;
274 }
275 } else if (c == '#') {
276 // TODO(justinfagnani): what else should be banned in groups? '/'?
277 throw new ArgumentError('illegal # inside group');
278 }
279 escaped = (c == r'\' && !escaped);
280 sb.write(c);
281 }
282 }
283 // sb.write(r'$');
284 _regex = new RegExp(sb.toString());
285 }
286
287 _setBasePattern(String basePattern) {
288 if (_hasFragment == true) {
289 throw new ArgumentError('multiple # characters');
290 }
291 _hasFragment = true;
292 _baseRegex = new RegExp('$basePattern\$');
293 }
294 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698