diff --git a/samples/data/books.db b/samples/data/books.db index 9886d969..3b2ae0c4 100644 Binary files a/samples/data/books.db and b/samples/data/books.db differ diff --git a/samples/data/books.sql b/samples/data/books.sql index 169d9de6..e3cbc1d2 100644 --- a/samples/data/books.sql +++ b/samples/data/books.sql @@ -3,5 +3,10 @@ CREATE TABLE books ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, book_name TEXT NOT NULL, author_name TEXT NOT NULL, - genre TEXT NOT NULL -); + genre TEXT NOT NULL, + rating INTEGER DEFAULT (0) NOT NULL); + + +/* fill data and then generate rating using the following update */ + +update books set rating = abs(random() % 6) \ No newline at end of file diff --git a/samples/htmx_templatepro/htmx_templatepro.dpr b/samples/htmx_templatepro/htmx_templatepro.dpr index 76bd560a..421b3d4f 100644 --- a/samples/htmx_templatepro/htmx_templatepro.dpr +++ b/samples/htmx_templatepro/htmx_templatepro.dpr @@ -62,8 +62,6 @@ begin // When MVCSerializeNulls = False empty nullables and nil are not serialized at all. MVCSerializeNulls := True; - MVCUseTemplatesCache := False; - try if WebRequestHandler <> nil then WebRequestHandler.WebModuleClass := WebModuleClass; diff --git a/samples/htmx_templatepro/htmx_templatepro.dproj b/samples/htmx_templatepro/htmx_templatepro.dproj index ded65f01..65650450 100644 --- a/samples/htmx_templatepro/htmx_templatepro.dproj +++ b/samples/htmx_templatepro/htmx_templatepro.dproj @@ -1,7 +1,7 @@  {563921D8-AB80-4E14-AD4E-36870C7E2008} - 20.1 + 20.2 VCL htmx_templatepro.dpr True @@ -125,14 +125,9 @@ Embarcadero C++Builder Office XP Servers Package - + - - - htmx_templatepro.exe - true - - + 1 @@ -141,16 +136,6 @@ 0 - - - classes - 64 - - - classes - 64 - - res\xml @@ -161,12 +146,6 @@ 1 - - - library\lib\armeabi-v7a - 1 - - library\lib\armeabi diff --git a/samples/instant_search_with_htmx/BooksSearch.dproj b/samples/instant_search_with_htmx/BooksSearch.dproj index 569e7a48..09b86a74 100644 --- a/samples/instant_search_with_htmx/BooksSearch.dproj +++ b/samples/instant_search_with_htmx/BooksSearch.dproj @@ -14,6 +14,16 @@ true + + true + Base + true + + + true + Base + true + true Base @@ -63,6 +73,16 @@ 1040 CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey=;minSdkVersion=23;targetSdkVersion=34 + Debug + activity-1.7.2.dex.jar;annotation-experimental-1.3.0.dex.jar;annotation-jvm-1.6.0.dex.jar;annotations-13.0.dex.jar;appcompat-1.2.0.dex.jar;appcompat-resources-1.2.0.dex.jar;billing-6.0.1.dex.jar;biometric-1.1.0.dex.jar;browser-1.4.0.dex.jar;cloud-messaging.dex.jar;collection-1.1.0.dex.jar;concurrent-futures-1.1.0.dex.jar;core-1.10.1.dex.jar;core-common-2.2.0.dex.jar;core-ktx-1.10.1.dex.jar;core-runtime-2.2.0.dex.jar;cursoradapter-1.0.0.dex.jar;customview-1.0.0.dex.jar;documentfile-1.0.0.dex.jar;drawerlayout-1.0.0.dex.jar;error_prone_annotations-2.9.0.dex.jar;exifinterface-1.3.6.dex.jar;firebase-annotations-16.2.0.dex.jar;firebase-common-20.3.1.dex.jar;firebase-components-17.1.0.dex.jar;firebase-datatransport-18.1.7.dex.jar;firebase-encoders-17.0.0.dex.jar;firebase-encoders-json-18.0.0.dex.jar;firebase-encoders-proto-16.0.0.dex.jar;firebase-iid-interop-17.1.0.dex.jar;firebase-installations-17.1.3.dex.jar;firebase-installations-interop-17.1.0.dex.jar;firebase-measurement-connector-19.0.0.dex.jar;firebase-messaging-23.1.2.dex.jar;fmx.dex.jar;fragment-1.2.5.dex.jar;google-play-licensing.dex.jar;interpolator-1.0.0.dex.jar;javax.inject-1.dex.jar;kotlin-stdlib-1.8.22.dex.jar;kotlin-stdlib-common-1.8.22.dex.jar;kotlin-stdlib-jdk7-1.8.22.dex.jar;kotlin-stdlib-jdk8-1.8.22.dex.jar;kotlinx-coroutines-android-1.6.4.dex.jar;kotlinx-coroutines-core-jvm-1.6.4.dex.jar;legacy-support-core-utils-1.0.0.dex.jar;lifecycle-common-2.6.1.dex.jar;lifecycle-livedata-2.6.1.dex.jar;lifecycle-livedata-core-2.6.1.dex.jar;lifecycle-runtime-2.6.1.dex.jar;lifecycle-service-2.6.1.dex.jar;lifecycle-viewmodel-2.6.1.dex.jar;lifecycle-viewmodel-savedstate-2.6.1.dex.jar;listenablefuture-1.0.dex.jar;loader-1.0.0.dex.jar;localbroadcastmanager-1.0.0.dex.jar;okio-jvm-3.4.0.dex.jar;play-services-ads-22.2.0.dex.jar;play-services-ads-base-22.2.0.dex.jar;play-services-ads-identifier-18.0.0.dex.jar;play-services-ads-lite-22.2.0.dex.jar;play-services-appset-16.0.1.dex.jar;play-services-base-18.1.0.dex.jar;play-services-basement-18.1.0.dex.jar;play-services-cloud-messaging-17.0.1.dex.jar;play-services-location-21.0.1.dex.jar;play-services-maps-18.1.0.dex.jar;play-services-measurement-base-20.1.2.dex.jar;play-services-measurement-sdk-api-20.1.2.dex.jar;play-services-stats-17.0.2.dex.jar;play-services-tasks-18.0.2.dex.jar;print-1.0.0.dex.jar;profileinstaller-1.3.0.dex.jar;room-common-2.2.5.dex.jar;room-runtime-2.2.5.dex.jar;savedstate-1.2.1.dex.jar;sqlite-2.1.0.dex.jar;sqlite-framework-2.1.0.dex.jar;startup-runtime-1.1.1.dex.jar;tracing-1.0.0.dex.jar;transport-api-3.0.0.dex.jar;transport-backend-cct-3.1.8.dex.jar;transport-runtime-3.1.8.dex.jar;user-messaging-platform-2.0.0.dex.jar;vectordrawable-1.1.0.dex.jar;vectordrawable-animated-1.1.0.dex.jar;versionedparcelable-1.1.1.dex.jar;viewpager-1.0.0.dex.jar;work-runtime-2.7.0.dex.jar + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey=;minSdkVersion=23;targetSdkVersion=34 + Debug + activity-1.7.2.dex.jar;annotation-experimental-1.3.0.dex.jar;annotation-jvm-1.6.0.dex.jar;annotations-13.0.dex.jar;appcompat-1.2.0.dex.jar;appcompat-resources-1.2.0.dex.jar;billing-6.0.1.dex.jar;biometric-1.1.0.dex.jar;browser-1.4.0.dex.jar;cloud-messaging.dex.jar;collection-1.1.0.dex.jar;concurrent-futures-1.1.0.dex.jar;core-1.10.1.dex.jar;core-common-2.2.0.dex.jar;core-ktx-1.10.1.dex.jar;core-runtime-2.2.0.dex.jar;cursoradapter-1.0.0.dex.jar;customview-1.0.0.dex.jar;documentfile-1.0.0.dex.jar;drawerlayout-1.0.0.dex.jar;error_prone_annotations-2.9.0.dex.jar;exifinterface-1.3.6.dex.jar;firebase-annotations-16.2.0.dex.jar;firebase-common-20.3.1.dex.jar;firebase-components-17.1.0.dex.jar;firebase-datatransport-18.1.7.dex.jar;firebase-encoders-17.0.0.dex.jar;firebase-encoders-json-18.0.0.dex.jar;firebase-encoders-proto-16.0.0.dex.jar;firebase-iid-interop-17.1.0.dex.jar;firebase-installations-17.1.3.dex.jar;firebase-installations-interop-17.1.0.dex.jar;firebase-measurement-connector-19.0.0.dex.jar;firebase-messaging-23.1.2.dex.jar;fmx.dex.jar;fragment-1.2.5.dex.jar;google-play-licensing.dex.jar;interpolator-1.0.0.dex.jar;javax.inject-1.dex.jar;kotlin-stdlib-1.8.22.dex.jar;kotlin-stdlib-common-1.8.22.dex.jar;kotlin-stdlib-jdk7-1.8.22.dex.jar;kotlin-stdlib-jdk8-1.8.22.dex.jar;kotlinx-coroutines-android-1.6.4.dex.jar;kotlinx-coroutines-core-jvm-1.6.4.dex.jar;legacy-support-core-utils-1.0.0.dex.jar;lifecycle-common-2.6.1.dex.jar;lifecycle-livedata-2.6.1.dex.jar;lifecycle-livedata-core-2.6.1.dex.jar;lifecycle-runtime-2.6.1.dex.jar;lifecycle-service-2.6.1.dex.jar;lifecycle-viewmodel-2.6.1.dex.jar;lifecycle-viewmodel-savedstate-2.6.1.dex.jar;listenablefuture-1.0.dex.jar;loader-1.0.0.dex.jar;localbroadcastmanager-1.0.0.dex.jar;okio-jvm-3.4.0.dex.jar;play-services-ads-22.2.0.dex.jar;play-services-ads-base-22.2.0.dex.jar;play-services-ads-identifier-18.0.0.dex.jar;play-services-ads-lite-22.2.0.dex.jar;play-services-appset-16.0.1.dex.jar;play-services-base-18.1.0.dex.jar;play-services-basement-18.1.0.dex.jar;play-services-cloud-messaging-17.0.1.dex.jar;play-services-location-21.0.1.dex.jar;play-services-maps-18.1.0.dex.jar;play-services-measurement-base-20.1.2.dex.jar;play-services-measurement-sdk-api-20.1.2.dex.jar;play-services-stats-17.0.2.dex.jar;play-services-tasks-18.0.2.dex.jar;print-1.0.0.dex.jar;profileinstaller-1.3.0.dex.jar;room-common-2.2.5.dex.jar;room-runtime-2.2.5.dex.jar;savedstate-1.2.1.dex.jar;sqlite-2.1.0.dex.jar;sqlite-framework-2.1.0.dex.jar;startup-runtime-1.1.1.dex.jar;tracing-1.0.0.dex.jar;transport-api-3.0.0.dex.jar;transport-backend-cct-3.1.8.dex.jar;transport-runtime-3.1.8.dex.jar;user-messaging-platform-2.0.0.dex.jar;vectordrawable-1.1.0.dex.jar;vectordrawable-animated-1.1.0.dex.jar;versionedparcelable-1.1.1.dex.jar;viewpager-1.0.0.dex.jar;work-runtime-2.7.0.dex.jar + DataSnapServer;fmx;emshosting;DbxCommonDriver;bindengine;FireDACCommonODBC;emsclient;FireDACCommonDriver;IndyProtocols;dbxcds;RadiantShapesFmx_Design;emsedge;inetdb;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;soapmidas;dbexpress;Python;FireDACInfxDriver;inet;DataSnapCommon;dbrtl;FireDACOracleDriver;CustomIPTransport;FireDACMSSQLDriver;DataSnapIndy10ServerTransport;DataSnapConnectors;FireDACMongoDBDriver;IndySystem;RadiantShapesFmx;FireDACTDataDriver;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;IndyCore;RESTBackendComponents;rtl;FireDACMySQLDriver;FireDACADSDriver;RESTComponents;dsnapxml;DataSnapClient;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;xmlrtl;dsnap;CloudService;FireDACDb2Driver;DataSnapNativeClient;DatasnapConnectorsFreePascal;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) @@ -995,6 +1015,8 @@ + False + False False True False diff --git a/samples/instant_search_with_htmx/Controllers.BooksU.pas b/samples/instant_search_with_htmx/Controllers.BooksU.pas index e45cc7c7..bc05b6b7 100644 --- a/samples/instant_search_with_htmx/Controllers.BooksU.pas +++ b/samples/instant_search_with_htmx/Controllers.BooksU.pas @@ -32,7 +32,7 @@ var lBaseSelect, lOrdering: String; begin SearchQueryText := SearchQueryText.ToLower; - lBaseSelect := 'select id, book_name, author_name, genre from books'; + lBaseSelect := 'select id, book_name, author_name, genre, rating from books'; lOrdering := 'order by book_name COLLATE NOCASE'; if SearchQueryText.IsEmpty then begin diff --git a/samples/instant_search_with_htmx/bin/templates/index.html b/samples/instant_search_with_htmx/bin/templates/index.html index 65ac11cb..1ff4c861 100644 --- a/samples/instant_search_with_htmx/bin/templates/index.html +++ b/samples/instant_search_with_htmx/bin/templates/index.html @@ -7,17 +7,18 @@ - -
+ +
-

⭐ DMVCFramework + HTMX :: Instant Search Demo ⭐

+ ciao
{{include "search_results.html"}}
diff --git a/samples/instant_search_with_htmx/bin/templates/search_results.html b/samples/instant_search_with_htmx/bin/templates/search_results.html index ebb58cb9..20a52743 100644 --- a/samples/instant_search_with_htmx/bin/templates/search_results.html +++ b/samples/instant_search_with_htmx/bin/templates/search_results.html @@ -5,6 +5,7 @@ ID Book Title Book Author + Rating Genre @@ -30,9 +31,14 @@ {{:book.id|lpad,8,"0"}} {{:book.book_name}} {{:book.author_name}} - + + {{if book.rating|ge,1}}⭐{{endif}} + {{if book.rating|ge,2}}⭐{{endif}} + {{if book.rating|ge,3}}⭐{{endif}} + {{if book.rating|ge,4}}⭐{{endif}} + {{if book.rating|ge,5}}⭐{{endif}} + + {{:book.genre|uppercase}} diff --git a/sources/TemplatePro.pas b/sources/TemplatePro.pas index 92d8d4fe..c53f0c4e 100644 --- a/sources/TemplatePro.pas +++ b/sources/TemplatePro.pas @@ -34,7 +34,7 @@ uses System.RTTI; const - TEMPLATEPRO_VERSION = '0.6'; + TEMPLATEPRO_VERSION = '0.6.1'; type ETProException = class(Exception) @@ -59,11 +59,11 @@ type TTokenType = ( ttContent, ttInclude, ttFor, ttEndFor, ttIfThen, ttBoolExpression, ttElse, ttEndIf, ttStartTag, ttComment, - ttLiteralString, ttEndTag, ttValue, ttFilterName, ttFilterParameter, ttLineBreak, ttSystemVersion, ttEOF); + ttContinue, ttLiteralString, ttEndTag, ttValue, ttFilterName, ttFilterParameter, ttLineBreak, ttSystemVersion, ttExit, ttEOF); const TOKEN_TYPE_DESCR: array [Low(TTokenType)..High(TTokenType)] of string = ('ttContent', 'ttInclude', 'ttFor', 'ttEndFor', 'ttIfThen', 'ttBoolExpression', 'ttElse', 'ttEndIf', 'ttStartTag', 'ttComment', - 'ttLiteralString', 'ttEndTag', 'ttValue', 'ttFilterName', 'ttFilterParameter', 'ttLineBreak', 'ttSystemVersion', 'ttEOF'); + 'ttContinue', 'ttLiteralString', 'ttEndTag', 'ttValue', 'ttFilterName', 'ttFilterParameter', 'ttLineBreak', 'ttSystemVersion', 'ttExit', 'ttEOF'); type TToken = packed record TokenType: TTokenType; @@ -144,7 +144,7 @@ type function GetVarAsString(const Name: string): string; function GetTValueVarAsString(const Value: TValue; const VarName: string = ''): String; function GetVarAsTValue(const aName: string): TValue; -// function EvaluateIfExpression(aIdentifier: string): Boolean; + function GetDataSetFieldAsTValue(const aDataSet: TDataSet; const FieldName: String): TValue; function EvaluateIfExpressionAt(var Idx: UInt64): Boolean; function GetVariables: TTProVariables; procedure SplitVariableName(const VariableWithMember: String; out VarName, VarMembers: String); @@ -289,16 +289,25 @@ begin fTemplateFunctions.Add(FunctionName.ToLower, FunctionImpl); end; +function TTProCompiledTemplate.GetDataSetFieldAsTValue(const aDataSet: TDataSet; const FieldName: String): TValue; +var + lField: TField; +begin + lField := aDataSet.FieldByName(FieldName); + case lField.DataType of + ftInteger: Result := lField.AsInteger; + ftLargeint, ftAutoInc: Result := lField.AsLargeInt; + ftString, ftWideString, ftMemo, ftWideMemo: Result := lField.AsWideString; + else + Error('Invalid data type for field "%s": %s', [FieldName, TRttiEnumerationType.GetName(lField.DataType)]); + end; +end; + function TTProCompiledTemplate.GetOnGetValue: TTProCompiledTemplateGetValueEvent; begin Result := fOnGetValue; end; -//function TTProCompiledTemplate.GetPseudoVariable(const Variable: TVarDataSource; const PseudoVarName: String): TValue; -//begin -// Result := GetPseudoVariable(Variable.VarIterator, PseudoVarName); -//end; - function TTProCompiledTemplate.GetPseudoVariable(const VarIterator: Integer; const PseudoVarName: String): TValue; begin if PseudoVarName = '@@index' then @@ -838,6 +847,10 @@ begin aTokens.Add(TToken.Create(lLastToken, '', '')); Dec(lForStatementCount); lStartVerbatim := fCharIndex; + end else if MatchSymbol('continue') then {continue} + begin + lLastToken := ttContinue; + aTokens.Add(TToken.Create(lLastToken, '', '')); end else if MatchSymbol('endif') then {endif} begin if lIfStatementCount = -1 then @@ -951,6 +964,8 @@ begin end else if MatchSymbol('exit') then {exit} begin + lLastToken := ttExit; + aTokens.Add(TToken.Create(lLastToken, '', '')); lLastToken := ttEOF; aTokens.Add(TToken.Create(lLastToken, '', '')); Break; @@ -1020,69 +1035,108 @@ end; procedure TTProCompiler.FixJumps(const aTokens: TList); var lForInStack: TStack; + lContinueStack: TStack; lIfStatementStack: TStack; I: UInt64; lToken: TToken; lForAddress: UInt64; lIfStackItem: TIfThenElseIndex; + lCheckForUnbalancedPair: Boolean; + lTmpContinueAddress: UInt64; begin + lCheckForUnbalancedPair := True; lForInStack := TStack.Create; try - lIfStatementStack := TStack.Create; + lContinueStack := TStack.Create; try - for I := 0 to aTokens.Count - 1 do - begin - case aTokens[I].TokenType of - ttFor: begin - lForInStack.Push(I); - end; - ttEndFor: begin - {ttFor.Ref1 --> endfor} - lForAddress := lForInStack.Pop; - lToken := aTokens[lForAddress]; - lToken.Ref1 := I; - aTokens[lForAddress] := lToken; + lIfStatementStack := TStack.Create; + try + for I := 0 to aTokens.Count - 1 do + begin + case aTokens[I].TokenType of + ttFor: begin + if lContinueStack.Count > 0 then + begin + Error('Continue stack corrupted'); + end; + lForInStack.Push(I); + end; - {ttEndFor.Ref1 --> for} - lToken := aTokens[I]; - lToken.Ref1 := lForAddress; - aTokens[I] := lToken; - end; + ttEndFor: begin + {ttFor.Ref1 --> endfor} + lForAddress := lForInStack.Pop; + lToken := aTokens[lForAddress]; + lToken.Ref1 := I; + aTokens[lForAddress] := lToken; - {ttIfThen.Ref1 points always to relative else (if present otherwise -1)} - {ttIfThen.Ref2 points always to relative endif} + {ttEndFor.Ref1 --> for} + lToken := aTokens[I]; + lToken.Ref1 := lForAddress; + aTokens[I] := lToken; - ttIfThen: begin - lIfStackItem.IfIndex := I; - lIfStackItem.ElseIndex := -1; {-1 means: "there isn't ttElse"} - lIfStatementStack.Push(lIfStackItem); - end; - ttElse: begin - lIfStackItem := lIfStatementStack.Pop; - lIfStackItem.ElseIndex := I; - lIfStatementStack.Push(lIfStackItem); - end; - ttEndIf: begin - lIfStackItem := lIfStatementStack.Pop; + {if there's a ttContinue (or more than one), it must jump to endfor} + while lContinueStack.Count > 0 do + begin + lTmpContinueAddress := lContinueStack.Pop; + lToken := aTokens[lTmpContinueAddress]; + lToken.Ref1 := I; + aTokens[lTmpContinueAddress] := lToken; + end; + end; - {fixup ifthen} - lToken := aTokens[lIfStackItem.IfIndex]; - lToken.Ref2 := I; {ttIfThen.Ref2 points always to relative endif} - lToken.Ref1 := lIfStackItem.ElseIndex; {ttIfThen.Ref1 points always to relative else (if present, otherwise -1)} - aTokens[lIfStackItem.IfIndex] := lToken; + ttContinue: begin + lContinueStack.Push(I); + end; - {fixup else} - if lIfStackItem.ElseIndex > -1 then - begin - lToken := aTokens[lIfStackItem.ElseIndex]; - lToken.Ref2 := I; {ttElse.Ref2 points always to relative endif} - aTokens[lIfStackItem.ElseIndex] := lToken; + {ttIfThen.Ref1 points always to relative else (if present otherwise -1)} + {ttIfThen.Ref2 points always to relative endif} + + ttIfThen: begin + lIfStackItem.IfIndex := I; + lIfStackItem.ElseIndex := -1; {-1 means: "there isn't ttElse"} + lIfStatementStack.Push(lIfStackItem); + end; + ttElse: begin + lIfStackItem := lIfStatementStack.Pop; + lIfStackItem.ElseIndex := I; + lIfStatementStack.Push(lIfStackItem); + end; + ttEndIf: begin + lIfStackItem := lIfStatementStack.Pop; + + {fixup ifthen} + lToken := aTokens[lIfStackItem.IfIndex]; + lToken.Ref2 := I; {ttIfThen.Ref2 points always to relative endif} + lToken.Ref1 := lIfStackItem.ElseIndex; {ttIfThen.Ref1 points always to relative else (if present, otherwise -1)} + aTokens[lIfStackItem.IfIndex] := lToken; + + {fixup else} + if lIfStackItem.ElseIndex > -1 then + begin + lToken := aTokens[lIfStackItem.ElseIndex]; + lToken.Ref2 := I; {ttElse.Ref2 points always to relative endif} + aTokens[lIfStackItem.ElseIndex] := lToken; + end; + end; + ttExit: begin + lCheckForUnbalancedPair := False; end; end; + end; // for + + if lCheckForUnbalancedPair and (lIfStatementStack.Count > 0) then + begin + Error('Unbalanced "if" - expected "endif"'); end; - end; // for + if lCheckForUnbalancedPair and (lForInStack.Count > 0) then + begin + Error('Unbalanced "for" - expected "endfor"'); + end; + finally + lIfStatementStack.Free; + end; finally - lIfStatementStack.Free; + lContinueStack.Free; end; finally lForInStack.Free; @@ -1163,6 +1217,18 @@ begin FunctionError('expected 1 parameter'); Result := aValue.AsInt64 <= StrToInt64(aParameters[0]); end + else if aFunctionName = 'contains' then + begin + if Length(aParameters) <> 1 then + FunctionError('expected 1 parameter'); + Result := aValue.AsString.Contains(aParameters[0]); + end + else if aFunctionName = 'contains_ignore_case' then + begin + if Length(aParameters) <> 1 then + FunctionError('expected 1 parameter'); + Result := aValue.AsString.ToLowerInvariant.Contains(aParameters[0].ToLowerInvariant); + end else if (aFunctionName = 'eq') or (aFunctionName = 'ne') then begin if Length(aParameters) <> 1 then @@ -1685,7 +1751,7 @@ end; procedure TTProCompiledTemplate.Error(const aMessage: String); begin - raise ETProRenderException.Create(aMessage); + raise ETProRenderException.Create(aMessage) at ReturnAddress; end; procedure TTProCompiledTemplate.ForEachToken( @@ -1916,45 +1982,6 @@ begin end; ttValue, ttLiteralString: begin lVarValue := EvaluateValue(lIdx, lMustBeEncoded {must be encoded}); - -// // Ref1 contains the optional filter parameter number (-1 if there isn't any filter) -// // Ref2 is -1 if the variable must be HTMLEncoded, while contains 1 is the value must not be HTMLEncoded -// lRef2 := fTokens[lIdx].Ref2; -// lCurrTokenType := fTokens[lIdx].TokenType; -// if fTokens[lIdx].Ref1 > -1 {has a filter with Ref1 parameters} then -// begin -// lVarName := fTokens[lIdx].Value1; -// Inc(lIdx); -// lFilterName := fTokens[lIdx].Value1; -// lFilterParCount := fTokens[lIdx].Ref1; // parameter count -// SetLength(lFilterParameters, lFilterParCount); -// for I := 0 to lFilterParCount - 1 do -// begin -// Inc(lIdx); -// Assert(fTokens[lIdx].TokenType = ttFilterParameter); -// lFilterParameters[I] := fTokens[lIdx].Value1; -// end; -// if lCurrTokenType = ttValue then -// begin -// lVarValue := ExecuteFilter(lFilterName, lFilterParameters, GetVarAsTValue(lVarName)); -// end -// else -// begin -// lVarValue := ExecuteFilter(lFilterName, lFilterParameters, lVarName); -// end; -// end -// else -// begin -// if lCurrTokenType = ttValue then -// begin -// lVarValue := GetVarAsString(fTokens[lIdx].Value1); -// end -// else -// begin -// lVarValue := fTokens[lIdx].Value1; -// end; -// end; - if lMustBeEncoded {lRef2 = -1 // encoded} then lBuff.Append(HTMLEncode(lVarValue.ToString)) else @@ -1972,6 +1999,13 @@ begin begin Error('Compiled template has been compiled with a different version. Expected ' + TEMPLATEPRO_VERSION + ' got ' + fTokens[lIdx].Value1); end; + end; + ttContinue: begin + lIdx := fTokens[lIdx].Ref1; + Continue; + end; + ttExit: begin + //do nothing end else begin @@ -1997,7 +2031,6 @@ end; function TTProCompiledTemplate.GetVarAsTValue(const aName: string): TValue; var lVariable: TVarDataSource; - lField: TField; lHasMember: Boolean; lJPath: string; lDataSource: string; @@ -2040,21 +2073,16 @@ begin begin Error('Empty field name while reading from iterator "%s"', [lVarName]); end; - lField := TDataSet(lVariable.VarValue.AsObject).FieldByName(lVarMembers); - case lField.DataType of - ftInteger: Result := lField.AsInteger; - ftLargeint, ftAutoInc: Result := lField.AsLargeInt; - ftString, ftWideString, ftMemo, ftWideMemo: Result := lField.AsWideString; - else - Error('Invalid data type for field "%s": %s', [lVarMembers, TRttiEnumerationType.GetName(lField.DataType)]); - end; + Result := GetDataSetFieldAsTValue(TDataSet(lVariable.VarValue.AsObject), lVarMembers); end; end else begin { not an interator } if lHasMember then - Error(lDataSource + ' members can be read only through an iterator') + begin + Result := GetDataSetFieldAsTValue(TDataSet(lVariable.VarValue.AsObject), lVarMembers); + end else begin Result := lVariable.VarValue.AsObject;