function encodeReserved ( str ) { return str . split ( /(%[0-9A-Fa-f]{2})/g ) . map ( function ( part ) { if ( ! /%[0-9A-Fa-f]/ . test ( part ) ) { part = encodeURI ( part ) . replace ( /%5B/g , "[" ) . replace ( /%5D/g , "]" ) ; } return part ; } ) . join ( "" ) ; } function encodeUnreserved ( str ) { return encodeURIComponent ( str ) . replace ( /[!'()*]/g , function ( c ) { return "%" + c . charCodeAt ( 0 ) . toString ( 16 ) . toUpperCase ( ) ; } ) ; } function encodeValue ( operator , value , key ? ) { value = operator === "+" || operator === "#" ? encodeReserved ( value ) : encodeUnreserved ( value ) ; if ( key ) { return encodeUnreserved ( key ) + "=" + value ; } else { return value ; } } function isDefined ( value ) { return value !== undefined && value !== null ; } function isKeyOperator ( operator ) { return operator === ";" || operator === "&" || operator === "?" ; } type Context = { [ key ] : any ; } ; function getValues ( context , operator , key , modifier ? ) { var value = context [ key ] , result = [ ] ; if ( isDefined ( value ) && value !== "" ) { if ( typeof value === "string" || typeof value === "number" || typeof value === "boolean" ) { value = value . toString ( ) ; if ( modifier && modifier !== "*" ) { value = value . substring ( 0 , parseInt ( modifier , 10 ) ) ; } result . push ( encodeValue ( operator , value , isKeyOperator ( operator ) ? key : "" ) ) ; } else { if ( modifier === "*" ) { if ( Array . isArray ( value ) ) { value . filter ( isDefined ) . forEach ( function ( value ) { result . push ( encodeValue ( operator , value , isKeyOperator ( operator ) ? key : "" ) ) ; } ) ; } else { Object . keys ( value ) . forEach ( function ( k ) { if ( isDefined ( value [ k ] ) ) { result . push ( encodeValue ( operator , value [ k ] , k ) ) ; } } ) ; } } else { const tmp = [ ] ; if ( Array . isArray ( value ) ) { value . filter ( isDefined ) . forEach ( function ( value ) { tmp . push ( encodeValue ( operator , value ) ) ; } ) ; } else { Object . keys ( value ) . forEach ( function ( k ) { if ( isDefined ( value [ k ] ) ) { tmp . push ( encodeUnreserved ( k ) ) ; tmp . push ( encodeValue ( operator , value [ k ] . toString ( ) ) ) ; } } ) ; } if ( isKeyOperator ( operator ) ) { result . push ( encodeUnreserved ( key ) + "=" + tmp . join ( "," ) ) ; } else if ( tmp . length !== 0 ) { result . push ( tmp . join ( "," ) ) ; } } } } else { if ( operator === ";" ) { if ( isDefined ( value ) ) { result . push ( encodeUnreserved ( key ) ) ; } } else if ( value === "" && ( operator === "&" || operator === "?" ) ) { result . push ( encodeUnreserved ( key ) + "=" ) ; } else if ( value === "" ) { result . push ( "" ) ; } } return result ; } export function parseUrl ( template ) { return { expand : expand . bind ( null , template ) , } ; } function expand ( template , context ) { var operators = [ "+" , "#" , "." , "/" , ";" , "?" , "&" ] ; return template . replace ( /\{([^\{\}]+)\}|([^\{\}]+)/g , function ( _ , expression , literal ) { if ( expression ) { let operator = "" ; const values = [ ] ; if ( operators . indexOf ( expression . charAt ( 0 ) ) !== - 1 ) { operator = expression . charAt ( 0 ) ; expression = expression . substr ( 1 ) ; } expression . split ( /,/g ) . forEach ( function ( variable ) { var tmp = /([^:\*]*)(?::(\d+)|(\*))?/ . exec ( variable ) as string [ ] ; values . push ( getValues ( context , operator , tmp [ 1 ] , tmp [ 2 ] || tmp [ 3 ] ) ) ; } ) ; if ( operator && operator !== "+" ) { var separator = "," ; if ( operator === "?" ) { separator = "&" ; } else if ( operator !== "#" ) { separator = operator ; } return ( values . length !== 0 ? operator : "" ) + values . join ( separator ) ; } else { return values . join ( "," ) ; } } else { return encodeReserved ( literal ) ; } } ) ; }