| lib/Template/Twostep.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % |
| statement | 211 | 219 | 96.3 |
| branch | 86 | 106 | 81.1 |
| condition | 5 | 9 | 55.5 |
| subroutine | 28 | 28 | 100.0 |
| pod | 2 | 22 | 9.0 |
| total | 332 | 384 | 86.4 |
| line | stmt | bran | cond | sub | pod | time | code |
|---|---|---|---|---|---|---|---|
| 1 | package Template::Twostep; | ||||||
| 2 | |||||||
| 3 | 1 | 1 | 9117 | use 5.008005; | |||
| 1 | 6 | ||||||
| 1 | 55 | ||||||
| 4 | 1 | 1 | 6 | use strict; | |||
| 1 | 2 | ||||||
| 1 | 51 | ||||||
| 5 | 1 | 1 | 21 | use warnings; | |||
| 1 | 3 | ||||||
| 1 | 37 | ||||||
| 6 | 1 | 1 | 1184 | use integer; | |||
| 1 | 14 | ||||||
| 1 | 6 | ||||||
| 7 | |||||||
| 8 | 1 | 1 | 31 | use Carp; | |||
| 1 | 1 | ||||||
| 1 | 87 | ||||||
| 9 | 1 | 1 | 984 | use IO::File; | |||
| 1 | 25495 | ||||||
| 1 | 3607 | ||||||
| 10 | |||||||
| 11 | our $VERSION = "1.05"; | ||||||
| 12 | |||||||
| 13 | #---------------------------------------------------------------------- | ||||||
| 14 | # Create a new template engine | ||||||
| 15 | |||||||
| 16 | sub new { | ||||||
| 17 | 8 | 8 | 1 | 613 | my ($pkg, %config) = @_; | ||
| 18 | |||||||
| 19 | 8 | 38 | my $parameters = $pkg->parameters(); | ||||
| 20 | 8 | 53 | my %self = (%$parameters, %config); | ||||
| 21 | |||||||
| 22 | 8 | 1772 | my $self = bless(\%self, $pkg); | ||||
| 23 | 8 | 141 | $self->set_patterns(); | ||||
| 24 | |||||||
| 25 | 8 | 43 | return $self; | ||||
| 26 | } | ||||||
| 27 | |||||||
| 28 | #---------------------------------------------------------------------- | ||||||
| 29 | # Coerce a value to the type indicated by the sigil | ||||||
| 30 | |||||||
| 31 | sub coerce { | ||||||
| 32 | 71 | 71 | 0 | 13178 | my ($self, $sigil, $value) = @_; | ||
| 33 | |||||||
| 34 | 71 | 83 | my $data; | ||||
| 35 | 71 | 100 | 148 | if (defined $value) { | |||
| 100 | |||||||
| 36 | 68 | 101 | my $ref = ref $value; | ||||
| 37 | |||||||
| 38 | 68 | 100 | 166 | if ($sigil eq '$') { | |||
| 100 | |||||||
| 50 | |||||||
| 39 | 52 | 100 | 86 | if (! $ref) { | |||
| 100 | |||||||
| 50 | |||||||
| 40 | 50 | 89 | $data = \$value; | ||||
| 41 | } elsif ($ref eq 'ARRAY') { | ||||||
| 42 | 1 | 3 | my $val = @$value; | ||||
| 43 | 1 | 2 | $data = \$val; | ||||
| 44 | } elsif ($ref eq 'HASH') { | ||||||
| 45 | 1 | 4 | my @data = %$value; | ||||
| 46 | 1 | 2 | my $val = @data; | ||||
| 47 | 1 | 3 | $data = \$val; | ||||
| 48 | } | ||||||
| 49 | |||||||
| 50 | } elsif ($sigil eq '@') { | ||||||
| 51 | 8 | 100 | 35 | if (! $ref) { | |||
| 100 | |||||||
| 50 | |||||||
| 52 | 1 | 3 | $data = [$value]; | ||||
| 53 | } elsif ($ref eq 'ARRAY') { | ||||||
| 54 | 6 | 32 | $data = $value; | ||||
| 55 | } elsif ($ref eq 'HASH') { | ||||||
| 56 | 1 | 4 | my @data = %$value; | ||||
| 57 | 1 | 4499 | $data = \@data; | ||||
| 58 | } | ||||||
| 59 | |||||||
| 60 | } elsif ($sigil eq '%') { | ||||||
| 61 | 8 | 100 | 66 | 42 | if ($ref eq 'ARRAY' && @$value % 2 == 0) { | ||
| 100 | |||||||
| 62 | 1 | 4 | my %data = @$value; | ||||
| 63 | 1 | 3 | $data = \%data; | ||||
| 64 | } elsif ($ref eq 'HASH') { | ||||||
| 65 | 6 | 14 | $data = $value; | ||||
| 66 | } | ||||||
| 67 | } | ||||||
| 68 | |||||||
| 69 | } elsif ($sigil eq '$') { | ||||||
| 70 | 1 | 3 | $data = \$value; | ||||
| 71 | } | ||||||
| 72 | |||||||
| 73 | 71 | 1008 | return $data; | ||||
| 74 | } | ||||||
| 75 | |||||||
| 76 | #---------------------------------------------------------------------- | ||||||
| 77 | # Compile a template into a subroutine which when called fills itself | ||||||
| 78 | |||||||
| 79 | sub compile { | ||||||
| 80 | 9 | 9 | 1 | 27514 | my ($pkg, @templates) = @_; | ||
| 81 | 9 | 100 | 57 | my $self = ref $pkg ? $pkg : $pkg->new(); | |||
| 82 | |||||||
| 83 | # Template precedes subtemplate, which precedes subsubtemplate | ||||||
| 84 | |||||||
| 85 | 9 | 20 | my $text; | ||||
| 86 | 9 | 18 | my $section = {}; | ||||
| 87 | 9 | 32 | while (my $template = pop(@templates)) { | ||||
| 88 | # If a template contains a newline, it is a string, | ||||||
| 89 | # if not, it is a filename | ||||||
| 90 | |||||||
| 91 | 12 | 100 | 59 | $text = ($template =~ /\n/) ? $template : $self->slurp($template); | |||
| 92 | 12 | 45 | $text = $self->substitute_sections($text, $section); | ||||
| 93 | } | ||||||
| 94 | |||||||
| 95 | 9 | 36 | return $self->construct_code($text); | ||||
| 96 | } | ||||||
| 97 | |||||||
| 98 | #---------------------------------------------------------------------- | ||||||
| 99 | # Compile a subroutine from the code embedded in the template | ||||||
| 100 | |||||||
| 101 | sub construct_code { | ||||||
| 102 | 9 | 9 | 0 | 21 | my ($self, $text) = @_; | ||
| 103 | |||||||
| 104 | 9 | 55 | my @lines = split(/\n/, $text); | ||||
| 105 | |||||||
| 106 | 9 | 18 | my $start = <<'EOQ'; | ||||
| 107 | sub { | ||||||
| 108 | $self->init_stack(); | ||||||
| 109 | $self->push_stack(@_); | ||||||
| 110 | my $text = ''; | ||||||
| 111 | EOQ | ||||||
| 112 | |||||||
| 113 | 9 | 36 | my @mid = $self->parse_code(\@lines); | ||||
| 114 | |||||||
| 115 | 9 | 121 | my $end .= <<'EOQ'; | ||||
| 116 | return $text; | ||||||
| 117 | } | ||||||
| 118 | EOQ | ||||||
| 119 | |||||||
| 120 | 9 | 42 | my $code = join("\n", $start, @mid, $end); | ||||
| 121 | 9 | 26434 | my $sub = eval ($code); | ||||
| 122 | 9 | 50 | 41 | croak $@ unless $sub; | |||
| 123 | |||||||
| 124 | 9 | 79 | return $sub; | ||||
| 125 | } | ||||||
| 126 | |||||||
| 127 | #---------------------------------------------------------------------- | ||||||
| 128 | # Replace variable references with hashlist fetches | ||||||
| 129 | |||||||
| 130 | sub encode_expression { | ||||||
| 131 | 31 | 31 | 0 | 46 | my ($self, $value) = @_; | ||
| 132 | |||||||
| 133 | 31 | 50 | 54 | if (defined $value) { | |||
| 134 | 31 | 44 | my $pre = '{$self->fetch_stack(\''; | ||||
| 135 | 31 | 35 | my $mid = '\',\''; | ||||
| 136 | 31 | 36 | my $post = '\')}'; | ||||
| 137 | 31 | 283 | $value =~ s/(? | ||||
| 138 | |||||||
| 139 | } else { | ||||||
| 140 | 0 | 0 | $value = ''; | ||||
| 141 | } | ||||||
| 142 | |||||||
| 143 | 31 | 76 | return $value; | ||||
| 144 | } | ||||||
| 145 | |||||||
| 146 | #---------------------------------------------------------------------- | ||||||
| 147 | # Replace variable references with hashlist fetches | ||||||
| 148 | |||||||
| 149 | sub encode_text { | ||||||
| 150 | 36 | 36 | 0 | 56 | my ($self, $value) = @_; | ||
| 151 | |||||||
| 152 | 36 | 50 | 54 | if (defined $value) { | |||
| 153 | 36 | 52 | my $pre = '${$self->fill_in(\''; | ||||
| 154 | 36 | 52 | my $mid = '\',\''; | ||||
| 155 | 36 | 43 | my $post = '\')}'; | ||||
| 156 | 36 | 153 | $value =~ s/(? | ||||
| 157 | |||||||
| 158 | } else { | ||||||
| 159 | 0 | 0 | $value = ''; | ||||
| 160 | } | ||||||
| 161 | |||||||
| 162 | 36 | 169 | return $value; | ||||
| 163 | } | ||||||
| 164 | |||||||
| 165 | #---------------------------------------------------------------------- | ||||||
| 166 | # Escape a set of characters | ||||||
| 167 | |||||||
| 168 | sub escape { | ||||||
| 169 | 34 | 34 | 0 | 4981 | my ($self, $data) = @_; | ||
| 170 | |||||||
| 171 | 34 | 181 | $data =~ s/($self->{escaped_chars_pattern})/'' . ord($1) . ';'/ge; | ||||
| 4 | 15 | ||||||
| 172 | 34 | 371 | return $data; | ||||
| 173 | } | ||||||
| 174 | |||||||
| 175 | #---------------------------------------------------------------------- | ||||||
| 176 | # Find and retrieve a value from the hash stack | ||||||
| 177 | |||||||
| 178 | sub fetch_stack { | ||||||
| 179 | 59 | 59 | 0 | 174 | my ($self, $sigil, $name) = @_; | ||
| 180 | |||||||
| 181 | 59 | 61 | my $value; | ||||
| 182 | 59 | 74 | for my $hash (@{$self->{stack}}) { | ||||
| 59 | 193 | ||||||
| 183 | 69 | 100 | 155 | if (exists $hash->{$name}) { | |||
| 184 | 59 | 90 | $value = $hash->{$name}; | ||||
| 185 | 59 | 164 | last; | ||||
| 186 | } | ||||||
| 187 | } | ||||||
| 188 | |||||||
| 189 | 59 | 146 | $value = $self->coerce($sigil, $value); | ||||
| 190 | 59 | 50 | 119 | croak "Illegal type conversion: $sigil$name" unless defined $value; | |||
| 191 | |||||||
| 192 | 59 | 1827 | return $value; | ||||
| 193 | } | ||||||
| 194 | |||||||
| 195 | #---------------------------------------------------------------------- | ||||||
| 196 | # Return a value to fill in a template | ||||||
| 197 | |||||||
| 198 | sub fill_in { | ||||||
| 199 | 28 | 28 | 0 | 1548 | my ($self, $sigil, $name) = @_; | ||
| 200 | |||||||
| 201 | 28 | 65 | my $data = $self->fetch_stack($sigil, $name); | ||||
| 202 | 28 | 70 | my $result = $self->render($data); | ||||
| 203 | |||||||
| 204 | 28 | 1135 | return \$result; | ||||
| 205 | } | ||||||
| 206 | |||||||
| 207 | #---------------------------------------------------------------------- | ||||||
| 208 | # Get the translation of a template command | ||||||
| 209 | |||||||
| 210 | sub get_command { | ||||||
| 211 | 56 | 56 | 0 | 74 | my ($self, $cmd) = @_; | ||
| 212 | |||||||
| 213 | 56 | 559 | my $commands = { | ||||
| 214 | do => "%%;", | ||||||
| 215 | each => "while (my (\$k, \$v) = each %%) {\n" . | ||||||
| 216 | "\$self->push_stack({key=>\$k, value=>\$v});", | ||||||
| 217 | endeach => "\$self->pop_stack();\n}", | ||||||
| 218 | for => "foreach (%%) {\n\$self->push_stack(\$_);", | ||||||
| 219 | endfor => "\$self->pop_stack();\n}", | ||||||
| 220 | if => "if (%%) {", | ||||||
| 221 | elsif => "} elsif (%%) {", | ||||||
| 222 | else => "} else {", | ||||||
| 223 | endif => "}", | ||||||
| 224 | set => \&set_command, | ||||||
| 225 | while => "while (%%) {", | ||||||
| 226 | endwhile => "}", | ||||||
| 227 | with => "\$self->push_stack(\\%%);", | ||||||
| 228 | endwith => "\$self->pop_stack();", | ||||||
| 229 | }; | ||||||
| 230 | |||||||
| 231 | 56 | 344 | return $commands->{$cmd}; | ||||
| 232 | } | ||||||
| 233 | |||||||
| 234 | #---------------------------------------------------------------------- | ||||||
| 235 | # Initialize the data stack | ||||||
| 236 | |||||||
| 237 | sub init_stack { | ||||||
| 238 | 11 | 11 | 0 | 26 | my ($self) = @_; | ||
| 239 | |||||||
| 240 | 11 | 45 | $self->{stack} = []; | ||||
| 241 | 11 | 280 | return; | ||||
| 242 | } | ||||||
| 243 | |||||||
| 244 | #---------------------------------------------------------------------- | ||||||
| 245 | # Set default parameters for package | ||||||
| 246 | |||||||
| 247 | sub parameters { | ||||||
| 248 | 8 | 8 | 0 | 16 | my ($pkg) = @_; | ||
| 249 | |||||||
| 250 | 8 | 64 | my $parameters = { | ||||
| 251 | command_start => '', | ||||||
| 253 | escaped_chars => '<>', | ||||||
| 254 | keep_sections => 0, | ||||||
| 255 | }; | ||||||
| 256 | |||||||
| 257 | 8 | 21 | return $parameters; | ||||
| 258 | } | ||||||
| 259 | |||||||
| 260 | #---------------------------------------------------------------------- | ||||||
| 261 | # Parse the templace source | ||||||
| 262 | |||||||
| 263 | sub parse_code { | ||||||
| 264 | 19 | 19 | 0 | 44 | my ($self, $lines, $command) = @_; | ||
| 265 | |||||||
| 266 | 19 | 24 | my @code; | ||||
| 267 | my @stash; | ||||||
| 268 | |||||||
| 269 | 19 | 56 | while (defined (my $line = shift @$lines)) { | ||||
| 270 | 67 | 293 | my ($cmd, $cmdline) = $self->parse_command($line); | ||||
| 271 | |||||||
| 272 | 67 | 100 | 126 | if (defined $cmd) { | |||
| 273 | 31 | 100 | 66 | if (@stash) { | |||
| 274 | 18 | 40 | push(@code, '$text .= <<"EOQ";', @stash, 'EOQ'); | ||||
| 275 | 18 | 30 | @stash = (); | ||||
| 276 | } | ||||||
| 277 | 31 | 48 | push(@code, $cmdline); | ||||
| 278 | |||||||
| 279 | 31 | 100 | 112 | if (substr($cmd, 0, 3) eq 'end') { | |||
| 100 | |||||||
| 280 | 10 | 15 | my $startcmd = substr($cmd, 3); | ||||
| 281 | 10 | 50 | 33 | 58 | die "Mismatched block end ($command/$cmd)" | ||
| 282 | if defined $startcmd && $startcmd ne $command; | ||||||
| 283 | 10 | 75 | return @code; | ||||
| 284 | |||||||
| 285 | } elsif ($self->get_command("end$cmd")) { | ||||||
| 286 | 10 | 43 | push(@code, $self->parse_code($lines, $cmd)); | ||||
| 287 | } | ||||||
| 288 | |||||||
| 289 | } else { | ||||||
| 290 | 36 | 80 | push(@stash, $self->encode_text($line)); | ||||
| 291 | } | ||||||
| 292 | } | ||||||
| 293 | |||||||
| 294 | 9 | 50 | 21 | die "Missing end (end$command)" if $command; | |||
| 295 | 9 | 100 | 29 | push(@code, '$text .= <<"EOQ";', @stash, 'EOQ') if @stash; | |||
| 296 | |||||||
| 297 | 9 | 60 | return @code; | ||||
| 298 | } | ||||||
| 299 | |||||||
| 300 | #---------------------------------------------------------------------- | ||||||
| 301 | # Parse a command and its argument | ||||||
| 302 | |||||||
| 303 | sub parse_command { | ||||||
| 304 | 67 | 67 | 0 | 104 | my ($self, $line) = @_; | ||
| 305 | |||||||
| 306 | 67 | 100 | 556 | return unless $line =~ s/$self->{command_start_pattern}//; | |||
| 307 | |||||||
| 308 | 35 | 246 | $line =~ s/$self->{command_end_pattern}//; | ||||
| 309 | 35 | 94 | my ($cmd, $arg) = split(' ', $line, 2); | ||||
| 310 | 35 | 100 | 76 | $arg = '' unless defined $arg; | |||
| 311 | |||||||
| 312 | 35 | 77 | my $cmdline = $self->get_command($cmd); | ||||
| 313 | 35 | 100 | 82 | return unless $cmdline; | |||
| 314 | |||||||
| 315 | 31 | 45 | my $ref = ref ($cmdline); | ||||
| 316 | |||||||
| 317 | 31 | 100 | 72 | if (! $ref) { | |||
| 50 | |||||||
| 318 | 24 | 55 | $arg = $self->encode_expression($arg); | ||||
| 319 | 24 | 62 | $cmdline =~ s/%%/$arg/; | ||||
| 320 | |||||||
| 321 | } elsif ($ref eq 'CODE') { | ||||||
| 322 | 7 | 24 | $cmdline = $cmdline->($self, $arg); | ||||
| 323 | |||||||
| 324 | } else { | ||||||
| 325 | 0 | 0 | die "I don't know how to handle a $ref: $cmd"; | ||||
| 326 | } | ||||||
| 327 | |||||||
| 328 | 31 | 159 | return ($cmd, $cmdline); | ||||
| 329 | } | ||||||
| 330 | |||||||
| 331 | #---------------------------------------------------------------------- | ||||||
| 332 | # Remove hash pushed on the stack | ||||||
| 333 | |||||||
| 334 | sub pop_stack { | ||||||
| 335 | 12 | 12 | 0 | 19 | my ($self) = @_; | ||
| 336 | 12 | 15 | return shift (@{$self->{stack}}); | ||||
| 12 | 814 | ||||||
| 337 | } | ||||||
| 338 | |||||||
| 339 | #---------------------------------------------------------------------- | ||||||
| 340 | # Push one or more hashes on the stack | ||||||
| 341 | |||||||
| 342 | sub push_stack { | ||||||
| 343 | 23 | 23 | 0 | 54 | my ($self, @hash) = @_; | ||
| 344 | |||||||
| 345 | 23 | 53 | foreach my $hash (@hash) { | ||||
| 346 | 23 | 178 | my $newhash; | ||||
| 347 | 23 | 100 | 64 | if (ref $hash eq 'HASH') { | |||
| 348 | 18 | 28 | $newhash = $hash; | ||||
| 349 | } else { | ||||||
| 350 | 5 | 13 | $newhash = {data => $hash}; | ||||
| 351 | } | ||||||
| 352 | |||||||
| 353 | 23 | 29 | unshift (@{$self->{stack}}, $newhash); | ||||
| 23 | 93 | ||||||
| 354 | } | ||||||
| 355 | |||||||
| 356 | 23 | 769 | return; | ||||
| 357 | } | ||||||
| 358 | |||||||
| 359 | #---------------------------------------------------------------------- | ||||||
| 360 | # Render a data structure as html | ||||||
| 361 | |||||||
| 362 | sub render { | ||||||
| 363 | 36 | 36 | 0 | 2605 | my ($self, $data) = @_; | ||
| 364 | |||||||
| 365 | 36 | 39 | my $result; | ||||
| 366 | 36 | 1079 | my $ref = ref $data; | ||||
| 367 | |||||||
| 368 | 36 | 100 | 225 | if ($ref eq 'SCALAR') { | |||
| 100 | |||||||
| 100 | |||||||
| 369 | 30 | 100 | 105 | $result = defined $$data ? $self->escape($$data) : ''; | |||
| 370 | |||||||
| 371 | } elsif ($ref eq 'ARRAY') { | ||||||
| 372 | 1 | 3 | my @result; | ||||
| 373 | 1 | 4 | foreach my $datum (@$data) { | ||||
| 374 | 2 | 11 | my $val = $self->render($datum); | ||||
| 375 | 2 | 6 | push(@result, " |
||||
| 376 | } | ||||||
| 377 | |||||||
| 378 | 1 | 6 | $result = join("\n", '
|
||||
| 379 | |||||||
| 380 | } elsif ($ref eq 'HASH') { | ||||||
| 381 | 1 | 3 | my @result; | ||||
| 382 | 1 | 9 | foreach my $key (sort keys %$data) { | ||||
| 383 | 2 | 8 | my $val = $self->render($data->{$key}); | ||||
| 384 | 2 | 10 | push(@result, " |
||||
| 385 | } | ||||||
| 386 | |||||||
| 387 | 1 | 5 | $result = join("\n", '
|
||||
| 388 | |||||||
| 389 | } else { | ||||||
| 390 | 4 | 12 | $result = $self->escape("$data"); | ||||
| 391 | } | ||||||
| 392 | |||||||
| 393 | |||||||
| 394 | 36 | 433 | return $result; | ||||
| 395 | } | ||||||
| 396 | |||||||
| 397 | #---------------------------------------------------------------------- | ||||||
| 398 | # Generate code for the set command, which stores results in the hashlist | ||||||
| 399 | |||||||
| 400 | sub set_command { | ||||||
| 401 | 7 | 7 | 0 | 15 | my ($self, $arg) = @_; | ||
| 402 | |||||||
| 403 | 7 | 33 | my ($var, $expr) = split (/\s*=\s*/, $arg, 2); | ||||
| 404 | 7 | 18 | $expr = $self->encode_expression($expr); | ||||
| 405 | |||||||
| 406 | 7 | 22 | return "\$self->store_stack(\'$var\', ($expr));\n"; | ||||
| 407 | } | ||||||
| 408 | |||||||
| 409 | #---------------------------------------------------------------------- | ||||||
| 410 | # Set the regular expression patterns used to match a command | ||||||
| 411 | |||||||
| 412 | sub set_patterns { | ||||||
| 413 | 8 | 8 | 0 | 16 | my ($self) = @_; | ||
| 414 | |||||||
| 415 | 8 | 62 | $self->{command_start_pattern} = '^\s*' . quotemeta($self->{command_start}); | ||||
| 416 | |||||||
| 417 | 8 | 35 | $self->{command_end_pattern} = quotemeta($self->{command_end}) . '\s*$'; | ||||
| 418 | |||||||
| 419 | 8 | 50 | 144 | $self->{command_end_pattern} = '\s*' . $self->{command_end_pattern} | |||
| 420 | if length $self->{command_end}; | ||||||
| 421 | |||||||
| 422 | 8 | 33 | $self->{escaped_chars_pattern} = | ||||
| 423 | '[' . quotemeta($self->{escaped_chars}) . ']'; | ||||||
| 424 | |||||||
| 425 | 8 | 15 | return; | ||||
| 426 | } | ||||||
| 427 | |||||||
| 428 | #---------------------------------------------------------------------- | ||||||
| 429 | # Read a file into a string | ||||||
| 430 | |||||||
| 431 | sub slurp { | ||||||
| 432 | 2 | 2 | 0 | 10 | my ($self, $input) = @_; | ||
| 433 | |||||||
| 434 | 2 | 7 | my $in; | ||||
| 435 | 2 | 19 | local $/; | ||||
| 436 | |||||||
| 437 | 2 | 19 | $in = IO::File->new ($input, 'r'); | ||||
| 438 | 2 | 50 | 209 | return '' unless defined $in; | |||
| 439 | |||||||
| 440 | 2 | 48 | my $text = <$in>; | ||||
| 441 | 2 | 19 | $in->close; | ||||
| 442 | |||||||
| 443 | 2 | 39 | return $text; | ||||
| 444 | } | ||||||
| 445 | |||||||
| 446 | #---------------------------------------------------------------------- | ||||||
| 447 | # Store a variable in the hashlist, used by set | ||||||
| 448 | |||||||
| 449 | sub store_stack { | ||||||
| 450 | 11 | 11 | 0 | 116 | my ($self, $var, @val) = @_; | ||
| 451 | |||||||
| 452 | 11 | 53 | my ($sigil, $name) = $var =~ /([\$\@\%])(\w+)/; | ||||
| 453 | 11 | 50 | 29 | die "Unrecognized variable type: $name" unless defined $sigil; | |||
| 454 | |||||||
| 455 | 11 | 16 | my $i; | ||||
| 456 | 11 | 14 | for ($i = 0; $i < @{$self->{stack}}; $i ++) { | ||||
| 19 | 53 | ||||||
| 457 | 15 | 100 | 44 | last if exists $self->{stack}[$i]{$name}; | |||
| 458 | } | ||||||
| 459 | |||||||
| 460 | 11 | 100 | 15 | $i = 0 unless $i < @{$self->{stack}}; | |||
| 11 | 32 | ||||||
| 461 | |||||||
| 462 | 11 | 50 | 24 | if ($sigil eq '$') { | |||
| 0 | |||||||
| 0 | |||||||
| 463 | 11 | 100 | 29 | my $val = @val == 1 ? $val[0] : @val; | |||
| 464 | 11 | 30 | $self->{stack}[$i]{$name} = $val; | ||||
| 465 | |||||||
| 466 | } elsif ($sigil eq '@') { | ||||||
| 467 | 0 | 0 | $self->{stack}[$i]{$name} = \@val; | ||||
| 468 | |||||||
| 469 | } elsif ($sigil eq '%') { | ||||||
| 470 | 0 | 0 | my %val = @val; | ||||
| 471 | 0 | 0 | $self->{stack}[$i]{$name} = \%val; | ||||
| 472 | } | ||||||
| 473 | |||||||
| 474 | 11 | 291 | return; | ||||
| 475 | } | ||||||
| 476 | |||||||
| 477 | #---------------------------------------------------------------------- | ||||||
| 478 | # Substitue comment delimeted sections for same blacks in template | ||||||
| 479 | |||||||
| 480 | sub substitute_sections { | ||||||
| 481 | 17 | 17 | 0 | 3560 | my ($self, $text, $section) = @_; | ||
| 482 | |||||||
| 483 | 17 | 94 | my $name; | ||||
| 484 | my @output; | ||||||
| 485 | |||||||
| 486 | 17 | 254 | my @tokens = split (/()/, $text); | ||||
| 487 | |||||||
| 488 | 17 | 48 | foreach my $token (@tokens) { | ||||
| 489 | 105 | 100 | 429 | if ($token =~ /^/) { | |||
| 100 | |||||||
| 100 | |||||||
| 490 | 22 | 50 | 56 | if (defined $name) { | |||
| 491 | 0 | 0 | die "Nested sections in template: $name\n"; | ||||
| 492 | } | ||||||
| 493 | |||||||
| 494 | 22 | 45 | $name = $1; | ||||
| 495 | 22 | 100 | 71 | push(@output, $token) if $self->{keep_sections}; | |||
| 496 | |||||||
| 497 | } elsif ($token =~ /^\s*/) { | ||||||
| 498 | 22 | 50 | 63 | if ($name ne $1) { | |||
| 499 | 0 | 0 | die "Nested sections in template: $name\n"; | ||||
| 500 | } | ||||||
| 501 | |||||||
| 502 | 22 | 30 | undef $name; | ||||
| 503 | 22 | 100 | 64 | push(@output, $token) if $self->{keep_sections}; | |||
| 504 | |||||||
| 505 | } elsif (defined $name) { | ||||||
| 506 | 22 | 66 | 99 | $section->{$name} ||= $token; | |||
| 507 | 22 | 46 | push(@output, $section->{$name}); | ||||
| 508 | |||||||
| 509 | } else { | ||||||
| 510 | 39 | 93 | push(@output, $token); | ||||
| 511 | } | ||||||
| 512 | } | ||||||
| 513 | |||||||
| 514 | 17 | 121 | return join('', @output); | ||||
| 515 | } | ||||||
| 516 | |||||||
| 517 | 1; | ||||||
| 518 | |||||||
| 519 | =pod | ||||||
| 520 | |||||||
| 521 | =encoding utf-8 | ||||||
| 522 | |||||||
| 523 | =head1 NAME | ||||||
| 524 | |||||||
| 525 | Template::Twostep - Compile templates into a subroutine | ||||||
| 526 | |||||||
| 527 | =head1 SYNOPSIS | ||||||
| 528 | |||||||
| 529 | use Template::Twostep; | ||||||
| 530 | my $tt = Template::Twostep->new; | ||||||
| 531 | my $sub = $tt->compile($template, $subtemplate); | ||||||
| 532 | my $output = $sub->($hash); | ||||||
| 533 | |||||||
| 534 | =head1 DESCRIPTION | ||||||
| 535 | |||||||
| 536 | This module simplifies the job of producing html text output by letting | ||||||
| 537 | you put data into a template. Templates support the control structures in | ||||||
| 538 | Perl: "for" and "while" loops, "if-else" blocks, and some others. Creating output | ||||||
| 539 | is a two step process. First you generate a subroutine from one or more | ||||||
| 540 | templates, then you call the subroutine with your data to generate the output. | ||||||
| 541 | |||||||
| 542 | The template format is line oriented. Commands occupy a single line and continue | ||||||
| 543 | to the end of line. By default commands are enclosed in html comments (), but the command start and end strings are configurable via the new method. | ||||||
| 545 | A command may be preceded by white space. If a command is a block command, it is | ||||||
| 546 | terminated by the word "end" followed by the command name. For example, the | ||||||
| 547 | "for" command is terminated by an "endfor" command and the "if" command by an | ||||||
| 548 | "endif" command. | ||||||
| 549 | |||||||
| 550 | All lines may contain variables. As in Perl, variables are a sigil character | ||||||
| 551 | ('$,' '@,' or '%') followed by one or more word characters. For example, | ||||||
| 552 | C<$name> or C<@names>. To indicate a literal character instead of a variable, | ||||||
| 553 | precede the sigil with a backslash. When you run the subroutine that this module | ||||||
| 554 | generates, you pass it a reference, usually a reference to a hash, containing | ||||||
| 555 | some data. The subroutine replaces variables in the template with the value in | ||||||
| 556 | the field of the same name in the hash. If the types of the two disagree, the | ||||||
| 557 | code will coerce the data to the type of the sigil. You can pass a reference to | ||||||
| 558 | an array instead of a hash to the subroutine this module generates. If you do, | ||||||
| 559 | the template will use C<@data> to refer to the array. | ||||||
| 560 | |||||||
| 561 | There are several other template packages. I wrote this one to have the specific | ||||||
| 562 | set of features I want in a template package. First, I wanted templates to be | ||||||
| 563 | compiled into code. This approach has the advantage of speeding things up when | ||||||
| 564 | the same template is used more than once. However, it also poses a security risk | ||||||
| 565 | because code you might not want executed may be included in the template. For | ||||||
| 566 | this reason if the script using this module can be run from the web, make sure | ||||||
| 567 | the account that runs it cannot write to the template. I made the templates | ||||||
| 568 | command language line oriented rather than tag oriented to prevent spurious | ||||||
| 569 | white space from appearing in the output. Template commands and variables are | ||||||
| 570 | similar to Perl for familiarity. The power of the template language is limited | ||||||
| 571 | to the essentials for the sake of simplicity and to prevent mixing code with | ||||||
| 572 | presentation. | ||||||
| 573 | |||||||
| 574 | =head1 METHODS | ||||||
| 575 | |||||||
| 576 | This module has two public methods. The first, new, changes the module | ||||||
| 577 | defaults. Compile generates a subroutine from one or more templates. You Tthen | ||||||
| 578 | call this subroutine with a reference to the data you want to substitute into | ||||||
| 579 | the template to produce output. | ||||||
| 580 | |||||||
| 581 | Using subtemplates along with a template allows you to place the common design | ||||||
| 582 | elements in the template. You indicate where to replace parts of the template | ||||||
| 583 | with parts of the subtemplate by using the "section" command. If the template | ||||||
| 584 | contains a section block with the same name as a section block in the | ||||||
| 585 | subtemplates it replaces the contents inside the section block in the template | ||||||
| 586 | with the contents of the corresponding block in the subtemplate. | ||||||
| 587 | |||||||
| 588 | =over 4 | ||||||
| 589 | |||||||
| 590 | =item C<$obj = Template::Twostep-E |
||||||
| 591 | |||||||
| 592 | Create a new parser. The configuration allows you to set a set of characters to | ||||||
| 593 | escape when found in the data (escaped_chars), the string which starts a command | ||||||
| 594 | (command_start), the string which ends a command (command_end), and whether | ||||||
| 595 | section comments are kept in the output (keep_sections). All commands end at the | ||||||
| 596 | end of line. However, you may wish to place commands inside comments and | ||||||
| 597 | comments may require a closing string. By setting command_end, the closing | ||||||
| 598 | string will be stripped from the end of the command. | ||||||
| 599 | |||||||
| 600 | =item C<$sub = $obj-E |
||||||
| 601 | |||||||
| 602 | Generate a subroutine used to render data from a template and optionally from | ||||||
| 603 | one or more subtemplates. It can be invoked by an object created by a call to | ||||||
| 604 | new, or you can invoke it using the package name (Template::Twostep), in which | ||||||
| 605 | case it will first call new for you. If the template string does not contain a | ||||||
| 606 | newline, the method assumes it is a filename and it reads the template from that | ||||||
| 607 | file. | ||||||
| 608 | |||||||
| 609 | =back | ||||||
| 610 | |||||||
| 611 | =head1 TEMPLATE SYNTAX | ||||||
| 612 | |||||||
| 613 | If the first non-white characters on a line are the command start string, the | ||||||
| 614 | line is interpreted as a command. The command name continues up to the first | ||||||
| 615 | white space character. The text following the initial span of white space is the | ||||||
| 616 | command argument. The argument continues up to the command end string, or if | ||||||
| 617 | this is empty, to the end of the line. | ||||||
| 618 | |||||||
| 619 | Variables in the template have the same format as ordinary Perl variables, | ||||||
| 620 | a string of word characters starting with a sigil character. for example, | ||||||
| 621 | |||||||
| 622 | $SUMMARY @data %dictionary | ||||||
| 623 | |||||||
| 624 | are examples of variables. The subroutine this module generates will substitute | ||||||
| 625 | values in the data it is passed for the variables in the template. New variables | ||||||
| 626 | can be added with the "set" command. | ||||||
| 627 | |||||||
| 628 | Arrays and hashes are rendered as unordered lists and definition lists when | ||||||
| 629 | interpolating them. This is done recursively, so arbitrary structures can be | ||||||
| 630 | rendered. This is mostly intended for debugging, as it does not provide fine | ||||||
| 631 | control over how the structures are rendered. For finer control, use the | ||||||
| 632 | commands described below so that the scalar fields in the structures can be | ||||||
| 633 | accessed. Scalar fields have the characters '<' and '>' escaped before | ||||||
| 634 | interpolating them. This set of characters can be changed by setting the | ||||||
| 635 | configuration parameter escaped chars. Undefined fields are replaced with the | ||||||
| 636 | empty string when rendering. If the type of data passed to the subroutine | ||||||
| 637 | differs from the sigil on the variable the variable is coerced to the type of | ||||||
| 638 | the sigil. This works the same as an assignment. If an array is referenced as a | ||||||
| 639 | scalar, the length of the array is output. | ||||||
| 640 | |||||||
| 641 | The following commands are supported in templates: | ||||||
| 642 | |||||||
| 643 | =over 4 | ||||||
| 644 | |||||||
| 645 | =item do | ||||||
| 646 | |||||||
| 647 | The remainder of the line is interpreted as Perl code. For assignments, use | ||||||
| 648 | the set command. | ||||||
| 649 | |||||||
| 650 | =item each | ||||||
| 651 | |||||||
| 652 | Repeat the text between the "each" and "endeach" commands for each entry in the | ||||||
| 653 | hash table. The hast table key can be accessed through the variable $key and | ||||||
| 654 | the hash table value through the variable $value. Key-value pairs are returned | ||||||
| 655 | in random order. For example, this code displays the contents of a hash as a | ||||||
| 656 | list: | ||||||
| 657 | |||||||
| 658 | |
||||||
| 659 | |||||||
| 660 | |
||||||
| 661 | |||||||
| 662 | |||||||
| 663 | |||||||
| 664 | =item for | ||||||
| 665 | |||||||
| 666 | Expand the text between the "for" and "endfor" commands several times. The | ||||||
| 667 | "for" command takes a name of a field in a hash as its argument. The value of this | ||||||
| 668 | name should be a reference to a list. It will expand the text in the for block | ||||||
| 669 | once for each element in the list. Within the "for" block, any element of the list | ||||||
| 670 | is accessible. This is especially useful for displaying lists of hashes. For | ||||||
| 671 | example, suppose the data field name PHONELIST points to an array. This array is | ||||||
| 672 | a list of hashes, and each hash has two entries, NAME and PHONE. Then the code | ||||||
| 673 | |||||||
| 674 | |||||||
| 675 | $NAME |
||||||
| 676 | $PHONE | ||||||
| 677 | |||||||
| 678 | |||||||
| 679 | displays the entire phone list. | ||||||
| 680 | |||||||
| 681 | =item if | ||||||
| 682 | |||||||
| 683 | The text until the matching C |
||||||
| 684 | "if" command is true. If false, the text is skipped. The "if" command can contain | ||||||
| 685 | an C |
||||||
| 686 | expression in the "if" command is true and the text after the "else" is included | ||||||
| 687 | if it is false. You can also place an "elsif" command in the "if" block, which | ||||||
| 688 | includes the following text if its expression is true. | ||||||
| 689 | |||||||
| 690 | |||||||
| 691 | $text | ||||||
| 692 | |||||||
| 693 | $text | ||||||
| 694 | |||||||
| 695 | |||||||
| 696 | =item section | ||||||
| 697 | |||||||
| 698 | If a template contains a section, the text until the endsection command will be | ||||||
| 699 | replaced by the section block with the same name in one the subtemplates. For | ||||||
| 700 | example, if the main template has the code | ||||||
| 701 | |||||||
| 702 | |||||||
| 703 | |||||||
| 704 | |||||||
| 705 | |||||||
| 706 | and the subtemplate has the lines | ||||||
| 707 | |||||||
| 708 | |||||||
| 709 | This template is copyright with a Creative Commons License. |
||||||
| 710 | |||||||
| 711 | |||||||
| 712 | The text will be copied from a section in the subtemplate into a section of the | ||||||
| 713 | same name in the template. If there is no block with the same name in the | ||||||
| 714 | subtemplate, the text is used unchanged. | ||||||
| 715 | |||||||
| 716 | =item set | ||||||
| 717 | |||||||
| 718 | Adds a new variable or updates the value of an existing variable. The argument | ||||||
| 719 | following the command name looks like any Perl assignment statement minus the | ||||||
| 720 | trailing semicolon. For example, | ||||||
| 721 | |||||||
| 722 | |||||||
| 723 | |||||||
| 724 | =item while | ||||||
| 725 | |||||||
| 726 | Expand the text between the C |
||||||
| 727 | expression following the C |
||||||
| 728 | |||||||
| 729 | |||||||
| 730 | Countdown ... |
||||||
| 731 | |||||||
| 732 | $i |
||||||
| 733 | |||||||
| 734 | |||||||
| 735 | |||||||
| 736 | =item with | ||||||
| 737 | |||||||
| 738 | Lists within a hash can be accessed using the "for" command. Hashes within a | ||||||
| 739 | hash are accessed using the "with" command. For example: | ||||||
| 740 | |||||||
| 741 | |||||||
| 742 | $street |
||||||
| 743 | $city, $state $zip | ||||||
| 744 | |||||||
| 745 | |||||||
| 746 | =back | ||||||
| 747 | |||||||
| 748 | =head1 ERRORS | ||||||
| 749 | |||||||
| 750 | What to check when this module throws an error | ||||||
| 751 | |||||||
| 752 | =over 4 | ||||||
| 753 | |||||||
| 754 | =item Couldn't read template | ||||||
| 755 | |||||||
| 756 | The template is in a file and the file could not be opened. Check the filename | ||||||
| 757 | and permissions on the file. Relative filenames can cause problems and the web | ||||||
| 758 | server is probably running another account than yours. | ||||||
| 759 | |||||||
| 760 | =item Illegal type conversion | ||||||
| 761 | |||||||
| 762 | The sigil on a variable differs from the data passed to the subroutine and | ||||||
| 763 | conversion. between the two would not be legal. Or you forgot to escape the '@' | ||||||
| 764 | in an email address by preceding it with a backslash. | ||||||
| 765 | |||||||
| 766 | =item Unknown command | ||||||
| 767 | |||||||
| 768 | Either a command was spelled incorrectly or a line that is not a command | ||||||
| 769 | begins with the command start string. | ||||||
| 770 | |||||||
| 771 | =item Missing end | ||||||
| 772 | |||||||
| 773 | The template contains a command for the start of a block, but | ||||||
| 774 | not the command for the end of the block. For example an "if" command | ||||||
| 775 | is missing an "endif" command. | ||||||
| 776 | |||||||
| 777 | =item Mismatched block end | ||||||
| 778 | |||||||
| 779 | The parser found a different end command than the begin command for the block | ||||||
| 780 | it was parsing. Either an end command is missing, or block commands are nested | ||||||
| 781 | incorrectly. | ||||||
| 782 | |||||||
| 783 | =item Syntax error | ||||||
| 784 | |||||||
| 785 | The expression used in a command is not valid Perl. | ||||||
| 786 | |||||||
| 787 | =back | ||||||
| 788 | |||||||
| 789 | =head1 LICENSE | ||||||
| 790 | |||||||
| 791 | Copyright (C) Bernie Simon. | ||||||
| 792 | |||||||
| 793 | This library is free software; you can redistribute it and/or modify | ||||||
| 794 | it under the same terms as Perl itself. | ||||||
| 795 | |||||||
| 796 | =head1 AUTHOR | ||||||
| 797 | |||||||
| 798 | Bernie Simon E |
||||||
| 799 | |||||||
| 800 | =cut |