Quick.Expression.pas 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. { ***************************************************************************
  2. Copyright (c) 2016-2019 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 : 31/05/2019
  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, 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. public
  49. property Value1 : string read fValue1 write fValue1;
  50. property &Operator : TOperator read fOperator write fOperator;
  51. property Value2 : string read fValue2 write fValue2;
  52. function Validate(aValue : TObject) : Boolean; override;
  53. function IsNull : Boolean; override;
  54. end;
  55. TExpressionArray = array of TExpression;
  56. TMultiExpression = class(TExpression)
  57. private
  58. fArray : TExpressionArray;
  59. public
  60. destructor Destroy; override;
  61. property Items : TExpressionArray read fArray write fArray;
  62. function Validate(aValue : TObject) : Boolean; override;
  63. function IsNull : Boolean; override;
  64. procedure Add(aExpression : TExpression);
  65. end;
  66. TExpressionParser = class
  67. private
  68. class function IsSingleExpression(const aExpression : string) : Boolean;
  69. class function GetSingleExpression(const aExpression : string) : TSingleExpression;
  70. class function GetMultiExpression(const aExpression : string) : TMultiExpression;
  71. class function GetOperator(const aOperator : string) : TOperator;
  72. class function GetCombine(const aValue : string) : TCombine;
  73. public
  74. class function Parse(const aExpression : string) : TExpression;
  75. class function Validate(const obj : TObject; const aExpression : string) : Boolean;
  76. end;
  77. ENotValidExpression = class(Exception);
  78. EExpressionValidateError = class(Exception);
  79. implementation
  80. const
  81. OperatorStr : array[Low(TOperator)..TOperator.opLike] of string = ('none','=','<>','>','>=','<','<=','LIKE');
  82. {$IFDEF NEXTGEN}
  83. LOWSTR = 0;
  84. {$ELSE}
  85. LOWSTR = 1;
  86. {$ENDIF}
  87. { TExpressionParser }
  88. //a > 10
  89. //(a > 10) AND (b < 1)
  90. //((a > 10) AND (b < 1)) OR (c = 10)
  91. class function TExpressionParser.GetCombine(const aValue: string): TCombine;
  92. begin
  93. if CompareText(aValue,'AND') = 0 then Result := TCombine.coAND
  94. else if CompareText(aValue,'OR') = 0 then Result := TCombine.coOR
  95. else if CompareText(aValue,'XOR') = 0 then Result := TCombine.coXOR;
  96. end;
  97. class function TExpressionParser.GetMultiExpression(const aExpression : string) : TMultiExpression;
  98. var
  99. count : Integer;
  100. i : Integer;
  101. idx : Integer;
  102. exp : string;
  103. combine : string;
  104. rexp : TExpression;
  105. str : string;
  106. begin
  107. i := LOWSTR;
  108. idx := 0;
  109. count := 0;
  110. Result := TMultiExpression.Create;
  111. exp := aExpression.TrimLeft;
  112. while not exp.IsEmpty do
  113. begin
  114. if exp[i] = '(' then
  115. begin
  116. Inc(count);
  117. if count = 1 then idx := i;
  118. end
  119. else if exp[i] = ')' then Dec(count);
  120. if (count = 0) and (idx > 0) then
  121. begin
  122. str := ExtractStr(exp,idx,i - idx +1);
  123. exp := exp.TrimLeft;
  124. if IsSingleExpression(str) then rexp := GetSingleExpression(str)
  125. else
  126. begin
  127. //remove outer parentesis
  128. if str.StartsWith('(') then str := Copy(str,LOWSTR + 1,str.Length - 2);
  129. rexp := GetMultiExpression(str);
  130. end;
  131. //get combine
  132. combine := ExtractStr(exp,LOWSTR,exp.IndexOf(' '));
  133. exp := exp.TrimLeft;
  134. rexp.Combine := GetCombine(combine);
  135. if (rexp.Combine = TCombine.coNone) and not (exp.IsEmpty) then raise ENotValidExpression.Create('Not valid expression defined!');
  136. //add to multiexpression
  137. Result.Add(rexp);
  138. idx := 0;
  139. i := -1;
  140. end;
  141. Inc(i);
  142. end;
  143. end;
  144. class function TExpressionParser.GetOperator(const aOperator: string): TOperator;
  145. var
  146. op : TOperator;
  147. begin
  148. for op := Low(TOperator) to High(TOperator) do
  149. begin
  150. if CompareText(OperatorStr[op],aOperator) = 0 then Exit(op);
  151. end;
  152. raise ENotValidExpression.Create('Not valid operator defined!');
  153. end;
  154. class function TExpressionParser.GetSingleExpression(const aExpression: string) : TSingleExpression;
  155. var
  156. exp : string;
  157. begin
  158. if aExpression.StartsWith('(') then exp := GetSubString(aExpression,'(',')')
  159. else exp := aExpression;
  160. Result := TSingleExpression.Create;
  161. Result.Value1 := ExtractStr(exp,LOWSTR,exp.IndexOf(' '));
  162. exp := exp.TrimLeft;
  163. Result.&Operator := GetOperator(ExtractStr(exp,LOWSTR,exp.IndexOf(' ')));
  164. Result.Value2 := UnDbQuotedStr(exp);
  165. //determine like
  166. if Result.&Operator = opLike then
  167. begin
  168. if Result.Value2.CountChar('%') = 2 then Result.Value2 := Copy(Result.Value2, 2, Result.Value2.Length - 2)
  169. else if Result.Value2.StartsWith('%') then
  170. begin
  171. Result.&Operator := TOperator.opLikeR;
  172. Result.Value2 := Copy(Result.Value2, 2, Result.Value2.Length);
  173. end
  174. else if Result.Value2.EndsWith('%') then
  175. begin
  176. Result.&Operator := TOperator.opLikeL;
  177. Result.Value2 := Copy(Result.Value2,LOWSTR,Result.Value2.Length - 1);
  178. end
  179. else raise ENotValidExpression.Create('Not valid Like specified!');
  180. end;
  181. end;
  182. class function TExpressionParser.IsSingleExpression(const aExpression: string): Boolean;
  183. begin
  184. Result := (aExpression.CountChar('(') < 2) and (aExpression.CountChar(')') < 2);
  185. end;
  186. class function TExpressionParser.Parse(const aExpression : string) : TExpression;
  187. var
  188. exp : string;
  189. begin
  190. if aExpression.IsEmpty then raise ENotValidExpression.Create('Expression is empty');
  191. exp := aExpression.TrimLeft;
  192. //single expression or multiexpression
  193. if IsSingleExpression(exp) then Exit(GetSingleExpression(exp))
  194. else Result := GetMultiExpression(exp);
  195. end;
  196. class function TExpressionParser.Validate(const obj: TObject; const aExpression: string): Boolean;
  197. var
  198. exp : TExpression;
  199. begin
  200. exp := TExpressionParser.Parse(aExpression);
  201. try
  202. Result := exp.Validate(obj);
  203. finally
  204. exp.Free;
  205. end;
  206. end;
  207. { TSingleExpression }
  208. function TSingleExpression.IsNull: Boolean;
  209. begin
  210. Result := (fValue1.IsEmpty) or (fValue2.IsEmpty);
  211. end;
  212. function TSingleExpression.Validate(aValue : TObject) : Boolean;
  213. var
  214. value1 : TFlexValue;
  215. value2 : TFlexValue;
  216. begin
  217. if aValue = nil then Exit;
  218. value1.AsTValue := TRTTI.GetPathValue(aValue,fValue1);
  219. case fOperator of
  220. TOperator.opEqual :
  221. begin
  222. if value1.IsString then Result := CompareText(value1,fValue2) = 0
  223. else Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF} = fValue2;
  224. end;
  225. TOperator.opNotEqual : Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF} <> fValue2;
  226. TOperator.opGreater : Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF} > fValue2;
  227. TOperator.opEqualOrGreater : Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF} >= fValue2;
  228. TOperator.opLower : Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF} < fValue2;
  229. TOperator.opEqualOrLower : Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF} <= fValue2;
  230. TOperator.opLike : Result := {$IFNDEF FPC}ContainsText(value1,fValue2);{$ELSE}AnsiContainsText(value1.AsAnsiString,fValue2);{$ENDIF}
  231. TOperator.opLikeR : Result := EndsText(fValue2,value1);
  232. TOperator.opLikeL : Result := StartsText(fValue2,value1);
  233. else raise ENotValidExpression.Create('Operator not defined');
  234. end;
  235. end;
  236. { TMultiExpression }
  237. procedure TMultiExpression.Add(aExpression: TExpression);
  238. begin
  239. fArray := fArray + [aExpression];
  240. end;
  241. destructor TMultiExpression.Destroy;
  242. var
  243. exp : TExpression;
  244. begin
  245. for exp in fArray do exp.Free;
  246. inherited;
  247. end;
  248. function TMultiExpression.IsNull: Boolean;
  249. begin
  250. Result := High(fArray) < 0;
  251. end;
  252. function TMultiExpression.Validate(aValue : TObject) : Boolean;
  253. var
  254. i : Integer;
  255. begin
  256. Result := False;
  257. for i := Low(fArray) to High(fArray) do
  258. begin
  259. if i = Low(fArray) then Result := fArray[i].Validate(aValue)
  260. else
  261. begin
  262. case fArray[i-1].Combine of
  263. TCombine.coAND : Result := Result and fArray[i].Validate(aValue);
  264. TCombine.coOR : Result := Result or fArray[i].Validate(aValue);
  265. TCombine.coXOR : Result := Result xor fArray[i].Validate(aValue);
  266. else Exit;
  267. end;
  268. end;
  269. end;
  270. end;
  271. end.