You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

260 lines
8.5 KiB

4 years ago
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.IO;
  5. using System.Text.RegularExpressions;
  6. #if NET20
  7. using Newtonsoft.Json.Utilities.LinqBridge;
  8. #else
  9. using System.Linq;
  10. #endif
  11. using Newtonsoft.Json.Utilities;
  12. namespace Newtonsoft.Json.Linq.JsonPath
  13. {
  14. internal enum QueryOperator
  15. {
  16. None = 0,
  17. Equals = 1,
  18. NotEquals = 2,
  19. Exists = 3,
  20. LessThan = 4,
  21. LessThanOrEquals = 5,
  22. GreaterThan = 6,
  23. GreaterThanOrEquals = 7,
  24. And = 8,
  25. Or = 9,
  26. RegexEquals = 10
  27. }
  28. internal abstract class QueryExpression
  29. {
  30. public QueryOperator Operator { get; set; }
  31. public abstract bool IsMatch(JToken root, JToken t);
  32. }
  33. internal class CompositeExpression : QueryExpression
  34. {
  35. public List<QueryExpression> Expressions { get; set; }
  36. public CompositeExpression()
  37. {
  38. Expressions = new List<QueryExpression>();
  39. }
  40. public override bool IsMatch(JToken root, JToken t)
  41. {
  42. switch (Operator)
  43. {
  44. case QueryOperator.And:
  45. foreach (QueryExpression e in Expressions)
  46. {
  47. if (!e.IsMatch(root, t))
  48. {
  49. return false;
  50. }
  51. }
  52. return true;
  53. case QueryOperator.Or:
  54. foreach (QueryExpression e in Expressions)
  55. {
  56. if (e.IsMatch(root, t))
  57. {
  58. return true;
  59. }
  60. }
  61. return false;
  62. default:
  63. throw new ArgumentOutOfRangeException();
  64. }
  65. }
  66. }
  67. internal class BooleanQueryExpression : QueryExpression
  68. {
  69. public object Left { get; set; }
  70. public object Right { get; set; }
  71. private IEnumerable<JToken> GetResult(JToken root, JToken t, object o)
  72. {
  73. if (o is JToken resultToken)
  74. {
  75. return new[] { resultToken };
  76. }
  77. if (o is List<PathFilter> pathFilters)
  78. {
  79. return JPath.Evaluate(pathFilters, root, t, false);
  80. }
  81. return CollectionUtils.ArrayEmpty<JToken>();
  82. }
  83. public override bool IsMatch(JToken root, JToken t)
  84. {
  85. if (Operator == QueryOperator.Exists)
  86. {
  87. return GetResult(root, t, Left).Any();
  88. }
  89. using (IEnumerator<JToken> leftResults = GetResult(root, t, Left).GetEnumerator())
  90. {
  91. if (leftResults.MoveNext())
  92. {
  93. IEnumerable<JToken> rightResultsEn = GetResult(root, t, Right);
  94. ICollection<JToken> rightResults = rightResultsEn as ICollection<JToken> ?? rightResultsEn.List();
  95. do
  96. {
  97. JToken leftResult = leftResults.Current;
  98. foreach (JToken rightResult in rightResults)
  99. {
  100. if (MatchTokens(leftResult, rightResult))
  101. {
  102. return true;
  103. }
  104. }
  105. } while (leftResults.MoveNext());
  106. }
  107. }
  108. return false;
  109. }
  110. private bool MatchTokens(JToken leftResult, JToken rightResult)
  111. {
  112. if (leftResult is JValue leftValue && rightResult is JValue rightValue)
  113. {
  114. switch (Operator)
  115. {
  116. case QueryOperator.RegexEquals:
  117. if (RegexEquals(leftValue, rightValue))
  118. {
  119. return true;
  120. }
  121. break;
  122. case QueryOperator.Equals:
  123. if (EqualsWithStringCoercion(leftValue, rightValue))
  124. {
  125. return true;
  126. }
  127. break;
  128. case QueryOperator.NotEquals:
  129. if (!EqualsWithStringCoercion(leftValue, rightValue))
  130. {
  131. return true;
  132. }
  133. break;
  134. case QueryOperator.GreaterThan:
  135. if (leftValue.CompareTo(rightValue) > 0)
  136. {
  137. return true;
  138. }
  139. break;
  140. case QueryOperator.GreaterThanOrEquals:
  141. if (leftValue.CompareTo(rightValue) >= 0)
  142. {
  143. return true;
  144. }
  145. break;
  146. case QueryOperator.LessThan:
  147. if (leftValue.CompareTo(rightValue) < 0)
  148. {
  149. return true;
  150. }
  151. break;
  152. case QueryOperator.LessThanOrEquals:
  153. if (leftValue.CompareTo(rightValue) <= 0)
  154. {
  155. return true;
  156. }
  157. break;
  158. case QueryOperator.Exists:
  159. return true;
  160. }
  161. }
  162. else
  163. {
  164. switch (Operator)
  165. {
  166. case QueryOperator.Exists:
  167. // you can only specify primitive types in a comparison
  168. // notequals will always be true
  169. case QueryOperator.NotEquals:
  170. return true;
  171. }
  172. }
  173. return false;
  174. }
  175. private static bool RegexEquals(JValue input, JValue pattern)
  176. {
  177. if (input.Type != JTokenType.String || pattern.Type != JTokenType.String)
  178. {
  179. return false;
  180. }
  181. string regexText = (string)pattern.Value;
  182. int patternOptionDelimiterIndex = regexText.LastIndexOf('/');
  183. string patternText = regexText.Substring(1, patternOptionDelimiterIndex - 1);
  184. string optionsText = regexText.Substring(patternOptionDelimiterIndex + 1);
  185. return Regex.IsMatch((string)input.Value, patternText, MiscellaneousUtils.GetRegexOptions(optionsText));
  186. }
  187. private bool EqualsWithStringCoercion(JValue value, JValue queryValue)
  188. {
  189. if (value.Equals(queryValue))
  190. {
  191. return true;
  192. }
  193. if (queryValue.Type != JTokenType.String)
  194. {
  195. return false;
  196. }
  197. string queryValueString = (string)queryValue.Value;
  198. string currentValueString;
  199. // potential performance issue with converting every value to string?
  200. switch (value.Type)
  201. {
  202. case JTokenType.Date:
  203. using (StringWriter writer = StringUtils.CreateStringWriter(64))
  204. {
  205. #if !NET20
  206. if (value.Value is DateTimeOffset offset)
  207. {
  208. DateTimeUtils.WriteDateTimeOffsetString(writer, offset, DateFormatHandling.IsoDateFormat, null, CultureInfo.InvariantCulture);
  209. }
  210. else
  211. #endif
  212. {
  213. DateTimeUtils.WriteDateTimeString(writer, (DateTime)value.Value, DateFormatHandling.IsoDateFormat, null, CultureInfo.InvariantCulture);
  214. }
  215. currentValueString = writer.ToString();
  216. }
  217. break;
  218. case JTokenType.Bytes:
  219. currentValueString = Convert.ToBase64String((byte[])value.Value);
  220. break;
  221. case JTokenType.Guid:
  222. case JTokenType.TimeSpan:
  223. currentValueString = value.Value.ToString();
  224. break;
  225. case JTokenType.Uri:
  226. currentValueString = ((Uri)value.Value).OriginalString;
  227. break;
  228. default:
  229. return false;
  230. }
  231. return string.Equals(currentValueString, queryValueString, StringComparison.Ordinal);
  232. }
  233. }
  234. }