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