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