feature: implement regex match operator for JsonPath queries. Syntax is compatibe with Jayway implementation

This commit is contained in:
andre-n
2018-02-10 16:04:35 +01:00
parent 865b2076c2
commit 1876b1bfdf
5 changed files with 119 additions and 40 deletions
@@ -317,7 +317,7 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath
[Test]
public void SinglePropertyAndFilterWithRegex()
{
JPath path = new JPath("Blah[ ?( @.name=~'/hi/i' ) ]");
JPath path = new JPath("Blah[ ?( @.name=~/hi/i ) ]");
Assert.AreEqual(2, path.Filters.Count);
Assert.AreEqual("Blah", ((FieldFilter)path.Filters[0]).Name);
BooleanQueryExpression expressions = (BooleanQueryExpression)((QueryFilter)path.Filters[1]).Expression;
@@ -29,6 +29,7 @@ using Newtonsoft.Json.Bson;
using System.Globalization;
using System.Runtime.CompilerServices;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json.Utilities;
namespace Newtonsoft.Json.Converters
{
@@ -149,25 +150,7 @@ namespace Newtonsoft.Json.Converters
string patternText = regexText.Substring(1, patternOptionDelimiterIndex - 1);
string optionsText = regexText.Substring(patternOptionDelimiterIndex + 1);
RegexOptions options = RegexOptions.None;
foreach (char c in optionsText)
{
switch (c)
{
case 'i':
options |= RegexOptions.IgnoreCase;
break;
case 'm':
options |= RegexOptions.Multiline;
break;
case 's':
options |= RegexOptions.Singleline;
break;
case 'x':
options |= RegexOptions.ExplicitCapture;
break;
}
}
RegexOptions options = MiscellaneousUtils.GetRegexOptions(optionsText);
return new Regex(patternText, options);
}
@@ -649,6 +649,11 @@ namespace Newtonsoft.Json.Linq.JsonPath
return true;
}
}
else if (currentChar == '/')
{
value = ReadRegexString();
return true;
}
value = null;
return false;
@@ -698,6 +703,83 @@ namespace Newtonsoft.Json.Linq.JsonPath
throw new JsonException("Path ended with an open string.");
}
private string ReadRegexString()
{
var sb = new StringBuilder("/");
_currentIndex++;
while (_currentIndex < _expression.Length)
{
char currentChar = _expression[_currentIndex];
if (currentChar == '\\' && _currentIndex + 1 < _expression.Length)
{
_currentIndex++;
if (_expression[_currentIndex] == '\'')
{
sb.Append('\'');
}
else if (_expression[_currentIndex] == '\\')
{
sb.Append('\\');
}
else if (_expression[_currentIndex] == '/')
{
sb.Append('/');
}
else
{
throw new JsonException(@"Unknown escape character: \" + _expression[_currentIndex]);
}
_currentIndex++;
}
else if (currentChar == '/')
{
sb.Append(currentChar);
_currentIndex++;
string options = ParseRegexOptions();
sb.Append(options);
return sb.ToString();
}
else
{
_currentIndex++;
sb.Append(currentChar);
}
}
throw new JsonException("Path ended with an open string.");
}
private string ParseRegexOptions()
{
var optionsSb = new StringBuilder();
while (_currentIndex < _expression.Length)
{
char currentChar = _expression[_currentIndex];
if (currentChar == 'i'
|| currentChar == 'm'
|| currentChar == 's'
|| currentChar == 'u'
|| currentChar == 'x')
{
_currentIndex++;
optionsSb.Append(currentChar);
}
else
{
break;
}
}
return optionsSb.ToString();
}
private bool Match(string s)
{
int currentPosition = _currentIndex;
@@ -194,33 +194,20 @@ namespace Newtonsoft.Json.Linq.JsonPath
return false;
}
private bool RegExEquals(JValue value, JValue queryValue)
private static bool RegExEquals(JValue input, JValue pattern)
{
if (queryValue.Type != JTokenType.String || value.Type != JTokenType.String)
if (input.Type != JTokenType.String || pattern.Type != JTokenType.String)
{
return false;
}
var regExArray = ((string)queryValue.Value).Split('/');
if (regExArray.Length < 3)
{
throw new Exception("Syntax error when using regex compare");
}
var regexText = (string)pattern.Value;
int patternOptionDelimiterIndex = regexText.LastIndexOf('/');
var trimFirstAndLastMember = regExArray.Skip(1).Take(regExArray.Length - 2).ToArray();
var regExQuery = string.Join("/", trimFirstAndLastMember);
string patternText = regexText.Substring(1, patternOptionDelimiterIndex - 1);
string optionsText = regexText.Substring(patternOptionDelimiterIndex + 1);
if (regExArray.Last() == string.Empty)
{
return Regex.IsMatch((string)value.Value, regExQuery);
}
if (regExArray.Last() == "i")
{
return Regex.IsMatch((string)value.Value, regExQuery, RegexOptions.IgnoreCase);
}
throw new Exception("Syntax error when using regex compare");
return Regex.IsMatch((string)input.Value, patternText, MiscellaneousUtils.GetRegexOptions(optionsText));
}
private bool EqualsWithStringCoercion(JValue value, JValue queryValue)
@@ -30,6 +30,7 @@ using System.ComponentModel;
using System.Reflection;
using System.Text;
using System.Globalization;
using System.Text.RegularExpressions;
namespace Newtonsoft.Json.Utilities
{
@@ -154,5 +155,31 @@ namespace Newtonsoft.Json.Utilities
return value.ToString();
}
internal static RegexOptions GetRegexOptions(string optionsText)
{
var options = RegexOptions.None;
foreach (char c in optionsText)
{
switch (c)
{
case 'i':
options |= RegexOptions.IgnoreCase;
break;
case 'm':
options |= RegexOptions.Multiline;
break;
case 's':
options |= RegexOptions.Singleline;
break;
case 'x':
options |= RegexOptions.ExplicitCapture;
break;
}
}
return options;
}
}
}