Quick.Expression.pas 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  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. {$IFNDEF FPC}
  49. function ListContains(aArrayObj : TObject; const aValue : string): Boolean;
  50. function IListContains(aArrayObj : TValue; const aValue : string): Boolean;
  51. {$ENDIF}
  52. function ArrayContains(aArray : TValue; const aValue : string): Boolean;
  53. public
  54. property Value1 : string read fValue1 write fValue1;
  55. property &Operator : TOperator read fOperator write fOperator;
  56. property Value2 : string read fValue2 write fValue2;
  57. function Validate(aValue : TObject) : Boolean; override;
  58. function IsNull : Boolean; override;
  59. end;
  60. TExpressionArray = array of TExpression;
  61. TMultiExpression = class(TExpression)
  62. private
  63. fArray : TExpressionArray;
  64. public
  65. destructor Destroy; override;
  66. property Items : TExpressionArray read fArray write fArray;
  67. function Validate(aValue : TObject) : Boolean; override;
  68. function IsNull : Boolean; override;
  69. procedure Add(aExpression : TExpression);
  70. end;
  71. TExpressionParser = class
  72. private
  73. class function IsSingleExpression(const aExpression : string) : Boolean;
  74. class function GetSingleExpression(const aExpression : string) : TSingleExpression;
  75. class function GetMultiExpression(const aExpression : string) : TMultiExpression;
  76. class function GetOperator(const aOperator : string) : TOperator;
  77. class function GetCombine(const aValue : string) : TCombine;
  78. public
  79. class function Parse(const aExpression : string) : TExpression;
  80. class function Validate(const obj : TObject; const aExpression : string) : Boolean;
  81. end;
  82. ENotValidExpression = class(Exception);
  83. EExpressionValidateError = class(Exception);
  84. EExpressionNotSupported = class(Exception);
  85. implementation
  86. const
  87. OperatorStr : array[Low(TOperator)..TOperator.opLike] of string = ('none','=','<>','>','>=','<','<=','CONTAINS','LIKE');
  88. {$IFDEF NEXTGEN}
  89. LOWSTR = 0;
  90. {$ELSE}
  91. LOWSTR = 1;
  92. {$ENDIF}
  93. { TExpressionParser }
  94. //a > 10
  95. //(a > 10) AND (b < 1)
  96. //((a > 10) AND (b < 1)) OR (c = 10)
  97. class function TExpressionParser.GetCombine(const aValue: string): TCombine;
  98. begin
  99. if CompareText(aValue,'AND') = 0 then Result := TCombine.coAND
  100. else if CompareText(aValue,'OR') = 0 then Result := TCombine.coOR
  101. else if CompareText(aValue,'XOR') = 0 then Result := TCombine.coXOR
  102. else if aValue.IsEmpty then Result := TCombine.coNone
  103. else raise EExpressionNotSupported.Create('Operator not supported!');
  104. end;
  105. class function TExpressionParser.GetMultiExpression(const aExpression : string) : TMultiExpression;
  106. var
  107. count : Integer;
  108. i : Integer;
  109. idx : Integer;
  110. exp : string;
  111. combine : string;
  112. rexp : TExpression;
  113. str : string;
  114. begin
  115. i := LOWSTR;
  116. idx := 0;
  117. count := 0;
  118. Result := TMultiExpression.Create;
  119. exp := aExpression.TrimLeft;
  120. while not exp.IsEmpty do
  121. begin
  122. if exp[i] = '(' then
  123. begin
  124. Inc(count);
  125. if count = 1 then idx := i;
  126. end
  127. else if exp[i] = ')' then Dec(count);
  128. if (count = 0) and (idx > 0) then
  129. begin
  130. str := ExtractStr(exp,idx,i - idx +1);
  131. exp := exp.TrimLeft;
  132. if IsSingleExpression(str) then rexp := GetSingleExpression(str)
  133. else
  134. begin
  135. //remove outer parentesis
  136. if str.StartsWith('(') then str := Copy(str,LOWSTR + 1,str.Length - 2);
  137. rexp := GetMultiExpression(str);
  138. end;
  139. //get combine
  140. combine := ExtractStr(exp,LOWSTR,exp.IndexOf(' '));
  141. exp := exp.TrimLeft;
  142. rexp.Combine := GetCombine(combine);
  143. if (rexp.Combine = TCombine.coNone) and not (exp.IsEmpty) then raise ENotValidExpression.Create('Not valid expression defined!');
  144. //add to multiexpression
  145. Result.Add(rexp);
  146. idx := 0;
  147. i := -1;
  148. end;
  149. Inc(i);
  150. end;
  151. end;
  152. class function TExpressionParser.GetOperator(const aOperator: string): TOperator;
  153. var
  154. op : TOperator;
  155. begin
  156. for op := Low(TOperator) to High(TOperator) do
  157. begin
  158. if CompareText(OperatorStr[op],aOperator) = 0 then Exit(op);
  159. end;
  160. raise ENotValidExpression.Create('Not valid operator defined!');
  161. end;
  162. class function TExpressionParser.GetSingleExpression(const aExpression: string) : TSingleExpression;
  163. var
  164. exp : string;
  165. begin
  166. if aExpression.StartsWith('(') then exp := GetSubString(aExpression,'(',')')
  167. else exp := aExpression;
  168. Result := TSingleExpression.Create;
  169. Result.Value1 := ExtractStr(exp,LOWSTR,exp.IndexOf(' '));
  170. exp := exp.TrimLeft;
  171. Result.&Operator := GetOperator(ExtractStr(exp,LOWSTR,exp.IndexOf(' ')));
  172. Result.Value2 := UnDbQuotedStr(exp);
  173. //determine like
  174. if Result.&Operator = opLike then
  175. begin
  176. if Result.Value2.CountChar('%') = 2 then Result.Value2 := Copy(Result.Value2, 2, Result.Value2.Length - 2)
  177. else if Result.Value2.StartsWith('%') then
  178. begin
  179. Result.&Operator := TOperator.opLikeR;
  180. Result.Value2 := Copy(Result.Value2, 2, Result.Value2.Length);
  181. end
  182. else if Result.Value2.EndsWith('%') then
  183. begin
  184. Result.&Operator := TOperator.opLikeL;
  185. Result.Value2 := Copy(Result.Value2,LOWSTR,Result.Value2.Length - 1);
  186. end
  187. else raise ENotValidExpression.Create('Not valid Like specified!');
  188. end;
  189. end;
  190. class function TExpressionParser.IsSingleExpression(const aExpression: string): Boolean;
  191. begin
  192. Result := (aExpression.CountChar('(') < 2) and (aExpression.CountChar(')') < 2);
  193. end;
  194. class function TExpressionParser.Parse(const aExpression : string) : TExpression;
  195. var
  196. exp : string;
  197. begin
  198. if aExpression.IsEmpty then raise ENotValidExpression.Create('Expression is empty');
  199. exp := aExpression.TrimLeft;
  200. //single expression or multiexpression
  201. if IsSingleExpression(exp) then Exit(GetSingleExpression(exp))
  202. else Result := GetMultiExpression(exp);
  203. end;
  204. class function TExpressionParser.Validate(const obj: TObject; const aExpression: string): Boolean;
  205. var
  206. exp : TExpression;
  207. begin
  208. exp := TExpressionParser.Parse(aExpression);
  209. try
  210. Result := exp.Validate(obj);
  211. finally
  212. exp.Free;
  213. end;
  214. end;
  215. { TSingleExpression }
  216. function TSingleExpression.IsNull: Boolean;
  217. begin
  218. Result := (fValue1.IsEmpty) or (fValue2.IsEmpty);
  219. end;
  220. function TSingleExpression.Validate(aValue : TObject) : Boolean;
  221. var
  222. value1 : TFlexValue;
  223. //rvalue : TValue;
  224. begin
  225. Result := False;
  226. if aValue = nil then Exit;
  227. value1.AsTValue := TRTTI.GetPathValue(aValue,fValue1);
  228. //rvalue := TRTTI.GetPathValue(aValue,fValue1);
  229. case fOperator of
  230. TOperator.opEqual :
  231. begin
  232. if value1.IsString then Result := CompareText(value1,fValue2) = 0
  233. else Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF} = fValue2;
  234. end;
  235. TOperator.opNotEqual : Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF} <> fValue2;
  236. TOperator.opGreater : Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF} > fValue2;
  237. TOperator.opEqualOrGreater : Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF} >= fValue2;
  238. TOperator.opLower : Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF} < fValue2;
  239. TOperator.opEqualOrLower : Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF} <= fValue2;
  240. TOperator.opLike : Result := {$IFNDEF FPC}ContainsText(value1,fValue2);{$ELSE}AnsiContainsText(value1.AsAnsiString,fValue2);{$ENDIF}
  241. TOperator.opLikeR : Result := EndsText(fValue2,value1);
  242. TOperator.opLikeL : Result := StartsText(fValue2,value1);
  243. TOperator.opContains :
  244. begin
  245. {$IFNDEF FPC}
  246. if value1.IsObject then Result := ListContains(value1.AsObject,fValue2)
  247. else if value1.IsInterface then Result := IListContains(value1.AsTValue,fValue2)
  248. else if value1.IsArray then Result := ArrayContains(value1.AsTValue,fValue2);
  249. {$ELSE}
  250. if value1.IsArray then Result := ArrayContains(value1.AsTValue,fValue2);
  251. {$ENDIF}
  252. end
  253. else raise ENotValidExpression.Create('Operator not defined');
  254. end;
  255. end;
  256. {$IFNDEF FPC}
  257. function TSingleExpression.ListContains(aArrayObj : TObject; const aValue : string): Boolean;
  258. var
  259. ctx : TRttiContext;
  260. rType: TRttiType;
  261. rMethod: TRttiMethod;
  262. value: TValue;
  263. begin
  264. Result := False;
  265. rType := ctx.GetType(aArrayObj.ClassInfo);
  266. rMethod := rType.GetMethod('ToArray');
  267. if Assigned(rMethod) then
  268. begin
  269. value := rMethod.Invoke(aArrayObj, []);
  270. Result := Self.ArrayContains(value,aValue);
  271. end;
  272. end;
  273. function TSingleExpression.IListContains(aArrayObj : TValue; const aValue : string): Boolean;
  274. var
  275. ctx : TRttiContext;
  276. rType: TRttiType;
  277. rMethod: TRttiMethod;
  278. value: TValue;
  279. obj : TObject;
  280. begin
  281. Result := False;
  282. try
  283. obj := TObject(aArrayObj.AsInterface);
  284. rType := ctx.GetType(obj.ClassInfo);
  285. rMethod := rType.GetMethod('ToArray');
  286. if Assigned(rMethod) then
  287. begin
  288. value := rMethod.Invoke(obj, []);
  289. Result := Self.ArrayContains(value,aValue);
  290. end;
  291. except
  292. raise EExpressionValidateError.Create('Interface property not supported');
  293. end;
  294. end;
  295. {$ENDIF}
  296. function TSingleExpression.ArrayContains(aArray : TValue; const aValue : string): Boolean;
  297. var
  298. count : Integer;
  299. arrItem : TValue;
  300. begin
  301. Result := False;
  302. if not aArray.IsArray then Exit(False);
  303. count := aArray.GetArrayLength;
  304. while count > 0 do
  305. begin
  306. Dec(count);
  307. arrItem := aArray.GetArrayElement(count);
  308. case arrItem.Kind of
  309. {$IFNDEF FPC}
  310. tkString,
  311. {$ENDIF}
  312. tkUnicodeString, tkWideString : Result := CompareText(arrItem.AsString,aValue) = 0;
  313. tkInteger, tkInt64 : Result := arrItem.AsInt64 = aValue.ToInt64;
  314. tkFloat : Result := arrItem.AsExtended = aValue.ToExtended;
  315. else raise EExpressionNotSupported.CreateFmt('Type Array<%s> not supported',[arrItem.TypeInfo.Name]);
  316. end;
  317. if Result then Exit;
  318. end;
  319. end;
  320. { TMultiExpression }
  321. procedure TMultiExpression.Add(aExpression: TExpression);
  322. begin
  323. fArray := fArray + [aExpression];
  324. end;
  325. destructor TMultiExpression.Destroy;
  326. var
  327. exp : TExpression;
  328. begin
  329. for exp in fArray do exp.Free;
  330. inherited;
  331. end;
  332. function TMultiExpression.IsNull: Boolean;
  333. begin
  334. Result := High(fArray) < 0;
  335. end;
  336. function TMultiExpression.Validate(aValue : TObject) : Boolean;
  337. var
  338. i : Integer;
  339. begin
  340. Result := False;
  341. for i := Low(fArray) to High(fArray) do
  342. begin
  343. if i = Low(fArray) then Result := fArray[i].Validate(aValue)
  344. else
  345. begin
  346. case fArray[i-1].Combine of
  347. TCombine.coAND : Result := Result and fArray[i].Validate(aValue);
  348. TCombine.coOR : Result := Result or fArray[i].Validate(aValue);
  349. TCombine.coXOR : Result := Result xor fArray[i].Validate(aValue);
  350. else Exit;
  351. end;
  352. end;
  353. end;
  354. end;
  355. end.