csharp-sdk-upm/Libs/Newtonsoft.Json.AOT/Linq/JsonPath/QueryExpression.cs

328 lines
11 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
using System.Diagnostics;
#if !HAVE_LINQ
using LC.Newtonsoft.Json.Utilities.LinqBridge;
#else
using System.Linq;
#endif
using LC.Newtonsoft.Json.Utilities;
namespace LC.Newtonsoft.Json.Linq.JsonPath
{
internal enum QueryOperator
{
None = 0,
Equals = 1,
NotEquals = 2,
Exists = 3,
LessThan = 4,
LessThanOrEquals = 5,
GreaterThan = 6,
GreaterThanOrEquals = 7,
And = 8,
Or = 9,
RegexEquals = 10,
StrictEquals = 11,
StrictNotEquals = 12
}
internal abstract class QueryExpression
{
internal QueryOperator Operator;
public QueryExpression(QueryOperator @operator)
{
Operator = @operator;
}
// For unit tests
public bool IsMatch(JToken root, JToken t)
{
return IsMatch(root, t, null);
}
public abstract bool IsMatch(JToken root, JToken t, JsonSelectSettings? settings);
}
internal class CompositeExpression : QueryExpression
{
public List<QueryExpression> Expressions { get; set; }
public CompositeExpression(QueryOperator @operator) : base(@operator)
{
Expressions = new List<QueryExpression>();
}
public override bool IsMatch(JToken root, JToken t, JsonSelectSettings? settings)
{
switch (Operator)
{
case QueryOperator.And:
foreach (QueryExpression e in Expressions)
{
if (!e.IsMatch(root, t, settings))
{
return false;
}
}
return true;
case QueryOperator.Or:
foreach (QueryExpression e in Expressions)
{
if (e.IsMatch(root, t, settings))
{
return true;
}
}
return false;
default:
throw new ArgumentOutOfRangeException();
}
}
}
internal class BooleanQueryExpression : QueryExpression
{
public readonly object Left;
public readonly object? Right;
public BooleanQueryExpression(QueryOperator @operator, object left, object? right) : base(@operator)
{
Left = left;
Right = right;
}
private IEnumerable<JToken> GetResult(JToken root, JToken t, object? o)
{
if (o is JToken resultToken)
{
return new[] { resultToken };
}
if (o is List<PathFilter> pathFilters)
{
return JPath.Evaluate(pathFilters, root, t, null);
}
return CollectionUtils.ArrayEmpty<JToken>();
}
public override bool IsMatch(JToken root, JToken t, JsonSelectSettings? settings)
{
if (Operator == QueryOperator.Exists)
{
return GetResult(root, t, Left).Any();
}
using (IEnumerator<JToken> leftResults = GetResult(root, t, Left).GetEnumerator())
{
if (leftResults.MoveNext())
{
IEnumerable<JToken> rightResultsEn = GetResult(root, t, Right);
ICollection<JToken> rightResults = rightResultsEn as ICollection<JToken> ?? rightResultsEn.ToList();
do
{
JToken leftResult = leftResults.Current;
foreach (JToken rightResult in rightResults)
{
if (MatchTokens(leftResult, rightResult, settings))
{
return true;
}
}
} while (leftResults.MoveNext());
}
}
return false;
}
private bool MatchTokens(JToken leftResult, JToken rightResult, JsonSelectSettings? settings)
{
if (leftResult is JValue leftValue && rightResult is JValue rightValue)
{
switch (Operator)
{
case QueryOperator.RegexEquals:
if (RegexEquals(leftValue, rightValue, settings))
{
return true;
}
break;
case QueryOperator.Equals:
if (EqualsWithStringCoercion(leftValue, rightValue))
{
return true;
}
break;
case QueryOperator.StrictEquals:
if (EqualsWithStrictMatch(leftValue, rightValue))
{
return true;
}
break;
case QueryOperator.NotEquals:
if (!EqualsWithStringCoercion(leftValue, rightValue))
{
return true;
}
break;
case QueryOperator.StrictNotEquals:
if (!EqualsWithStrictMatch(leftValue, rightValue))
{
return true;
}
break;
case QueryOperator.GreaterThan:
if (leftValue.CompareTo(rightValue) > 0)
{
return true;
}
break;
case QueryOperator.GreaterThanOrEquals:
if (leftValue.CompareTo(rightValue) >= 0)
{
return true;
}
break;
case QueryOperator.LessThan:
if (leftValue.CompareTo(rightValue) < 0)
{
return true;
}
break;
case QueryOperator.LessThanOrEquals:
if (leftValue.CompareTo(rightValue) <= 0)
{
return true;
}
break;
case QueryOperator.Exists:
return true;
}
}
else
{
switch (Operator)
{
case QueryOperator.Exists:
// you can only specify primitive types in a comparison
// notequals will always be true
case QueryOperator.NotEquals:
return true;
}
}
return false;
}
private static bool RegexEquals(JValue input, JValue pattern, JsonSelectSettings? settings)
{
if (input.Type != JTokenType.String || pattern.Type != JTokenType.String)
{
return false;
}
string regexText = (string)pattern.Value!;
int patternOptionDelimiterIndex = regexText.LastIndexOf('/');
string patternText = regexText.Substring(1, patternOptionDelimiterIndex - 1);
string optionsText = regexText.Substring(patternOptionDelimiterIndex + 1);
#if HAVE_REGEX_TIMEOUTS
TimeSpan timeout = settings?.RegexMatchTimeout ?? Regex.InfiniteMatchTimeout;
return Regex.IsMatch((string)input.Value!, patternText, MiscellaneousUtils.GetRegexOptions(optionsText), timeout);
#else
return Regex.IsMatch((string)input.Value!, patternText, MiscellaneousUtils.GetRegexOptions(optionsText));
#endif
}
internal static bool EqualsWithStringCoercion(JValue value, JValue queryValue)
{
if (value.Equals(queryValue))
{
return true;
}
// Handle comparing an integer with a float
// e.g. Comparing 1 and 1.0
if ((value.Type == JTokenType.Integer && queryValue.Type == JTokenType.Float)
|| (value.Type == JTokenType.Float && queryValue.Type == JTokenType.Integer))
{
return JValue.Compare(value.Type, value.Value, queryValue.Value) == 0;
}
if (queryValue.Type != JTokenType.String)
{
return false;
}
string queryValueString = (string)queryValue.Value!;
string currentValueString;
// potential performance issue with converting every value to string?
switch (value.Type)
{
case JTokenType.Date:
using (StringWriter writer = StringUtils.CreateStringWriter(64))
{
#if HAVE_DATE_TIME_OFFSET
if (value.Value is DateTimeOffset offset)
{
DateTimeUtils.WriteDateTimeOffsetString(writer, offset, DateFormatHandling.IsoDateFormat, null, CultureInfo.InvariantCulture);
}
else
#endif
{
DateTimeUtils.WriteDateTimeString(writer, (DateTime)value.Value!, DateFormatHandling.IsoDateFormat, null, CultureInfo.InvariantCulture);
}
currentValueString = writer.ToString();
}
break;
case JTokenType.Bytes:
currentValueString = Convert.ToBase64String((byte[])value.Value!);
break;
case JTokenType.Guid:
case JTokenType.TimeSpan:
currentValueString = value.Value!.ToString();
break;
case JTokenType.Uri:
currentValueString = ((Uri)value.Value!).OriginalString;
break;
default:
return false;
}
return string.Equals(currentValueString, queryValueString, StringComparison.Ordinal);
}
internal static bool EqualsWithStrictMatch(JValue value, JValue queryValue)
{
MiscellaneousUtils.Assert(value != null);
MiscellaneousUtils.Assert(queryValue != null);
// Handle comparing an integer with a float
// e.g. Comparing 1 and 1.0
if ((value.Type == JTokenType.Integer && queryValue.Type == JTokenType.Float)
|| (value.Type == JTokenType.Float && queryValue.Type == JTokenType.Integer))
{
return JValue.Compare(value.Type, value.Value, queryValue.Value) == 0;
}
// we handle floats and integers the exact same way, so they are pseudo equivalent
if (value.Type != queryValue.Type)
{
return false;
}
return value.Equals(queryValue);
}
}
}