@@ -94,15 +94,15 @@ function UrlMatcher(pattern, config, parentMatcher) {
9494 return params [ id ] ;
9595 }
9696
97- function quoteRegExp ( string , pattern , squashPolicy ) {
98- var flags = [ '' , '' ] , result = string . replace ( / [ \\ \[ \] \^ $ * + ? . ( ) | { } ] / g, "\\$&" ) ;
97+ function quoteRegExp ( string , pattern , squash ) {
98+ var surroundPattern = [ '' , '' ] , result = string . replace ( / [ \\ \[ \] \^ $ * + ? . ( ) | { } ] / g, "\\$&" ) ;
9999 if ( ! pattern ) return result ;
100- switch ( squashPolicy ) {
101- case "nosquash" : flags = [ '' , '' ] ; break ;
102- case "value" : flags = [ '' , '?' ] ; break ;
103- case "slash" : flags = [ '?' , '?' ] ; break ;
100+ switch ( squash ) {
101+ case false : surroundPattern = [ '( ' , ') ' ] ; break ;
102+ case true : surroundPattern = [ '?( ' , ') ?' ] ; break ;
103+ default : surroundPattern = [ '(' + squash + "|" , ') ?' ] ; break ;
104104 }
105- return result + flags [ 0 ] + '(' + pattern + ')' + flags [ 1 ] ;
105+ return result + surroundPattern [ 0 ] + pattern + surroundPattern [ 1 ] ;
106106 }
107107
108108 this . source = pattern ;
@@ -231,7 +231,7 @@ UrlMatcher.prototype.exec = function (path, searchParams) {
231231
232232 var paramNames = this . parameters ( ) , nTotal = paramNames . length ,
233233 nPath = this . segments . length - 1 ,
234- values = { } , i , cfg , paramName ;
234+ values = { } , i , j , cfg , paramName ;
235235
236236 if ( nPath !== m . length - 1 ) throw new Error ( "Unbalanced capture group in route '" + this . source + "'" ) ;
237237
@@ -244,8 +244,11 @@ UrlMatcher.prototype.exec = function (path, searchParams) {
244244 for ( i = 0 ; i < nPath ; i ++ ) {
245245 paramName = paramNames [ i ] ;
246246 var param = this . params [ paramName ] ;
247- // if the param is optional, convert an empty string to `undefined`
248- var paramVal = m [ i + 1 ] === "" ? param . emptyString : m [ i + 1 ] ;
247+ var paramVal = m [ i + 1 ] ;
248+ // if the param value matches a pre-replace pair, replace the value before decoding.
249+ for ( j = 0 ; j < param . replace ; j ++ ) {
250+ if ( param . replace [ j ] . from === paramVal ) paramVal = param . replace [ j ] . to ;
251+ }
249252 if ( paramVal && param . array === true ) paramVal = decodePathArray ( paramVal ) ;
250253 values [ paramName ] = param . value ( paramVal ) ;
251254 }
@@ -323,12 +326,12 @@ UrlMatcher.prototype.format = function (values) {
323326 var isPathParam = i < nPath ;
324327 var name = params [ i ] , param = paramset [ name ] , value = param . value ( values [ name ] ) ;
325328 var isDefaultValue = param . isOptional && param . type . equals ( param . value ( ) , value ) ;
326- var squash = isDefaultValue ? param . squash : "nosquash" ;
329+ var squash = isDefaultValue ? param . squash : false ;
327330 var encoded = param . type . encode ( value ) ;
328331
329332 if ( isPathParam ) {
330333 var nextSegment = segments [ i + 1 ] ;
331- if ( squash === "nosquash" ) {
334+ if ( squash === false ) {
332335 if ( encoded != null ) {
333336 if ( isArray ( encoded ) ) {
334337 result += encoded . map ( encodeDashes ) . join ( "-" ) ;
@@ -337,14 +340,14 @@ UrlMatcher.prototype.format = function (values) {
337340 }
338341 }
339342 result += nextSegment ;
340- } else if ( squash === "value" ) {
341- result += nextSegment ;
342- } else if ( squash === "slash" ) {
343+ } else if ( squash === true ) {
343344 var capture = result . match ( / \/ $ / ) ? / \/ ? ( .* ) / : / ( .* ) / ;
344345 result += nextSegment . match ( capture ) [ 1 ] ;
346+ } else if ( isString ( squash ) ) {
347+ result += squash + nextSegment ;
345348 }
346349 } else {
347- if ( encoded == null || ( isDefaultValue && squash !== "nosquash" ) ) continue ;
350+ if ( encoded == null || ( isDefaultValue && squash !== false ) ) continue ;
348351 if ( ! isArray ( encoded ) ) encoded = [ encoded ] ;
349352 encoded = encoded . map ( encodeURIComponent ) . join ( '&' + name + '=' ) ;
350353 result += ( search ? '&' : '?' ) + ( name + '=' + encoded ) ;
@@ -525,7 +528,7 @@ Type.prototype.$asArray = function(mode, isSearch) {
525528function $UrlMatcherFactory ( ) {
526529 $$UMFP = this ;
527530
528- var isCaseInsensitive = false , isStrictMode = true , defaultSquashPolicy = "nosquash" ;
531+ var isCaseInsensitive = false , isStrictMode = true , defaultSquashPolicy = false ;
529532
530533 function valToString ( val ) { return val != null ? val . toString ( ) . replace ( "/" , "%2F" ) : val ; }
531534 function valFromString ( val ) { return val != null ? val . toString ( ) . replace ( "%2F" , "/" ) : val ; }
@@ -631,14 +634,15 @@ function $UrlMatcherFactory() {
631634 *
632635 * @param {string } value A string that defines the default parameter URL squashing behavior.
633636 * `nosquash`: When generating an href with a default parameter value, do not squash the parameter value from the URL
634- * `value`: When generating an href with a default parameter value, squash (remove) the parameter value from the URL
635637 * `slash`: When generating an href with a default parameter value, squash (remove) the parameter value, and, if the
636638 * parameter is surrounded by slashes, squash (remove) one slash from the URL
639+ * any other string, e.g. "~": When generating an href with a default parameter value, squash (remove)
640+ * the parameter value from the URL and replace it with this string.
637641 */
638642 this . defaultSquashPolicy = function ( value ) {
639- if ( ! value ) return defaultSquashPolicy ;
640- if ( value !== "nosquash" && value !== "value" && value !== "slash" )
641- throw new Error ( "Invalid squash policy: " + value + ". Valid policies: 'nosquash', 'value', 'slash' " ) ;
643+ if ( ! isDefined ( value ) ) return defaultSquashPolicy ;
644+ if ( value !== true && value !== false && ! isString ( value ) )
645+ throw new Error ( "Invalid squash policy: " + value + ". Valid policies: false, true, arbitrary-string " ) ;
642646 defaultSquashPolicy = value ;
643647 return value ;
644648 } ;
@@ -836,7 +840,7 @@ function $UrlMatcherFactory() {
836840 type = arrayMode ? type . $asArray ( arrayMode , isSearch ) : type ;
837841 var isOptional = defaultValueConfig . value !== undefined ;
838842 var squash = getSquashPolicy ( config , isOptional ) ;
839- var emptyString = getEmptyStringValue ( config , arrayMode , isOptional ) ;
843+ var replace = getReplace ( config , arrayMode , isOptional , squash ) ;
840844
841845 function getDefaultValueConfig ( config ) {
842846 var keys = isObject ( config ) ? objectKeys ( config ) : [ ] ;
@@ -864,25 +868,26 @@ function $UrlMatcherFactory() {
864868 }
865869
866870 /**
867- * returns "nosquash", "value", "slash" to indicate the "default parameter url squash policy".
868- * undefined aliases to urlMatcherFactory default. `false` aliases to "nosquash". `true` aliases to "slash".
871+ * returns false, true, or the squash value to indicate the "default parameter url squash policy".
869872 */
870873 function getSquashPolicy ( config , isOptional ) {
871874 var squash = config . squash ;
872- if ( ! isOptional || squash === false ) return "nosquash" ;
873- if ( ! isDefined ( squash ) ) return defaultSquashPolicy ;
874- if ( squash === true ) return "slash" ;
875- if ( squash === "nosquash" || squash === "value" || squash === "slash" ) return squash ;
876- throw new Error ( "Invalid squash policy: '" + squash + "'. Valid policies: 'nosquash' (false), 'value', 'slash' (true)" ) ;
875+ if ( ! isOptional || squash === false ) return false ;
876+ if ( ! isDefined ( squash ) || squash == null ) return defaultSquashPolicy ;
877+ if ( squash === true || isString ( squash ) ) return squash ;
878+ throw new Error ( "Invalid squash policy: '" + squash + "'. Valid policies: false, true, or arbitrary string" ) ;
877879 }
878880
879- /**
880- * Returns "" or undefined, or whatever is defined in the param's config.emptyString.
881- * If the parameter was matched in a URL, but was matched as an empty string, this value will be used instead.
882- */
883- function getEmptyStringValue ( config , arrayMode , isOptional ) {
884- var defaultPolicy = { emptyString : ( isOptional || arrayMode ? undefined : "" ) } ;
885- return extend ( defaultPolicy , config ) . emptyString ;
881+ function getReplace ( config , arrayMode , isOptional , squash ) {
882+ var replace , configuredKeys , defaultPolicy = [
883+ { from : "" , to : ( isOptional || arrayMode ? undefined : "" ) } ,
884+ { from : null , to : ( isOptional || arrayMode ? undefined : "" ) }
885+ ] ;
886+ replace = isArray ( config . replace ) ? config . replace : [ ] ;
887+ if ( isString ( squash ) )
888+ replace . push ( { from : squash , to : undefined } ) ;
889+ configuredKeys = replace . map ( function ( item ) { return item . from ; } ) ;
890+ return defaultPolicy . filter ( function ( item ) { return configuredKeys . indexOf ( item . from ) === - 1 ; } ) . concat ( replace ) ;
886891 }
887892
888893 /**
@@ -898,19 +903,24 @@ function $UrlMatcherFactory() {
898903 * default value, which may be the result of an injectable function.
899904 */
900905 function $value ( value ) {
901- if ( value === "" ) value = self . emptyString ;
906+ function hasReplaceVal ( val ) { return function ( obj ) { return obj . from === val ; } ; }
907+ function $replace ( value ) {
908+ var replacement = self . replace . filter ( hasReplaceVal ( value ) ) . map ( function ( obj ) { return obj . to ; } ) ;
909+ return replacement . length ? replacement [ 0 ] : value ;
910+ }
911+ value = $replace ( value ) ;
902912 return isDefined ( value ) ? self . type . decode ( value ) : $$getDefaultValue ( ) ;
903913 }
904914
905- function toString ( ) { return "{Param:" + id + " " + type + " squash: " + squash + " optional: " + isOptional + "}" ; }
915+ function toString ( ) { return "{Param:" + id + " " + type + " squash: ' " + squash + "' optional: " + isOptional + "}" ; }
906916
907917 extend ( this , {
908918 id : id ,
909919 type : type ,
910920 array : arrayMode ,
911921 config : config ,
912922 squash : squash ,
913- emptyString : emptyString ,
923+ replace : replace ,
914924 isOptional : isOptional ,
915925 dynamic : undefined ,
916926 value : $value ,
0 commit comments