Writing Template Processors in Delphi

By: Cary Jensen

Abstract: One of my favorite functions in Delphi is StringReplace, and this article shows you how to use it to write a template processor.

One of my favorite functions in the Delphi runtime library (rtl) is StringReplace. StringReplace performs a search and replace within a string, and it can be particularly valuable for writing template processors.

A template processor is a function that performs a systematic search and replace on a template file, replacing placeholders (tags) with meaningful data.

I have written a little function named PerformTemplateReplace that uses StringReplace to perform a data-driven search and replace on a template file, replacing embedded tags in the template with data from a TDataSet.

Here is the function. An explanation of how it works follows:

function PerformTemplateReplace(const Template: String;
  DataSetRecord: TDataSet): String;
var
  sl: TStringList;
  str: String;
  i: Integer;
begin

  //Delphi 7 note
  //Make sure your uses clause includes Classes and StrUtils
  //Delphi 8 note
  //Make sure your uses clause includes Borland.Vcl.Classes
  //and Borland.Vcl.StrUtils
  sl := TStringList.Create;

  try
    sl.Text := Template;
    //remove all comment lines
    i := 0;
    while i < sl.Count do
    begin
      if UpperCase(Copy(sl.Strings[i],1,3)) = 'REM' then
      begin
        sl.Delete(i);
        continue;
      end;
      //This line is executed if a comment is NOT removed
      inc(i);
    end;
    str := sl.Text;
  finally
    sl.Free;
  end;

  //we now have the template in a string.
  //Use StringReplace to replace parts of the template
  //with data from our database

  //begin by iterating through the AttendeesDataSource.DataSet Fields
  //Replace {fieldname} tags in str with field contents
  for i := 0 to Pred(DataSetRecord.Fields.Count) do
    str := StringReplace(str, '{'+DataSetRecord.Fields[i].FieldName+'}',
      DataSetRecord.Fields[i].AsString, [rfReplaceAll,rfIgnoreCase]);

  //Check for today's date tag {TODAY}
  if Pos('{TODAY}', UpperCase(str)) > 0 then
  begin
    str := StringReplace(str, '{today}',
      FormatDateTime('mmmm, dd, yyyy',Date),[rfReplaceAll, rfIgnoreCase]);
  end;

  //Check for current time tag {TIME}
  if Pos('{TIME}', UpperCase(str)) > 0 then
  begin
    str := StringReplace(str, '{time}',
      FormatDateTime('tt',Time),[rfReplaceAll, rfIgnoreCase]);
  end;

  //You can create any additional
  //custom tags using this same technique

  Result := str;
end;

This function assumes that a template consists of comment lines, field reference tags, custom tags, and plain text. Comment lines are any line that begins with the letters REM. They are used to embed documentation into your template.

Field reference tags are field names from a TDataSet. This template processor assumes that field names will appear within curly braces. For example, if you have a field named Last Name, a valid field reference tag for this field is {Last Name}. Field reference tags are case insensitive.

Custom tags are any custom, curly braced-enclosed tags that you want to manually program. PerformTemplateReplace assumes that there are two custom tags, {TODAY} and {TIME}. You can create as many of your own custom tags as you like by mimicking the {TODAY} and {TIME} processing code in PerformTemplateReplace.

Finally, plain text is any static text that you want in your processed result. It will be untouched by PerformTemplateReplace, so long as your plain text does not duplicate a field reference tag or a custom tag.

PerformTemplateReplace is passed two arguments, a string that contains the template text and a TDataSet. PerformTemplateReplace begins by creating a TStringList, which the template text is placed into. This TStringList is then navigated, and any line that begins with REM is deleted. Once this loop finishes, the TStringList is no longer necessary, and its Text property is assigned to a String variable.

Now the real fun begins. We assume that the data that should be embedded into the template comes from the current record of the passed in TDataSet. Therefore, we iterate through the fields of the current record, performing a StringReplace on each field name enclosed in curly braces, replacing any discovered field reference tags with the text of that field.

Finally, any custom tags that you have programmed are searched for in the text, and replaced if found. When done, PerformTemplateReplace returns a string that contains the processed result.

Consider the following template example:

REM This is a comment line
REM
REM All comment lines are removed before the template is processed
REM
REM To include dataa from a field from the TDataSet in the processed result,
REM add the field name enclosed in curly braces.
REM For example, to include the Company field, use {Company}
REM You can also use the special tags {TODAY} and {TIME}
{Contact}
{Company}
{Addr1} {Addr2}
{City}, {State} {Zip}

{TODAY}

Dear {Contact}:

I am writing to you because I noticed that we have not done business with you since {LASTINVOICEDATE}. You are a valued customer and we want to make sure that you are being treated well by us. I hope that you will agree that we are unequaled when it comes to quality customer services.

If you could take a few minutes out of your day, I would appreciate the opportunity to discuss some of our newest services and products, as well as to hear from you what we can do better to make your experience as our client a positive one.

Thank you very much for your time,

Bob Jones
Regional Sales Manager
Universal Software Corporation, LLP
(555) 555-1212

 Assume that you have a ClientDataSet that points to the Customer.xml file found in c:Program FilesCommon FilesBorland SharedData, and you click a button that invokes PerformTemplateReplace using the following event handler:

with TForm2.Create(nil) do
  begin
    Memo1.Text := PerformTemplateReplace(Self.Memo1.Text, ClientDataSet1);
    Show;
  end;
end;

The template will be a processed and the result will look something like the following:

Erica Norman
Kauai Dive Shoppe
4-976 Sugarloaf Hwy Suite 103
Kapaa Kauai, HI 94766-1234

March, 29, 2004

Dear Erica Norman:

I am writing to you because I noticed that we have not done business with you since 2/2/1995 1:05:03 AM. You are a valued customer and we want to make sure that you are being treated well by us. I hope that you will agree that we are unequaled when it comes to quality customer services.

If you could take a few minutes out of your day, I would appreciate the opportunity to discuss some of our newest services and products, as well as to hear from you what we can do better to make your experience as our client a positive one.

Thank you very much for your time,

Bob Jones
Regional Sales Manager
Universal Software Corporation, LLP
(555) 555-1212

The TemplateProcessor application, shown in the following figure, demonstrates the use of PerformTemplateReplace. You can download this application from Borland's Code Central by using this link.

The following figure shows a processed template result, which was created by clicking the process template button on the main form.

 

About the Author

Cary Jensen is President of Jensen Data Systems, Inc., a training and consulting company that won the 2002 and 2003 Delphi Informant Magazine Readers Choice Awards for Best Training. He is the author and presenter for Delphi Developer Days 2004 (www.DelphiDeveloperDays.com) and Advantage Developer Days 2004 (www.AdvantageDeveloperDays.com), information-packed seminars that tour North America and Europe. Cary is also an award-winning, best-selling co-author of nineteen books, including Advantage Database Server: The Official Guide (2003, McGraw-Hill/Osborne), Building Kylix Applications (2001, Osborne/McGraw-Hill), Oracle JDeveloper (1999, Oracle Press), JBuilder Essentials (1998, Osborne/McGraw-Hill), and Delphi In Depth (1996, Osborne/McGraw-Hill). For information about onsite training and consulting you can contact Cary at cjensen@jensendatasystems.com, or visit his Web site at www.JensenDataSystems.com.

    

Copyright ) 2004 Cary Jensen, Jensen Data Systems, Inc.
ALL RIGHTS RESERVED. NO PART OF THIS DOCUMENT CAN BE COPIED IN ANY FORM WITHOUT THE EXPRESS, WRITTEN CONSENT OF THE AUTHOR.


Server Response from: ETNASC01