1Template::Tutorial::DatUasfeirleC(o3n)tributed Perl DocuTmeemnptlaattieo:n:Tutorial::Datafile(3)
2
3
4
6 Template::Tutorial::Datafile - Creating Data Output Files Using the
7 Template Toolkit
8
11 There are a number of Perl modules that are universally recognised as
12 The Right Thing To Use for certain tasks. If you accessed a database
13 without using DBI, pulled data from the WWW without using one of the
14 LWP modules or parsed XML without using XML::Parser or one of its
15 subclasses then you'd run the risk of being shunned by polite Perl
16 society.
17
18 I believe that the year 2000 saw the emergence of another 'must have'
19 Perl module - the Template Toolkit. I don't think I'm alone in this
20 belief as the Template Toolkit won the 'Best New Module' award at the
21 Perl Conference last summer. Version 2.0 of the Template Toolkit (known
22 as TT2 to its friends) was recently released to the CPAN.
23
24 TT2 was designed and written by Andy Wardley <abw@wardley.org>. It was
25 born out of Andy's previous templating module, Text::Metatext, in best
26 Fred Brooks 'plan to throw one away' manner; and aims to be the most
27 useful (or, at least, the most used) Perl templating system.
28
29 TT2 provides a way to take a file of fixed boilerplate text (the
30 template) and embed variable data within it. One obvious use of this is
31 in the creation of dynamic web pages and this is where a lot of the
32 attention that TT2 has received has been focussed. In this article, I
33 hope to demonstrate that TT2 is just as useful in non-web applications.
34
36 Let's look at how we'd use TT2 to process a simple data file. TT2 is
37 an object oriented Perl module. Having downloaded it from CPAN and
38 installed it in the usual manner, using it in your program is as easy
39 as putting the lines
40
41 use Template;
42 my $tt = Template->new;
43
44 in your code. The constructor function, "new", takes a number of
45 optional parameters which are documented in the copious manual pages
46 that come with the module, but for the purposes of this article we'll
47 keep things as simple as possible.
48
49 To process the template, you would call the "process" method like this
50
51 $tt->process('my_template', \%data)
52 || die $tt->error;
53
54 We pass two parameters to "process", the first is the name of the file
55 containing the template to process (in this case, my_template) and the
56 second is a reference to a hash which contains the data items that you
57 want to use in the template. If processing the template gives any kind
58 of error, the program will die with a (hopefully) useful error message.
59
60 So what kinds of things can go in %data? The answer is just about
61 anything. Here's an example showing data about English Premier League
62 football teams.
63
64 my @teams = ({ name => 'Man Utd',
65 played => 16,
66 won => 12,
67 drawn => 3,
68 lost => 1 },
69 { name => 'Bradford',
70 played => 16,
71 won => 2,
72 drawn => 5,
73 lost => 9 });
74
75 my %data = ( name => 'English Premier League',
76 season => '2000/01',
77 teams => \@teams );
78
79 This creates three data items which can be accessed within the
80 template, called "name", "season" and "teams". Notice that "teams" is a
81 complex data structure.
82
83 Here is a template that we might use to process this data.
84
85 League Standings
86
87 League Name: [% name %]
88 Season : [% season %]
89
90 Teams:
91 [% FOREACH team = teams -%]
92 [% team.name %] [% team.played -%]
93 [% team.won %] [% team.drawn %] [% team.lost %]
94 [% END %]
95
96 Running this template with this data gives us the following output
97
98 League Standings
99
100 League Name: English Premier League
101 Season : 2000/01
102
103 Teams:
104 Man Utd 16 12 3 1
105 Bradford 16 2 5 9
106
107 Hopefully the syntax of the template is simple enough to follow. There
108 are a few points to note.
109
110 • Template processing directives are written using a simple language
111 which is not Perl.
112
113 • The keys of the %data have become the names of the data variables
114 within the template.
115
116 • Template processing directives are surrounded by "[%" and "%]"
117 sequences.
118
119 • If these tags are replaced with "[%-" "-%]" then the preceding or
120 following linefeed is suppressed.
121
122 • In the "FOREACH" loop, each element of the "teams" list was
123 assigned, in turn, to the temporary variable "team".
124
125 • Each item assigned to the "team" variable is a Perl hash.
126 Individual values within the hash are accessed using a dot
127 notation.
128
129 It's probably the first and last of these points which are the most
130 important. The first point emphasises the separation of the data
131 acquisition logic from the presentation logic. The person creating the
132 presentation template doesn't need to know Perl, they only need to know
133 the data items which will be passed into the template.
134
135 The last point demonstrates the way that TT2 protects the template
136 designer from the implementation of the data structures. The data
137 objects passed to the template processor can be scalars, arrays,
138 hashes, objects or even subroutines. The template processor will just
139 interpret your data correctly and Do The Right Thing to return the
140 correct value to you. In this example each team was a hash, but in a
141 larger system each team might be an object, in which case "name",
142 "played", etc. would be accessor methods to the underlying object
143 attributes. No changes would be required to the template as the
144 template processor would realise that it needed to call methods rather
145 than access hash values.
146
147 A more complex example
148 Stats about the English Football League are usually presented in a
149 slightly more complex format than the one we used above. A full set of
150 stats will show the number of games that a team has won, lost or drawn,
151 the number of goals scored for and against the team and the number of
152 points that the team therefore has. Teams gain three points for a win
153 and one point for a draw. When teams have the same number of points
154 they are separated by the goal difference, that is the number of goals
155 the team has scored minus the number of team scored against them. To
156 complicate things even further, the games won, drawn and lost and the
157 goals for and against are often split between home and away games.
158
159 Therefore if you have a data source which lists the team name together
160 with the games won, drawn and lost and the goals for and against split
161 into home and away (a total of eleven data items) you can calculate all
162 of the other items (goal difference, points awarded and even position
163 in the league). Let's take such a file, but we'll only look at the top
164 three teams. It will look something like this:
165
166 Man Utd,7,1,0,26,4,5,2,1,15,6
167 Arsenal,7,1,0,17,4,2,3,3,7,9
168 Leicester,4,3,1,10,8,4,2,2,7,4
169
170 A simple script to read this data into an array of hashes will look
171 something like this (I've simplified the names of the data columns - w,
172 d, and l are games won, drawn and lost and f and a are goals scored for
173 and against; h and a at the front of a data item name indicates whether
174 it's a home or away statistic):
175
176 my @cols = qw(name hw hd hl hf ha aw ad al af aa);
177
178 my @teams;
179 while (<>) {
180 chomp;
181
182 my %team;
183
184 @team{@cols} = split /,/;
185
186 push @teams, \%team;
187 }
188
189 We can then go thru the teams again and calculate all of the derived
190 data items:
191
192 foreach (@teams) {
193 $_->{w} = $_->{hw} + $_->{aw};
194 $_->{d} = $_->{hd} + $_->{ad};
195 $_->{l} = $_->{hl} + $_->{al};
196
197 $_->{pl} = $_->{w} + $_->{d} + $_->{l};
198
199 $_->{f} = $_->{hf} + $_->{af};
200 $_->{a} = $_->{ha} + $_->{aa};
201
202 $_->{gd} = $_->{f} - $_->{a};
203 $_->{pt} = (3 * $_->{w}) + $_->{d};
204 }
205
206 And then produce a list sorted in descending order:
207
208 @teams = sort {
209 $b->{pt} <=> $b->{pt} || $b->{gd} <=> $a->{gd}
210 } @teams;
211
212 And finally add the league position data item:
213
214 $teams[$_]->{pos} = $_ + 1
215 foreach 0 .. $#teams;
216
217 Having pulled all of our data into an internal data structure we can
218 start to produce output using out templates. A template to create a CSV
219 file containing the data split between home and away stats would look
220 like this:
221
222 [% FOREACH team = teams -%]
223 [% team.pos %],[% team.name %],[% team.pl %],[% team.hw %],
224 [%- team.hd %],[% team.hl %],[% team.hf %],[% team.ha %],
225 [%- team.aw %],[% team.ad %],[% team.al %],[% team.af %],
226 [%- team.aa %],[% team.gd %],[% team.pt %]
227 [%- END %]
228
229 And processing it like this:
230
231 $tt->process('split.tt', { teams => \@teams }, 'split.csv')
232 || die $tt->error;
233
234 produces the following output:
235
236 1,Man Utd,16,7,1,0,26,4,5,2,1,15,6,31,39
237 2,Arsenal,16,7,1,0,17,4,2,3,3,7,9,11,31
238 3,Leicester,16,4,3,1,10,8,4,2,2,7,4,5,29
239
240 Notice that we've introduced the third parameter to "process". If this
241 parameter is missing then the TT2 sends its output to "STDOUT". If this
242 parameter is a scalar then it is taken as the name of a file to write
243 the output to. This parameter can also be (amongst other things) a
244 filehandle or a reference to an object which is assumed to implement a
245 "print" method.
246
247 If we weren't interested in the split between home and away games, then
248 we could use a simpler template like this:
249
250 [% FOREACH team = teams -%]
251 [% team.pos %],[% team.name %],[% team.pl %],[% team.w %],
252 [%- team.d %],[% team.l %],[% team.f %],[% team.a %],
253 [%- team.aa %],[% team.gd %],[% team.pt %]
254 [% END -%]
255
256 Which would produce output like this:
257
258 1,Man Utd,16,12,3,1,41,10,6,31,39
259 2,Arsenal,16,9,4,3,24,13,9,11,31
260 3,Leicester,16,8,5,3,17,12,4,5,29
261
263 This is starting to show some of the power and flexibility of TT2, but
264 you may be thinking that you could just as easily produce this output
265 with a "foreach" loop and a couple of "print" statements in your code.
266 This is, of course, true; but that's because I've chosen a deliberately
267 simple example to explain the concepts. What if we wanted to produce an
268 XML file containing the data? And what if (as I mentioned earlier) the
269 league data was held in an object? The code would then look even easier
270 as most of the code we've written earlier would be hidden away in
271 "FootballLeague.pm".
272
273 use FootballLeague;
274 use Template;
275
276 my $league = FootballLeague->new(name => 'English Premier');
277
278 my $tt = Template->new;
279
280 $tt->process('league_xml.tt', { league => $league })
281 || die $tt->error;
282
283 And the template in "league_xml.tt" would look something like this:
284
285 <?xml version="1.0"?>
286 <!DOCTYPE LEAGUE SYSTEM "league.dtd">
287
288 <league name="[% league.name %]" season="[% league.season %]">
289 [% FOREACH team = league.teams -%]
290 <team name="[% team.name %]"
291 pos="[% team.pos %]"
292 played="[% team.pl %]"
293 goal_diff="[% team.gd %]"
294 points="[% team.pt %]">
295 <stats type="home">
296 win="[% team.hw %]"
297 draw="[%- team.hd %]"
298 lose="[% team.hl %]"
299 for="[% team.hf %]"
300 against="[% team.ha %]" />
301 <stats type="away">
302 win="[% team.aw %]"
303 draw="[%- team.ad %]"
304 lose="[% team.al %]"
305 for="[% team.af %]"
306 against="[% team.aa %]" />
307 </team>
308 [% END -%]
309 &/league>
310
311 Notice that as we've passed the whole object into "process" then we
312 need to put an extra level of indirection on our template variables -
313 everything is now a component of the "league" variable. Other than
314 that, everything in the template is very similar to what we've used
315 before. Presumably now "team.name" calls an accessor function rather
316 than carrying out a hash lookup, but all of this is transparent to our
317 template designer.
318
320 As a final example, let's suppose that we need to create output
321 football league tables in a number of formats. Perhaps we are passing
322 this data on to other people and they can't all use the same format.
323 Some of our users need CSV files and others need XML. Some require data
324 split between home and away matches and other just want the totals. In
325 total, then, we'll need four different templates, but the good news is
326 that they can use the same data object. All the script needs to do is
327 to establish which template is required and process it.
328
329 use FootballLeague;
330 use Template;
331
332 my ($name, $type, $stats) = @_;
333
334 my $league = FootballLeague->new(name => $name);
335
336 my $tt = Template->new;
337
338 $tt->process("league_${type}_$stats.tt",
339 { league => $league }
340 "league_$stats.$type")
341 || die $tt->error;
342
343 For example, you can call this script as
344
345 league.pl 'English Premier' xml split
346
347 This will process a template called "league_xml_split.tt" and put the
348 results in a file called "league_split.xml".
349
350 This starts to show the true strength of the Template Toolkit. If we
351 later wanted to add another file format - perhaps we wanted to create a
352 league table HTML page or even a LaTeX document - then we would just
353 need to create the appropriate template and name it according to our
354 existing naming convention. We would need to make no changes to the
355 code.
356
357 I hope you can now see why the Template Toolkit is fast becoming an
358 essential part of many people's Perl installation.
359
361 Dave Cross <dave@dave.org.uk>
362
364 Template Toolkit version 2.19, released on 27 April 2007.
365
367 Copyright (C) 2001 Dave Cross <dave@dave.org.uk>
368
369 This module is free software; you can redistribute it and/or modify it
370 under the same terms as Perl itself.
371
372
373
374perl v5.34.0 2021-07-23 Template::Tutorial::Datafile(3)