2
0

Quick.Expression.pas 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. { ***************************************************************************
  2. Copyright (c) 2016-2021 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 : 06/02/2021
  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. TypInfo,
  28. RTTI,
  29. Quick.Commons,
  30. Quick.RTTI.Utils,
  31. Quick.Value,
  32. Quick.Value.RTTI;
  33. type
  34. TOperator = (opNone, opEqual, opNotEqual, opGreater, opEqualOrGreater, opLower, opEqualOrLower, opContains, opLike, opLikeR, opLikeL);
  35. TCombine = (coNone, coAND, coOR, coXOR);
  36. TExpression = class
  37. private
  38. fCombine : TCombine;
  39. public
  40. property Combine : TCombine read fCombine write fCombine;
  41. function Validate(const aValue : TValue) : Boolean; virtual; abstract;
  42. function IsNull : Boolean; virtual; abstract;
  43. end;
  44. TSingleExpression = class(TExpression)
  45. private
  46. fValue1 : string;
  47. fOperator : TOperator;
  48. fValue2 : TFlexValue;
  49. {$IFNDEF FPC}
  50. function ListContains(aArrayObj : TObject; const aValue : string): Boolean;
  51. function IListContains(aArrayObj : TValue; const aValue : string): Boolean;
  52. {$ENDIF}
  53. function ArrayContains(aArray : TValue; const aValue : string): Boolean;
  54. class function IsEqual(aValue1, aValue2 : TFlexValue) : Boolean;
  55. class function IsEqualOrLower(aValue1, aValue2 : TFlexValue) : Boolean;
  56. class function IsEqualOrGreater(aValue1, aValue2 : TFlexValue) : Boolean;
  57. class function IsGreater(aValue1, aValue2 : TFlexValue) : Boolean;
  58. class function IsLower(aValue1, aValue2 : TFlexValue) : Boolean;
  59. public
  60. property Value1 : string read fValue1 write fValue1;
  61. property &Operator : TOperator read fOperator write fOperator;
  62. property Value2 : TFlexValue read fValue2 write fValue2;
  63. function Validate(const aValue : TValue) : Boolean; override;
  64. function IsNull : Boolean; override;
  65. end;
  66. TExpressionArray = array of TExpression;
  67. TMultiExpression = class(TExpression)
  68. private
  69. fArray : TExpressionArray;
  70. public
  71. destructor Destroy; override;
  72. property Items : TExpressionArray read fArray write fArray;
  73. function Validate(const aValue : TValue) : Boolean; override;
  74. function IsNull : Boolean; override;
  75. procedure Add(aExpression : TExpression);
  76. end;
  77. TExpressionParser = class
  78. private
  79. class function IsSingleExpression(const aExpression : string) : Boolean;
  80. class function GetSingleExpression(const aExpression : string) : TSingleExpression;
  81. class function GetMultiExpression(const aExpression : string) : TMultiExpression;
  82. class function GetOperator(const aOperator : string) : TOperator;
  83. class function GetCombine(const aValue : string) : TCombine;
  84. public
  85. class function Parse(const aExpression : string) : TExpression;
  86. class function Validate(const aValue : TValue; const aExpression : string) : Boolean; overload;
  87. class function Validate(const aExpression : string) : Boolean; overload;
  88. end;
  89. ENotValidExpression = class(Exception);
  90. EExpressionValidateError = class(Exception);
  91. EExpressionNotSupported = class(Exception);
  92. implementation
  93. const
  94. OperatorStr : array[Low(TOperator)..TOperator.opLike] of string = ('none','=','<>','>','>=','<','<=','CONTAINS','LIKE');
  95. {$IFDEF NEXTGEN}
  96. LOWSTR = 0;
  97. {$ELSE}
  98. LOWSTR = 1;
  99. {$ENDIF}
  100. { TExpressionParser }
  101. //a > 10
  102. //(a > 10) AND (b < 1)
  103. //((a > 10) AND (b < 1)) OR (c = 10)
  104. class function TExpressionParser.GetCombine(const aValue: string): TCombine;
  105. begin
  106. if CompareText(aValue,'AND') = 0 then Result := TCombine.coAND
  107. else if CompareText(aValue,'OR') = 0 then Result := TCombine.coOR
  108. else if CompareText(aValue,'XOR') = 0 then Result := TCombine.coXOR
  109. else if aValue.IsEmpty then Result := TCombine.coNone
  110. else raise EExpressionNotSupported.Create('Operator not supported!');
  111. end;
  112. class function TExpressionParser.GetMultiExpression(const aExpression : string) : TMultiExpression;
  113. var
  114. count : Integer;
  115. i : Integer;
  116. idx : Integer;
  117. exp : string;
  118. combine : string;
  119. rexp : TExpression;
  120. str : string;
  121. begin
  122. i := LOWSTR;
  123. idx := LOWSTR;
  124. count := 0;
  125. Result := TMultiExpression.Create;
  126. exp := aExpression.TrimLeft;
  127. while not exp.IsEmpty do
  128. begin
  129. if exp[i] = '(' then
  130. begin
  131. Inc(count);
  132. if count = 1 then idx := i;
  133. end
  134. else if exp[i] = ')' then Dec(count);
  135. if (count = 0) and (idx > 0) then
  136. begin
  137. str := ExtractStr(exp,idx,i - idx +1);
  138. exp := exp.TrimLeft;
  139. if IsSingleExpression(str) then rexp := GetSingleExpression(str)
  140. else
  141. begin
  142. //remove outer parentesis
  143. if str.StartsWith('(') then str := Copy(str,LOWSTR + 1,str.Length - 2);
  144. rexp := GetMultiExpression(str);
  145. end;
  146. //get combine
  147. combine := ExtractStr(exp,LOWSTR,exp.IndexOf(' '));
  148. exp := exp.TrimLeft;
  149. rexp.Combine := GetCombine(combine);
  150. if (rexp.Combine = TCombine.coNone) and not (exp.IsEmpty) then raise ENotValidExpression.Create('Not valid expression defined!');
  151. //add to multiexpression
  152. Result.Add(rexp);
  153. idx := LOWSTR;
  154. i := 0;
  155. end;
  156. Inc(i);
  157. end;
  158. end;
  159. class function TExpressionParser.GetOperator(const aOperator: string): TOperator;
  160. var
  161. op : TOperator;
  162. begin
  163. for op := Low(TOperator) to High(TOperator) do
  164. begin
  165. if CompareText(OperatorStr[op],aOperator) = 0 then Exit(op);
  166. end;
  167. raise ENotValidExpression.Create('Not valid operator defined!');
  168. end;
  169. class function TExpressionParser.GetSingleExpression(const aExpression: string) : TSingleExpression;
  170. var
  171. exp : string;
  172. begin
  173. if aExpression.StartsWith('(') then exp := GetSubString(aExpression,'(',')')
  174. else exp := aExpression;
  175. Result := TSingleExpression.Create;
  176. Result.Value1 := ExtractStr(exp,LOWSTR,exp.IndexOf(' '));
  177. exp := exp.TrimLeft;
  178. Result.&Operator := GetOperator(ExtractStr(exp,LOWSTR,exp.IndexOf(' ')));
  179. Result.Value2 := UnDbQuotedStr(exp);
  180. //determine like
  181. if Result.&Operator = opLike then
  182. begin
  183. if Result.Value2.AsString.CountChar('%') = 2 then Result.Value2 := Copy(Result.Value2.AsString, 2, Result.Value2.AsString.Length - 2)
  184. else if Result.Value2.AsString.StartsWith('%') then
  185. begin
  186. Result.&Operator := TOperator.opLikeR;
  187. Result.Value2 := Copy(Result.Value2.AsString, 2, Result.Value2.AsString.Length);
  188. end
  189. else if Result.Value2.AsString.EndsWith('%') then
  190. begin
  191. Result.&Operator := TOperator.opLikeL;
  192. Result.Value2 := Copy(Result.Value2.AsString,LOWSTR,Result.Value2.AsString.Length - 1);
  193. end
  194. else raise ENotValidExpression.Create('Not valid Like specified!');
  195. end;
  196. end;
  197. class function TExpressionParser.IsSingleExpression(const aExpression: string): Boolean;
  198. begin
  199. Result := (aExpression.CountChar('(') < 2) and (aExpression.CountChar(')') < 2);
  200. end;
  201. class function TExpressionParser.Parse(const aExpression : string) : TExpression;
  202. var
  203. exp : string;
  204. begin
  205. if aExpression.IsEmpty then raise ENotValidExpression.Create('Expression is empty');
  206. exp := aExpression.TrimLeft;
  207. //single expression or multiexpression
  208. if IsSingleExpression(exp) then Exit(GetSingleExpression(exp))
  209. else Result := GetMultiExpression(exp);
  210. end;
  211. class function TExpressionParser.Validate(const aExpression: string): Boolean;
  212. begin
  213. Result := Validate(nil,aExpression);
  214. end;
  215. class function TExpressionParser.Validate(const aValue: TValue; const aExpression: string): Boolean;
  216. var
  217. exp : TExpression;
  218. begin
  219. exp := TExpressionParser.Parse(aExpression);
  220. try
  221. Result := exp.Validate(aValue);
  222. finally
  223. exp.Free;
  224. end;
  225. end;
  226. { TSingleExpression }
  227. class function TSingleExpression.IsEqual(aValue1, aValue2: TFlexValue): Boolean;
  228. begin
  229. case aValue1.DataType of
  230. TValueDataType.dtNull : Exit(False);
  231. TValueDataType.dtString,
  232. TValueDataType.dtWideString,
  233. TValueDataType.dtAnsiString : Result := CompareText(aValue1,aValue2) = 0;
  234. TValueDataType.dtInteger,
  235. TValueDataType.dtInt64 : Result := aValue1.AsInt64 = aValue2.AsInt64;
  236. TValueDataType.dtExtended,
  237. TValueDataType.dtDouble : Result := aValue1.AsExtended = aValue2.AsExtended;
  238. TValueDataType.dtBoolean : Result := aValue1.AsBoolean = aValue2.AsBoolean;
  239. else raise EExpressionNotSupported.Create('Expression type not supported!');
  240. end;
  241. end;
  242. class function TSingleExpression.IsEqualOrGreater(aValue1, aValue2: TFlexValue): Boolean;
  243. begin
  244. case aValue1.DataType of
  245. TValueDataType.dtNull : Exit(False);
  246. TValueDataType.dtString,
  247. TValueDataType.dtWideString,
  248. TValueDataType.dtAnsiString : Result := CompareText(aValue1,aValue2) >= 0;
  249. TValueDataType.dtInteger,
  250. TValueDataType.dtInt64 : Result := aValue1.AsInt64 >= aValue2.AsInt64;
  251. TValueDataType.dtExtended,
  252. TValueDataType.dtDouble : Result := aValue1.AsExtended >= aValue2.AsExtended;
  253. TValueDataType.dtBoolean : Result := aValue1.AsBoolean >= aValue2.AsBoolean;
  254. else raise EExpressionNotSupported.Create('Expression type not supported!');
  255. end;
  256. end;
  257. class function TSingleExpression.IsEqualOrLower(aValue1, aValue2: TFlexValue): Boolean;
  258. begin
  259. case aValue1.DataType of
  260. TValueDataType.dtNull : Exit(False);
  261. TValueDataType.dtString,
  262. TValueDataType.dtWideString,
  263. TValueDataType.dtAnsiString : Result := CompareText(aValue1,aValue2) <= 0;
  264. TValueDataType.dtInteger,
  265. TValueDataType.dtInt64 : Result := aValue1.AsInt64 <= aValue2.AsInt64;
  266. TValueDataType.dtExtended,
  267. TValueDataType.dtDouble,
  268. TValueDataType.dtDateTime : Result := aValue1.AsExtended <= aValue2.AsExtended;
  269. TValueDataType.dtBoolean : Result := aValue1.AsBoolean <= aValue2.AsBoolean;
  270. else raise EExpressionNotSupported.Create('Expression type not supported!');
  271. end;
  272. end;
  273. class function TSingleExpression.IsGreater(aValue1, aValue2: TFlexValue): Boolean;
  274. begin
  275. case aValue1.DataType of
  276. TValueDataType.dtNull : Exit(False);
  277. TValueDataType.dtString,
  278. TValueDataType.dtWideString,
  279. TValueDataType.dtAnsiString : Result := CompareText(aValue1,aValue2) > 0;
  280. TValueDataType.dtInteger,
  281. TValueDataType.dtInt64 : Result := aValue1.AsInt64 > aValue2.AsInt64;
  282. TValueDataType.dtExtended,
  283. TValueDataType.dtDouble,
  284. TValueDataType.dtDateTime : Result := aValue1.AsExtended > aValue2.AsExtended;
  285. TValueDataType.dtBoolean : Result := aValue1.AsBoolean > aValue2.AsBoolean;
  286. else raise EExpressionNotSupported.Create('Expression type not supported!');
  287. end;
  288. end;
  289. class function TSingleExpression.IsLower(aValue1, aValue2: TFlexValue): Boolean;
  290. begin
  291. case aValue1.DataType of
  292. TValueDataType.dtNull : Exit(False);
  293. TValueDataType.dtString,
  294. TValueDataType.dtWideString,
  295. TValueDataType.dtAnsiString : Result := CompareText(aValue1,aValue2) < 0;
  296. TValueDataType.dtInteger,
  297. TValueDataType.dtInt64 : Result := aValue1.AsInt64 < aValue2.AsInt64;
  298. TValueDataType.dtExtended,
  299. TValueDataType.dtDouble,
  300. TValueDataType.dtDateTime : Result := aValue1.AsExtended < aValue2.AsExtended;
  301. TValueDataType.dtBoolean : Result := aValue1.AsBoolean < aValue2.AsBoolean;
  302. else raise EExpressionNotSupported.Create('Expression type not supported!');
  303. end;
  304. end;
  305. function TSingleExpression.IsNull: Boolean;
  306. begin
  307. Result := (fValue1.IsEmpty) or (fValue2.IsNullOrEmpty);
  308. end;
  309. function TSingleExpression.Validate(const aValue : TValue) : Boolean;
  310. var
  311. value1 : TFlexValue;
  312. begin
  313. Result := False;
  314. if aValue.IsEmpty then value1 := fValue1
  315. else
  316. begin
  317. if aValue.IsObject then
  318. begin
  319. if fValue1.Contains('.') then value1.AsTValue := TRTTI.GetPathValue(aValue.AsObject,fValue1)
  320. else value1.AsTValue := TRTTI.GetPropertyValueEx(aValue.AsObject,fValue1);
  321. end
  322. else value1.AsTValue := aValue;
  323. end;
  324. case fOperator of
  325. TOperator.opEqual : Result := IsEqual(value1,fValue2);
  326. TOperator.opNotEqual : Result := not IsEqual(value1,fValue2);
  327. TOperator.opGreater : Result := IsGreater(value1,fValue2);
  328. TOperator.opEqualOrGreater : Result := IsEqualOrGreater(value1,fValue2);
  329. TOperator.opLower : Result := IsLower(value1,fValue2);
  330. TOperator.opEqualOrLower : Result := IsEqualOrLower(value1,fValue2);
  331. TOperator.opLike : Result := {$IFNDEF FPC}ContainsText(value1,fValue2);{$ELSE}AnsiContainsText(value1.AsAnsiString,fValue2);{$ENDIF}
  332. TOperator.opLikeR : Result := EndsText(fValue2,value1);
  333. TOperator.opLikeL : Result := StartsText(fValue2,value1);
  334. TOperator.opContains :
  335. begin
  336. {$IFNDEF FPC}
  337. if value1.IsObject then Result := ListContains(value1.AsObject,fValue2)
  338. else if value1.IsInterface then Result := IListContains(value1.AsTValue,fValue2)
  339. else if value1.IsArray then Result := ArrayContains(value1.AsTValue,fValue2);
  340. {$ELSE}
  341. if value1.IsArray then Result := ArrayContains(value1.AsTValue,fValue2);
  342. {$ENDIF}
  343. end
  344. else raise ENotValidExpression.Create('Operator not defined');
  345. end;
  346. end;
  347. //function TSingleExpression.Validate(aValue : TObject) : Boolean;
  348. //var
  349. // value1 : TFlexValue;
  350. // //rvalue : TValue;
  351. //begin
  352. // Result := False;
  353. // if aValue = nil then Exit;
  354. // value1.AsTValue := TRTTI.GetPathValue(aValue,fValue1);
  355. // //rvalue := TRTTI.GetPathValue(aValue,fValue1);
  356. // case fOperator of
  357. // TOperator.opEqual :
  358. // begin
  359. // if value1.IsString then Result := CompareText(value1,fValue2) = 0
  360. // else Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF} = fValue2;
  361. // end;
  362. // TOperator.opNotEqual : Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF} <> fValue2;
  363. // TOperator.opGreater : Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF} > fValue2;
  364. // TOperator.opEqualOrGreater : Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF} >= fValue2;
  365. // TOperator.opLower : Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF} < fValue2;
  366. // TOperator.opEqualOrLower : Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF} <= fValue2;
  367. // TOperator.opLike : Result := {$IFNDEF FPC}ContainsText(value1,fValue2);{$ELSE}AnsiContainsText(value1.AsAnsiString,fValue2);{$ENDIF}
  368. // TOperator.opLikeR : Result := EndsText(fValue2,value1);
  369. // TOperator.opLikeL : Result := StartsText(fValue2,value1);
  370. // TOperator.opContains :
  371. // begin
  372. // {$IFNDEF FPC}
  373. // if value1.IsObject then Result := ListContains(value1.AsObject,fValue2)
  374. // else if value1.IsInterface then Result := IListContains(value1.AsTValue,fValue2)
  375. // else if value1.IsArray then Result := ArrayContains(value1.AsTValue,fValue2);
  376. // {$ELSE}
  377. // if value1.IsArray then Result := ArrayContains(value1.AsTValue,fValue2);
  378. // {$ENDIF}
  379. // end
  380. // else raise ENotValidExpression.Create('Operator not defined');
  381. // end;
  382. //end;
  383. {$IFNDEF FPC}
  384. function TSingleExpression.ListContains(aArrayObj : TObject; const aValue : string): Boolean;
  385. var
  386. ctx : TRttiContext;
  387. rType: TRttiType;
  388. rMethod: TRttiMethod;
  389. value: TValue;
  390. begin
  391. Result := False;
  392. rType := ctx.GetType(aArrayObj.ClassInfo);
  393. rMethod := rType.GetMethod('ToArray');
  394. if Assigned(rMethod) then
  395. begin
  396. value := rMethod.Invoke(aArrayObj, []);
  397. Result := Self.ArrayContains(value,aValue);
  398. end;
  399. end;
  400. function TSingleExpression.IListContains(aArrayObj : TValue; const aValue : string): Boolean;
  401. var
  402. ctx : TRttiContext;
  403. rType: TRttiType;
  404. rMethod: TRttiMethod;
  405. value: TValue;
  406. obj : TObject;
  407. begin
  408. Result := False;
  409. try
  410. obj := TObject(aArrayObj.AsInterface);
  411. rType := ctx.GetType(obj.ClassInfo);
  412. rMethod := rType.GetMethod('ToArray');
  413. if Assigned(rMethod) then
  414. begin
  415. value := rMethod.Invoke(obj, []);
  416. Result := Self.ArrayContains(value,aValue);
  417. end;
  418. except
  419. raise EExpressionValidateError.Create('Interface property not supported');
  420. end;
  421. end;
  422. {$ENDIF}
  423. function TSingleExpression.ArrayContains(aArray : TValue; const aValue : string): Boolean;
  424. var
  425. count : Integer;
  426. arrItem : TValue;
  427. begin
  428. Result := False;
  429. if not aArray.IsArray then Exit(False);
  430. count := aArray.GetArrayLength;
  431. while count > 0 do
  432. begin
  433. Dec(count);
  434. arrItem := aArray.GetArrayElement(count);
  435. case arrItem.Kind of
  436. {$IFNDEF FPC}
  437. tkString,
  438. {$ENDIF}
  439. tkUnicodeString, tkWideString : Result := CompareText(arrItem.AsString,aValue) = 0;
  440. tkInteger,
  441. tkInt64 : Result := arrItem.AsInt64 = aValue.ToInt64;
  442. tkFloat : Result := arrItem.AsExtended = aValue.ToExtended;
  443. else raise EExpressionNotSupported.CreateFmt('Type Array<%s> not supported',[arrItem.TypeInfo.Name]);
  444. end;
  445. if Result then Exit;
  446. end;
  447. end;
  448. { TMultiExpression }
  449. procedure TMultiExpression.Add(aExpression: TExpression);
  450. begin
  451. fArray := fArray + [aExpression];
  452. end;
  453. destructor TMultiExpression.Destroy;
  454. var
  455. exp : TExpression;
  456. begin
  457. for exp in fArray do exp.Free;
  458. inherited;
  459. end;
  460. function TMultiExpression.IsNull: Boolean;
  461. begin
  462. Result := High(fArray) < 0;
  463. end;
  464. function TMultiExpression.Validate(const aValue : TValue) : Boolean;
  465. var
  466. i : Integer;
  467. begin
  468. Result := False;
  469. for i := Low(fArray) to High(fArray) do
  470. begin
  471. if i = Low(fArray) then Result := fArray[i].Validate(aValue)
  472. else
  473. begin
  474. case fArray[i-1].Combine of
  475. TCombine.coAND : Result := Result and fArray[i].Validate(aValue);
  476. TCombine.coOR : Result := Result or fArray[i].Validate(aValue);
  477. TCombine.coXOR : Result := Result xor fArray[i].Validate(aValue);
  478. else Exit;
  479. end;
  480. end;
  481. end;
  482. end;
  483. end.