Quick.Expression.pas 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. { ***************************************************************************
  2. Copyright (c) 2016-2020 Kike Pérez
  3. Unit : Quick.Expression
  4. Description : Expression parser & validator
  5. Author : Kike Pérez
  6. Version : 1.0
  7. Created : 04/05/2019
  8. Modified : 08/03/2020
  9. This file is part of QuickLib: https://github.com/exilon/QuickLib
  10. ***************************************************************************
  11. Licensed under the Apache License, Version 2.0 (the "License");
  12. you may not use this file except in compliance with the License.
  13. You may obtain a copy of the License at
  14. http://www.apache.org/licenses/LICENSE-2.0
  15. Unless required by applicable law or agreed to in writing, software
  16. distributed under the License is distributed on an "AS IS" BASIS,
  17. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. See the License for the specific language governing permissions and
  19. limitations under the License.
  20. *************************************************************************** }
  21. unit Quick.Expression;
  22. {$i QuickLib.inc}
  23. interface
  24. uses
  25. SysUtils,
  26. StrUtils,
  27. RTTI,
  28. Quick.Commons,
  29. Quick.RTTI.Utils,
  30. Quick.Value,
  31. Quick.Value.RTTI;
  32. type
  33. TOperator = (opNone, opEqual, opNotEqual, opGreater, opEqualOrGreater, opLower, opEqualOrLower, opContains, opLike, opLikeR, opLikeL);
  34. TCombine = (coNone, coAND, coOR, coXOR);
  35. TExpression = class
  36. private
  37. fCombine : TCombine;
  38. public
  39. property Combine : TCombine read fCombine write fCombine;
  40. function Validate(aValue : TObject) : Boolean; virtual; abstract;
  41. function IsNull : Boolean; virtual; abstract;
  42. end;
  43. TSingleExpression = class(TExpression)
  44. private
  45. fValue1 : string;
  46. fOperator : TOperator;
  47. fValue2 : string;
  48. function ListContains(aArrayObj : TObject; const aValue : string): Boolean;
  49. function IListContains(aArrayObj : TValue; const aValue : string): Boolean;
  50. function ArrayContains(aArray : TValue; const aValue : string): Boolean;
  51. public
  52. property Value1 : string read fValue1 write fValue1;
  53. property &Operator : TOperator read fOperator write fOperator;
  54. property Value2 : string read fValue2 write fValue2;
  55. function Validate(aValue : TObject) : Boolean; override;
  56. function IsNull : Boolean; override;
  57. end;
  58. TExpressionArray = array of TExpression;
  59. TMultiExpression = class(TExpression)
  60. private
  61. fArray : TExpressionArray;
  62. public
  63. destructor Destroy; override;
  64. property Items : TExpressionArray read fArray write fArray;
  65. function Validate(aValue : TObject) : Boolean; override;
  66. function IsNull : Boolean; override;
  67. procedure Add(aExpression : TExpression);
  68. end;
  69. TExpressionParser = class
  70. private
  71. class function IsSingleExpression(const aExpression : string) : Boolean;
  72. class function GetSingleExpression(const aExpression : string) : TSingleExpression;
  73. class function GetMultiExpression(const aExpression : string) : TMultiExpression;
  74. class function GetOperator(const aOperator : string) : TOperator;
  75. class function GetCombine(const aValue : string) : TCombine;
  76. public
  77. class function Parse(const aExpression : string) : TExpression;
  78. class function Validate(const obj : TObject; const aExpression : string) : Boolean;
  79. end;
  80. ENotValidExpression = class(Exception);
  81. EExpressionValidateError = class(Exception);
  82. EExpressionNotSupported = class(Exception);
  83. implementation
  84. const
  85. OperatorStr : array[Low(TOperator)..TOperator.opLike] of string = ('none','=','<>','>','>=','<','<=','CONTAINS','LIKE');
  86. {$IFDEF NEXTGEN}
  87. LOWSTR = 0;
  88. {$ELSE}
  89. LOWSTR = 1;
  90. {$ENDIF}
  91. { TExpressionParser }
  92. //a > 10
  93. //(a > 10) AND (b < 1)
  94. //((a > 10) AND (b < 1)) OR (c = 10)
  95. class function TExpressionParser.GetCombine(const aValue: string): TCombine;
  96. begin
  97. if CompareText(aValue,'AND') = 0 then Result := TCombine.coAND
  98. else if CompareText(aValue,'OR') = 0 then Result := TCombine.coOR
  99. else if CompareText(aValue,'XOR') = 0 then Result := TCombine.coXOR
  100. else if aValue.IsEmpty then Result := TCombine.coNone
  101. else raise EExpressionNotSupported.Create('Operator not supported!');
  102. end;
  103. class function TExpressionParser.GetMultiExpression(const aExpression : string) : TMultiExpression;
  104. var
  105. count : Integer;
  106. i : Integer;
  107. idx : Integer;
  108. exp : string;
  109. combine : string;
  110. rexp : TExpression;
  111. str : string;
  112. begin
  113. i := LOWSTR;
  114. idx := 0;
  115. count := 0;
  116. Result := TMultiExpression.Create;
  117. exp := aExpression.TrimLeft;
  118. while not exp.IsEmpty do
  119. begin
  120. if exp[i] = '(' then
  121. begin
  122. Inc(count);
  123. if count = 1 then idx := i;
  124. end
  125. else if exp[i] = ')' then Dec(count);
  126. if (count = 0) and (idx > 0) then
  127. begin
  128. str := ExtractStr(exp,idx,i - idx +1);
  129. exp := exp.TrimLeft;
  130. if IsSingleExpression(str) then rexp := GetSingleExpression(str)
  131. else
  132. begin
  133. //remove outer parentesis
  134. if str.StartsWith('(') then str := Copy(str,LOWSTR + 1,str.Length - 2);
  135. rexp := GetMultiExpression(str);
  136. end;
  137. //get combine
  138. combine := ExtractStr(exp,LOWSTR,exp.IndexOf(' '));
  139. exp := exp.TrimLeft;
  140. rexp.Combine := GetCombine(combine);
  141. if (rexp.Combine = TCombine.coNone) and not (exp.IsEmpty) then raise ENotValidExpression.Create('Not valid expression defined!');
  142. //add to multiexpression
  143. Result.Add(rexp);
  144. idx := 0;
  145. i := -1;
  146. end;
  147. Inc(i);
  148. end;
  149. end;
  150. class function TExpressionParser.GetOperator(const aOperator: string): TOperator;
  151. var
  152. op : TOperator;
  153. begin
  154. for op := Low(TOperator) to High(TOperator) do
  155. begin
  156. if CompareText(OperatorStr[op],aOperator) = 0 then Exit(op);
  157. end;
  158. raise ENotValidExpression.Create('Not valid operator defined!');
  159. end;
  160. class function TExpressionParser.GetSingleExpression(const aExpression: string) : TSingleExpression;
  161. var
  162. exp : string;
  163. begin
  164. if aExpression.StartsWith('(') then exp := GetSubString(aExpression,'(',')')
  165. else exp := aExpression;
  166. Result := TSingleExpression.Create;
  167. Result.Value1 := ExtractStr(exp,LOWSTR,exp.IndexOf(' '));
  168. exp := exp.TrimLeft;
  169. Result.&Operator := GetOperator(ExtractStr(exp,LOWSTR,exp.IndexOf(' ')));
  170. Result.Value2 := UnDbQuotedStr(exp);
  171. //determine like
  172. if Result.&Operator = opLike then
  173. begin
  174. if Result.Value2.CountChar('%') = 2 then Result.Value2 := Copy(Result.Value2, 2, Result.Value2.Length - 2)
  175. else if Result.Value2.StartsWith('%') then
  176. begin
  177. Result.&Operator := TOperator.opLikeR;
  178. Result.Value2 := Copy(Result.Value2, 2, Result.Value2.Length);
  179. end
  180. else if Result.Value2.EndsWith('%') then
  181. begin
  182. Result.&Operator := TOperator.opLikeL;
  183. Result.Value2 := Copy(Result.Value2,LOWSTR,Result.Value2.Length - 1);
  184. end
  185. else raise ENotValidExpression.Create('Not valid Like specified!');
  186. end;
  187. end;
  188. class function TExpressionParser.IsSingleExpression(const aExpression: string): Boolean;
  189. begin
  190. Result := (aExpression.CountChar('(') < 2) and (aExpression.CountChar(')') < 2);
  191. end;
  192. class function TExpressionParser.Parse(const aExpression : string) : TExpression;
  193. var
  194. exp : string;
  195. begin
  196. if aExpression.IsEmpty then raise ENotValidExpression.Create('Expression is empty');
  197. exp := aExpression.TrimLeft;
  198. //single expression or multiexpression
  199. if IsSingleExpression(exp) then Exit(GetSingleExpression(exp))
  200. else Result := GetMultiExpression(exp);
  201. end;
  202. class function TExpressionParser.Validate(const obj: TObject; const aExpression: string): Boolean;
  203. var
  204. exp : TExpression;
  205. begin
  206. exp := TExpressionParser.Parse(aExpression);
  207. try
  208. Result := exp.Validate(obj);
  209. finally
  210. exp.Free;
  211. end;
  212. end;
  213. { TSingleExpression }
  214. function TSingleExpression.IsNull: Boolean;
  215. begin
  216. Result := (fValue1.IsEmpty) or (fValue2.IsEmpty);
  217. end;
  218. function TSingleExpression.Validate(aValue : TObject) : Boolean;
  219. var
  220. value1 : TFlexValue;
  221. //rvalue : TValue;
  222. begin
  223. Result := False;
  224. if aValue = nil then Exit;
  225. value1.AsTValue := TRTTI.GetPathValue(aValue,fValue1);
  226. //rvalue := TRTTI.GetPathValue(aValue,fValue1);
  227. case fOperator of
  228. TOperator.opEqual :
  229. begin
  230. if value1.IsString then Result := CompareText(value1,fValue2) = 0
  231. else Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF} = fValue2;
  232. end;
  233. TOperator.opNotEqual : Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF} <> fValue2;
  234. TOperator.opGreater : Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF} > fValue2;
  235. TOperator.opEqualOrGreater : Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF} >= fValue2;
  236. TOperator.opLower : Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF} < fValue2;
  237. TOperator.opEqualOrLower : Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF} <= fValue2;
  238. TOperator.opLike : Result := {$IFNDEF FPC}ContainsText(value1,fValue2);{$ELSE}AnsiContainsText(value1.AsAnsiString,fValue2);{$ENDIF}
  239. TOperator.opLikeR : Result := EndsText(fValue2,value1);
  240. TOperator.opLikeL : Result := StartsText(fValue2,value1);
  241. TOperator.opContains :
  242. begin
  243. if value1.IsObject then Result := ListContains(value1.AsObject,fValue2)
  244. else if value1.IsInterface then Result := IListContains(value1.AsTValue,fValue2)
  245. else if value1.IsArray then Result := ArrayContains(value1.AsTValue,fValue2);
  246. end
  247. else raise ENotValidExpression.Create('Operator not defined');
  248. end;
  249. end;
  250. function TSingleExpression.ListContains(aArrayObj : TObject; const aValue : string): Boolean;
  251. var
  252. ctx : TRttiContext;
  253. rType: TRttiType;
  254. rMethod: TRttiMethod;
  255. value: TValue;
  256. begin
  257. Result := False;
  258. rType := ctx.GetType(aArrayObj.ClassInfo);
  259. rMethod := rType.GetMethod('ToArray');
  260. if Assigned(rMethod) then
  261. begin
  262. value := rMethod.Invoke(aArrayObj, []);
  263. Result := Self.ArrayContains(value,aValue);
  264. end;
  265. end;
  266. function TSingleExpression.IListContains(aArrayObj : TValue; const aValue : string): Boolean;
  267. var
  268. ctx : TRttiContext;
  269. rType: TRttiType;
  270. rMethod: TRttiMethod;
  271. value: TValue;
  272. obj : TObject;
  273. begin
  274. Result := False;
  275. try
  276. obj := TObject(aArrayObj.AsInterface);
  277. rType := ctx.GetType(obj.ClassInfo);
  278. rMethod := rType.GetMethod('ToArray');
  279. if Assigned(rMethod) then
  280. begin
  281. value := rMethod.Invoke(obj, []);
  282. Result := Self.ArrayContains(value,aValue);
  283. end;
  284. except
  285. raise EExpressionValidateError.Create('Interface property not supported');
  286. end;
  287. end;
  288. function TSingleExpression.ArrayContains(aArray : TValue; const aValue : string): Boolean;
  289. var
  290. count : Integer;
  291. arrItem : TValue;
  292. begin
  293. Result := False;
  294. if not aArray.IsArray then Exit(False);
  295. count := aArray.GetArrayLength;
  296. while count > 0 do
  297. begin
  298. Dec(count);
  299. arrItem := aArray.GetArrayElement(count);
  300. case arrItem.Kind of
  301. tkString, tkUnicodeString, tkWideString : Result := CompareText(arrItem.AsString,aValue) = 0;
  302. tkInteger, tkInt64 : Result := arrItem.AsInt64 = aValue.ToInt64;
  303. tkFloat : Result := arrItem.AsExtended = aValue.ToExtended;
  304. else raise EExpressionNotSupported.CreateFmt('Type Array<%s> not supported',[arrItem.TypeInfo.Name]);
  305. end;
  306. if Result then Exit;
  307. end;
  308. end;
  309. { TMultiExpression }
  310. procedure TMultiExpression.Add(aExpression: TExpression);
  311. begin
  312. fArray := fArray + [aExpression];
  313. end;
  314. destructor TMultiExpression.Destroy;
  315. var
  316. exp : TExpression;
  317. begin
  318. for exp in fArray do exp.Free;
  319. inherited;
  320. end;
  321. function TMultiExpression.IsNull: Boolean;
  322. begin
  323. Result := High(fArray) < 0;
  324. end;
  325. function TMultiExpression.Validate(aValue : TObject) : Boolean;
  326. var
  327. i : Integer;
  328. begin
  329. Result := False;
  330. for i := Low(fArray) to High(fArray) do
  331. begin
  332. if i = Low(fArray) then Result := fArray[i].Validate(aValue)
  333. else
  334. begin
  335. case fArray[i-1].Combine of
  336. TCombine.coAND : Result := Result and fArray[i].Validate(aValue);
  337. TCombine.coOR : Result := Result or fArray[i].Validate(aValue);
  338. TCombine.coXOR : Result := Result xor fArray[i].Validate(aValue);
  339. else Exit;
  340. end;
  341. end;
  342. end;
  343. end;
  344. end.