1package Template::Alloy::HTE; 2 3=head1 NAME 4 5Template::Alloy::HTE - HTML::Template and HTML::Template::Expr roles. 6 7=cut 8 9use strict; 10use warnings; 11use Template::Alloy; 12 13our $VERSION = $Template::Alloy::VERSION; 14 15sub new { die "This class is a role for use by packages such as Template::Alloy" } 16 17###----------------------------------------------------------------### 18### support for few HTML::Template and HTML::Template::Expr calling syntax 19 20sub register_function { 21 my ($name, $sub) = @_; 22 $Template::Alloy::SCALAR_OPS->{$name} = $sub; 23} 24 25sub clear_param { shift->{'_vars'} = {} } 26 27sub query { shift->throw('query', "Not implemented in Template::Alloy") } 28 29sub new_file { my $class = shift; my $in = shift; $class->new(source => $in, type => 'filename', @_) } 30sub new_scalar_ref { my $class = shift; my $in = shift; $class->new(source => $in, type => 'scalarref', @_) } 31sub new_array_ref { my $class = shift; my $in = shift; $class->new(source => $in, type => 'arrayref', @_) } 32sub new_filehandle { my $class = shift; my $in = shift; $class->new(source => $in, type => 'filehandle', @_) } 33 34###----------------------------------------------------------------### 35 36sub parse_tree_hte { 37 my $self = shift; 38 my $str_ref = shift; 39 if (! $str_ref || ! defined $$str_ref) { 40 $self->throw('parse.no_string', "No string or undefined during parse", undef, 1); 41 } 42 43 local $self->{'V2EQUALS'} = $self->{'V2EQUALS'} || 0; 44 local $self->{'NO_TT'} = $self->{'NO_TT'} || ($self->{'SYNTAX'} eq 'hte' ? 0 : 1); 45 46 local $self->{'START_TAG'} = qr{<(|!--\s*)(/?)([+=~-]?)[Tt][Mm][Pp][Ll]_(\w+)\b}; 47 local $self->{'_start_tag'} = (! $self->{'INTERPOLATE'}) ? $self->{'START_TAG'} : qr{(?: $self->{'START_TAG'} | (\$))}sx; 48 local $self->{'_end_tag'}; # changes over time 49 50 my $dirs = $Template::Alloy::Parse::DIRECTIVES; 51 my $aliases = $Template::Alloy::Parse::ALIASES; 52 local @{ $dirs }{ keys %$aliases } = values %$aliases; # temporarily add to the table 53 local @{ $self }{@Template::Alloy::CONFIG_COMPILETIME} = @{ $self }{@Template::Alloy::CONFIG_COMPILETIME}; 54 delete $dirs->{'JS'} if ! $self->{'COMPILE_JS'}; 55 56 my @tree; # the parsed tree 57 my $pointer = \@tree; # pointer to current tree to handle nested blocks 58 my @state; # maintain block levels 59 local $self->{'_state'} = \@state; # allow for items to introspect (usually BLOCKS) 60 local $self->{'_no_interp'} = 0; # no interpolation in perl 61 my @in_view; # let us know if we are in a view 62 my @blocks; # storage for defined blocks 63 my @meta; # place to store any found meta information (to go into META) 64 my $post_chomp = 0; # previous post_chomp setting 65 my $continue = 0; # flag for multiple directives in the same tag 66 my $post_op = 0; # found a post-operative DIRECTIVE 67 my $capture; # flag to start capture 68 my $func; 69 my $pre_chomp; 70 my $node; 71 my ($comment, $is_close); 72 pos($$str_ref) = 0; 73 my $allow_expr = ! defined($self->{'EXPR'}) || $self->{'EXPR'}; # default is on 74 75 while (1) { 76 ### allow for TMPL_SET foo = PROCESS foo 77 if ($capture) { 78 $func = $$str_ref =~ m{ \G \s* (\w+)\b }gcx 79 ? uc $1 : $self->throw('parse', "Error looking for block in capture DIRECTIVE", undef, pos($$str_ref)); 80 $func = $aliases->{$func} if $aliases->{$func}; 81 if ($func ne 'VAR' && ! $dirs->{$func}) { 82 $self->throw('parse', "Found unknown DIRECTIVE ($func)", undef, pos($$str_ref) - length($func)); 83 } 84 85 $node = [$func, pos($$str_ref) - length($func), undef]; 86 87 push @{ $capture->[4] }, $node; 88 undef $capture; 89 90 ### handle all other TMPL tags 91 } else { 92 ### find the next opening tag 93 $$str_ref =~ m{ \G (.*?) $self->{'_start_tag'} }gcxs 94 || last; 95 my ($text, $dollar) = ($1, $6); 96 ($comment, $is_close, $pre_chomp, $func) = ($2, $3, $4, uc $5) if ! $dollar; 97 98 ### found a text portion - chomp it and store it 99 if (length $text) { 100 if (! $post_chomp) { } 101 elsif ($post_chomp == 1) { $text =~ s{ ^ [^\S\n]* \n }{}x } 102 elsif ($post_chomp == 2) { $text =~ s{ ^ \s+ }{ }x } 103 elsif ($post_chomp == 3) { $text =~ s{ ^ \s+ }{}x } 104 push @$pointer, $text if length $text; 105 } 106 107 ### handle variable interpolation ($2 eq $) 108 if ($dollar) { 109 ### inspect previous text chunk for escape slashes 110 my $n = ($text =~ m{ (\\+) $ }x) ? length($1) : 0; 111 if ($n && ! $self->{'_no_interp'}) { 112 my $chop = int(($n + 1) / 2); # were there odd escapes 113 substr($pointer->[-1], -$chop, $chop, '') if defined($pointer->[-1]) && ! ref($pointer->[-1]); 114 } 115 if ($self->{'_no_interp'} || $n % 2) { 116 push @$pointer, $dollar; 117 next; 118 } 119 120 my $not = $$str_ref =~ m{ \G ! }gcx; 121 my $mark = pos($$str_ref); 122 my $ref; 123 if ($$str_ref =~ m{ \G \{ }gcx) { 124 local $self->{'_operator_precedence'} = 0; # allow operators 125 local $self->{'_end_tag'} = qr{\}}; 126 $ref = $self->parse_expr($str_ref); 127 $$str_ref =~ m{ \G \s* $Template::Alloy::Parse::QR_COMMENTS \} }gcxo 128 || $self->throw('parse', 'Missing close }', undef, pos($$str_ref)); 129 } else { 130 local $self->{'_operator_precedence'} = 1; # no operators 131 local $Template::Alloy::Parse::QR_COMMENTS = qr{}; 132 $ref = $self->parse_expr($str_ref); 133 } 134 $self->throw('parse', "Error while parsing for interpolated string", undef, pos($$str_ref)) 135 if ! defined $ref; 136 if (! $not && $self->{'SHOW_UNDEFINED_INTERP'}) { 137 $ref = [[undef, '//', $ref, '$'.substr($$str_ref, $mark, pos($$str_ref)-$mark)], 0]; 138 } 139 push @$pointer, ['GET', $mark, pos($$str_ref), $ref]; 140 $post_chomp = 0; # no chomping after dollar vars 141 next; 142 } 143 144 ### make sure we know this directive 145 $func = $aliases->{$func} if $aliases->{$func}; 146 if ($func ne 'VAR' && ! $dirs->{$func}) { 147 $self->throw('parse', "Found unknow DIRECTIVE ($func)", undef, pos($$str_ref) - length($func)); 148 } 149 $node = [$func, pos($$str_ref) - length($func) - length($pre_chomp) - 5, undef]; 150 151 ### take care of chomping - yes HT now get CHOMP SUPPORT 152 $pre_chomp ||= $self->{'PRE_CHOMP'}; 153 $pre_chomp =~ y/-=~+/1230/ if $pre_chomp; 154 if ($pre_chomp && $pointer->[-1] && ! ref $pointer->[-1]) { 155 if ($pre_chomp == 1) { $pointer->[-1] =~ s{ (?:\n|^) [^\S\n]* \z }{}x } 156 elsif ($pre_chomp == 2) { $pointer->[-1] =~ s{ (\s+) \z }{ }x } 157 elsif ($pre_chomp == 3) { $pointer->[-1] =~ s{ (\s+) \z }{}x } 158 splice(@$pointer, -1, 1, ()) if ! length $pointer->[-1]; # remove the node if it is zero length 159 } 160 161 push @$pointer, $node; 162 $self->{'_end_tag'} = $comment ? qr{([+=~-]?)-->} : qr{([+=~-]?)>}; # how will this tag end 163 } 164 165 $$str_ref =~ m{ \G \s+ }gcx; 166 167 ### parse remaining tag details 168 if (! $is_close) { 169 ### handle HT style nodes 170 if ($func =~ /^(IF|ELSIF|ELSE|UNLESS|LOOP|VAR|INCLUDE)$/) { 171 $func = $node->[0] = 'GET' if $func eq 'VAR'; 172 173 ### handle EXPR attribute 174 if ($func eq 'ELSE') { 175 # do nothing 176 } elsif ($$str_ref =~ m{ \G [Ee][Xx][Pp][Rr] \s*=\s* ([\"\']?) \s* }gcx) { 177 if (! $allow_expr) { 178 $self->throw('parse', 'EXPR are not allowed without hte mode', undef, pos($$str_ref)); 179 } 180 my $quote = $1; 181 local $self->{'_end_tag'} = $quote ? qr{$quote\s*$self->{'_end_tag'}} : $self->{'_end_tag'}; 182 $node->[3] = eval { $self->parse_expr($str_ref) }; 183 if (! defined($node->[3])) { 184 my $err = $@ || $self->exception('parse', 'Error while looking for EXPR', undef, pos($$str_ref)); 185 $err->info($err->info . " (Could be a missing close quote near expr=$quote)") if $quote && UNIVERSAL::can($err, 'info'); 186 $self->throw($err); 187 } 188 if ($quote) { 189 $$str_ref =~ m{ \G $quote }gcx 190 || $self->throw('parse', "Missing close quote ($quote)", undef, pos($$str_ref)); 191 } 192 if ($func eq 'INCLUDE') { 193 $node->[0] = 'PROCESS'; # no need to localize the stash 194 $node->[3] = [[[undef, '{}'],0], $node->[3]]; 195 } elsif ($func eq 'UNLESS') { 196 $node->[0] = 'IF'; 197 $node->[3] = [[undef, '!', $node->[3]], 0]; 198 } 199 if ($self->{'AUTO_FILTER'}) { 200 $node->[3] = [[undef, '~', $node->[3]], 0] if ! ref $node->[3]; 201 push @{ $node->[3] }, '|', $self->{'AUTO_FILTER'}, 0 if @{ $node->[3] } < 3 || $node->[3]->[-3] ne '|'; 202 } 203 204 ### handle "normal" NAME attributes 205 } else { 206 207 my ($name, $escape, $default); 208 while (1) { 209 if ($$str_ref =~ m{ \G (\w+) \s*=\s* }gcx) { 210 my $key = lc $1; 211 my $val = $$str_ref =~ m{ \G ([\"\']) (.*?) (?<!\\) \1 \s* }gcx ? $2 212 : $$str_ref =~ m{ \G ([\w./+_]+) \s* }gcx ? $1 213 : $self->throw('parse', "Error while looking for value of \"$key\" attribute", undef, pos($$str_ref)); 214 if ($key eq 'name') { 215 $name ||= $val; 216 } else { 217 $self->throw('parse', uc($key)." not allowed in TMPL_$func tag", undef, pos($$str_ref)) if $func ne 'GET'; 218 if ($key eq 'escape') { $escape ||= lc $val } 219 elsif ($key eq 'default') { $default ||= $val } 220 else { $self->throw('parse', uc($key)." not allowed in TMPL_$func tag", undef, pos($$str_ref)) } 221 } 222 } elsif ($$str_ref =~ m{ \G ([\w./+_]+) \s* }gcx) { 223 $name ||= $1; 224 } elsif ($$str_ref =~ m{ \G ([\"\']) (.*?) (?<!\\) \1 \s* }gcx) { 225 $name ||= $2; 226 } else { 227 last; 228 } 229 } 230 231 $self->throw('parse', 'Error while looking for NAME', undef, pos($$str_ref)) if ! defined($name) || ! length($name); 232 if ($func eq 'INCLUDE') { 233 $node->[0] = 'PROCESS'; # no need to localize the stash 234 $node->[3] = [[[undef, '{}'],0], $name]; 235 } elsif ($func eq 'UNLESS') { 236 $node->[0] = 'IF'; 237 $node->[3] = [[undef, '!', [$name, 0]], 0]; 238 } else { 239 $node->[3] = [$name, 0]; # set the variable 240 } 241 $node->[3] = [[undef, '||', $node->[3], $default], 0] if $default; 242 243 ### dress up node before finishing 244 $escape = lc $self->{'DEFAULT_ESCAPE'} if ! $escape && $self->{'DEFAULT_ESCAPE'}; 245 if ($escape) { 246 $self->throw('parse', "ESCAPE not allowed in TMPL_$func tag", undef, pos($$str_ref)) if $func ne 'GET'; 247 if ($escape eq 'html' || $escape eq '1') { 248 push @{ $node->[3] }, '|', 'html', 0; 249 } elsif ($escape eq 'url') { 250 push @{ $node->[3] }, '|', 'url', 0; 251 } elsif ($escape eq 'js') { 252 push @{ $node->[3] }, '|', 'js', 0; 253 } 254 } elsif ($self->{'AUTO_FILTER'}) { 255 push @{ $node->[3] }, '|', $self->{'AUTO_FILTER'}, 0; 256 } 257 } 258 $node->[2] = pos $$str_ref; 259 260 261 ### handle TT Directive extensions 262 } else { 263 $self->throw('parse', "Found a TT tag $func with NO_TT enabled", undef, pos($$str_ref)) if $self->{'NO_TT'}; 264 $node->[3] = eval { $dirs->{$func}->[0]->($self, $str_ref, $node) }; 265 if (my $err = $@) { 266 $err->node($node) if UNIVERSAL::can($err, 'node') && ! $err->node; 267 die $err; 268 } 269 $node->[2] = pos $$str_ref; 270 } 271 } 272 273 ### handle ending tags - or continuation blocks 274 if ($is_close || $dirs->{$func}->[4]) { 275 if (! @state) { 276 $self->throw('parse', "Found an $func tag while not in a block", $node, pos($$str_ref)); 277 } 278 my $parent_node = pop @state; 279 280 ### TODO - check for matching loop close name 281 $func = $node->[0] = 'END' if $is_close; 282 283 ### handle continuation blocks such as elsif, else, catch etc 284 if ($dirs->{$func}->[4]) { 285 pop @$pointer; # we will store the node in the parent instead 286 $parent_node->[5] = $node; 287 my $parent_type = $parent_node->[0]; 288 if (! $dirs->{$func}->[4]->{$parent_type}) { 289 $self->throw('parse', "Found unmatched nested block", $node, pos($$str_ref)); 290 } 291 } 292 293 ### restore the pointer up one level (because we hit the end of a block) 294 $pointer = (! @state) ? \@tree : $state[-1]->[4]; 295 296 ### normal end block 297 if (! $dirs->{$func}->[4]) { 298 if ($parent_node->[0] eq 'BLOCK') { # move BLOCKS to front 299 if (defined($parent_node->[3]) && @in_view) { 300 push @{ $in_view[-1] }, $parent_node; 301 } else { 302 push @blocks, $parent_node; 303 } 304 if ($pointer->[-1] && ! $pointer->[-1]->[6]) { # capturing doesn't remove the var 305 splice(@$pointer, -1, 1, ()); 306 } 307 } elsif ($parent_node->[0] eq 'VIEW') { 308 my $ref = { map {($_->[3] => $_->[4])} @{ pop @in_view }}; 309 unshift @{ $parent_node->[3] }, $ref; 310 } elsif ($dirs->{$parent_node->[0]}->[5]) { # allow no_interp to turn on and off 311 $self->{'_no_interp'}--; 312 } 313 314 315 ### continuation block - such as an elsif 316 } else { 317 push @state, $node; 318 $pointer = $node->[4] ||= []; 319 } 320 $node->[2] = pos $$str_ref; 321 322 ### handle block directives 323 } elsif ($dirs->{$func}->[2]) { 324 push @state, $node; 325 $pointer = $node->[4] ||= []; # allow future parsed nodes before END tag to end up in current node 326 push @in_view, [] if $func eq 'VIEW'; 327 $self->{'_no_interp'}++ if $dirs->{$node->[0]}->[5] # allow no_interp to turn on and off 328 329 } elsif ($func eq 'META') { 330 unshift @meta, @{ $node->[3] }; # first defined win 331 $node->[3] = undef; # only let these be defined once - at the front of the tree 332 } 333 334 335 ### look for the closing tag 336 if ($$str_ref =~ m{ \G \s* $self->{'_end_tag'} }gcxs) { 337 $post_chomp = $1 || $self->{'POST_CHOMP'}; 338 $post_chomp =~ y/-=~+/1230/ if $post_chomp; 339 $continue = 0; 340 $post_op = 0; 341 next; 342 343 ### setup capturing 344 } elsif ($node->[6]) { 345 $capture = $node; 346 next; 347 348 ### no closing tag 349 } else { 350 $self->throw('parse', "Not sure how to handle tag", $node, pos($$str_ref)); 351 } 352 } 353 354 ### cleanup the tree 355 unshift(@tree, @blocks) if @blocks; 356 unshift(@tree, ['META', 1, 1, \@meta]) if @meta; 357 $self->throw('parse', "Missing </TMPL_ close tag", $state[-1], pos($$str_ref)) if @state > 0; 358 359 ### pull off the last text portion - if any 360 if (pos($$str_ref) != length($$str_ref)) { 361 my $text = substr $$str_ref, pos($$str_ref); 362 if (! $post_chomp) { } 363 elsif ($post_chomp == 1) { $text =~ s{ ^ [^\S\n]* \n }{}x } 364 elsif ($post_chomp == 2) { $text =~ s{ ^ \s+ }{ }x } 365 elsif ($post_chomp == 3) { $text =~ s{ ^ \s+ }{}x } 366 push @$pointer, $text if length $text; 367 } 368 369 return \@tree; 370} 371 372###----------------------------------------------------------------### 373### a few HTML::Template and HTML::Template::Expr routines 374 375sub param { 376 my $self = shift; 377 my $args; 378 if (@_ == 1) { 379 my $key = shift; 380 if (ref($key) ne 'HASH') { 381 $key = lc $key if ! $self->{'CASE_SENSITIVE'}; 382 return $self->{'_vars'}->{$key}; 383 } 384 $args = [%$key]; 385 } else { 386 $self->throw('param', "Odd number of parameters") if @_ % 2; 387 $args = \@_; 388 } 389 while (@$args) { 390 my $key = shift @$args; 391 $key = lc $key if ! $self->{'CASE_SENSITIVE'}; 392 $self->{'_vars'}->{$key} = shift @$args; 393 } 394 return; 395} 396 397sub output { 398 my $self = shift; 399 my $args = ref($_[0]) eq 'HASH' ? shift : {@_}; 400 my $type = $self->{'TYPE'} || ''; 401 402 my $content; 403 if ($type eq 'filehandle' || $self->{'FILEHANDLE'}) { 404 my $in = $self->{'FILEHANDLE'} || $self->{'SOURCE'} || $self->throw('output', 'Missing source for type filehandle'); 405 local $/ = undef; 406 $content = <$in>; 407 $content = \$content; 408 } elsif ($type eq 'arrayref' || $self->{'ARRAYREF'}) { 409 my $in = $self->{'ARRAYREF'} || $self->{'SOURCE'} || $self->throw('output', 'Missing source for type arrayref'); 410 $content = join "", @$in; 411 $content = \$content; 412 } elsif ($type eq 'filename' || $self->{'FILENAME'}) { 413 $content = $self->{'FILENAME'} || $self->{'SOURCE'} || $self->throw('output', 'Missing source for type filename'); 414 } elsif ($type eq 'scalarref' || $self->{'SCALARREF'}) { 415 $content = $self->{'SCALARREF'} || $self->{'SOURCE'} || $self->throw('output', 'Missing source for type scalarref'); 416 } else { 417 $self->throw('output', "Unknown input type"); 418 } 419 420 421 my $param = $self->{'_vars'} || {}; 422 if (my $ref = $self->{'ASSOCIATE'}) { 423 foreach my $obj (ref($ref) eq 'ARRAY' ? @$ref : $ref) { 424 foreach my $key ($obj->param) { 425 $self->{'_vars'}->{$self->{'CASE_SENSITIVE'} ? $key : lc($key)} = $obj->param($key); 426 } 427 } 428 } 429 430 431 ### override some TT defaults 432 local $self->{'FILE_CACHE'} = $self->{'DOUBLE_FILE_CACHE'} ? 1 : $self->{'FILE_CACHE'}; 433 my $cache_size = ($self->{'CACHE'}) ? undef : 0; 434 my $compile_dir = (! $self->{'FILE_CACHE'}) ? undef : $self->{'FILE_CACHE_DIR'} || $self->throw('output', 'Missing file_cache_dir'); 435 my $stat_ttl = (! $self->{'BLIND_CACHE'}) ? undef : 60; # not sure how high to set the blind cache 436 $cache_size = undef if $self->{'DOUBLE_FILE_CACHE'}; 437 438 local $self->{'SYNTAX'} = $self->{'SYNTAX'} || 'hte'; 439 local $self->{'GLOBAL_CACHE'} = $self->{'CACHE'}; 440 local $self->{'ADD_LOCAL_PATH'} = defined($self->{'ADD_LOCAL_PATH'}) ? $self->{'ADD_LOCAL_PATH'} : 1; 441 local $self->{'CACHE_SIZE'} = $cache_size; 442 local $self->{'STAT_TTL'} = $stat_ttl; 443 local $self->{'COMPILE_DIR'} = $compile_dir; 444 local $self->{'ABSOLUTE'} = 1; 445 local $self->{'RELATIVE'} = 1; 446 local $self->{'INCLUDE_PATH'} = $self->{'PATH'}; 447 local $self->{'LOWER_CASE_VAR_FALLBACK'} = ! $self->{'CASE_SENSITIVE'}; # un-smart HTML::Template default 448 local $Template::Alloy::QR_PRIVATE = undef; 449 450 my $out = ''; 451 $self->process_simple($content, $param, \$out) || die $self->error; 452 453 if ($args->{'print_to'}) { 454 print {$args->{'print_to'}} $out; 455 return undef; 456 } else { 457 return $out; 458 } 459} 460 461###----------------------------------------------------------------### 462 4631; 464 465__END__ 466 467=head1 DESCRIPTION 468 469The Template::Alloy::HTE role provides syntax and interface support for 470the HTML::Template and HTML::Template::Expr modules. 471 472Provides for extra or extended features that may not be as commonly used. 473This module should not normally be used by itself. 474 475See the Template::Alloy documentation for configuration and other parameters. 476 477=head1 HOW IS Template::Alloy DIFFERENT FROM HTML::Template 478 479Alloy can use the same base template syntax and configuration items as HTE 480and HT. The internals of Alloy were written to support TT3, but were 481general enough to be extended to support HTML::Template as well. The result 482is HTML::Template::Expr compatible syntax, with Alloy speed and a wide range 483of additional features. 484 485The TMPL_VAR, TMPL_IF, TMPL_ELSE, TMPL_UNLESS, TMPL_LOOP, and TMPL_INCLUDE 486all work identically to HTML::Template. 487 488=over 4 489 490=item 491 492Added support for other TT3 directives and for TT style "dot notation." 493 494 <TMPL_SET a = "bar"> 495 <TMPL_SET b = [1 .. 25]> 496 <TMPL_SET foo = PROCESS 'filename.tt'> 497 498 <TMPL_GET foo> # similar to <TMPL_VAR NAME="foo"> 499 <TMPL_GET b.3> 500 <TMPL_GET my.nested.chained.variable.1> 501 <TMPL_GET my_var | html> 502 503 <TMPL_USE foo = DBI(db => ...)> 504 <TMPL_CALL foo.connect> 505 506Any of the TT directives can be used in HTML::Template documents. 507 508For many die-hard HTML::Template fans, it is probably quite scary to 509be providing all of the TT functionality. All of the extended 510TT functionality can be disabled by setting the NO_TT configuration 511item. The NO_TT configuration is automatically set if the SYNTAX is 512set to "ht" and the output method is called. 513 514=item 515 516There is an ELSIF!!! 517 518 <TMPL_IF foo> 519 FOO 520 <TMPL_ELSIF bar> 521 BAR 522 <TMPL_ELSE> 523 Done then 524 </TMPL_IF> 525 526=item 527 528Added CHOMP capabilities (PRE_CHOMP and POST_CHOMP) 529 530 Foo 531 <~TMPL_VAR EXPR="1+2"~> 532 Bar 533 534 Prints Foo3Bar 535 536=item 537 538Added INTERPOLATE capability 539 540 <TMPL_SET foo = 'FOO'> 541 <TMPL_CONFIG INTERPOLATE => 1> 542 $foo <TMPL_GET foo> ${ 1 + 2 } 543 544 Prints 545 546 FOO FOO 3 547 548=item 549 550Allow for HTML::Template templates to include TT style templates. 551 552 <TMPL_CONFIG SYNTAX => 'tt3'> 553 <TMPL_INCLUDE "filename.tt"> 554 555=item 556 557Allow for Expr parsing to follow proper precedence rules. 558 559 <TMPL_VAR EXPR="1 + 2 * 3"> 560 561 Properly prints 7. 562 563=item 564 565Uses all of the caching and opcode tree optimizations provided by 566Template::Alloy and Template::Alloy::XS. 567 568=item 569 570Alloy does not provide the query method from HTML::Template. This 571is because parsing of the document is delayed until the output 572method is called, and because Alloy supports TT style chained 573variables which often are not resolvable until run time. 574 575=back 576 577=head1 UNSUPPORTED HT CONFIGURATION 578 579=over 4 580 581=item die_on_bad_params 582 583Alloy does not resolve variables until the template is output. 584 585=item force_untaint 586 587=item strict 588 589Alloy is strict on parsing HT documents. 590 591=item shared_cache, double_cache 592 593Alloy doesn't have shared caching. Yet. 594 595=item search_path_on_include 596 597Alloy will check the full path array on each include. 598 599=item debug items 600 601The HTML::Template style options are included here, but you 602can use the TT style DEBUG and DUMP directives to do introspection. 603 604=item max_includes 605 606Alloy uses TT's recursion protection. 607 608=item filter 609 610Alloy doesn't offer these. 611 612=back 613 614=head1 ROLE METHODS 615 616=over 4 617 618=item C<register_function> 619 620Defines a new function for later use as text vmethod or top level function. 621 622=item C<clear_param> 623 624Empties the parameter list. 625 626=item C<query> 627 628Not supported. 629 630=item C<new_file> 631 632Creates a new object that will process the passed file. 633 634 $obj = Template::Alloy->new_file("my/file.hte"); 635 636=item C<new_scalar_ref> 637 638Creates a new object that will process the passed scalar ref. 639 640 $obj = Template::Alloy->new_scalar_ref(\"some template text"); 641 642=item C<new_array_ref> 643 644New object that will process the passed array (each item represents a line). 645 646 $obj = Template::Alloy->new_array_ref(\@array); 647 648=item C<new_filehandle> 649 650 $obj = Template::Alloy->new_filehandle(\*FH); 651 652=item C<parse_tree_hte> 653 654Called by parse_tree when syntax is set to ht or hte. Parses for tags HTML::Template style. 655 656=item C<param> 657 658See L<Template::Alloy>. 659 660=item C<output> 661 662See L<Template::Alloy>. 663 664=back 665 666=head1 AUTHOR 667 668Paul Seamons <paul@seamons.com> 669 670=head1 LICENSE 671 672This module may be distributed under the same terms as Perl itself. 673 674=cut 675