1
1
var invariant = require ( 'react/lib/invariant' ) ;
2
- var copyProperties = require ( 'react/lib/copyProperties' ) ;
3
- var qs = require ( 'querystring' ) ;
4
- var URL = require ( './URL' ) ;
2
+ var merge = require ( 'qs/lib/utils' ) . merge ;
3
+ var qs = require ( 'qs' ) ;
5
4
6
- var paramMatcher = / ( (?: : [ a - z _ $ ] [ a - z 0 - 9 _ $ ] * ) | \* ) / ig;
7
- var queryMatcher = / \? ( .+ ) / ;
8
-
9
- function getParamName ( pathSegment ) {
10
- return pathSegment === '*' ? 'splat' : pathSegment . substr ( 1 ) ;
5
+ function encodeURL ( url ) {
6
+ return encodeURIComponent ( url ) . replace ( / % 2 0 / g, '+' ) ;
11
7
}
12
8
13
- var _compiledPatterns = { } ;
9
+ function decodeURL ( url ) {
10
+ return decodeURIComponent ( url . replace ( / \+ / g, ' ' ) ) ;
11
+ }
14
12
15
- function compilePattern ( pattern ) {
16
- if ( _compiledPatterns [ pattern ] )
17
- return _compiledPatterns [ pattern ] ;
13
+ function encodeSegment ( segment ) {
14
+ // Preserve forward slashes.
15
+ return String ( segment ) . split ( '/' ) . map ( encodeURL ) . join ( '/' ) ;
16
+ }
18
17
19
- var compiled = _compiledPatterns [ pattern ] = { } ;
20
- var paramNames = compiled . paramNames = [ ] ;
18
+ var paramMatcher = / : ( [ a - z A - Z _ $ ] [ a - z A - Z 0 - 9 _ $ ] * ) | [ * . ( ) ^ $ ] / g ;
19
+ var queryMatcher = / \? ( . + ) / ;
21
20
22
- var source = pattern . replace ( paramMatcher , function ( match , pathSegment ) {
23
- paramNames . push ( getParamName ( pathSegment ) ) ;
24
- return pathSegment === '*' ? '(.*?)' : '([^/?#]+)' ;
25
- } ) ;
21
+ var _compiledPatterns = { } ;
26
22
27
- compiled . matcher = new RegExp ( '^' + source + '$' , 'i' ) ;
23
+ function compilePattern ( pattern ) {
24
+ if ( ! ( pattern in _compiledPatterns ) ) {
25
+ var paramNames = [ ] ;
26
+ var source = pattern . replace ( paramMatcher , function ( match , paramName ) {
27
+ switch ( match ) {
28
+ case '*' :
29
+ paramNames . push ( 'splat' ) ;
30
+ return '(.*?)' ;
31
+ case '.' :
32
+ case '(' :
33
+ case ')' :
34
+ case '^' :
35
+ case '$' :
36
+ return '\\' + match ;
37
+ }
38
+
39
+ paramNames . push ( paramName ) ;
40
+
41
+ return '([^./?#]+)' ;
42
+ } ) ;
28
43
29
- return compiled ;
30
- }
44
+ _compiledPatterns [ pattern ] = {
45
+ matcher : new RegExp ( '^' + source + '$' , 'i' ) ,
46
+ paramNames : paramNames
47
+ } ;
48
+ }
31
49
32
- function isDynamicPattern ( pattern ) {
33
- return pattern . indexOf ( ':' ) !== - 1 || pattern . indexOf ( '*' ) !== - 1 ;
50
+ return _compiledPatterns [ pattern ] ;
34
51
}
35
52
36
53
var Path = {
37
54
55
+ /**
56
+ * Returns an array of the names of all parameters in the given pattern.
57
+ */
58
+ extractParamNames : function ( pattern ) {
59
+ return compilePattern ( pattern ) . paramNames ;
60
+ } ,
61
+
38
62
/**
39
63
* Extracts the portions of the given URL path that match the given pattern
40
64
* and returns an object of param name => value pairs. Returns null if the
41
65
* pattern does not match the given path.
42
66
*/
43
67
extractParams : function ( pattern , path ) {
44
- if ( ! pattern )
45
- return null ;
46
-
47
- if ( ! isDynamicPattern ( pattern ) ) {
48
- if ( pattern === URL . decode ( path ) )
49
- return { } ; // No dynamic segments, but the paths match.
50
-
51
- return null ;
52
- }
53
-
54
- var compiled = compilePattern ( pattern ) ;
55
- var match = URL . decode ( path ) . match ( compiled . matcher ) ;
68
+ var object = compilePattern ( pattern ) ;
69
+ var match = decodeURL ( path ) . match ( object . matcher ) ;
56
70
57
71
if ( ! match )
58
72
return null ;
59
73
60
74
var params = { } ;
61
75
62
- compiled . paramNames . forEach ( function ( paramName , index ) {
76
+ object . paramNames . forEach ( function ( paramName , index ) {
63
77
params [ paramName ] = match [ index + 1 ] ;
64
78
} ) ;
65
79
66
80
return params ;
67
81
} ,
68
82
69
- /**
70
- * Returns an array of the names of all parameters in the given pattern.
71
- */
72
- extractParamNames : function ( pattern ) {
73
- if ( ! pattern )
74
- return [ ] ;
75
- return compilePattern ( pattern ) . paramNames ;
76
- } ,
77
-
78
83
/**
79
84
* Returns a version of the given route path with params interpolated. Throws
80
85
* if there is a dynamic segment of the route path for which there is no param.
81
86
*/
82
87
injectParams : function ( pattern , params ) {
83
- if ( ! pattern )
84
- return null ;
85
-
86
- if ( ! isDynamicPattern ( pattern ) )
87
- return pattern ;
88
-
89
88
params = params || { } ;
90
89
91
- return pattern . replace ( paramMatcher , function ( match , pathSegment ) {
92
- var paramName = getParamName ( pathSegment ) ;
90
+ var splatIndex = 0 ;
91
+
92
+ return pattern . replace ( paramMatcher , function ( match , paramName ) {
93
+ paramName = paramName || 'splat' ;
93
94
94
95
invariant (
95
96
params [ paramName ] != null ,
96
97
'Missing "' + paramName + '" parameter for path "' + pattern + '"'
97
98
) ;
98
99
99
- // Preserve forward slashes.
100
- return String ( params [ paramName ] ) . split ( '/' ) . map ( URL . encode ) . join ( '/' ) ;
100
+ var segment ;
101
+ if ( paramName === 'splat' && Array . isArray ( params [ paramName ] ) ) {
102
+ segment = params [ paramName ] [ splatIndex ++ ] ;
103
+
104
+ invariant (
105
+ segment != null ,
106
+ 'Missing splat # ' + splatIndex + ' for path "' + pattern + '"'
107
+ ) ;
108
+ } else {
109
+ segment = params [ paramName ] ;
110
+ }
111
+
112
+ return encodeSegment ( segment ) ;
101
113
} ) ;
102
114
} ,
103
115
104
116
/**
105
- * Returns an object that is the result of parsing any query string contained in
106
- * the given path, null if the path contains no query string.
117
+ * Returns an object that is the result of parsing any query string contained
118
+ * in the given path, null if the path contains no query string.
107
119
*/
108
120
extractQuery : function ( path ) {
109
- var match = path . match ( queryMatcher ) ;
121
+ var match = decodeURL ( path ) . match ( queryMatcher ) ;
110
122
return match && qs . parse ( match [ 1 ] ) ;
111
123
} ,
112
124
@@ -118,14 +130,14 @@ var Path = {
118
130
} ,
119
131
120
132
/**
121
- * Returns a version of the given path with the parameters in the given query
122
- * added to the query string.
133
+ * Returns a version of the given path with the parameters in the given
134
+ * query merged into the query string.
123
135
*/
124
136
withQuery : function ( path , query ) {
125
137
var existingQuery = Path . extractQuery ( path ) ;
126
138
127
139
if ( existingQuery )
128
- query = query ? copyProperties ( existingQuery , query ) : existingQuery ;
140
+ query = query ? merge ( existingQuery , query ) : existingQuery ;
129
141
130
142
var queryString = query && qs . stringify ( query ) ;
131
143
0 commit comments