Kaynağa Gözat

New unit Quick.Expression

Unknown 6 yıl önce
ebeveyn
işleme
8120a6e169
1 değiştirilmiş dosya ile 311 ekleme ve 0 silme
  1. 311 0
      Quick.Expression.pas

+ 311 - 0
Quick.Expression.pas

@@ -0,0 +1,311 @@
+{ ***************************************************************************
+
+  Copyright (c) 2016-2019 Kike Pérez
+
+  Unit        : Quick.Expression
+  Description : Expression parser & validator
+  Author      : Kike Pérez
+  Version     : 1.0
+  Created     : 04/05/2019
+  Modified    : 31/05/2019
+
+  This file is part of QuickLib: https://github.com/exilon/QuickLib
+
+ ***************************************************************************
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+ *************************************************************************** }
+
+unit Quick.Expression;
+
+{$i QuickLib.inc}
+
+interface
+
+uses
+  SysUtils,
+  StrUtils,
+  RTTI,
+  Quick.Commons,
+  Quick.RTTI.Utils,
+  Quick.Value,
+  Quick.Value.RTTI;
+
+type
+  TOperator = (opNone, opEqual, opNotEqual, opGreater, opEqualOrGreater, opLower, opEqualOrLower, opLike, opLikeR, opLikeL);
+
+  TCombine = (coNone, coAND, coOR, coXOR);
+
+  TExpression = class
+  private
+    fCombine : TCombine;
+  public
+    property Combine : TCombine read fCombine write fCombine;
+    function Validate(aValue : TObject) : Boolean; virtual; abstract;
+    function IsNull : Boolean; virtual; abstract;
+  end;
+
+  TSingleExpression = class(TExpression)
+  private
+    fValue1 : string;
+    fOperator : TOperator;
+    fValue2 : string;
+  public
+    property Value1 : string read fValue1 write fValue1;
+    property &Operator : TOperator read fOperator write fOperator;
+    property Value2 : string read fValue2 write fValue2;
+    function Validate(aValue : TObject) : Boolean; override;
+    function IsNull : Boolean; override;
+  end;
+
+  TExpressionArray = array of TExpression;
+
+  TMultiExpression = class(TExpression)
+  private
+    fArray : TExpressionArray;
+  public
+    destructor Destroy; override;
+    property Items : TExpressionArray read fArray write fArray;
+    function Validate(aValue : TObject) : Boolean; override;
+    function IsNull : Boolean; override;
+    procedure Add(aExpression : TExpression);
+  end;
+
+  TExpressionParser = class
+  private
+    class function IsSingleExpression(const aExpression : string) : Boolean;
+    class function GetSingleExpression(const aExpression : string) : TSingleExpression;
+    class function GetMultiExpression(const aExpression : string) : TMultiExpression;
+    class function GetOperator(const aOperator : string) : TOperator;
+    class function GetCombine(const aValue : string) : TCombine;
+  public
+    class function Parse(const aExpression : string) : TExpression;
+    class function Validate(const obj : TObject; const aExpression : string) : Boolean;
+  end;
+
+  ENotValidExpression = class(Exception);
+  EExpressionValidateError = class(Exception);
+
+implementation
+
+const
+  OperatorStr : array[Low(TOperator)..TOperator.opLike] of string = ('none','=','<>','>','>=','<','<=','LIKE');
+  {$IFDEF NEXTGEN}
+  LOWSTR = 0;
+  {$ELSE}
+  LOWSTR = 1;
+  {$ENDIF}
+
+{ TExpressionParser }
+
+//a > 10
+//(a > 10) AND (b < 1)
+//((a > 10) AND (b < 1)) OR (c = 10)
+
+class function TExpressionParser.GetCombine(const aValue: string): TCombine;
+begin
+  if CompareText(aValue,'AND') = 0 then Result := TCombine.coAND
+    else if CompareText(aValue,'OR') = 0 then Result := TCombine.coOR
+    else if CompareText(aValue,'XOR') = 0 then Result := TCombine.coXOR;
+end;
+
+class function TExpressionParser.GetMultiExpression(const aExpression : string) : TMultiExpression;
+var
+  count : Integer;
+  i : Integer;
+  idx : Integer;
+  exp : string;
+  combine : string;
+  rexp : TExpression;
+  str : string;
+begin
+  i := LOWSTR;
+  idx := 0;
+  count := 0;
+  Result := TMultiExpression.Create;
+  exp := aExpression.TrimLeft;
+  while not exp.IsEmpty do
+  begin
+    if exp[i] = '(' then
+    begin
+      Inc(count);
+      if count = 1 then idx := i;
+    end
+    else if exp[i] = ')' then Dec(count);
+    if (count = 0) and (idx > 0) then
+    begin
+      str := ExtractStr(exp,idx,i - idx +1);
+      exp := exp.TrimLeft;
+      if IsSingleExpression(str) then rexp := GetSingleExpression(str)
+      else
+      begin
+        //remove outer parentesis
+        if str.StartsWith('(') then str := Copy(str,LOWSTR + 1,str.Length - 2);
+        rexp := GetMultiExpression(str);
+      end;
+      //get combine
+      combine := ExtractStr(exp,LOWSTR,exp.IndexOf(' '));
+      exp := exp.TrimLeft;
+      rexp.Combine := GetCombine(combine);
+      if (rexp.Combine = TCombine.coNone) and not (exp.IsEmpty) then raise ENotValidExpression.Create('Not valid expression defined!');
+      //add to multiexpression
+      Result.Add(rexp);
+      idx := 0;
+      i := -1;
+    end;
+    Inc(i);
+  end;
+end;
+
+class function TExpressionParser.GetOperator(const aOperator: string): TOperator;
+var
+  op : TOperator;
+begin
+  for op := Low(TOperator) to High(TOperator) do
+  begin
+    if CompareText(OperatorStr[op],aOperator) = 0 then Exit(op);
+  end;
+  raise ENotValidExpression.Create('Not valid operator defined!');
+end;
+
+class function TExpressionParser.GetSingleExpression(const aExpression: string) : TSingleExpression;
+var
+  exp : string;
+begin
+  if aExpression.StartsWith('(') then exp := GetSubString(aExpression,'(',')')
+    else exp := aExpression;
+  Result := TSingleExpression.Create;
+  Result.Value1 := ExtractStr(exp,LOWSTR,exp.IndexOf(' '));
+  exp := exp.TrimLeft;
+  Result.&Operator := GetOperator(ExtractStr(exp,LOWSTR,exp.IndexOf(' ')));
+  Result.Value2 := UnDbQuotedStr(exp);
+  //determine like
+  if Result.&Operator = opLike then
+  begin
+    if Result.Value2.CountChar('%') = 2 then Result.Value2 := Copy(Result.Value2, 2, Result.Value2.Length - 2)
+    else if Result.Value2.StartsWith('%') then
+    begin
+      Result.&Operator := TOperator.opLikeR;
+      Result.Value2 := Copy(Result.Value2, 2, Result.Value2.Length);
+    end
+    else if Result.Value2.EndsWith('%') then
+    begin
+      Result.&Operator := TOperator.opLikeL;
+      Result.Value2 := Copy(Result.Value2,LOWSTR,Result.Value2.Length - 1);
+    end
+    else raise ENotValidExpression.Create('Not valid Like specified!');
+  end;
+end;
+
+class function TExpressionParser.IsSingleExpression(const aExpression: string): Boolean;
+begin
+  Result := (aExpression.CountChar('(') < 2) and (aExpression.CountChar(')') < 2);
+end;
+
+class function TExpressionParser.Parse(const aExpression : string) : TExpression;
+var
+  exp : string;
+begin
+  if aExpression.IsEmpty then raise ENotValidExpression.Create('Expression is empty');
+  exp := aExpression.TrimLeft;
+  //single expression or multiexpression
+  if IsSingleExpression(exp) then Exit(GetSingleExpression(exp))
+    else Result := GetMultiExpression(exp);
+end;
+
+class function TExpressionParser.Validate(const obj: TObject; const aExpression: string): Boolean;
+var
+  exp : TExpression;
+begin
+  exp := TExpressionParser.Parse(aExpression);
+  try
+    Result := exp.Validate(obj);
+  finally
+    exp.Free;
+  end;
+end;
+
+{ TSingleExpression }
+
+function TSingleExpression.IsNull: Boolean;
+begin
+  Result := (fValue1.IsEmpty) or (fValue2.IsEmpty);
+end;
+
+function TSingleExpression.Validate(aValue : TObject) : Boolean;
+var
+  value1 : TFlexValue;
+  value2 : TFlexValue;
+begin
+  if aValue = nil then Exit;
+  value1.AsTValue := TRTTI.GetPathValue(aValue,fValue1);
+  case fOperator of
+    TOperator.opEqual :
+      begin
+        if value1.IsString then Result := CompareText(value1,fValue2) = 0
+          else Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF} = fValue2;
+      end;
+    TOperator.opNotEqual : Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF}  <> fValue2;
+    TOperator.opGreater : Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF}  > fValue2;
+    TOperator.opEqualOrGreater : Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF}  >= fValue2;
+    TOperator.opLower : Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF}  < fValue2;
+    TOperator.opEqualOrLower : Result := value1{$IFDEF FPC}.AsAnsiString{$ENDIF}  <= fValue2;
+    TOperator.opLike : Result := {$IFNDEF FPC}ContainsText(value1,fValue2);{$ELSE}AnsiContainsText(value1.AsAnsiString,fValue2);{$ENDIF}
+    TOperator.opLikeR : Result := EndsText(fValue2,value1);
+    TOperator.opLikeL : Result := StartsText(fValue2,value1);
+    else raise ENotValidExpression.Create('Operator not defined');
+  end;
+end;
+
+{ TMultiExpression }
+
+procedure TMultiExpression.Add(aExpression: TExpression);
+begin
+  fArray := fArray + [aExpression];
+end;
+
+destructor TMultiExpression.Destroy;
+var
+  exp : TExpression;
+begin
+  for exp in fArray do exp.Free;
+  inherited;
+end;
+
+function TMultiExpression.IsNull: Boolean;
+begin
+  Result := High(fArray) < 0;
+end;
+
+function TMultiExpression.Validate(aValue : TObject) : Boolean;
+var
+  i : Integer;
+begin
+  Result := False;
+  for i := Low(fArray) to High(fArray) do
+  begin
+    if i = Low(fArray) then Result := fArray[i].Validate(aValue)
+    else
+    begin
+      case fArray[i-1].Combine of
+        TCombine.coAND : Result := Result and fArray[i].Validate(aValue);
+        TCombine.coOR : Result := Result or fArray[i].Validate(aValue);
+        TCombine.coXOR : Result := Result xor fArray[i].Validate(aValue);
+        else Exit;
+      end;
+    end;
+  end;
+end;
+
+end.