The Sempare Template Engine (available at https://github.com/sempare/sempare-delphi-template-engine and GetIt) is a versatile templating system designed specifically for Delphi developers to streamline the creation and management of dynamic HTML, text, or any other text-based output formats. Whether you are building web applications, reports, or email templates, the Sempare Template Engine offers a powerful yet straightforward solution that integrates with minimal boilerplate into Delphi projects. Having been around since 2019, the template engine has matured into a robust tool with many features for you to explore.
In this article, we will explore the various ways in which templates can be accessed using the Template Registry that accompanies the Sempare Template Engine.
This article assumes you are already familiar with the template engine and are interested in some of its more advanced features. Introductory topics on the engine are covered elsewhere and in other articles.
Introduction: Why Use a Template Registry?
Managing templates in larger applications often leads to repetitive boilerplate code, especially when dealing with multiple sources like files, streams, or resources. This can make the process error-prone and time-consuming. The Template Registry addresses this challenge by offering a general and consistent approach for template access management.
The Sempare Template Engine’s main entry point is the Template helper class. Under the hood, it leverages several components, including:
- A lexer to tokenize text streams.
- A parser to process tokens based on the template language grammar.
- An Abstract Syntax Tree (AST) with a visitor pattern, enabling loose coupling between evaluation, pretty printing, and other functionalities.
The Template helper class provides overloaded methods to parse and evaluate templates seamlessly, whether you’re working with strings, byte arrays, or streams.
Here’s an example of parsing and evaluating a simple template:
var LTemplate: ITemplate := Template.Parse('hello <% _ %>');
LOutput := Template.Eval(LTemplate, 'world');
writeln(LOutput);
You can also load templates from a stream:
var LStream: TStream := TBufferedFileStream.Create('hello.tpl', fmOpenRead);
LTemplate: ITemplate := Template.Parse(LStream);
LOutput := Template.Eval(LTemplate, 'world');
writeln(LOutput);
While these examples demonstrate the basics, scaling this approach to manage many templates often results in different developers re-implementing the same type of solution. This is where the Template Registry shines - providing a centralized, thread-safe mechanism for managing templates efficiently.
The Template Registry assumes:
- A single context contains the core configuration.
- Templates are uniquely named.
- Templates are loaded lazily and parsed only once.
- Template access is thread-safe.
The Template Helper Class
The Template helper class includes a Resolver method and several overloaded Resolve methods for accessing templates through the registry:
type
Template = class
// ...
// TEMPLATE REGISTRY
class function Resolver(): TTemplateRegistry; static; inline;
class procedure Resolve<T>(const ATemplateName: string; const AData: T; const AOutputStream: TStream); overload; static; inline;
class function Resolve<T>(const ATemplateName: string; const AData: T): string; overload; static; inline;
class procedure Resolve(const ATemplateName: string; const AOutputStream: TStream); overload; static; inline;
class function Resolve(const ATemplateName: string): string; overload; static; inline;
// ...
end;
Basic Usage
Using the Template.Resolve methods simplifies template usage:
writeln(Template.Resolve('hello.tpl', 'world'));
Or, outputting directly to a stream:
var
LStream: TStringStream := TStringStream.Create;
try
Template.Resolve('hello.tpl', 'world', LStream);
writeln(LStream.DataString);
finally
LStream.Free;
end;
Load Strategy
The TTemplateRegistry.LoadStrategy property determines how templates are loaded:
type
TTemplateLoadStrategy = (tlsLoadResource, tlsLoadFile, tlsLoadCustom);
By default:
Template.Resolver.LoadStrategy := [tlsLoadFile, tlsLoadResource];
This means the registry first attempts to load from files, falling back to resources if the file isn’t found.
Using Resources
When embedding templates as resources, naming conventions matter. Resource keys do not support slashes, so a default resolver replaces slashes (/ or ) with underscores (_). For example:
hello_tpl RCDATA "templates\\hello.tpl"
The default ResourceNameResolver transforms names:
FResourceNameResolver := function(const AName: string): string
begin
exit(AName.ToUpper
.Replace('-', '_', [rfReplaceAll])
.Replace('\', '_', [rfReplaceAll])
.Replace('/', '_', [rfReplaceAll])
.Replace('.', '_', [rfReplaceAll]));
end;
Similarly, FileNameResolver adjusts paths for the target platform:
FFileNameResolver := function(const AName: string): string
begin
result := TPath.Combine(TemplateRootFolder, AName);
{$IFDEF MSWINDOWS}
exit(result.Replace('/', '\'));
{$ELSE}
exit(result.Replace('\', '/'));
{$ENDIF}
end;
Custom Loading
For advanced use cases, templates can be loaded from external sources like databases or APIs. Add tlsLoadCustom to the load strategy:
Template.Resolver.LoadStrategy := [tlsFile, tlsLoadCustom];
Define a custom loader:
Template.Resolver.CustomTemplateLoader :=
function(const AContext: ITemplateContext; const AName: string; const AResolveContext: TTemplateValue; out ACacheInContext: boolean): ITemplate
begin
// Load template from a custom source
end;
Automatic Refresh
During development, templates can automatically refresh in debug mode:
Template.Resolver.AutomaticRefresh := true;
Template.Resolver.RefreshIntervalS := 5;
This background feature detects and reloads modified templates, ensuring changes are reflected immediately. Disable this in production to avoid performance overhead:
Template.Resolver.AutomaticRefresh := false;
Conclusion
The Sempare Template Engine eliminates boilerplate and offers robust template management for Delphi developers. Its flexibility, ease of use, and advanced features, like the Template Registry, make it an indispensable tool for projects requiring dynamic text generation.
Unlike frameworks tied to specific ecosystems, the Sempare Template Engine is framework-agnostic, allowing you to use it anywhere templates are needed.
We are proud to hear from our users that the Sempare Template Engine is the most feature-rich template engine for Delphi. We hope it enhances your productivity as well.
Thank you to our supporters!
We need your help to maintain the project. Support us via GitHub Sponsors (https://github.com/sponsors/sempare) or Stripe (https://buy.stripe.com/aEU7t61N88pffQIdQQ). Sponsors gain access to exclusive features like our RAD Studio IDE wizard.
Top comments (0)