Quick.Expression.pas 18 KB

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