From 3d686e048ffbac532f116ba2e4a8a20ecf5c1fac Mon Sep 17 00:00:00 2001 From: Khalid Dermoumi Date: Tue, 21 Jun 2016 14:57:24 +0200 Subject: [PATCH 1/3] Added a unit test that shows that currently GetParameters' result depends on order of parameters --- .../ParameterMatchingTests.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/UriTemplateTests/ParameterMatchingTests.cs b/src/UriTemplateTests/ParameterMatchingTests.cs index fc8e0fd..7580f67 100644 --- a/src/UriTemplateTests/ParameterMatchingTests.cs +++ b/src/UriTemplateTests/ParameterMatchingTests.cs @@ -87,6 +87,7 @@ public void GetParametersFromMultipleQueryString() Assert.Equal("45", parameters["blur"]); } + [Fact] public void GetParametersFromMultipleQueryStringWithTwoParamValues() { @@ -104,6 +105,24 @@ public void GetParametersFromMultipleQueryStringWithTwoParamValues() } + [Fact] + public void GetParametersFromMultipleQueryStringWithTwoParamValuesDoesNotDependOnOrderOfQueryParameters() + { + 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.NotNull(parameters); + 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 GetParametersFromMultipleQueryStringWithOptionalAndMandatoryParameters() { From bab9ec45aaccb4618db178a4ffab25c8489efaec Mon Sep 17 00:00:00 2001 From: Khalid Dermoumi Date: Thu, 30 Jun 2016 18:47:53 +0200 Subject: [PATCH 2/3] Added "GetParametersNonStrict()" to create an alternative to "GetParameters()" that ignores the order of query parameters. Note that this method returns an empty dictionary instead of null when no match is found. Plus some unit tests. --- .../ParameterMatchingTests.cs | 119 ++- src/UriTemplates/UriTemplate.cs | 715 +++++++++--------- 2 files changed, 477 insertions(+), 357 deletions(-) diff --git a/src/UriTemplateTests/ParameterMatchingTests.cs b/src/UriTemplateTests/ParameterMatchingTests.cs index 7580f67..2b0f9b5 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); } @@ -106,15 +108,14 @@ public void GetParametersFromMultipleQueryStringWithTwoParamValues() } [Fact] - public void GetParametersFromMultipleQueryStringWithTwoParamValuesDoesNotDependOnOrderOfQueryParameters() + public void GetParametersFromMultipleQueryStringWithOptionalAndMandatoryParameters() { - var uri = new Uri("http://example.com/foo/bar?blob=23&blur=45"); + 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 template = new UriTemplate("http://example.com/{+p1}/{p2*}{?blur}{&blob}"); var parameters = template.GetParameters(uri); - Assert.NotNull(parameters); Assert.Equal(4, parameters.Count); Assert.Equal("foo", parameters["p1"]); Assert.Equal("bar", parameters["p2"]); @@ -124,36 +125,126 @@ public void GetParametersFromMultipleQueryStringWithTwoParamValuesDoesNotDependO } [Fact] - public void GetParametersFromMultipleQueryStringWithOptionalAndMandatoryParameters() + public void GetParametersFromMultipleQueryStringWithOptionalParameters() { - var uri = new Uri("http://example.com/foo/bar?blur=45&blob=23"); + var uri = new Uri("http://example.com/foo/bar"); - var template = new UriTemplate("http://example.com/{+p1}/{p2*}{?blur}{&blob}"); + var template = new UriTemplate("http://example.com/{+p1}/{p2*}{?blur,blob}"); var parameters = template.GetParameters(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 GetParametersFromMultipleQueryStringWithOptionalParameters() + public void GetParametersRespectsOrderOfQueryParameters() { - var uri = new Uri("http://example.com/foo/bar"); + 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 GetParametersNonStrict() + { + // Make sure GetParametersNonStrict() behaves just like GetParameters() in this case + GetParameters(); + } + + [Fact] + public void GetParametersNonStrictWithOperators() + { + // Make sure GetParametersNonStrict() behaves just like GetParameters() in this case + GetParametersWithOperators(); + } + + [Fact] + public void GetParametersNonStrictFromQueryString() + { + // Make sure GetParametersNonStrict() behaves just like GetParameters() in this case + GetParametersFromQueryString(); + } + + [Fact] + public void GetParametersNonStrictFromMultipleQueryString() + { + // Make sure GetParametersNonStrict() behaves just like GetParameters() in this case + GetParametersFromMultipleQueryString(); + } + + [Fact] + public void GetParametersNonStrictFromMultipleQueryStringWithTwoParamValues() + { + // Make sure GetParametersNonStrict() behaves just like GetParameters() in this case + GetParametersFromMultipleQueryStringWithTwoParamValues(); + } + + [Fact] + public void GetParametersNonStrictFromMultipleQueryStringWithOptionalAndMandatoryParameters() + { + // Make sure GetParametersNonStrict() behaves just like GetParameters() in this case + GetParametersFromMultipleQueryStringWithOptionalAndMandatoryParameters(); + } + + [Fact] + public void GetParametersNonStrictFromMultipleQueryStringWithOptionalParameters() + { + // Make sure GetParametersNonStrict() behaves just like GetParameters() in this case + GetParametersFromMultipleQueryStringWithOptionalParameters(); + } + + [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() @@ -182,8 +273,6 @@ public void TestUrlWithQuestionMarkAsFirstCharacter() } - - [Fact] public void TestExactParameterCount() { diff --git a/src/UriTemplates/UriTemplate.cs b/src/UriTemplates/UriTemplate.cs index d724de1..b243ed4 100644 --- a/src/UriTemplates/UriTemplate.cs +++ b/src/UriTemplates/UriTemplate.cs @@ -13,450 +13,481 @@ 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("}"); + } + + 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 (!_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) + { + result.Append(varSpec.OperatorInfo.First); + } + else + { + result.Append(varSpec.OperatorInfo.Seperator); + } + + object value = _Parameters[varname]; - if (varSpec.First) + // 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) + { + // Note that the search for parameters independent of order can also be done using regex, but will not perform well. + + var uriWithoutQuery = new Uri(uri.ToString().Replace(uri.Query, ""), UriKind.RelativeOrAbsolute); + + var parameters = GetParameters(uriWithoutQuery) ?? new Dictionary(); + + var parameterNames = GetParameterNames(); + foreach(var queryStringParameter in uri.GetQueryStringParameters()) { - var findParam = new Regex(varspec); + if (parameterNames.Contains(queryStringParameter.Key)) + { + parameters.Add(queryStringParameter.Key, queryStringParameter.Value); + } + } + + return parameters; + } - var template = new Regex(@"([^{]|^)\?").Replace(uriTemplate, @"$+\?"); ;//.Replace("?",@"\?"); - var regex = findParam.Replace(template, delegate (Match m) + public static string CreateMatchingRegex(string uriTemplate) + { + var findParam = new Regex(varspec); + + 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) { - 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); - } + 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 +"$"; - } + return regex +"$"; + } - private static string GetQueryExpression(List paramNames, string prefix) + private static string GetQueryExpression(List paramNames, string prefix) + { + StringBuilder sb = new StringBuilder(); + foreach (var paramname in paramNames) { - StringBuilder sb = new StringBuilder(); - foreach (var paramname in paramNames) - { - sb.Append(@"\"+prefix+"?"); - if (prefix == "?") prefix = "&"; + sb.Append(@"\"+prefix+"?"); + if (prefix == "?") prefix = "&"; - sb.Append("(?:"); - sb.Append(paramname); - sb.Append("="); + sb.Append("(?:"); + sb.Append(paramname); + sb.Append("="); - sb.Append("(?<"); - sb.Append(paramname); - sb.Append(">"); - sb.Append("[^/?&]+"); - sb.Append(")"); - sb.Append(")?"); - } - - return sb.ToString(); + 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(); } - - } + } +} From 79e1ba17542a4863b71311c1acaad1060b603daa Mon Sep 17 00:00:00 2001 From: Khalid Dermoumi Date: Fri, 1 Jul 2016 10:00:34 +0200 Subject: [PATCH 3/3] Fixed bug in "GetParametersNonStrict()" with empty query string --- .../ParameterMatchingTests.cs | 80 ++++++++++++++----- src/UriTemplates/UriTemplate.cs | 5 ++ 2 files changed, 66 insertions(+), 19 deletions(-) diff --git a/src/UriTemplateTests/ParameterMatchingTests.cs b/src/UriTemplateTests/ParameterMatchingTests.cs index 2b0f9b5..59bc18b 100644 --- a/src/UriTemplateTests/ParameterMatchingTests.cs +++ b/src/UriTemplateTests/ParameterMatchingTests.cs @@ -166,53 +166,95 @@ public void GetParametersReturnsNullWhenTheresNoMatch() #region GetParametersNonStrict() - [Fact] - public void GetParametersNonStrict() - { - // Make sure GetParametersNonStrict() behaves just like GetParameters() in this case - GetParameters(); - } - [Fact] public void GetParametersNonStrictWithOperators() { - // Make sure GetParametersNonStrict() behaves just like GetParameters() in this case - GetParametersWithOperators(); + 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() { - // Make sure GetParametersNonStrict() behaves just like GetParameters() in this case - GetParametersFromQueryString(); + 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() { - // Make sure GetParametersNonStrict() behaves just like GetParameters() in this case - GetParametersFromMultipleQueryString(); + 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() { - // Make sure GetParametersNonStrict() behaves just like GetParameters() in this case - GetParametersFromMultipleQueryStringWithTwoParamValues(); + 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() { - // Make sure GetParametersNonStrict() behaves just like GetParameters() in this case - GetParametersFromMultipleQueryStringWithOptionalAndMandatoryParameters(); + 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() { - // Make sure GetParametersNonStrict() behaves just like GetParameters() in this case - GetParametersFromMultipleQueryStringWithOptionalParameters(); + 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] diff --git a/src/UriTemplates/UriTemplate.cs b/src/UriTemplates/UriTemplate.cs index b243ed4..abff6c6 100644 --- a/src/UriTemplates/UriTemplate.cs +++ b/src/UriTemplates/UriTemplate.cs @@ -393,6 +393,11 @@ public IDictionary GetParameters(Uri uri) /// A dictionary containing URI template parameters and their values. public IDictionary GetParametersNonStrict(Uri uri) { + if (string.IsNullOrEmpty(uri.Query)) + { + return GetParameters(uri) ?? new Dictionary(); + } + // Note that the search for parameters independent of order can also be done using regex, but will not perform well. var uriWithoutQuery = new Uri(uri.ToString().Replace(uri.Query, ""), UriKind.RelativeOrAbsolute);