1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
|
<html>
<head>
<link href="../lg.css" rel="stylesheet" type="text/css" media="screen, projection" />
<title>Using the HTML::Template module LG #97</title>
<style type="text/css" media="screen, projection">
<!--
.articlecontent {
position:absolute;
top:143px;
}
-->
</style>
</head>
<body>
<img src="../gx/2003/newlogo-blank-200-gold2.jpg" id="logo" alt="Linux Gazette"/>
<p id="fun">...making Linux just a little more fun!</p>
<div class="content articlecontent">
<div id="previousnexttop">
<A HREF="moen.html" ><-- prev</A> | <A HREF="oregan.html" >next --></A>
</div>
<h1>Using the HTML::Template module</h1>
<p id="by"><b>By <A HREF="../authors/okopnik.html">Ben Okopnik</A></b></p>
<p>
<p>
Recently, I needed to generate a Web page - the Linux Gazette's <a
href="http://linuxgazette.net/mirrors.html">"Mirrors and Translations"</a>
page, actually - based on the contents of a database. Perl is famous for
its ability to connect to almost any database via a common interface, given
its <code>DBD::DBI</code> module kit; however, the challenge in this case
came from the front end, the HTML generation. Sure, I could use the CGI
module to output whatever I needed - but in this case, I already had the
static page that I wanted to create, and saw no reason to rewrite all the
static content in CGI. Also, the final product was not to be a CGI file but
a generated HTML page. In fact, everything in this case hinted at
<b>templating</b>, a process in which I would use the static HTML with a
few special tags and a script which would then apply processing based on
those tags. This made especially good sense since it drew a clean
separating line between writing HTML and creating code, very different
tasks and ones for which I have different mental states (layout designer
vs. programmer.)
<p>
As with anything in Perl, <a
href="http://okopnik.freeshell.org/acronyms.cgi?string=TMTOWTDI&type=acronym">TMTOWTDI</a>
- there was a number of modules available on <a href="http://cpan.org">CPAN</a>
(the Comprehensive Perl Archive Network) that could do the job. However, I
had used the <code>HTML::Template</code> module in the past, and the job
wasn't particularly complicated (although <code>HTML::Template</code> can
handle some very complex jobs indeed), so that's what I settled on. My
first task was to hunt through the HTML, removing the dozens of repetitive
stanzas and replacing them with the appropriate tag framework that the
module would utilize later. We had also made the decision not to display
the maintainers email addresses, even in the <a
href="../issue86/okopnik.html">munged form</a> that I use to deter
spammers; those of you who use our mirrors and want to thank these fine
folks for making LG available should be able to find an address link on the
mirror site without much trouble.
<p>
<b>Fragment of the old page (there were several dozen entries like this):</b>
<pre><hr>
...
<A name="AU"></A>
<DT><B><font color="maroon">AUSTRALIA (AU)</font></B></DT>
<DD>
<STRONG><FONT COLOR="green"><TT>[WWW]</TT></FONT></STRONG>
<A HREF="http://www.localnet.com.au/lg/index.html">http://www.localnet.com.au/lg/index.html</A>
<BR>
<SMALL>
Maintainer: Jim McGregor &lt;<A HREF="mailto:nospam@here.please">nospam@here.please</A>&gt; &nbsp;
</SMALL>
<P>
</DD>
<DD>
<STRONG><FONT COLOR="green"><TT>[WWW]</TT></FONT></STRONG>
<A HREF="http://www.eastwood.apana.org.au/Linux/LinuxGazette/">http://www.eastwood.apana.org.au/Linux/LinuxGazette/</A>
<BR>
<SMALL>
Maintainer: Mick Stock &lt;<A HREF="mailto:nospam@here.please">nospam@here.please</A>&gt; &nbsp;
</SMALL>
<P>
</DD>
</BODY></HTML>
...
<hr></pre>
<b>Single-stanza replacement for all the entries (new template):</b><br>
<pre><hr>
...
<a name="<TMPL_VAR NAME=FQDN>"></a>
<dt><b><font color="maroon"><TMPL_VAR NAME=FQDN> (<TMPL_VAR NAME=TLD>)</font></b></dt>
<dd><strong><font color="green"><tt>[WWW]</tt></font></strong>
<a href="<TMPL_VAR NAME=HTTP>"><TMPL_VAR NAME=HTTP></a>
<br>
<strong><font color="red"><tt>[FTP]</tt></font></strong>
<a href="<TMPL_VAR NAME=FTP>"><TMPL_VAR NAME=FTP></a>
<br>
<small>
Maintainer: <TMPL_VAR NAME=MAINT>
</small>
<p>
</dd>
</BODY></HTML>
...
<hr></pre>
Now, the challenge had shifted away from generating the HTML to just
dealing with code. What I needed to do was sort the data into groups and
subgroups - that is, there would some number of "country" headings, some
number of "mirror" headings under each of those, and either one or two
(WWW, FTP, or both) hosts plus a maintainer under each "mirror" heading. In
programmatic terms, these are known as "nested loops", and are not that
difficult to code. However, translating that into HTML terms could be an
exercise in kind of language abilities of which your mother would not
approve... if it wasn't for <code>HTML::Template</code>.
<p>
<b>Note:</b> Using <code>HTML::Template</code> is normally very simple; in
fact, learning the basics of using it usually takes only a minute or two
(see the example at the top of "perldoc HTML::Template".) However, in this
instance, we're creating <i>nested lists</i> - a rather more complex issue
than simple variable/tag replacement - and thus, the coding issues get a bit
deeper. However, this isn't due to <code>HTML::Template</code>; if you
think about the issues inherent in modeling what is already a complex data
structure and then transferring that structure into a "passive" layout
language... truth to tell, I'm somewhat surprised that it can be done at
all. Kudos and my hat's off to Sam Tregar (author of the module) and Jesse
Erlbaum (the man responsible for TMPL_LOOP.)
<p>
<h3>References</h3>
The area that seems to strike fear into the heart of neophyte programmers,
more so than anything else, is the topic of <i>references</i>. Particularly
in Perl, where everything is supposed to be warm, fuzzy, and easy to
understand. However, understanding references and objects - in my opinion -
are the very things that take one from being a Perl user to a Perl
programmer. I'm going to simply show how references work with a bit of an
explanation, but the real comprehensive reference for references :) is
included with the standard Perl documentation. Simply type "perldoc
perlreftut" at your command line for a good introduction, and be sure to
take a look at "perldoc perlref" for the complete documentation.
<p>
First, in order to understand how the data structure must be laid out to
create the pattern that we need, let's take a look at that pattern.
Fortunately, in Perl it's easy to lay out the data structures to match what
they represent (whitespace is arbitrary, so you can follow your preferences
- but see "perldoc perlstyle".) What we'll want to do here is build the
structure that contains all the values we want to assign within the loop as
well as the names which are associated with those values. Those of you with
a little Perl experience are nodding and smiling already: the word
"associated" points very clearly to the type of variable we need - a hash!
Taking a single "row" (per-country entry) - Austria, as a random example -
here is how it looks:
<pre><hr>
%row = (
tld => AT,
fqdn => Austria,
sites => [
{
http => "http://www.luchs.at/linuxgazette/",
maint => "Rene Pfeiffer"
},
{
http => "http://info.ccone.at/lg/",
maint => "Gerhard Beck"
},
{
http => "http://linuxgazette.tuwien.ac.at/",
ftp => "ftp://linuxgazette.tuwien.ac.at/pub/linuxgazette/",
maint => "Tony Sprinzl"
}
]
);
<hr></pre>
The above hash, <code>%row</code>, matches our requirements exactly: its
keys will be used to match (case-insensitively) the tag names in the HTML,
and the values associated with those keys will be used to replace those
tags. That is, every instance of <code><TMPL_VAR NAME=FQDN></code>
in the template will be replaced by "Austria" while this entry is being
processed. Here are some of the less-obvious points of the above
structure:
<p>
<ul>
<li>The <i>anonymous hash constructor</i>, defined by the curly braces
surrounding each group, stores all the data in an <font
color="red"><b>anonymous hash</b></font> and returns a reference to it.
<li>In turn, the <i>anonymous array constructor</i>, defined by the square
braces surrounding the list of groups, stores all of the above references
in an <font color="green"><b>anonymous array</b></font> and returns a reference to it.
<li>The <code><b>sites</b></code> key points to (is associated with) the
reference to the above <font color="green"><b>anonymous array</b></font>,
and is the name of the loop that we'll use within the HTML to iterate
through all of the above data.
</ul>
As we create a "row" for each country, we will need to store all of them in
a list. Each entry in this list must, of course, contain a reference to
each row that we have built:
<pre><hr>
# Add the hashref to the end of the array
push @mirrors, \%row;
<hr></pre>
Note the '\' preceding the <code>%row</code>; this stores a reference to
<code>%row</code> rather than the hash itself (stuffing a hash into an
array would result in a generally unusable mess - key/value pairs in
effectively random order as array elements.) This is a standard mechanism
for creating multidimensional arrays, lists-of-hashes, etc. in Perl.
<p>
And - one more time, <b><i>with gusto</i></b> - HTML::Template's
<code>param()</code> subroutine, as most other subroutines in Perl and many
other languages, expects a reference to the array rather than the array
itself:
<pre><hr>
# Create a new HTML::Template object
my $t = HTML::Template -> new( filename => "mirrors.tmpl" );
# Pass the listref to param()
$t -> param( MIRR => \@mirrors );
<hr></pre>
"<b>And</b>", as Austin Powers would say, "<b>Oi'm <i>spent.</i></b>" Those of you scared
of the Big Bad References may come out from under the bed now. :)
<p>
Looking at it from the other end, the matching part of the template for
this loop looks like this:
<pre><hr>
<dl>
<TMPL_LOOP NAME=MIRR>
<dt><b><font color="maroon">
<a name="<TMPL_VAR NAME=TLD>">
<img src="gx/flags/<TMPL_VAR NAME=TLD>.jpg" border="1">
</a>
<TMPL_VAR FQDN> [<TMPL_VAR NAME=TLD>]
</font></b></dt>
<TMPL_LOOP NAME=SITES>
<dd>
<TMPL_IF NAME="HTTP">
<strong><font color="green"><tt>[WWW]</tt></font></strong>
<a href=<TMPL_VAR HTTP>>
<TMPL_VAR HTTP>
</a><br>
</TMPL_IF>
<TMPL_IF NAME="FTP">
<strong><font color="green"><tt>[FTP]</tt></font></strong>
<a href=<TMPL_VAR NAME=FTP>>
<TMPL_VAR NAME=FTP>
</a><br>
</TMPL_IF>
<small>
Maintainer:
<TMPL_VAR NAME=MAINT>
</small>
<p>
</dd>
</TMPL_LOOP>
</TMPL_LOOP>
</dl>
</BODY></HTML>
<hr></pre>
To recap what we're looking at, there are two loops defined in the above
template, one inside the other: <code><TMPL_LOOP NAME=MIRR></code> and
<code><TMPL_LOOP NAME=SITES></code>. Note that the outside loop corresponds
to the name of the parameter key that we assigned when passing the data
construct to <code>param()</code>, and the name of the inside loop is the
same as the key associated with the groups inside the hash we created.
<p><br></p>
However, fine as the above may be for static data that we can simply type
into those anonymous hashes in the 'groups' listref, static data isn't
often what we get in the real world. Databases are updated, file contents
change - and we obviously need to reflect this in our HTML. So, let's take
a look at a code fragment that does this:
<pre><hr>
for $tld ( @tlds ){
# Set some temporary (per-loop) variables
my @sites;
my %row;
my %line;
# Here's the inner loop!
for ( grep /^$tld/, @mirr ){
# Parse the CSV into fields
my @rec;
my %site;
s/\\,/&comma;/g;
@rec = split /,/;
s/&comma;/,/g for @rec;
# Mirror listings don't require much data
$site{ http } = $rec[2];
$site{ ftp } = $rec[3];
$site{ maint } = $rec[4];
# Load it up!
push @sites, \%site;
}
# Outer loop vars
$row{ tld } = $tld;
$row{ country } = $country{ $tld };
# Ref to the inner loop, attached
$row{ sites } = \@sites;
# ...and load up the total into the array to be passed to param()
push @mirrs, \%row;
}
# Feed the data to the hungry HTML::Template object
$t -> param( MIRR => \@mirrs );
<hr></pre>
By the way, the data we're reading in looks like this:
<pre><hr>
AT,,http://www.luchs.at/linuxgazette/,,Rene Pfeiffer,nospam@here.please,
AT,,http://info.ccone.at/lg/,,Gerhard Beck,nospam@here.please,
BE,,http://linuxgazette.linuxbe.org/,,Cedric Gavage,nospam@here.please,
CA,,http://blue7green.crosswinds.net/hobbies/lg/,,Jim Pierce,nospam@here.please,
<hr></pre>
Now we have a highly dynamic chunk of code that will process the data that
we give it, generate the necessary data structure on the fly, and feed it
out to the template. <i>Voila!</i>
<p>
If you want to see the complete script that I wrote for this project, go <a
href="../issue97/misc/okopnik/mirrorgen.txt">here</a>; the template can be
found <a href="../issue97/misc/okopnik/mirrors.tmpl">here</a>. If you would
like to see the latest generated page, go <a
href="http://linuxgazette.net/mirrors.html">here</a>. If you would like to
change the way the page looks and do something great for the Linux
community, join the folks on the list and become a mirror maintainer:
commit some of your disk space and bandwidth and let the Linux Gazette
"mirrors and translations" person - that's me! - know about it <a
href="mailto:mirrors@linuxgazette.net">here</a>.
<p>
Happy Linuxing to all!
<hr>
<b>Source material: (I <i>was</i> going to write "References"... :)</b>
<p>
<pre>
perldoc perlreftut
perldoc perlref
perldoc HTML::Template
</pre>
<b>Motivation:</b>
<p>
My annoyance at the lack of good documentation for nested loops under
HTML::Template. :)
</p>
<!-- *** BEGIN author bio *** -->
<P>
<P>
<P> Ben is a Contributing Editor for Linux Gazette and a member of
The Answer Gang.
<!-- *** BEGIN bio *** -->
<P>
<IMG ALT="picture" SRC="../gx/2002/tagbio/ben-okopnik.jpg" WIDTH="199"
HEIGHT="200" ALIGN="left" HSPACE="10" VSPACE="10">
<em>
Ben was born in Moscow, Russia in 1962. He became interested in
electricity at age six--promptly demonstrating it by sticking a fork into
a socket and starting a fire--and has been falling down technological mineshafts
ever since. He has been working with computers since the Elder Days, when
they had to be built by soldering parts onto printed circuit boards and
programs had to fit into 4k of memory. He would gladly pay good money to any
psychologist who can cure him of the resulting nightmares.
<p>Ben's subsequent experiences include creating software in nearly a dozen
languages, network and database maintenance during the approach of a hurricane,
and writing articles for publications ranging from sailing magazines to
technological journals. Having recently completed a seven-year
Atlantic/Caribbean cruise under sail, he is currently docked in Baltimore, MD,
where he works as a technical instructor for Sun Microsystems.
<p>Ben has been working with Linux since 1997, and credits it with his complete
loss of interest in waging nuclear warfare on parts of the Pacific Northwest.
</em>
<br CLEAR="all">
<!-- *** END bio *** -->
<!-- *** END author bio *** -->
<div id="articlefooter">
<p>
Copyright © 2003, Ben Okopnik. Copying license
<a href="http://linuxgazette.net/copying.html">http://linuxgazette.net/copying.html</a>
</p>
<p>
Published in Issue 97 of Linux Gazette, December 2003
</p>
</div>
<div id="previousnextbottom">
<A HREF="moen.html" ><-- prev</A> | <A HREF="oregan.html" >next --></A>
</div>
</div>
<div id="navigation">
<a href="../index.html">Home</a>
<a href="../faq/index.html">FAQ</a>
<a href="../lg_index.html">Site Map</a>
<a href="../mirrors.html">Mirrors</a>
<a href="../mirrors.html">Translations</a>
<a href="../search.html">Search</a>
<a href="../archives.html">Archives</a>
<a href="../authors/index.html">Authors</a>
<a href="../contact.html">Contact Us</a>
</div>
<div id="breadcrumbs">
<a href="../index.html">Home</a> >
<a href="index.html">December 2003 (#97)</a> >
Article
</div>
<img src="../gx/2003/sit3-shine.7-2.gif" id="tux" alt="Tux"/>
</body>
</html>
|