Meta templates (Delphi)
Note
This demo is available in your FlexCel installation at <FlexCel Install Folder>\Demo\Delphi\Modules\20.Reports\90.Meta Templates and also at https://github.com/tmssoftware/TMS-FlexCel.VCL-demos/tree/master/Delphi/Modules/20.Reports/90.Meta Templates
Overview
FlexCel allows self-modifying templates, that can adapt themselves to the data that is going to be reported. This provides other way to create generic reports, besides what is shown in the Generic Reports example. But the utility of meta templates can go much further.
In this example, we have a template that will show rss feeds. If the feed contains a pubDate field, it will be added. If it doesn't, the row will be deleted from the template before running the report.
Concepts
You can use the <#Preprocess> tag to evaluate expressions when the template is being read. You can delete columns and rows, clear fields, etc. Once the preprocessing is over, the report will be run on the modified template.
Database fields and expressions can have default values, that will be applied when the field or expression does not exist. For example: <#DataTable.field;20> will evaluate to the value in DataTable.Field if Field exists, or 20 otherwise.
The <#Defined> tag can be used to know wheter a field or table is defined or not, and to for example delete the column if the field does not exist.
Files
DataModel.pas
unit DataModel;
interface
type
TFeedData = record
private
FName: string;
FLogo: string;
FUrl: string;
FHasPubDate: boolean;
public
property Name: string read FName;
property Url: string read FUrl;
property Logo: string read FLogo;
property HasPubDate: boolean read FHasPubDate;
constructor Create(const aName, aUrl, aLogo: string; const aHasPubDate: boolean);
end;
function GetFeeds: TArray<TFeedData>;
type
TFeedContent = record
public
Title: string;
Description: string;
Link: string;
constructor Create(aTitle, aDescription, aLink: string);
end;
TFeedContentEx = record
public
Title: string;
Description: string;
Link: string;
PubDate: Variant;
constructor Create(aTitle, aDescription, aLink: string; const aPubDate: Variant);
end;
implementation
function GetFeeds: TArray<TFeedData>;
begin
SetLength(Result, 3);
Result[0] := TFeedData.Create('TMS', 'https://www.tmssoftware.com/rss/tms.xml', 'tms.gif', false);
Result[1] := TFeedData.Create('MSDN','http://sxpdata.microsoft.com/feeds/3.0/msdntn/MSDNMagazine_enus', 'msdn.jpg', true);
Result[2] := TFeedData.Create('SLASHDOT' , 'http://rss.slashdot.org/Slashdot/slashdot', 'slashdot.gif', false);
end;
{ TFeedData }
constructor TFeedData.Create(const aName, aUrl, aLogo: string; const aHasPubDate: boolean);
begin
FName := aName;
FUrl := aUrl;
FLogo := aLogo;
FHasPubDate := aHasPubDate;
end;
{ TFeedContentEx }
constructor TFeedContentEx.Create(aTitle, aDescription, aLink: string;
const aPubDate: Variant);
begin
Title := aTitle;
Description := aDescription;
Link := aLink;
PubDate := aPubDate;
end;
{ TFeedContent }
constructor TFeedContent.Create(aTitle, aDescription, aLink: string);
begin
Title := aTitle;
Description := aDescription;
Link := aLink;
end;
end.
UMainForm.pas
unit UMainForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
FlexCel.VCLSupport, FlexCel.Core, FlexCel.XlsAdapter, FlexCel.Report, FlexCel.Render,
{$if CompilerVersion >= 23.0} System.UITypes, {$IFEND}
ShellApi, DataModel, Generics.Collections,
XMLDoc, XMLIntf,
Controls, Forms, Dialogs, StdCtrls, ExtCtrls;
type
TMainForm = class(TForm)
btnCancel: TButton;
btnGo: TButton;
SaveDialog: TSaveDialog;
cbFeeds: TComboBox;
cbOffline: TCheckBox;
cbShowFeedCount: TCheckBox;
procedure btnCancelClick(Sender: TObject);
procedure btnGoClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
Feeds: TArray<TFeedData>;
procedure RunReport;
function GetDataPath: string;
function GetLogo: TBytes;
function CurrentFeed: TFeedData;
function GetFeedContents: TArray<TFeedContent>;
function GetFeedContentsEx: TArray<TFeedContentEx>;
function GetOfflineData: IXmlDocument;
procedure LoadFeeds(node: IXmlNode; const FeedList: TList<TFeedContent>);
procedure LoadFeedsEx(node: IXmlNode;
const FeedList: TList<TFeedContentEx>);
function GetOnlineData: IXmlDocument;
{ Private declarations }
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
uses IOUtils, IdHTTP, IdComponent;
{$R *.dfm}
procedure TMainForm.btnCancelClick(Sender: TObject);
begin
Close;
end;
procedure TMainForm.btnGoClick(Sender: TObject);
begin
RunReport;
end;
procedure TMainForm.FormCreate(Sender: TObject);
var
i: Integer;
begin
Feeds := GetFeeds;
for i := 0 to High(Feeds) do
begin
cbFeeds.Items.Add(Feeds[i].Name);
end;
cbFeeds.ItemIndex := 0;
end;
function TMainForm.GetDataPath: string;
begin
Result := TPath.Combine(TPath.GetDirectoryName(ParamStr(0)), '..\..');
end;
function TMainForm.GetLogo: TBytes;
var
fs: TFileStream;
begin
fs := TFileStream.Create(
TPath.Combine(TPath.Combine(GetDataPath, 'logos'), CurrentFeed.Logo), fmOpenRead);
try
SetLength(Result, fs.Size);
if (Length(Result) > 0) then fs.Read(Result[0], Length(Result));
finally
fs.Free;
end;
end;
function TMainForm.CurrentFeed: TFeedData;
begin
if (cbFeeds.ItemIndex < 0) or (cbFeeds.ItemIndex >= Length(Feeds)) then exit(Feeds[0]);
Result := Feeds[cbFeeds.ItemIndex];
end;
function TMainForm.GetOfflineData: IXmlDocument;
begin
Result := TXMLDocument.Create(
TPath.Combine(TPath.Combine(GetDataPath, 'data'), CurrentFeed.Name + '.xml'));
end;
function TMainForm.GetOnlineData: IXmlDocument;
var
Http: TIdHTTP;
ms: TMemoryStream;
begin
Http := TIdHTTP.Create(nil);
try
ms := TMemoryStream.Create;
try
Http.Get(CurrentFeed.Url, ms);
Result := TXMLDocument.Create(nil);
Result.LoadFromStream(ms);
finally
ms.Free;
end;
finally
Http.Free;
end;
end;
procedure TMainForm.LoadFeeds(node: IXmlNode; const FeedList: TList<TFeedContent>);
var
i: Integer;
begin
if (node.LocalName = 'item') then
begin
FeedList.Add(TFeedContent.Create(node.ChildValues['title'], node.ChildValues['description'], node.ChildValues['link']));
exit;
end;
for i := 0 to node.ChildNodes.Count - 1 do
begin
LoadFeeds(node.ChildNodes[i], FeedList);
end;
end;
function TMainForm.GetFeedContents: TArray<TFeedContent>;
var
doc: IXmlDocument;
FeedList: TList<TFeedContent>;
begin
if cbOffline.Checked then doc := GetOfflineData else doc := GetOnlineData;
FeedList := TList<TFeedContent>.Create;
try
LoadFeeds(doc.DocumentElement, FeedList);
Result := FeedList.ToArray;
finally
FeedList.Free;
end;
end;
procedure TMainForm.LoadFeedsEx(node: IXmlNode; const FeedList: TList<TFeedContentEx>);
var
i: Integer;
begin
if (node.LocalName = 'item') then
begin
FeedList.Add(TFeedContentEx.Create(node.ChildValues['title'], node.ChildValues['description'], node.ChildValues['link'], node.ChildValues['pubDate']));
exit;
end;
for i := 0 to node.ChildNodes.Count - 1 do
begin
LoadFeedsEx(node.ChildNodes[i], FeedList);
end;
end;
function TMainForm.GetFeedContentsEx: TArray<TFeedContentEx>;
var
doc: IXmlDocument;
FeedList: TList<TFeedContentEx>;
begin
if cbOffline.Checked then doc := GetOfflineData else doc := GetOnlineData;
FeedList := TList<TFeedContentEx>.Create;
try
LoadFeedsEx(doc.DocumentElement, FeedList);
Result := FeedList.ToArray;
finally
FeedList.Free;
end;
end;
procedure TMainForm.RunReport;
var
Report: TFlexCelReport;
begin
if not SaveDialog.Execute then exit;
Report := TFlexCelReport.Create(true);
try
if not CurrentFeed.HasPubDate
then Report.AddTable<TFeedContent>('item', GetFeedContents)
else Report.AddTable<TFeedContentEx>('item', GetFeedContentsEx);
Report.SetValue('FeedName', CurrentFeed.Name);
Report.SetValue('FeedUrl', CurrentFeed.Url);
Report.SetValue('ShowCount', cbShowFeedCount.Checked);
Report.SetValue('Logo', GetLogo);
Report.Run(
TPath.Combine(GetDataPath, 'Meta Templates.template.xls'),
SaveDialog.FileName);
finally
Report.Free;
end;
if MessageDlg('Do you want to open the generated file?', mtConfirmation, [mbYes, mbNo], 0) = mrYes then
begin
ShellExecute(0, 'open', PCHAR(SaveDialog.FileName), nil, nil, SW_SHOWNORMAL);
end;
end;
end.