feature: implement regex match operator for JsonPath queries. Syntax is compatibe with Jayway implementation
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user