diff --git a/src/UriTemplateTests/ParameterMatchingTests.cs b/src/UriTemplateTests/ParameterMatchingTests.cs index fc8e0fd..59bc18b 100644 --- a/src/UriTemplateTests/ParameterMatchingTests.cs +++ b/src/UriTemplateTests/ParameterMatchingTests.cs @@ -26,6 +26,8 @@ public void MatchUriToTemplate() Assert.True(match); } + #region GetParameters() + [Fact] public void GetParameters() { @@ -38,7 +40,7 @@ public void GetParameters() var match = regex.Match(uri.AbsoluteUri); - Assert.Equal("foo",match.Groups["p1"].Value); + Assert.Equal("foo", match.Groups["p1"].Value); Assert.Equal("bar", match.Groups["p2"].Value); } @@ -87,6 +89,7 @@ public void GetParametersFromMultipleQueryString() Assert.Equal("45", parameters["blur"]); } + [Fact] public void GetParametersFromMultipleQueryStringWithTwoParamValues() { @@ -135,6 +138,155 @@ public void GetParametersFromMultipleQueryStringWithOptionalParameters() } + [Fact] + public void GetParametersRespectsOrderOfQueryParameters() + { + var uri = new Uri("http://example.com/foo/bar?blob=23&blur=45"); + + var template = new UriTemplate("http://example.com/{+p1}/{p2*}{?blur,blob}"); + + var parameters = template.GetParameters(uri); + + Assert.Null(parameters); + } + + [Fact] + public void GetParametersReturnsNullWhenTheresNoMatch() + { + var uri = new Uri("http://example.com/foo/bar?one=23&two=45"); + + var template = new UriTemplate("http://example.com/foo/bar{?blur,blob}"); + + var parameters = template.GetParameters(uri); + + Assert.Null(parameters); + } + + #endregion + + #region GetParametersNonStrict() + + [Fact] + public void GetParametersNonStrictWithOperators() + { + var uri = new Uri("http://example.com/foo/bar"); + + var template = new UriTemplate("http://example.com/{+p1}/{p2*}"); + + var parameters = template.GetParametersNonStrict(uri); + + Assert.Equal(2, parameters.Count); + Assert.Equal("foo", parameters["p1"]); + Assert.Equal("bar", parameters["p2"]); + } + + [Fact] + public void GetParametersNonStrictFromQueryString() + { + var uri = new Uri("http://example.com/foo/bar?blur=45"); + + var template = new UriTemplate("http://example.com/{+p1}/{p2*}{?blur}"); + + var parameters = template.GetParametersNonStrict(uri); + + Assert.Equal(3, parameters.Count); + + Assert.Equal("foo", parameters["p1"]); + Assert.Equal("bar", parameters["p2"]); + Assert.Equal("45", parameters["blur"]); + } + + [Fact] + public void GetParametersNonStrictFromMultipleQueryString() + { + var uri = new Uri("http://example.com/foo/bar?blur=45"); + + var template = new UriTemplate("http://example.com/{+p1}/{p2*}{?blur,blob}"); + + var parameters = template.GetParametersNonStrict(uri); + + Assert.Equal(3, parameters.Count); + Assert.Equal("foo", parameters["p1"]); + Assert.Equal("bar", parameters["p2"]); + Assert.Equal("45", parameters["blur"]); + + } + + [Fact] + public void GetParametersNonStrictFromMultipleQueryStringWithTwoParamValues() + { + var uri = new Uri("http://example.com/foo/bar?blur=45&blob=23"); + + var template = new UriTemplate("http://example.com/{+p1}/{p2*}{?blur,blob}"); + + var parameters = template.GetParametersNonStrict(uri); + + Assert.Equal(4, parameters.Count); + Assert.Equal("foo", parameters["p1"]); + Assert.Equal("bar", parameters["p2"]); + Assert.Equal("45", parameters["blur"]); + Assert.Equal("23", parameters["blob"]); + } + + [Fact] + public void GetParametersNonStrictFromMultipleQueryStringWithOptionalAndMandatoryParameters() + { + var uri = new Uri("http://example.com/foo/bar?blur=45&blob=23"); + + var template = new UriTemplate("http://example.com/{+p1}/{p2*}{?blur}{&blob}"); + + var parameters = template.GetParametersNonStrict(uri); + + Assert.Equal(4, parameters.Count); + Assert.Equal("foo", parameters["p1"]); + Assert.Equal("bar", parameters["p2"]); + Assert.Equal("45", parameters["blur"]); + Assert.Equal("23", parameters["blob"]); + } + + [Fact] + public void GetParametersNonStrictFromMultipleQueryStringWithOptionalParameters() + { + var uri = new Uri("http://example.com/foo/bar"); + + var template = new UriTemplate("http://example.com/{+p1}/{p2*}{?blur,blob}"); + + var parameters = template.GetParametersNonStrict(uri); + + Assert.Equal("foo", parameters["p1"]); + Assert.Equal("bar", parameters["p2"]); + } + + [Fact] + public void GetParametersNonStrictIgnoresOrderOfQueryParameters() + { + var uri = new Uri("http://example.com/foo/bar?blob=23&blur=45&irrelevant=true"); + + var template = new UriTemplate("http://example.com/{+p1}/{p2*}{?blur,blob}"); + + var parameters = template.GetParametersNonStrict(uri); + + Assert.Equal(4, parameters.Count); + Assert.Equal("foo", parameters["p1"]); + Assert.Equal("bar", parameters["p2"]); + Assert.Equal("23", parameters["blob"]); + Assert.Equal("45", parameters["blur"]); + } + + [Fact] + public void GetParametersNonStrictReturnsEmptyDictionaryWhenTheresNoMatch() + { + var uri = new Uri("http://example.com/foo/bar?one=23&two=45"); + + var template = new UriTemplate("http://example.com/foo/bar{?blur,blob}"); + + var parameters = template.GetParametersNonStrict(uri); + + Assert.NotNull(parameters); + Assert.Equal(0, parameters.Count); + } + + #endregion [Fact] public void TestGlimpseUrl() @@ -163,8 +315,6 @@ public void TestUrlWithQuestionMarkAsFirstCharacter() } - - [Fact] public void TestExactParameterCount() { diff --git a/src/UriTemplates/UriTemplate.cs b/src/UriTemplates/UriTemplate.cs index d724de1..abff6c6 100644 --- a/src/UriTemplates/UriTemplate.cs +++ b/src/UriTemplates/UriTemplate.cs @@ -13,450 +13,486 @@ namespace Tavis.UriTemplates using System.Text; - public class UriTemplate + public class UriTemplate + { + + + private static Dictionary _Operators = new Dictionary() { + {'\0', new OperatorInfo {Default = true, First = "", Seperator = ',', Named = false, IfEmpty = "",AllowReserved = false}}, + {'+', new OperatorInfo {Default = false, First = "", Seperator = ',', Named = false, IfEmpty = "",AllowReserved = true}}, + {'.', new OperatorInfo {Default = false, First = ".", Seperator = '.', Named = false, IfEmpty = "",AllowReserved = false}}, + {'/', new OperatorInfo {Default = false, First = "/", Seperator = '/', Named = false, IfEmpty = "",AllowReserved = false}}, + {';', new OperatorInfo {Default = false, First = ";", Seperator = ';', Named = true, IfEmpty = "",AllowReserved = false}}, + {'?', new OperatorInfo {Default = false, First = "?", Seperator = '&', Named = true, IfEmpty = "=",AllowReserved = false}}, + {'&', new OperatorInfo {Default = false, First = "&", Seperator = '&', Named = true, IfEmpty = "=",AllowReserved = false}}, + {'#', new OperatorInfo {Default = false, First = "#", Seperator = ',', Named = false, IfEmpty = "",AllowReserved = true}} + }; + + private readonly string _template; + private readonly Dictionary _Parameters; + private enum States { CopyingLiterals, ParsingExpression } + + + private readonly bool _resolvePartially; + + public UriTemplate(string template, bool resolvePartially = false, bool caseInsensitiveParameterNames = false) { + _resolvePartially = resolvePartially; + _template = template; + _Parameters = caseInsensitiveParameterNames + ? new Dictionary(StringComparer.OrdinalIgnoreCase) + : new Dictionary(); + } + public override string ToString() + { + return _template; + } - private static Dictionary _Operators = new Dictionary() { - {'\0', new OperatorInfo {Default = true, First = "", Seperator = ',', Named = false, IfEmpty = "",AllowReserved = false}}, - {'+', new OperatorInfo {Default = false, First = "", Seperator = ',', Named = false, IfEmpty = "",AllowReserved = true}}, - {'.', new OperatorInfo {Default = false, First = ".", Seperator = '.', Named = false, IfEmpty = "",AllowReserved = false}}, - {'/', new OperatorInfo {Default = false, First = "/", Seperator = '/', Named = false, IfEmpty = "",AllowReserved = false}}, - {';', new OperatorInfo {Default = false, First = ";", Seperator = ';', Named = true, IfEmpty = "",AllowReserved = false}}, - {'?', new OperatorInfo {Default = false, First = "?", Seperator = '&', Named = true, IfEmpty = "=",AllowReserved = false}}, - {'&', new OperatorInfo {Default = false, First = "&", Seperator = '&', Named = true, IfEmpty = "=",AllowReserved = false}}, - {'#', new OperatorInfo {Default = false, First = "#", Seperator = ',', Named = false, IfEmpty = "",AllowReserved = true}} - }; - - private readonly string _template; - private readonly Dictionary _Parameters; - private enum States { CopyingLiterals, ParsingExpression } - + public void SetParameter(string name, object value) + { + _Parameters[name] = value; + } - private readonly bool _resolvePartially; + public void ClearParameter(string name) + { + _Parameters.Remove(name); + } - public UriTemplate(string template, bool resolvePartially = false, bool caseInsensitiveParameterNames = false) - { - _resolvePartially = resolvePartially; - _template = template; - _Parameters = caseInsensitiveParameterNames - ? new Dictionary(StringComparer.OrdinalIgnoreCase) - : new Dictionary(); - } + public void SetParameter(string name, string value) + { + _Parameters[name] = value; + } - public override string ToString() - { - return _template; - } + public void SetParameter(string name, IEnumerable value) + { + _Parameters[name] = value; + } - public void SetParameter(string name, object value) - { - _Parameters[name] = value; - } + public void SetParameter(string name, IDictionary value) + { + _Parameters[name] = value; + } - public void ClearParameter(string name) - { - _Parameters.Remove(name); - } + public IEnumerable GetParameterNames() + { + Result result = ResolveResult(); + return result.ParameterNames; + } - public void SetParameter(string name, string value) - { - _Parameters[name] = value; - } + public string Resolve() + { + var result = ResolveResult(); + return result.ToString(); + } - public void SetParameter(string name, IEnumerable value) + private Result ResolveResult() + { + var currentState = States.CopyingLiterals; + var result = new Result(); + StringBuilder currentExpression = null; + foreach (var character in _template.ToCharArray()) { - _Parameters[name] = value; - } + switch (currentState) + { + case States.CopyingLiterals: + if (character == '{') + { + currentState = States.ParsingExpression; + currentExpression = new StringBuilder(); + } + else if (character == '}') + { + throw new ArgumentException("Malformed template, unexpected } : " + result.ToString()); + } + else + { + result.Append(character); + } + break; + case States.ParsingExpression: + if (character == '}') + { + ProcessExpression(currentExpression, result); - public void SetParameter(string name, IDictionary value) - { - _Parameters[name] = value; - } + currentState = States.CopyingLiterals; + } + else + { + currentExpression.Append(character); + } - public IEnumerable GetParameterNames() + break; + } + } + if (currentState == States.ParsingExpression) { - Result result = ResolveResult(); - return result.ParameterNames; + result.Append("{"); + result.Append(currentExpression.ToString()); + + throw new ArgumentException("Malformed template, missing } : " + result.ToString()); } - public string Resolve() + if (result.ErrorDetected) { - var result = ResolveResult(); - return result.ToString(); + throw new ArgumentException("Malformed template : " + result.ToString()); } + return result; + } + + private void ProcessExpression(StringBuilder currentExpression, Result result) + { - private Result ResolveResult() + if (currentExpression.Length == 0) { - var currentState = States.CopyingLiterals; - var result = new Result(); - StringBuilder currentExpression = null; - foreach (var character in _template.ToCharArray()) - { - switch (currentState) - { - case States.CopyingLiterals: - if (character == '{') - { - currentState = States.ParsingExpression; - currentExpression = new StringBuilder(); - } - else if (character == '}') - { - throw new ArgumentException("Malformed template, unexpected } : " + result.ToString()); - } - else - { - result.Append(character); - } - break; - case States.ParsingExpression: - if (character == '}') - { - ProcessExpression(currentExpression, result); - - currentState = States.CopyingLiterals; - } - else - { - currentExpression.Append(character); - } - - break; - } - } - if (currentState == States.ParsingExpression) - { - result.Append("{"); - result.Append(currentExpression.ToString()); + result.ErrorDetected = true; + result.Append("{}"); + return; + } - throw new ArgumentException("Malformed template, missing } : " + result.ToString()); - } + OperatorInfo op = GetOperator(currentExpression[0]); - if (result.ErrorDetected) - { - throw new ArgumentException("Malformed template : " + result.ToString()); - } - return result; - } + var firstChar = op.Default ? 0 : 1; + bool multivariableExpression = false; - private void ProcessExpression(StringBuilder currentExpression, Result result) + var varSpec = new VarSpec(op); + for (int i = firstChar; i < currentExpression.Length; i++) { - - if (currentExpression.Length == 0) + char currentChar = currentExpression[i]; + switch (currentChar) { - result.ErrorDetected = true; - result.Append("{}"); - return; - } - - OperatorInfo op = GetOperator(currentExpression[0]); + case '*': + varSpec.Explode = true; + break; - var firstChar = op.Default ? 0 : 1; - bool multivariableExpression = false; + case ':': // Parse Prefix Modifier + var prefixText = new StringBuilder(); + currentChar = currentExpression[++i]; + while (currentChar >= '0' && currentChar <= '9' && i < currentExpression.Length) + { + prefixText.Append(currentChar); + i++; + if (i < currentExpression.Length) currentChar = currentExpression[i]; + } + varSpec.PrefixLength = int.Parse(prefixText.ToString()); + i--; + break; - var varSpec = new VarSpec(op); - for (int i = firstChar; i < currentExpression.Length; i++) - { - char currentChar = currentExpression[i]; - switch (currentChar) - { - case '*': - varSpec.Explode = true; - break; - - case ':': // Parse Prefix Modifier - var prefixText = new StringBuilder(); - currentChar = currentExpression[++i]; - while (currentChar >= '0' && currentChar <= '9' && i < currentExpression.Length) - { - prefixText.Append(currentChar); - i++; - if (i < currentExpression.Length) currentChar = currentExpression[i]; - } - varSpec.PrefixLength = int.Parse(prefixText.ToString()); - i--; - break; - - case ',': - multivariableExpression = true; - var success = ProcessVariable(varSpec, result, multivariableExpression); - bool isFirst = varSpec.First; - // Reset for new variable - varSpec = new VarSpec(op); - if (success || !isFirst || _resolvePartially) varSpec.First = false; - if (!success && _resolvePartially) {result.Append(",") ; } - break; + case ',': + multivariableExpression = true; + var success = ProcessVariable(varSpec, result, multivariableExpression); + bool isFirst = varSpec.First; + // Reset for new variable + varSpec = new VarSpec(op); + if (success || !isFirst || _resolvePartially) varSpec.First = false; + if (!success && _resolvePartially) {result.Append(",") ; } + break; - default: - if (IsVarNameChar(currentChar)) - { - varSpec.VarName.Append(currentChar); - } - else - { - result.ErrorDetected = true; - } - break; - } + default: + if (IsVarNameChar(currentChar)) + { + varSpec.VarName.Append(currentChar); + } + else + { + result.ErrorDetected = true; + } + break; } - - ProcessVariable(varSpec, result, multivariableExpression); - if (multivariableExpression && _resolvePartially) result.Append("}"); } - private bool ProcessVariable(VarSpec varSpec, Result result, bool multiVariableExpression = false) - { - var varname = varSpec.VarName.ToString(); - result.ParameterNames.Add(varname); + ProcessVariable(varSpec, result, multivariableExpression); + if (multivariableExpression && _resolvePartially) result.Append("}"); + } - if (!_Parameters.ContainsKey(varname) - || _Parameters[varname] == null - || (_Parameters[varname] is IList && ((IList) _Parameters[varname]).Count == 0) - || (_Parameters[varname] is IDictionary && ((IDictionary) _Parameters[varname]).Count == 0)) + private bool ProcessVariable(VarSpec varSpec, Result result, bool multiVariableExpression = false) + { + var varname = varSpec.VarName.ToString(); + result.ParameterNames.Add(varname); + + if (!_Parameters.ContainsKey(varname) + || _Parameters[varname] == null + || (_Parameters[varname] is IList && ((IList) _Parameters[varname]).Count == 0) + || (_Parameters[varname] is IDictionary && ((IDictionary) _Parameters[varname]).Count == 0)) + { + if (_resolvePartially == true) { - if (_resolvePartially == true) + if (multiVariableExpression) { - if (multiVariableExpression) - { - if (varSpec.First) - { - result.Append("{"); - } - - result.Append(varSpec.ToString()); - } - else + if (varSpec.First) { result.Append("{"); - result.Append(varSpec.ToString()); - result.Append("}"); } - return false; + + result.Append(varSpec.ToString()); + } + else + { + result.Append("{"); + result.Append(varSpec.ToString()); + result.Append("}"); } return false; } + return false; + } - if (varSpec.First) + if (varSpec.First) + { + result.Append(varSpec.OperatorInfo.First); + } + else + { + result.Append(varSpec.OperatorInfo.Seperator); + } + + object value = _Parameters[varname]; + + // Handle Strings + if (value is string) + { + var stringValue = (string)value; + if (varSpec.OperatorInfo.Named) { - result.Append(varSpec.OperatorInfo.First); + result.AppendName(varname, varSpec.OperatorInfo, string.IsNullOrEmpty(stringValue)); } - else + result.AppendValue(stringValue, varSpec.PrefixLength, varSpec.OperatorInfo.AllowReserved); + } + else + { + // Handle Lists + var list = value as IList; + if (list == null && value is IEnumerable) { - result.Append(varSpec.OperatorInfo.Seperator); - } - - object value = _Parameters[varname]; - - // Handle Strings - if (value is string) + list = ((IEnumerable)value).ToList(); + } ; + if (list != null) { - var stringValue = (string)value; - if (varSpec.OperatorInfo.Named) + if (varSpec.OperatorInfo.Named && !varSpec.Explode) // exploding will prefix with list name { - result.AppendName(varname, varSpec.OperatorInfo, string.IsNullOrEmpty(stringValue)); + result.AppendName(varname, varSpec.OperatorInfo, list.Count == 0); } - result.AppendValue(stringValue, varSpec.PrefixLength, varSpec.OperatorInfo.AllowReserved); + + result.AppendList(varSpec.OperatorInfo, varSpec.Explode, varname, list); } else { - // Handle Lists - var list = value as IList; - if (list == null && value is IEnumerable) - { - list = ((IEnumerable)value).ToList(); - } ; - if (list != null) + + // Handle associative arrays + var dictionary = value as IDictionary; + if (dictionary != null) { if (varSpec.OperatorInfo.Named && !varSpec.Explode) // exploding will prefix with list name { - result.AppendName(varname, varSpec.OperatorInfo, list.Count == 0); + result.AppendName(varname, varSpec.OperatorInfo, dictionary.Count() == 0); } - - result.AppendList(varSpec.OperatorInfo, varSpec.Explode, varname, list); + result.AppendDictionary(varSpec.OperatorInfo, varSpec.Explode, dictionary); } else { - - // Handle associative arrays - var dictionary = value as IDictionary; - if (dictionary != null) + // If above all fails, convert the object to string using the default object.ToString() implementation + var stringValue = value.ToString(); + if (varSpec.OperatorInfo.Named) { - if (varSpec.OperatorInfo.Named && !varSpec.Explode) // exploding will prefix with list name - { - result.AppendName(varname, varSpec.OperatorInfo, dictionary.Count() == 0); - } - result.AppendDictionary(varSpec.OperatorInfo, varSpec.Explode, dictionary); + result.AppendName(varname, varSpec.OperatorInfo, string.IsNullOrEmpty(stringValue)); } - else - { - // If above all fails, convert the object to string using the default object.ToString() implementation - var stringValue = value.ToString(); - if (varSpec.OperatorInfo.Named) - { - result.AppendName(varname, varSpec.OperatorInfo, string.IsNullOrEmpty(stringValue)); - } - result.AppendValue(stringValue, varSpec.PrefixLength, varSpec.OperatorInfo.AllowReserved); - } - + result.AppendValue(stringValue, varSpec.PrefixLength, varSpec.OperatorInfo.AllowReserved); } } - return true; - } - private static bool IsVarNameChar(char c) - { - return ((c >= 'A' && c <= 'z') //Alpha - || (c >= '0' && c <= '9') // Digit - || c == '_' - || c == '%' - || c == '.'); } + return true; + } - private static OperatorInfo GetOperator(char operatorIndicator) - { - OperatorInfo op; - switch (operatorIndicator) - { + private static bool IsVarNameChar(char c) + { + return ((c >= 'A' && c <= 'z') //Alpha + || (c >= '0' && c <= '9') // Digit + || c == '_' + || c == '%' + || c == '.'); + } - case '+': - case ';': - case '/': - case '#': - case '&': - case '?': - case '.': - op = _Operators[operatorIndicator]; - break; + private static OperatorInfo GetOperator(char operatorIndicator) + { + OperatorInfo op; + switch (operatorIndicator) + { - default: - op = _Operators['\0']; - break; - } - return op; + case '+': + case ';': + case '/': + case '#': + case '&': + case '?': + case '.': + op = _Operators[operatorIndicator]; + break; + + default: + op = _Operators['\0']; + break; } + return op; + } - private const string varname = "[a-zA-Z0-9_]*"; - private const string op = "(?[+#./;?&]?)"; - private const string var = "(?(?:(?" + varname + ")[*]?,?)*)"; - private const string varspec = "(?{" + op + var + "})"; + private const string varname = "[a-zA-Z0-9_]*"; + private const string op = "(?[+#./;?&]?)"; + private const string var = "(?(?:(?" + varname + ")[*]?,?)*)"; + private const string varspec = "(?{" + op + var + "})"; - // (?{(?[+#./;?&]?)(?[a-zA-Z0-9_]*[*]?|(?:(?[a-zA-Z0-9_]*[*]?),?)*)}) + // (?{(?[+#./;?&]?)(?[a-zA-Z0-9_]*[*]?|(?:(?[a-zA-Z0-9_]*[*]?),?)*)}) - private Regex _ParameterRegex = null; + private Regex _ParameterRegex = null; - public IDictionary GetParameters(Uri uri) + /// + /// Gets the values of URI template parameters and returns them as a dictionary. + /// Be aware that the order of parameters is significant here, i. e. given a template "http://test.tld{? foo, bar}", + /// then "http://test.tld?foo=1&bar=2" will get 2 matching parameters, while "http://test.tld?bar=2&foo=1" will get none. + /// Use if you want a less strict behaviour. + /// + /// The uri to extract the parameters from. + /// A dictionary containing URI template parameters and their values. Will return null when no parameter is found. + public IDictionary GetParameters(Uri uri) + { + if (_ParameterRegex == null) { - if (_ParameterRegex == null) + var matchingRegex = CreateMatchingRegex(_template); + lock (this) { - var matchingRegex = CreateMatchingRegex(_template); - lock (this) - { - _ParameterRegex = new Regex(matchingRegex); - } + _ParameterRegex = new Regex(matchingRegex); } + } - var match = _ParameterRegex.Match(uri.OriginalString); - var parameters = new Dictionary(); + var match = _ParameterRegex.Match(uri.OriginalString); + var parameters = new Dictionary(); - for(int x = 1; x < match.Groups.Count; x ++) + for (int x = 1; x < match.Groups.Count; x++) + { + if (match.Groups[x].Success) { - if (match.Groups[x].Success) + var paramName = _ParameterRegex.GroupNameFromNumber(x); + if (!string.IsNullOrEmpty(paramName)) { - var paramName = _ParameterRegex.GroupNameFromNumber(x); - if (!string.IsNullOrEmpty(paramName)) - { - parameters.Add(paramName, Uri.UnescapeDataString(match.Groups[x].Value)); - } - + parameters.Add(paramName, Uri.UnescapeDataString(match.Groups[x].Value)); } + } - return match.Success ? parameters : null; } + return match.Success ? parameters : null; + + } - public static string CreateMatchingRegex(string uriTemplate) + /// + /// Gets the values of URI template parameters and returns them as a dictionary. + /// The difference to is that the order of query string params is not significant here. + /// + /// The uri to extract the parameters from. + /// A dictionary containing URI template parameters and their values. + public IDictionary GetParametersNonStrict(Uri uri) + { + if (string.IsNullOrEmpty(uri.Query)) { - var findParam = new Regex(varspec); + return GetParameters(uri) ?? new Dictionary(); + } - var template = new Regex(@"([^{]|^)\?").Replace(uriTemplate, @"$+\?"); ;//.Replace("?",@"\?"); - var regex = findParam.Replace(template, delegate (Match m) - { - var paramNames = m.Groups["lvar"].Captures.Cast().Where(c => !string.IsNullOrEmpty(c.Value)).Select(c => c.Value).ToList(); - var op = m.Groups["op"].Value; - switch (op) - { - case "?": - return GetQueryExpression(paramNames, prefix: "?"); - case "&": - return GetQueryExpression(paramNames, prefix: "&"); - case "#": - return GetExpression(paramNames, prefix: "#" ); - case "/": - return GetExpression(paramNames, prefix: "/"); - - case "+": - return GetExpression(paramNames); - default: - return GetExpression(paramNames); - } - - }); + // Note that the search for parameters independent of order can also be done using regex, but will not perform well. - return regex +"$"; - } + var uriWithoutQuery = new Uri(uri.ToString().Replace(uri.Query, ""), UriKind.RelativeOrAbsolute); + + var parameters = GetParameters(uriWithoutQuery) ?? new Dictionary(); - private static string GetQueryExpression(List paramNames, string prefix) + var parameterNames = GetParameterNames(); + foreach(var queryStringParameter in uri.GetQueryStringParameters()) { - StringBuilder sb = new StringBuilder(); - foreach (var paramname in paramNames) + if (parameterNames.Contains(queryStringParameter.Key)) { + parameters.Add(queryStringParameter.Key, queryStringParameter.Value); + } + } + + return parameters; + } - sb.Append(@"\"+prefix+"?"); - if (prefix == "?") prefix = "&"; + public static string CreateMatchingRegex(string uriTemplate) + { + var findParam = new Regex(varspec); - sb.Append("(?:"); - sb.Append(paramname); - sb.Append("="); - - sb.Append("(?<"); - sb.Append(paramname); - sb.Append(">"); - sb.Append("[^/?&]+"); - sb.Append(")"); - sb.Append(")?"); + var template = new Regex(@"([^{]|^)\?").Replace(uriTemplate, @"$+\?"); ;//.Replace("?",@"\?"); + var regex = findParam.Replace(template, delegate (Match m) + { + var paramNames = m.Groups["lvar"].Captures.Cast().Where(c => !string.IsNullOrEmpty(c.Value)).Select(c => c.Value).ToList(); + var op = m.Groups["op"].Value; + switch (op) + { + case "?": + return GetQueryExpression(paramNames, prefix: "?"); + case "&": + return GetQueryExpression(paramNames, prefix: "&"); + case "#": + return GetExpression(paramNames, prefix: "#" ); + case "/": + return GetExpression(paramNames, prefix: "/"); + + case "+": + return GetExpression(paramNames); + default: + return GetExpression(paramNames); } + + }); + + return regex +"$"; + } + + private static string GetQueryExpression(List paramNames, string prefix) + { + StringBuilder sb = new StringBuilder(); + foreach (var paramname in paramNames) + { + + sb.Append(@"\"+prefix+"?"); + if (prefix == "?") prefix = "&"; - return sb.ToString(); + sb.Append("(?:"); + sb.Append(paramname); + sb.Append("="); + + sb.Append("(?<"); + sb.Append(paramname); + sb.Append(">"); + sb.Append("[^/?&]+"); + sb.Append(")"); + sb.Append(")?"); } + return sb.ToString(); + } + + private static string GetExpression(List paramNames, string prefix = null) + { + StringBuilder sb = new StringBuilder(); - private static string GetExpression(List paramNames, string prefix = null) + foreach (var paramname in paramNames) { - StringBuilder sb = new StringBuilder(); + if (string.IsNullOrEmpty(paramname)) continue; - foreach (var paramname in paramNames) + if (prefix != null) { - if (string.IsNullOrEmpty(paramname)) continue; - - if (prefix != null) - { - sb.Append(@"\" + prefix + "?"); - prefix = ","; - } - sb.Append("(?<"); - sb.Append(paramname); - sb.Append(">"); - sb.Append("[^/?&,]+"); // Param Value - sb.Append(")?"); + sb.Append(@"\" + prefix + "?"); + prefix = ","; } - - return sb.ToString(); + sb.Append("(?<"); + sb.Append(paramname); + sb.Append(">"); + sb.Append("[^/?&,]+"); // Param Value + sb.Append(")?"); } - + return sb.ToString(); } - - } + } +}