Merge branch 'feature/json-path-filter-using-regex' of https://github.com/andre-n/Newtonsoft.Json into andre-n-feature/json-path-filter-using-regex
This commit is contained in:
@@ -314,6 +314,17 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath
|
||||
Assert.AreEqual("h\\i", (string)(JToken)expressions.Right);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SinglePropertyAndFilterWithRegex()
|
||||
{
|
||||
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;
|
||||
Assert.AreEqual(QueryOperator.RegExEquals, expressions.Operator);
|
||||
Assert.AreEqual("/hi/i", (string)(JToken)expressions.Right);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SinglePropertyAndFilterWithUnknownEscape()
|
||||
{
|
||||
|
||||
@@ -157,6 +157,70 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath
|
||||
|
||||
Assert.IsFalse(compositeExpression.IsMatch(o3, o3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BooleanExpressionTest_RegExEqualsOperator()
|
||||
{
|
||||
BooleanQueryExpression e1 = new BooleanQueryExpression
|
||||
{
|
||||
Operator = QueryOperator.RegExEquals,
|
||||
Right = new JValue("/foo.*d/"),
|
||||
Left = new List<PathFilter>
|
||||
{
|
||||
new ArrayIndexFilter()
|
||||
}
|
||||
};
|
||||
|
||||
Assert.IsTrue(e1.IsMatch(null, new JArray("food")));
|
||||
Assert.IsTrue(e1.IsMatch(null, new JArray("fooood and drink")));
|
||||
Assert.IsFalse(e1.IsMatch(null, new JArray("FOOD")));
|
||||
Assert.IsFalse(e1.IsMatch(null, new JArray("foo", "foog", "good")));
|
||||
|
||||
BooleanQueryExpression e2 = new BooleanQueryExpression
|
||||
{
|
||||
Operator = QueryOperator.RegExEquals,
|
||||
Right = new JValue("/Foo.*d/i"),
|
||||
Left = new List<PathFilter>
|
||||
{
|
||||
new ArrayIndexFilter()
|
||||
}
|
||||
};
|
||||
|
||||
Assert.IsTrue(e2.IsMatch(null, new JArray("food")));
|
||||
Assert.IsTrue(e2.IsMatch(null, new JArray("fooood and drink")));
|
||||
Assert.IsTrue(e2.IsMatch(null, new JArray("FOOD")));
|
||||
Assert.IsFalse(e2.IsMatch(null, new JArray("foo", "foog", "good")));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BooleanExpressionTest_RegExEqualsOperator_CornerCase()
|
||||
{
|
||||
BooleanQueryExpression e1 = new BooleanQueryExpression
|
||||
{
|
||||
Operator = QueryOperator.RegExEquals,
|
||||
Right = new JValue("/// comment/"),
|
||||
Left = new List<PathFilter>
|
||||
{
|
||||
new ArrayIndexFilter()
|
||||
}
|
||||
};
|
||||
|
||||
Assert.IsTrue(e1.IsMatch(null, new JArray("// comment")));
|
||||
Assert.IsFalse(e1.IsMatch(null, new JArray("//comment", "/ comment")));
|
||||
|
||||
BooleanQueryExpression e2 = new BooleanQueryExpression
|
||||
{
|
||||
Operator = QueryOperator.RegExEquals,
|
||||
Right = new JValue("/<tag>.*</tag>/i"),
|
||||
Left = new List<PathFilter>
|
||||
{
|
||||
new ArrayIndexFilter()
|
||||
}
|
||||
};
|
||||
|
||||
Assert.IsTrue(e2.IsMatch(null, new JArray("<Tag>Test</Tag>", "")));
|
||||
Assert.IsFalse(e2.IsMatch(null, new JArray("<tag>Test<tag>")));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BooleanExpressionTest()
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -154,25 +155,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);
|
||||
}
|
||||
|
||||
@@ -647,6 +647,11 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (currentChar == '/')
|
||||
{
|
||||
value = ReadRegexString();
|
||||
return true;
|
||||
}
|
||||
|
||||
value = null;
|
||||
return false;
|
||||
@@ -712,6 +717,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;
|
||||
@@ -742,6 +824,12 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
{
|
||||
return QueryOperator.Equals;
|
||||
}
|
||||
|
||||
if (Match("=~"))
|
||||
{
|
||||
return QueryOperator.RegExEquals;
|
||||
}
|
||||
|
||||
if (Match("!=") || Match("<>"))
|
||||
{
|
||||
return QueryOperator.NotEquals;
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
#if !HAVE_LINQ
|
||||
using Newtonsoft.Json.Utilities.LinqBridge;
|
||||
#else
|
||||
@@ -22,7 +23,8 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
GreaterThan = 6,
|
||||
GreaterThanOrEquals = 7,
|
||||
And = 8,
|
||||
Or = 9
|
||||
Or = 9,
|
||||
RegExEquals = 10
|
||||
}
|
||||
|
||||
internal abstract class QueryExpression
|
||||
@@ -126,6 +128,12 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
{
|
||||
switch (Operator)
|
||||
{
|
||||
case QueryOperator.RegExEquals:
|
||||
if (RegexEquals(leftValue, rightValue))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case QueryOperator.Equals:
|
||||
if (EqualsWithStringCoercion(leftValue, rightValue))
|
||||
{
|
||||
@@ -181,6 +189,22 @@ namespace Newtonsoft.Json.Linq.JsonPath
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool RegexEquals(JValue input, JValue pattern)
|
||||
{
|
||||
if (input.Type != JTokenType.String || pattern.Type != JTokenType.String)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var regexText = (string)pattern.Value;
|
||||
int patternOptionDelimiterIndex = regexText.LastIndexOf('/');
|
||||
|
||||
string patternText = regexText.Substring(1, patternOptionDelimiterIndex - 1);
|
||||
string optionsText = regexText.Substring(patternOptionDelimiterIndex + 1);
|
||||
|
||||
return Regex.IsMatch((string)input.Value, patternText, MiscellaneousUtils.GetRegexOptions(optionsText));
|
||||
}
|
||||
|
||||
private bool EqualsWithStringCoercion(JValue value, JValue queryValue)
|
||||
{
|
||||
if (value.Equals(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
|
||||
{
|
||||
@@ -150,5 +151,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