diff --git a/ideexpert/DMVC.Expert.CodeGen.Commands.pas b/ideexpert/DMVC.Expert.CodeGen.Commands.pas index f1e44348..cd03f99d 100644 --- a/ideexpert/DMVC.Expert.CodeGen.Commands.pas +++ b/ideexpert/DMVC.Expert.CodeGen.Commands.pas @@ -1459,15 +1459,15 @@ procedure TUnitTemplateProHelpersDeclarationCommand.ExecuteImplementation( .AppendLine('implementation') .AppendLine .AppendLine('uses') - .AppendLine(' TemplatePro, System.SysUtils;') + .AppendLine(' System.SysUtils;') .AppendLine .AppendLine - .AppendLine('function MyHelper1(const Value: TValue; const Parameters: TArray): TValue;') + .AppendLine('function MyHelper1(const Value: TValue; const Parameters: TArray): TValue;') .AppendLine('begin') .AppendLine(' Result := Value.ToString + '' (I''''m The MyHelper1)'';') .AppendLine('end;') .AppendLine - .AppendLine('function MyHelper2(const Value: TValue; const Parameters: TArray): TValue;') + .AppendLine('function MyHelper2(const Value: TValue; const Parameters: TArray): TValue;') .AppendLine('begin') .AppendLine(' Result := Value.ToString + '' (I''''m The MyHelper2)'';') .AppendLine('end;') @@ -1516,10 +1516,10 @@ procedure TUnitTemplateProHelpersDeclarationCommand.ExecuteInterface( .AppendLine('interface') .AppendLine .AppendLine('uses') - .AppendLine(' System.Rtti;') + .AppendLine(' System.Rtti, TemplatePro;') .AppendLine - .AppendLine('function MyHelper1(const Value: TValue; const Parameters: TArray): TValue;') - .AppendLine('function MyHelper2(const Value: TValue; const Parameters: TArray): TValue;') + .AppendLine('function MyHelper1(const Value: TValue; const Parameters: TArray): TValue;') + .AppendLine('function MyHelper2(const Value: TValue; const Parameters: TArray): TValue;') .AppendLine .AppendLine .AppendLine('procedure TemplateProContextConfigure;') diff --git a/packages/d120/dmvcframeworkRT.dpk b/packages/d120/dmvcframeworkRT.dpk index 45c2373a..24d20103 100644 --- a/packages/d120/dmvcframeworkRT.dpk +++ b/packages/d120/dmvcframeworkRT.dpk @@ -56,7 +56,6 @@ contains MVCFramework.Middleware.ETag in '..\..\sources\MVCFramework.Middleware.ETag.pas', MVCFramework.ActiveRecord in '..\..\sources\MVCFramework.ActiveRecord.pas', MVCFramework.ActiveRecordController in '..\..\sources\MVCFramework.ActiveRecordController.pas', - MVCFramework.ApplicationSession in '..\..\sources\MVCFramework.ApplicationSession.pas', MVCFramework.Cache in '..\..\sources\MVCFramework.Cache.pas', MVCFramework.Commons in '..\..\sources\MVCFramework.Commons.pas', MVCFramework.Console in '..\..\sources\MVCFramework.Console.pas', diff --git a/packages/d120/dmvcframeworkRT.dproj b/packages/d120/dmvcframeworkRT.dproj index 5945524e..8f4f9186 100644 --- a/packages/d120/dmvcframeworkRT.dproj +++ b/packages/d120/dmvcframeworkRT.dproj @@ -159,7 +159,6 @@ - diff --git a/sources/MVCFramework.View.Renderers.TemplatePro.pas b/sources/MVCFramework.View.Renderers.TemplatePro.pas index 45675011..593565c9 100644 --- a/sources/MVCFramework.View.Renderers.TemplatePro.pas +++ b/sources/MVCFramework.View.Renderers.TemplatePro.pas @@ -50,7 +50,7 @@ implementation {$WARNINGS OFF} -function GetDataSetOrObjectListCount(const aValue: TValue; const aParameters: TArray): TValue; +function GetDataSetOrObjectListCount(const aValue: TValue; const aParameters: TArray): TValue; var lWrappedList: IMVCList; begin @@ -89,7 +89,21 @@ function GetDataSetOrObjectListCount(const aValue: TValue; const aParameters: TA end; end; -function DumpAsJSONString(const aValue: TValue; const aParameters: TArray): TValue; +function GetNow(const aValue: TValue; const aParameters: TArray): TValue; +begin + if not aValue.IsEmpty then + begin + Exit('(Error: Now cannot be applied to a value)'); + end; + if Length(aParameters) <> 0 then + begin + raise EMVCSSVException.Create('Expected 0 params, got ' + Length(aParameters).ToString); + end; + Result := Now(); +end; + + +function DumpAsJSONString(const aValue: TValue; const aParameters: TArray): TValue; var lWrappedList: IMVCList; begin @@ -193,8 +207,9 @@ procedure TMVCTemplateProViewEngine.Execute(const ViewName: string; const Builde end; lCompiledTemplate.AddFilter('json', DumpAsJSONString); lCompiledTemplate.AddFilter('count', GetDataSetOrObjectListCount); +// lCompiledTemplate.AddFilter('now', GetNow); lCompiledTemplate.AddFilter('fromquery', - function (const aValue: TValue; const aParameters: TArray): TValue + function (const aValue: TValue; const aParameters: TArray): TValue begin if not aValue.IsEmpty then begin @@ -202,7 +217,7 @@ procedure TMVCTemplateProViewEngine.Execute(const ViewName: string; const Builde end; if Length(aParameters) = 1 then begin - Result := Self.WebContext.Request.QueryStringParam(aParameters[0]); + Result := Self.WebContext.Request.QueryStringParam(aParameters[0].ParStrText); end else begin diff --git a/sources/MVCFramework.pas b/sources/MVCFramework.pas index edcc444a..b8fcb9fd 100644 --- a/sources/MVCFramework.pas +++ b/sources/MVCFramework.pas @@ -936,14 +936,6 @@ TMVCController = class(TMVCRenderer) function Page(const AViewNames: TArray; const UseCommonHeadersAndFooters: Boolean = True; const OnBeforeRenderCallback: TMVCSSVBeforeRenderCallback = nil): string; overload; inline; function Page(const AViewName: string; const OnBeforeRenderCallback: TMVCSSVBeforeRenderCallback = nil): string; overload; - /// - /// Page calls GetRenderedView with sensible defaults. - /// Page method with UseCommonHeadersAndFooters = True (default) concatenates - // commonheader_header_views + views + commonfooter_views - /// - function Page(const AViewNames: TArray; const JSONModel: TJSONObject; - const UseCommonHeadersAndFooters: Boolean = True; const OnBeforeRenderCallback: TMVCSSVBeforeRenderCallback = nil): string; overload; inline; - /// /// Load mustache view located in TMVCConfigKey.ViewsPath /// returns the rendered views and generates output using @@ -4423,15 +4415,6 @@ procedure TMVCController.OnBeforeAction(AContext: TWebContext; const AActionName { Implement if need be. } end; -function TMVCController.Page(const AViewNames: TArray; - const JSONModel: TJSONObject; const UseCommonHeadersAndFooters: Boolean; const OnBeforeRenderCallback: TMVCSSVBeforeRenderCallback): string; -begin - if UseCommonHeadersAndFooters then - Result := GetRenderedView(fPageHeaders + AViewNames + fPageFooters, JSONModel, OnBeforeRenderCallback) - else - Result := GetRenderedView(AViewNames, JSONModel, OnBeforeRenderCallback) -end; - function TMVCController.Page(const AViewName: string; const OnBeforeRenderCallback: TMVCSSVBeforeRenderCallback): string; begin Result := GetRenderedView([AViewName], nil, OnBeforeRenderCallback); diff --git a/sources/TemplatePro.pas b/sources/TemplatePro.pas index 92bb5377..f39ebd58 100644 --- a/sources/TemplatePro.pas +++ b/sources/TemplatePro.pas @@ -35,7 +35,7 @@ interface System.RTTI; const - TEMPLATEPRO_VERSION = '0.7.1'; + TEMPLATEPRO_VERSION = '0.7.2'; type ETProException = class(Exception) @@ -74,11 +74,27 @@ TIfThenElseIndex = record STR_END_OF_LAYOUT = 'end_of_layout'; type + TFilterParameterType = (fptInteger, fptFloat, fptString, fptVariable); + TFilterParameterTypes = set of TFilterParameterType; + TFilterParameter = record + {can be number, string or variable} + ParType: TFilterParameterType; + {contains the literal string if partype = string, + contains the variable name if partype = variable} + ParStrText: String; + {contains the literal integer if partype = integer} + ParIntValue: Integer; + {contains the literal float if partype = float} + ParFloatValue: Extended; + end; + PFilterParameter = ^TFilterParameter; + TToken = packed record TokenType: TTokenType; Value1: String; Value2: String; - Ref1, Ref2: Int64; + Ref1: Int64; + Ref2: Int64; {in case of tokentype = filter, contains the integer value, if any} class function Create(TokType: TTokenType; Value1: String; Value2: String; Ref1: Int64 = -1; Ref2: Int64 = -1) : TToken; static; function TokenTypeAsString: String; @@ -94,8 +110,10 @@ TBlockAddress = record TTokenWalkProc = reference to procedure(const Index: Integer; const Token: TToken); - TTProTemplateFunction = function(const aValue: TValue; const aParameters: TArray): TValue; - TTProTemplateAnonFunction = reference to function(const aValue: TValue; const aParameters: TArray): TValue; + TComparandType = (ctEQ, ctNE, ctGT, ctGE, ctLT, ctLE); + + TTProTemplateFunction = function(const aValue: TValue; const aParameters: TArray): TValue; + TTProTemplateAnonFunction = reference to function(const aValue: TValue; const aParameters: TArray): TValue; TTProVariablesInfo = (viSimpleType, viObject, viDataSet, viListOfObject, viJSONObject, viIterable); TTProVariablesInfos = set of TTProVariablesInfo; @@ -179,14 +197,15 @@ TTProCompiledTemplate = class(TInterfacedObject, ITProCompiledTemplate) function IsTruthy(const Value: TValue): Boolean; function GetVarAsString(const Name: string): string; function GetTValueVarAsString(const Value: PValue; const VarName: string = ''): String; + function GetNullableTValueAsTValue(const Value: PValue; const VarName: string = ''): TValue; function GetVarAsTValue(const aName: string): TValue; function GetDataSetFieldAsTValue(const aDataSet: TDataSet; const FieldName: String): TValue; function EvaluateIfExpressionAt(var Idx: Int64): Boolean; function GetVariables: TTProVariables; procedure SplitVariableName(const VariableWithMember: String; out VarName, VarMembers: String); - function ExecuteFilter(aFunctionName: string; var aParameters: TArray; aValue: TValue; const aVarNameWhereShoudBeApplied: String): TValue; - procedure CheckParNumber(const aHowManyPars: Integer; const aParameters: TArray); overload; - procedure CheckParNumber(const aMinParNumber, aMaxParNumber: Integer; const aParameters: TArray); overload; + function ExecuteFilter(aFunctionName: string; var aParameters: TArray; aValue: TValue; const aVarNameWhereShoudBeApplied: String): TValue; + procedure CheckParNumber(const aHowManyPars: Integer; const aParameters: TArray); overload; + procedure CheckParNumber(const aMinParNumber, aMaxParNumber: Integer; const aParameters: TArray); overload; function GetPseudoVariable(const VarIterator: Integer; const PseudoVarName: String): TValue; overload; function IsAnIterator(const VarName: String; out DataSourceName: String; out CurrentIterator: TLoopStackItem): Boolean; @@ -197,6 +216,8 @@ TTProCompiledTemplate = class(TInterfacedObject, ITProCompiledTemplate) function GetFormatSettings: PTProFormatSettings; procedure SetFormatSettings(const Value: PTProFormatSettings); class procedure InternalDumpToFile(const FileName: String; const aTokens: TList); + function ComparandOperator(const aComparandType: TComparandType; const aValue: TValue; + const aParameters: TArray; const aLocaleFormatSettings: TFormatSettings): TValue; public destructor Destroy; override; function Render: String; @@ -223,13 +244,14 @@ TTProCompiler = class function MatchStartTag: Boolean; function MatchEndTag: Boolean; function MatchVariable(var aIdentifier: string): Boolean; - function MatchFilterParamValue(var aParamValue: string): Boolean; + function MatchFilterParamValue(var aParamValue: TFilterParameter): Boolean; function MatchSymbol(const aSymbol: string): Boolean; function MatchSpace: Boolean; function MatchString(out aStringValue: string): Boolean; procedure InternalMatchFilter(lIdentifier: String; var lStartVerbatim: Int64; const CurrToken: TTokenType; aTokens: TList; const lRef2: Integer); - function GetFunctionParameters: TArray; + function GetFunctionParameters: TArray; + function CreateFilterParameterToken(const FilterParameter: PFilterParameter): TToken; procedure Error(const aMessage: string); function Step: Char; function CurrentChar: Char; @@ -242,10 +264,11 @@ TTProCompiler = class procedure ProcessJumps(const aTokens: TList); procedure Compile(const aTemplate: string; const aTokens: TList; const aFileNameRefPath: String); overload; constructor Create(const aEncoding: TEncoding; const aOptions: TTProCompilerOptions = []); overload; - procedure MatchFilter(lVarName: string; var lFuncName: string; var lFuncParamsCount: Integer; var lFuncParams: TArray); + procedure MatchFilter(lVarName: string; var lFuncName: string; var lFuncParamsCount: Integer; var lFuncParams: TArray); public function Compile(const aTemplate: string; const aFileNameRefPath: String = ''): ITProCompiledTemplate; overload; constructor Create(aEncoding: TEncoding = nil); overload; + class function CompileAndRender(const aTemplate: string; const VarNames: TArray; const VarValues: TArray): String; end; ITProWrappedList = interface @@ -325,7 +348,6 @@ TTProDuckTypedList = class(TInterfacedObject, ITProWrappedList) class function CanBeWrappedAsList(const AInterfaceAsDuck: IInterface): Boolean; overload; static; class function Wrap(const AObjectAsDuck: TObject): ITProWrappedList; static; end; - TComparandType = (ctEQ, ctNE, ctGT, ctGE, ctLT, ctLE); var GlContext: TRttiContext; @@ -340,11 +362,12 @@ procedure FunctionError(const aFunctionName, aErrMessage: string); raise ETProRenderException.Create(Format('[%1:s] %0:s (error in filter call for function [%1:s])', [aErrMessage, aFunctionName])) at ReturnAddress; end; -function _Comparand(const aComparandType: TComparandType; const aValue: TValue; const aParameters: TArray; const aLocaleFormatSettings: TFormatSettings): TValue; +function TTProCompiledTemplate.ComparandOperator(const aComparandType: TComparandType; const aValue: TValue; const aParameters: TArray; const aLocaleFormatSettings: TFormatSettings): TValue; var lInt64Value: Int64; lStrValue: string; lExtendedValue: Extended; + lValue: TValue; function GetComparandResultStr(const aComparandType: TComparandType; const aLeftValue, aRightValue: String): TValue; begin case aComparandType of @@ -367,8 +390,17 @@ function _Comparand(const aComparandType: TComparandType; const aValue: TValue; end; case aValue.TypeInfo.Kind of tkInteger,tkEnumeration,tkInt64: begin - if TryStrToInt64(aParameters[0], lInt64Value) then + if aParameters[0].ParType = fptString then + begin + raise ETProRenderException.Create('Invalid type for comparand'); + end; + if aParameters[0].ParType = fptInteger then + lInt64Value := aParameters[0].ParIntValue + else begin + lInt64Value := GetVarAsTValue(aParameters[0].ParStrText).AsInt64; + end; + case aComparandType of ctEQ: Result := aValue.AsInt64 = lInt64Value; ctNE: Result := aValue.AsInt64 <> lInt64Value; @@ -379,26 +411,58 @@ function _Comparand(const aComparandType: TComparandType; const aValue: TValue; else raise ETProRenderException.Create('Invalid Comparand Type: ' + TRttiEnumerationType.GetName(aComparandType)); end; - end - else - raise ETProRenderException.CreateFmt('Cannot convert comparand value for "%s" function to Integer', - [TRttiEnumerationType.GetName(aComparandType)]); end; tkFloat: begin if aValue.TypeInfo.Name = 'TDateTime' then begin lStrValue := DateTimeToStr(aValue.AsExtended, aLocaleFormatSettings); - Result := GetComparandResultStr(aComparandType, lStrValue, aParameters[0]); + case aParameters[0].ParType of + fptString: + begin + Result := GetComparandResultStr(aComparandType, lStrValue, aParameters[0].ParStrText); + end; + fptVariable: + begin + lValue := GetVarAsTValue(aParameters[0].ParStrText); + Result := GetComparandResultStr(aComparandType, lStrValue, lValue.AsString); + end; + else + Error('Invalid parameter type for ' + TRttiEnumerationType.GetName(aComparandType)); + end; end else if aValue.TypeInfo.Name = 'TDate' then begin lStrValue := DateToStr(aValue.AsExtended, aLocaleFormatSettings); - Result := GetComparandResultStr(aComparandType, lStrValue, aParameters[0]); + case aParameters[0].ParType of + fptString: + begin + Result := GetComparandResultStr(aComparandType, lStrValue, aParameters[0].ParStrText) + end; + fptVariable: + begin + lValue := GetVarAsTValue(aParameters[0].ParStrText); + Result := GetComparandResultStr(aComparandType, lStrValue, lValue.AsString); + end; + else + Error('Invalid parameter type for ' + TRttiEnumerationType.GetName(aComparandType)); + end; end else begin - if TryStrToFloat(aParameters[0], lExtendedValue) then + lExtendedValue := 0; + case aParameters[0].ParType of + fptFloat: + begin + lExtendedValue := aParameters[0].ParFloatValue; + end; + fptVariable: begin + lValue := GetVarAsTValue(aParameters[0].ParStrText); + lExtendedValue := lValue.AsExtended; + end; + else + Error('Invalid parameter type for ' + TRttiEnumerationType.GetName(aComparandType)); + end; case aComparandType of ctEQ: Result := aValue.AsExtended = lExtendedValue; ctNE: Result := aValue.AsExtended <> lExtendedValue; @@ -409,17 +473,23 @@ function _Comparand(const aComparandType: TComparandType; const aValue: TValue; else raise ETProRenderException.Create('Invalid Comparand Type: ' + TRttiEnumerationType.GetName(aComparandType)); end - end + end; + end; else begin - raise ETProRenderException.CreateFmt('Cannot convert comparand value for "%s" function to Float', - [TRttiEnumerationType.GetName(aComparandType)]); - end; + case aParameters[0].ParType of + fptString: + begin + Result := GetComparandResultStr(aComparandType, aValue.AsString, aParameters[0].ParStrText) end; + fptVariable: + begin + lValue := GetVarAsTValue(aParameters[0].ParStrText); + Result := GetComparandResultStr(aComparandType, aValue.AsString, lValue.AsString); end; else - begin - Result := GetComparandResultStr(aComparandType, aValue.AsString, aParameters[0]); + Error('Invalid parameter type for ' + TRttiEnumerationType.GetName(aComparandType)); + end; end; end; end; @@ -492,6 +562,113 @@ function TTProCompiledTemplate.GetFormatSettings: PTProFormatSettings; Result := @fLocaleFormatSettings; end; +function TTProCompiledTemplate.GetNullableTValueAsTValue(const Value: PValue; const VarName: string): TValue; +var + lNullableInt32: NullableInt32; + lNullableUInt32: NullableUInt32; + lNullableInt16: NullableInt16; + lNullableUInt16: NullableUInt16; + lNullableInt64: NullableInt64; + lNullableUInt64: NullableUInt64; + lNullableCurrency: NullableCurrency; + lNullableBoolean: NullableBoolean; + lNullableTDate: NullableTDate; + lNullableTTime: NullableTTime; + lNullableTDateTime: NullableTDateTime; + lNullableString: NullableString; +begin + Result := TValue.Empty; + + if Value.IsEmpty then + begin + Exit; + end; + + if Value.TypeInfo.Kind = tkRecord then + begin + if Value.TypeInfo = TypeInfo(NullableInt32) then + begin + lNullableInt32 := Value.AsType; + if lNullableInt32.HasValue then + Exit(lNullableInt32.Value); + end + else if Value.TypeInfo = TypeInfo(NullableUInt32) then + begin + lNullableUInt32 := Value.AsType; + if lNullableUInt32.HasValue then + Exit(lNullableUInt32.Value); + end + else if Value.TypeInfo = TypeInfo(NullableInt16) then + begin + lNullableInt16 := Value.AsType; + if lNullableInt16.HasValue then + Exit(lNullableInt16.Value); + end + else if Value.TypeInfo = TypeInfo(NullableUInt16) then + begin + lNullableUInt16 := Value.AsType; + if lNullableUInt16.HasValue then + Exit(lNullableUInt16.Value); + end + else if Value.TypeInfo = TypeInfo(NullableInt64) then + begin + lNullableInt64 := Value.AsType; + if lNullableInt64.HasValue then + Exit(lNullableInt64.Value); + end + else if Value.TypeInfo = TypeInfo(NullableUInt64) then + begin + lNullableUInt64 := Value.AsType; + if lNullableUInt64.HasValue then + Exit(lNullableUInt64.Value); + end + else if Value.TypeInfo = TypeInfo(NullableString) then + begin + lNullableString := Value.AsType; + if lNullableString.HasValue then + Exit(lNullableString.Value); + end + else if Value.TypeInfo = TypeInfo(NullableCurrency) then + begin + lNullableCurrency := Value.AsType; + if lNullableCurrency.HasValue then + Exit(lNullableCurrency.Value); + end + else if Value.TypeInfo = TypeInfo(NullableBoolean) then + begin + lNullableBoolean := Value.AsType; + if lNullableBoolean.HasValue then + Exit(lNullableBoolean.Value); + end + else if Value.TypeInfo = TypeInfo(NullableTDate) then + begin + lNullableTDate := Value.AsType; + if lNullableTDate.HasValue then + Exit(lNullableTDate.Value); + end + else if Value.TypeInfo = TypeInfo(NullableTTime) then + begin + lNullableTTime := Value.AsType; + if lNullableTTime.HasValue then + Exit(lNullableTTime.Value); + end + else if Value.TypeInfo = TypeInfo(NullableTDateTime) then + begin + lNullableTDateTime := Value.AsType; + if lNullableTDateTime.HasValue then + Exit(lNullableTDateTime.Value); + end + else + begin + raise ETProException.Create('Unsupported type for variable "' + VarName + '"'); + end; + end + else + begin + Result := Value^; + end; +end; + function TTProCompiledTemplate.GetOnGetValue: TTProCompiledTemplateGetValueEvent; begin Result := fOnGetValue; @@ -674,7 +851,7 @@ procedure TTProCompiledTemplate.AddFilter(const FunctionName: string; end; procedure TTProCompiledTemplate.CheckParNumber(const aMinParNumber, aMaxParNumber: Integer; - const aParameters: TArray); + const aParameters: TArray); var lParNumber: Integer; begin @@ -714,7 +891,7 @@ procedure TTProCompiler.InternalMatchFilter(lIdentifier: String; var lStartVerba var lFilterName: string; lFilterParamsCount: Integer; - lFilterParams: TArray; + lFilterParams: TArray; I: Integer; begin lFilterName := ''; @@ -742,7 +919,8 @@ procedure TTProCompiler.InternalMatchFilter(lIdentifier: String; var lStartVerba begin for I := 0 to lFilterParamsCount - 1 do begin - aTokens.Add(TToken.Create(ttFilterParameter, lFilterParams[I], '')); + raise Exception.Create('Error Message'); + //aTokens.Add(TToken.Create(ttFilterParameter, lFilterParams[I], '')); end; end; end; @@ -757,7 +935,34 @@ constructor TTProCompiler.Create(aEncoding: TEncoding = nil); Create(aEncoding, []); end; -procedure TTProCompiler.MatchFilter(lVarName: string; var lFuncName: string; var lFuncParamsCount: Integer; var lFuncParams: TArray); +function TTProCompiler.CreateFilterParameterToken(const FilterParameter: PFilterParameter): TToken; +begin + case FilterParameter.ParType of + fptString: begin + Result.TokenType := ttFilterParameter; + Result.Value1 := FilterParameter.ParStrText; + Result.Ref2 := Ord(FilterParameter.ParType); + end; + + fptInteger: begin + Result.TokenType := ttFilterParameter; + Result.Value1 := FilterParameter.ParIntValue.ToString; + Result.Ref2 := Ord(FilterParameter.ParType); + end; + + fptVariable: begin + Result.TokenType := ttFilterParameter; + Result.Value1 := FilterParameter.ParStrText; + Result.Ref2 := Ord(FilterParameter.ParType); + end; + + else + raise ETProCompilerException.Create('Invalid filter parameter type'); + end; + +end; + +procedure TTProCompiler.MatchFilter(lVarName: string; var lFuncName: string; var lFuncParamsCount: Integer; var lFuncParams: TArray); begin MatchSpace; if not MatchVariable(lFuncName) then @@ -820,14 +1025,19 @@ function TTProCompiler.MatchVariable(var aIdentifier: string): Boolean; end; end; -function TTProCompiler.MatchFilterParamValue(var aParamValue: string): Boolean; +function TTProCompiler.MatchFilterParamValue(var aParamValue: TFilterParameter): Boolean; var lTmp: String; + lIntegerPart, lDecimalPart: Integer; + lDigits: Integer; + lTmpFloat: Extended; begin lTmp := ''; Result := False; - if MatchString(aParamValue) then + if MatchString(lTmp) then begin + aParamValue.ParType := fptString; + aParamValue.ParStrText := lTmp; Result := True; end else if CharInSet(fInputString.Chars[fCharIndex], SignAndNumbers) then @@ -839,20 +1049,44 @@ function TTProCompiler.MatchFilterParamValue(var aParamValue: string): Boolean; lTmp := lTmp + fInputString.Chars[fCharIndex]; Inc(fCharIndex); end; + lIntegerPart := StrToInt(lTmp); + if MatchSymbol('.') then + begin + lTmp := ''; + while CharInSet(fInputString.Chars[fCharIndex], Numbers) do + begin + lTmp := lTmp + fInputString.Chars[fCharIndex]; + Inc(fCharIndex); + end; + lDigits := lTmp.Trim.Length; + if lDigits = 0 then + begin + Error('Expected digit/s after "."'); + end; + lDecimalPart := lTmp.Trim.ToInteger; + lTmpFloat := Power(10, lDigits); Result := True; - aParamValue := lTmp.Trim; + aParamValue.ParType := fptFloat; + aParamValue.ParFloatValue := lIntegerPart + lDecimalPart / lTmpFloat; + end + else + begin + Result := True; + aParamValue.ParType := fptInteger; + aParamValue.ParIntValue := lTmp.Trim.ToInteger + end; + end + else if CharInSet(fInputString.Chars[fCharIndex], IdenfierAllowedChars) then + begin + while CharInSet(fInputString.Chars[fCharIndex], ValueAllowedChars) do + begin + lTmp := lTmp + fInputString.Chars[fCharIndex]; + Inc(fCharIndex); + end; + Result := True; + aParamValue.ParType := fptVariable; + aParamValue.ParStrText := lTmp.Trim; end; - - // if CharInSet(fInputString.Chars[fCharIndex], IdenfierAllowedChars) then - // begin - // while CharInSet(fInputString.Chars[fCharIndex], ValueAllowedChars) do - // begin - // lTmp := lTmp + fInputString.Chars[fCharIndex]; - // Inc(fCharIndex); - // end; - // Result := True; - // aParamValue := lTmp.Trim; - // end; end; function TTProCompiler.MatchSpace: Boolean; @@ -936,6 +1170,26 @@ function TTProCompiler.Compile(const aTemplate: string; const aFileNameRefPath: end; end; +class function TTProCompiler.CompileAndRender(const aTemplate: String; const VarNames: TArray; + const VarValues: TArray): String; +var + lComp: TTProCompiler; + lCompiledTemplate: ITProCompiledTemplate; + I: Integer; +begin + lComp := TTProCompiler.Create(); + try + lCompiledTemplate := lComp.Compile(aTemplate); + for I := 0 to Length(VarNames) - 1 do + begin + lCompiledTemplate.SetData(VarNames[I], VarValues[I]); + end; + Result := lCompiledTemplate.Render; + finally + lComp.Free; + end; +end; + constructor TTProCompiler.Create(const aEncoding: TEncoding; const aOptions: TTProCompilerOptions); begin inherited Create; @@ -956,7 +1210,7 @@ procedure TTProCompiler.Compile(const aTemplate: string; const aTokens: TList; + lFuncParams: TArray; lFuncParamsCount: Integer; I: Integer; lTemplateSource: string; @@ -1050,6 +1304,7 @@ procedure TTProCompiler.Compile(const aTemplate: string; const aTokens: TList); //TTProCompiledTemplate.InternalDumpToFile('debug.compiled.txt', aTokens); end; -function TTProCompiler.GetFunctionParameters: TArray; +function TTProCompiler.GetFunctionParameters: TArray; var - lFuncPar: string; + lFuncPar: TFilterParameter; begin Result := []; while MatchSymbol(',') do begin - lFuncPar := ''; MatchSpace; if not MatchFilterParamValue(lFuncPar) then Error('Expected function parameter'); @@ -1651,12 +1910,12 @@ function TTProCompiler.GetSubsequentText: String; Result := Result.QuotedString('"'); end; -procedure TTProCompiledTemplate.CheckParNumber(const aHowManyPars: Integer; const aParameters: TArray); +procedure TTProCompiledTemplate.CheckParNumber(const aHowManyPars: Integer; const aParameters: TArray); begin CheckParNumber(aHowManyPars, aHowManyPars, aParameters); end; -function TTProCompiledTemplate.ExecuteFilter(aFunctionName: string; var aParameters: TArray; +function TTProCompiledTemplate.ExecuteFilter(aFunctionName: string; var aParameters: TArray; aValue: TValue; const aVarNameWhereShoudBeApplied: String): TValue; var lDateValue: TDateTime; @@ -1667,44 +1926,87 @@ function TTProCompiledTemplate.ExecuteFilter(aFunctionName: string; var aParamet lIntegerPar1: Integer; lDecimalMask: string; lExecuteAsFilterOnAValue: Boolean; + lNullableDate: NullableTDate; + lValue, lVarValue: TValue; + lExtendedValue: Extended; + procedure CheckParamType(const FunctionName: String; const FilterParameter: PFilterParameter; const Types: TFilterParameterTypes); + begin + if not (FilterParameter.ParType in Types) then + begin + FunctionError(FunctionName, 'Invalid parameter type'); + end; + end; begin lExecuteAsFilterOnAValue := not aVarNameWhereShoudBeApplied.IsEmpty; aFunctionName := lowercase(aFunctionName); if SameText(aFunctionName, 'gt') then begin - Result := _Comparand(ctGT, aValue, aParameters, fLocaleFormatSettings); + Result := ComparandOperator(ctGT, aValue, aParameters, fLocaleFormatSettings); end else if SameText(aFunctionName, 'ge') then begin - Result := _Comparand(ctGE, aValue, aParameters, fLocaleFormatSettings); + Result := ComparandOperator(ctGE, aValue, aParameters, fLocaleFormatSettings); end else if SameText(aFunctionName, 'lt') then begin - Result := _Comparand(ctLT, aValue, aParameters, fLocaleFormatSettings); + Result := ComparandOperator(ctLT, aValue, aParameters, fLocaleFormatSettings); end else if SameText(aFunctionName, 'le') then begin - Result := _Comparand(ctLE, aValue, aParameters, fLocaleFormatSettings); + Result := ComparandOperator(ctLE, aValue, aParameters, fLocaleFormatSettings); end else if SameText(aFunctionName, 'eq') then begin - Result := _Comparand(ctEQ, aValue, aParameters, fLocaleFormatSettings); + Result := ComparandOperator(ctEQ, aValue, aParameters, fLocaleFormatSettings); end else if SameText(aFunctionName, 'ne') then begin - Result := _Comparand(ctNE, aValue, aParameters, fLocaleFormatSettings); + Result := ComparandOperator(ctNE, aValue, aParameters, fLocaleFormatSettings); end else if SameText(aFunctionName, 'contains') then begin if Length(aParameters) <> 1 then FunctionError(aFunctionName, 'expected 1 parameter'); - Result := aValue.AsString.Contains(aParameters[0]); + CheckParamType(aFunctionName, @aParameters[0], [fptString, fptVariable]); + if aParameters[0].ParType = fptVariable then + begin + lValue := GetVarAsTValue(aParameters[0].ParStrText); + lStrValue := GetNullableTValueAsTValue(@lValue, aParameters[0].ParStrText).AsString; + end + else + begin + lStrValue := aParameters[0].ParStrText; + end; + Result := aValue.AsString.Contains(lStrValue); end else if SameText(aFunctionName, 'icontains') then begin if Length(aParameters) <> 1 then FunctionError(aFunctionName, 'expected 1 parameter'); - Result := aValue.AsString.ToLowerInvariant.Contains(aParameters[0].ToLowerInvariant); + CheckParamType(aFunctionName, @aParameters[0], [fptString, fptVariable]); + if aParameters[0].ParType = fptVariable then + begin + lValue := GetVarAsTValue(aParameters[0].ParStrText); + lStrValue := GetNullableTValueAsTValue(@lValue, aParameters[0].ParStrText).AsString; + end + else + begin + lStrValue := aParameters[0].ParStrText; + end; + Result := aValue.AsString.ToLowerInvariant.Contains(lStrValue); + end + else if SameText(aFunctionName, 'mod') then + begin + if Length(aParameters) <> 1 then + FunctionError(aFunctionName, 'expected 1 parameter'); + lValue := GetNullableTValueAsTValue(@aValue); + if lValue.IsEmpty then + Result := False + else + begin + var l := lValue.AsInt64; + Result := l mod aParameters[0].ParIntValue; + end; end else if SameText(aFunctionName, 'uppercase') then begin @@ -1716,7 +2018,7 @@ function TTProCompiledTemplate.ExecuteFilter(aFunctionName: string; var aParamet else begin CheckParNumber(1, aParameters); - Result := UpperCase(aParameters[0]); + Result := UpperCase(aParameters[0].ParStrText); end; end else if SameText(aFunctionName, 'lowercase') then @@ -1729,7 +2031,7 @@ function TTProCompiledTemplate.ExecuteFilter(aFunctionName: string; var aParamet else begin CheckParNumber(1, aParameters); - Result := lowercase(aParameters[0]); + Result := lowercase(aParameters[0].ParStrText); end; end else if SameText(aFunctionName, 'capitalize') then @@ -1742,17 +2044,17 @@ function TTProCompiledTemplate.ExecuteFilter(aFunctionName: string; var aParamet else begin CheckParNumber(1, aParameters); - Result := CapitalizeString(aParameters[0], True); + Result := CapitalizeString(aParameters[0].ParStrText, True); end; end else if SameText(aFunctionName, 'trunc') then begin CheckParNumber(1, 1, aParameters); lStrValue := aValue.AsString.TrimRight; - lIntegerPar1 := aParameters[0].ToInteger; + lIntegerPar1 := aParameters[0].ParIntValue; if Length(lStrValue) > lIntegerPar1 then begin - Result := lStrValue.Substring(0, aParameters[0].ToInteger) + '...'; + Result := lStrValue.Substring(0, aParameters[0].ParIntValue) + '...'; end else begin @@ -1771,41 +2073,67 @@ function TTProCompiledTemplate.ExecuteFilter(aFunctionName: string; var aParamet CheckParNumber(1, 2, aParameters); if Length(aParameters) = 1 then begin - Result := lStrValue.PadRight(aParameters[0].ToInteger); + Result := lStrValue.PadRight(aParameters[0].ParIntValue); end else begin - Result := lStrValue.PadRight(aParameters[0].ToInteger, aParameters[1].Chars[0]); + Result := lStrValue.PadRight(aParameters[0].ParIntValue, aParameters[1].ParStrText.Chars[0]); end; end else if SameText(aFunctionName, 'lpad') then begin + if not (aParameters[0].ParType in [fptInteger, fptVariable]) then + begin + FunctionError('lpad', 'Invalid parameter type'); + end; + if aValue.IsType then lStrValue := aValue.AsInteger.ToString else if aValue.IsType then lStrValue := aValue.AsString else - FunctionError(aFunctionName, 'Invalid parameter/s'); + FunctionError(aFunctionName, 'Cannot apply function lpad on this value'); CheckParNumber(1, 2, aParameters); if Length(aParameters) = 1 then begin - Result := lStrValue.PadLeft(aParameters[0].ToInteger); + if aParameters[0].ParType = fptVariable then + begin + lVarValue := GetVarAsTValue(aParameters[0].ParStrText); + Result := lStrValue.PadLeft(lVarValue.AsInteger); end else begin - Result := lStrValue.PadLeft(aParameters[0].ToInteger, aParameters[1].Chars[0]); + Result := lStrValue.PadLeft(aParameters[0].ParIntValue); + end; + end + else + begin + Result := lStrValue.PadLeft(aParameters[0].ParIntValue, aParameters[1].ParStrText.Chars[0]); end; end else if SameText(aFunctionName, 'round') then begin CheckParNumber(1, aParameters); + CheckParamType('round', @aParameters[0], [fptInteger, fptVariable]); lDecimalMask := ''; - if aParameters[0].ToInteger < 0 then + + if aParameters[0].ParType = fptVariable then + begin + lVarValue := GetVarAsTValue(aParameters[0].ParStrText); + lIntegerPar1 := lVarValue.AsInteger; + end + else begin - lDecimalMask := '.' + StringOfChar('0', Abs(aParameters[0].ToInteger)); + lIntegerPar1 := aParameters[0].ParIntValue; end; - Result := FormatFloat('0' + lDecimalMask, RoundTo(aValue.AsExtended, aParameters[0].ToInteger)); + + if lIntegerPar1 < 0 then + begin + lDecimalMask := '.' + StringOfChar('0', Abs(lIntegerPar1)); + end; + lExtendedValue := RoundTo(aValue.AsExtended, lIntegerPar1); + Result := FormatFloat('0' + lDecimalMask, lExtendedValue); end else if SameText(aFunctionName, 'datetostr') then begin @@ -1822,13 +2150,34 @@ function TTProCompiledTemplate.ExecuteFilter(aFunctionName: string; var aParamet else begin CheckParNumber(1, aParameters); - lDateFilterFormatSetting.ShortDateFormat := aParameters[0]; + lDateFilterFormatSetting.ShortDateFormat := aParameters[0].ParStrText; Result := DateToStr(lDateValue, lDateFilterFormatSetting) end; end + else if aValue.TypeInfo = TypeInfo(NullableTDate) then + begin + lNullableDate := aValue.AsType(True); + if lNullableDate.IsNull then + begin + Result := ''; + end + else + begin + lDateValue := lNullableDate.Value; + if Length(aParameters) = 0 then + begin + Result := DateToStr(lDateValue, fLocaleFormatSettings) + end + else + begin + CheckParNumber(1, aParameters); + Result := FormatDateTime(aParameters[0].ParStrText, lDateValue); + end; + end; + end else begin - FunctionError(aFunctionName, 'Invalid date ' + aValue.AsString.QuotedString); + FunctionError(aFunctionName, 'Invalid date ' + GetTValueVarAsString(@aValue, aVarNameWhereShoudBeApplied)); end; end else if SameText(aFunctionName, 'datetimetostr') or SameText(aFunctionName, 'formatdatetime') then @@ -1844,7 +2193,7 @@ function TTProCompiledTemplate.ExecuteFilter(aFunctionName: string; var aParamet else begin CheckParNumber(1, aParameters); - Result := FormatDateTime(aParameters[0], lDateValue); + Result := FormatDateTime(aParameters[0].ParStrText, lDateValue); end; end else @@ -3143,7 +3492,7 @@ function TTProCompiledTemplate.EvaluateValue(var Idx: Int64; out MustBeEncoded: lVarName: string; lFilterName: string; lFilterParCount: Int64; - lFilterParameters: TArray; + lFilterParameters: TArray; I: Integer; lNegated: Boolean; begin @@ -3168,7 +3517,12 @@ function TTProCompiledTemplate.EvaluateValue(var Idx: Int64; out MustBeEncoded: begin Inc(Idx); Assert(fTokens[Idx].TokenType = ttFilterParameter); - lFilterParameters[I] := fTokens[Idx].Value1; + lFilterParameters[I].ParType := TFilterParameterType(fTokens[Idx].Ref2); + + case lFilterParameters[I].ParType of + fptInteger: lFilterParameters[I].ParIntValue := fTokens[Idx].Value1.ToInteger; + fptString, fptVariable: lFilterParameters[I].ParStrText := fTokens[Idx].Value1; + end; end; case lCurrTokenType of ttValue: