DEV Community

Sempare Limited
Sempare Limited

Posted on

The Sempare Template Engine for Delphi: Advanced Template Registry Features

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Basic Usage

Using the Template.Resolve methods simplifies template usage:

writeln(Template.Resolve('hello.tpl', 'world'));
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Load Strategy

The TTemplateRegistry.LoadStrategy property determines how templates are loaded:

type
  TTemplateLoadStrategy = (tlsLoadResource, tlsLoadFile, tlsLoadCustom);
Enter fullscreen mode Exit fullscreen mode

By default:

Template.Resolver.LoadStrategy := [tlsLoadFile, tlsLoadResource];
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

The default ResourceNameResolver transforms names:

FResourceNameResolver := function(const AName: string): string
  begin
    exit(AName.ToUpper
        .Replace('-', '_', [rfReplaceAll])
        .Replace('\', '_', [rfReplaceAll])
        .Replace('/', '_', [rfReplaceAll])
        .Replace('.', '_', [rfReplaceAll]));
  end;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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];
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Automatic Refresh

During development, templates can automatically refresh in debug mode:

Template.Resolver.AutomaticRefresh := true;
Template.Resolver.RefreshIntervalS := 5;
Enter fullscreen mode Exit fullscreen mode

This background feature detects and reloads modified templates, ensuring changes are reflected immediately.

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)