package Template::Magic ;
$VERSION = 1.39 ;
use strict ;
use 5.006_001 ;
# This file uses the "Perlish" coding style
# please read http://perl.4pro.net/perlish_coding_style.html
; use Carp
; $Carp::Internal{+__PACKAGE__}++
; use warnings::register
; use Template::Magic::Zone
; use IO::Util
; use Class::Util
; use File::Spec
; sub NEXT_HANDLER () { 0 }
; sub LAST_HANDLER () { 1 }
; sub import
{ my ($pkg, $pragma) = @_
; if ( $pragma
&& $pragma eq '-compile'
)
{ carp "The -compile pragma has no effect since version 1.39"
if warnings::enabled
}
else
{ require Exporter
; our @ISA = 'Exporter'
; our @EXPORT_OK = qw| NEXT_HANDLER
LAST_HANDLER
|
; $pkg->export_to_level(1, @_)
}
}
; sub new
{ my ($c) = shift
; my ($s) = @_
; $s = { @_ } # passing hash backward compatibility
unless ref $s eq 'HASH'
; foreach ( keys %$s ) # passing -flag backward compatibility
{ $$s{$_} = delete $$s{-$_}
if s/^-//
}
; foreach ( values %$s ) # each value should be an ARRAY ref
{ $_ = [ $_ ]
unless ref eq 'ARRAY'
}
; bless $s, $c
; $$s{markers} ||= $s->DEFAULT_MARKERS
; $$s{output_handlers} ||= $s->DEFAULT_PRINT_HANDLERS
; $$s{text_handlers} ||= $s->DEFAULT_TEXT_HANDLERS
|| $$s{output_handlers}
; $$s{zone_handlers} ||= $s->DEFAULT_ZONE_HANDLERS
; $$s{value_handlers} ||= $s->DEFAULT_VALUE_HANDLERS
; $$s{post_handlers} ||= $s->DEFAULT_POST_HANDLERS
; $$s{lookups} ||= [ (caller)[0] ]
; $$s{options} ||= $s->DEFAULT_OPTIONS
; $$s{options} = { map { /^(no_)*(.+)$/
; $2 => $1 ? 0 : 1
}
@{$$s{options}}
}
; foreach my $n qw| zone
value
text
output
post
|
{ $$s{$n.'_handlers'}
&&= [ $s->_Hload( $$s{$n.'_handlers'}
, $n
)
]
}
; $s
}
; sub _Hload
{ my ($s, $arr, $n) = @_
; map
{ if ( ref eq 'CODE' )
{ $_
}
elsif ( not ref )
{ my $C = $s->can($_)
|| $s->can( join ( '_'
, $_
, uc $n
, 'HANDLERS'
)
)
|| croak qq(Unknown handler "$_")
; my $ref = $s->$C
; if ( ref $ref eq 'ARRAY' )
{ $s->_Hload( $ref, $n )
}
elsif ( ref $ref eq 'CODE' )
{ $ref
}
}
}
@$arr
}
; sub _re
{ my ($s) = @_
; unless ( $$s{_re} ) # execute it just the first time AND if it has to parse
{ unless ( @{$$s{markers}} == 3 )
{ no strict 'refs'
; my $m = $$s{markers}[0]
; my $M = $s->can($m)
|| $s->can($m.'_MARKERS') # backward compatibility
|| croak qq(Unknown markers "$m")
; $$s{markers} = $s->$M
}
; $$s{markers} = [ map { qr/$_/s
}
( @{$$s{markers}}
, '(?:(?!' .$$s{markers}[2]. ').)*'
, '\w+'
)
]
; my ($S, $I, $E, $A, $ID) = @{$$s{markers}}
; $$s{_re}{label} = qr/$S$I*$ID$A$E/s
; $$s{_re}{start_label} = qr/$S($ID)($A)$E/s
; $$s{_re}{end_label} = qr/$S$I($ID)$E/s
; $$s{_re}{include_label} = qr/$S\bINCLUDE_TEMPLATE\b($A)$E/s
}
; wantarray
? @{$$s{markers}}
: $$s{_re}
}
; sub find_file
{ my ($s, $t) = @_
; my $find = sub{(grep -s, @_)[0]}
; File::Spec->file_name_is_absolute($t)
? $find->($t)
: ( $ENV{TEMPLATE_MAGIC_ROOT}
&& $find->( File::Spec->catfile( $ENV{TEMPLATE_MAGIC_ROOT}
, $t
)
)
|| $find->( map File::Spec->catfile( $_
, $t
)
, @{$$s{paths}}
)
|| $ENV{TEMPLATE_MAGIC_ROOT}
&& $find->( map File::Spec->catfile( $ENV{TEMPLATE_MAGIC_ROOT}
, $_
, $t
)
, @{$$s{paths}}
)
|| $find->($t)
)
}
; sub output
{ my $s = shift
; my $args
; $$args{template} = shift
; $$args{lookups} = [ @_ ] if @_
; IO::Util::capture { $s->_process( $args ) }
}
; sub print
{ my $s = shift
; my $args
; $$args{template} = shift
; $$args{lookups} = [ @_ ] if @_
; $s->_process( $args )
}
; sub noutput
{ my ($s, %args) = @_
; $args{lookups} = [ $args{lookups} ]
unless ref $args{lookups} eq 'ARRAY'
; IO::Util::capture { $s->_process( \%args ) }
}
; sub nprint
{ my ($s, %args) = @_
; $args{lookups} = [ $args{lookups} ]
unless ref $args{lookups} eq 'ARRAY'
; $s->_process( \%args )
}
; sub _process
{ my ($s, $args) = @_
; $$s{_temp_lookups} = $$args{lookups} if exists $$args{lookups}
; my $t
; if ( $t = $$args{container_template}
|| ${$$s{container_template}}[0]
)
{ $$s{_included_template} = $$args{template}
}
else
{ $t = $$args{template}
}
; my $z = $s->load( $t )
; $$z{tm} = $s
; $z->content_process
; delete $$z{tm} # to avoid tm object caching
; delete @$s{qw|_included_template _temp_lookups _NOT_lookup|}
}
; sub load
{ my ($s, $t) = @_
; my $main_zone
; if ( not ref $t )
{ $t = $s->find_file($t)
or croak qq(Template file "$t" empty or not found)
; if ( $$s{options}{cache} )
{ $main_zone = IO::Util::_get_parsing_cache('magic_zone', $t)
; return $main_zone if $main_zone
}
}
; my $content = ref $t eq 'SCALAR' ? $t : IO::Util::slurp $t
; $main_zone = $s->_parse( $content )
; $$s{options}{cache} &&! ref($t) # set cache
&& IO::Util::_set_parsing_cache 'magic_zone', $t, $main_zone
; $main_zone
}
; sub purge_cache
{ $_[0] = 'magic_zone'
; goto &IO::Util::_purge_parsing_cache
}
; sub _parse
{ my ($s, $content_ref) = @_
; my $re = $s->_re
; my @temp
= map { [ $_
, do { /$$re{end_label}/ && $1
|| /$$re{include_label}/ && do{ (my $t = $1) =~ s/^\s+//
; $t
? $s->load($t)
: 'CONTAINER_INCLUDE'
}
|| /$$re{start_label}/ && { id => $1
, attributes => $2
}
}
]
}
split /($$re{label})/ , $$content_ref
; for ( my $i = $#temp # find end
; $i >= 0
; $i --
)
{ my $id = $temp[$i][1]
; next if ( ref $id or not $id )
; for ( ( my $ii = $i-1 # find THE start
, my $l = 0
)
; $ii >= 0 # condition
; ( $ii --
, $l ++
)
)
{ my $the_start = $temp[$ii][1]
; next unless ref($the_start) eq 'HASH' # next if not start
; next unless $$the_start{id} eq $id # next if not THE start
; $$the_start{_s} = $ii + 1
; $$the_start{_e} = $ii + $l
; last
}
}
# allows to set protected props from outside class
; local $Class::props::force = 1
; Template::Magic::Zone->new( _s => 0
, _e => $#temp
, _t => \@temp
, is_main => 1
)
}
############################# STANDARD HANDLERS #############################
# override these DEFAULT subs in subclasses to change defaults
; sub DEFAULT_ZONE_HANDLERS
{
}
; sub DEFAULT_POST_HANDLERS
{
}
; sub DEFAULT_TEXT_HANDLERS
{
}
; sub DEFAULT_VALUE_HANDLERS
{ my ($s, @args) = @_
; [ $s->SCALAR
, $s->REF
, $s->CODE(@args)
, $s->ARRAY
, $s->HASH
, $s->OBJECT
]
}
; sub DEFAULT_PRINT_HANDLERS
{ [ sub
{ print $_[1] if defined $_[1]
; NEXT_HANDLER
}
]
}
; { no warnings 'once'
; *DEFAULT_OUTPUT_HANDLERS = \&DEFAULT_PRINT_HANDLER # deprecated
}
; sub DEFAULT_OPTIONS
{ [ qw| cache | ]
}
; sub DEFAULT_MARKERS
{ [ qw| { / } | ]
}
; sub HTML_MARKERS
{ [ qw| | ]
}
; sub CODE_MARKERS
{ [ qw| <- / -> | ]
}
; sub HTML_VALUE_HANDLERS # value handler
{ my ($s, @args) = @_
; [ $s->SCALAR
, $s->REF
, $s->CODE(@args)
, $s->TableTiler
, $s->ARRAY
, $s->HASH
, $s->FillInForm
, $s->OBJECT
]
}
; sub SCALAR # value handler
{ sub
{ my ($z) = @_
; my $v = $z->value
; if ( not ref $v ) # if it's a plain string
{ $z->output($v) # set output
; $z->output_process( $v ) # process output (requires string)
; LAST_HANDLER
}
}
}
; sub REF # value handler
{ sub
{ my ($z) = @_
; my $v = $z->value
; if (ref($v) =~ /^(SCALAR|REF)$/) # if it's a reference
{ $z->value($$v) # dereference
; $z->value_process # process the new value
; LAST_HANDLER
}
}
}
; sub ARRAY # value handler
{ sub
{ my ($z) = @_
; if (ref $z->value eq 'ARRAY') # if it's an ARRAY
{ my ($i, $attr, $val_key, $ix_key, $named) = 0
; if ( $attr = $z->attributes )
{ $attr =~ s/^\s*(OF\s)*\s*//i
; ($val_key, $ix_key, $i) = split /\s+/, $attr
; $named = 1
}
; foreach my $item ( @{$z->value} ) # for each value in the array
{ $z->value( $named # set the value for the zone
? { $val_key => $item
, $ix_key ? ($ix_key => $i ++) : ()
}
: $item
)
; $z->value_process # process it
}
; LAST_HANDLER
}
}
}
; sub HASH # value handler
{ sub
{ my ($z) = @_
; if (ref $z->value eq 'HASH') # if it's a HASH
{ $z->content_process # start again the process
; LAST_HANDLER
}
}
}
; sub CODE # value handler
{ my ( undef, @args ) = @_
; sub
{ my ($z) = @_
; my $v = $z->value
; if ( ref $v eq 'CODE' )
{ my $l = $z->location
; my $nv = Class::Util::blessed($l)
? do { no strict 'refs'
; $l->$v( ${ref($l).'::no_template_magic_zone'}
? ()
: $z
, @args
)
}
: $v->( $z , @args )
; if ( $v ne ($nv||'') ) # avoid infinite loop
{ $z->value($nv)
; $z->value_process
}
; LAST_HANDLER
}
}
}
; sub OBJECT
{ sub
{ my ($z) = @_
; if ( Class::Util::blessed($z->value) )
{ $z->content_process # process content
; LAST_HANDLER
}
}
}
; sub ID_list
{ my ($s, $indent, $end) = @_
; $indent ||= ' ' x 4
; $end ||= '/'
; my $re = $s->_re
; $$s{text_handlers} = [ sub{} ] # does not print any text
; $$s{zone_handlers}
= [ sub # takes control of the whole process
{ my ($z) = @_
; $z->output_process( $indent x $z->level
. $z->id
. ":\n"
)
; $z->content_process
; my $cont = $z->content
; if ( $z->_e # if it is a block
&& $cont =~ /$$re{label}/ # and contains labels
)
{ $z->output_process( $indent x $z->level # print the end
. $end
. $z->id
. ":\n"
)
}
; LAST_HANDLER
}
]
}
# START AutoLoaded handlers
# 'sub' must be at start of line to be found by AutoSplit
# no fancy coding here :-(
sub _EVAL_ # zone handler
{ sub
{ my ($z) = @_;
; if ( $z->id eq '_EVAL_' )
{ $z->value( eval $z->content )
}
; NEXT_HANDLER
# lookup is skipped by the defined $z->value
# value_process is entered by default
}
}
sub _EVAL_ATTRIBUTES_ # zone handler
{ sub
{ my ($z) = @_
; if ( $z->attributes )
{ $z->param( eval $z->attributes )
}
; NEXT_HANDLER
# $z->attributes should be a ref to a structure
}
}
sub TRACE_DELETIONS # zone handler
{ sub
{ my ($z) = @_
# do lookup and value processes as usual
; $z->lookup_process
; $z->value_process
# if they fail to find a true output trace the deletion
; if ( not defined $z->output )
{ $z->output_process ( '<<' . $z->id . ' not found>>' )
unless ref $z->value eq 'HASH'
}
elsif ( not $z->output )
{ $z->output_process ( '<<' . $z->id . ' found but empty>>' )
}
; LAST_HANDLER
}
}
sub INCLUDE_TEXT # zone handler
{ sub
{ my ($z) = @_
; if ( $z->id eq 'INCLUDE_TEXT' )
{ my $file = $z->attributes
; open my $itxt, $file
or croak qq(Error opening text file "$file": $^E)
; $z->text_process($_) while <$itxt>
; close $itxt
; LAST_HANDLER
}
}
}
############### HTML HANDLERS ##############
sub TableTiler # value handler
{ eval
{ local $SIG{__DIE__}
; require HTML::TableTiler
; return $HTML::TableTiler::VERSION >= 1.14
}
; if ( $@ )
{ carp qq("HTML::TableTiler" is not installed on this system or it is not current\n)
; return sub {} # no action
}
else
{ sub # normal handler
{ my ($z) = @_
; my $v = $z->value
; if ( ref($v) eq 'ARRAY'
&& HTML::TableTiler::is_matrix($v) # if matrix
)
{ $z->value
( do { my $cont = $z->content
; HTML::TableTiler::tile_table( $v
, $cont && \$cont
, $z->attributes
, 1
)
}
)
; $z->value_process
; LAST_HANDLER
}
}
}
}
sub FillInForm # value handler
{ eval
{ local $SIG{__DIE__}
; require HTML::FillInForm
}
; if ( $@ )
{ carp qq("HTML::FillInForm" is not installed on this system\n)
; sub {}
}
else
{ sub
{ my ($z) = @_
; my $v = $z->value
; if ( ref($v)
&& defined UNIVERSAL::can( $v , 'param' )
)
{ my $cont = IO::Util::capture { $z->content_process }
; my $attr = $z->attributes
; my ($list) = $attr =~ /ignore_fields\s*=>\s*\[(.*)\]/
; my @if = map /(?:'|")(.+)(?:'|")/ #'
, split /\s*,\s*/
, $list||''
; $z->value( HTML::FillInForm
->new
->fill( scalarref => $cont
, fobject => $v
, ignore_fields => \@if
)
)
; $z->value_process
; LAST_HANDLER
}
}
}
}
__END__
=pod
=head1 NAME
Template::Magic - Magic merger of runtime values with templates
=head1 VERSION 1.39
Included in Template-Magic 1.39 distribution.
The latest version changes are reported in the F file in this distribution.
=head1 INSTALLATION
=over
=item Prerequisites
Perl version >= 5.6.1
OOTools >= 2
IO::Util >= 1.46
File::Spec >= 0
=item CPAN
If you want to install Template::Magic plus all related extensions (the prerequisites to use also L), all in one easy step:
perl -MCPAN -e 'install Bundle::Template::Magic'
=item Standard installation
From the directory where this file is located, type:
perl Makefile.PL
make
make test
make install
B: this installs just the main distribution and does not install the prerequisites of L.
=item Distribution structure
Bundle::Template::Magic a bundle to install everything in one step
Template::Magic the main module
Template::Magic::Zone defines the zone object
Template::Magic::HTML handlers useful in HTML environment
=back
=head1 SYNOPSIS
Just add these 2 magic lines to your code...
use Template::Magic;
Template::Magic->new->print( '/path/to/template' );
to have all your variable and subroutines merged with the F file, or set one or more constructor array to customize the output generation as you need:
use Template::Magic qw( -compile );
$tm = new Template::Magic
paths => [ qw(/any/path /any/other/path) ] ,
markers => [ qw( < / > ) ] ,
lookups => [ \%my_hash, $my_obj, 'main' ] ,
zone_handlers => [ \&my_zone_handler, '_EVAL_' ] ,
value_handlers => [ 'DEFAULT', \&my_value_handler ] ,
text_handlers => sub {print lc $_[1]} ,
output_handlers => sub {print uc $_[1]} ,
post_handlers => \&my_post_handler ,
options => 'no_cache' ;
$tm->nprint( template => '/path/to/template'
lookups => \%my_special_hash );
=head1 DESCRIPTION
Template::Magic is a "magic" interface between programming and design. It makes "magically" available all the runtime values - stored in your variables or returned by your subroutines - inside a static template file. B. Template outputs are linked to runtime values by their I, which are added to the template in the form of simple I or I of content.
a label: {identifier}
a block: {identifier} content of the block {/identifier}
From the designer point of view, this makes things very simple. The designer has just to decide B value and B to put it. Nothing else is required, no complicated new syntax to learn! B.
On the other side, the programmer has just to define variables and subroutines as usual and their values will appear in the right place within the output. The automatic interface allows the programmer to focus just on the code, saving him the hassle of interfacing code with output, and even complicated output - with complex switch branching and nested loops - can be easily organized by minding just a few simple concepts.
=over
=item 1
The object parses the template and searches for any I
=item 2
When a I is found, the object looks into your code and searches for any variable or sub with the same identifier (name)
=item 3
When a match is found the object replaces the label or the block with the value returned by the variable or sub found into your code (dereferencing and/or executing code as needed). (see L<"Understand the output generation"> for details)
=back
B: If you are planning to use this module in CGI environment, take a look at L that transparently integrates this module in a very handy and powerful framework.
=head2 Simple example
The following is a very simple example only aimed to better understand how it works: obviously, the usefulness of Template::Magic comes up when the output become more complex.
Imagine you need an output that looks like this template file:
City: {city}
Date and Time: {date_and_time}
where {city} and {date_and_time} are just placeholder that you want to be replaced in the output by some real runtime values. Somewhere in your code you have defined a scalar and a sub to return the 'city' and the 'date_and_time' values:
$city = 'NEW YORK';
sub date_and_time { localtime }
you have just to add these 2 magic lines to the code:
use Template::Magic;
Template::Magic->new->print( 'my_template_file' );
to generate this output:
City: NEW YORK
Date and Time: Sat Nov 16 21:03:31 2002
With the same 2 magic lines of code, Template::Magic can automatically look up values from I, I, I, I and I from your code and produce very complex outputs. The default settings are usually smart enough to do the right job for you, however if you need complete control over the output generation, you can fine tune them by controlling them explicitly. See L<"CUSTOMIZATION"> for details.
=head2 More complex example
=over
=item the template
The template file F<'my_template_file'>... I<(this example uses plain text for clarity, but Template::Magic works with any type of text file)>
A scalar variable: {a_scalar}.
A reference to a scalar variable: {a_ref_to_scalar}.
A subroutine: {a_sub}
A reference to subroutine: {a_ref_to_sub}
A reference to reference: {a_ref_to_ref}
A hash: {a_hash}this block contains a {a_scalar} and a {a_sub}{/a_hash}
A loop:{an_array_of_hashes}
Iteration #{ID}: {guy} is a {job}{/an_array_of_hashes}
An included file:
{INCLUDE_TEMPLATE my_included_file}
... and another template file F<'my_included_file'> that will be included...
this is the included file 'my_included_file'
that contains a label: {a_scalar}
=item the code
... some variables and subroutines already defined somewhere in your code...
B: This example uses globals just for simplicity. Please notice that Template::Magic can be used to write sloppy code or very strict code, exactly as perl itself can. Magic lookups is a very handly feature for simple scripts, while it is not recommended for complex script where you should explicitly limit the lookups to some specific package or hash (see L<"lookups">).
$a_scalar = 'THIS IS A SCALAR VALUE';
$a_ref_to_scalar = \$a_scalar;
@an_array_of_hashes = ( { ID => 1, guy => 'JOHN SMITH', job => 'PROGRAMMER' },
{ ID => 2, guy => 'TED BLACK', job => 'WEBMASTER' },
{ ID => 3, guy => 'DAVID BYRNE', job => 'MUSICIAN' } );
%a_hash = ( a_scalar => 'NEW SCALAR VALUE'
a_sub => sub { 'NEW SUB RESULT' } );
sub a_sub { 'THIS SUB RETURNS A SCALAR' }
sub a_ref_to_sub { \&a_sub }
sub a_ref_to_ref { $a_ref_to_scalar }
Just add these 2 magic lines...
use Template::Magic;
Template::Magic->new->print( 'my_template_file' );
=item the output
I<(in this example Lower case are from templates and Upper case are from code)>:
A scalar variable: THIS IS A SCALAR VALUE.
A reference to a scalar variable: THIS IS A SCALAR VALUE.
A subroutine: THIS SUB RETURNS A SCALAR
A reference to subroutine: THIS SUB RETURNS A SCALAR
A reference to reference: THIS IS A SCALAR VALUE
A hash: this block contains a NEW SCALAR VALUE and a NEW SUB RESULT
A loop:
Iteration #1: JOHN SMITH is a PROGRAMMER
Iteration #2: TED BLACK is a WEBMASTER
Iteration #3: DAVID BYRNE is a MUSICIAN
An included file:
this is the included file 'my_included_file'
that contains a label: THIS IS A SCALAR VALUE.
=back
=head2 Features
Since syntax and coding related to this module are very simple and mostly automatic, you should careful read this section to have the right idea about its features and power. This is a list - with no particular order - of the most useful features and advantages:
=over
=item * Simple, flexible and powerful to use
In simple cases, you will have just to use L and L methods, without having to pass any other value to the object: it will do the right job for you. However you can fine tune the behaviour as you need. (see L<"CUSTOMIZATION">)
=item * Extremely simple and configurable template syntax
The template syntax is so simple and code-independent that even the less skilled webmaster will manage it without bothering you :-). By default Template::Magic recognizes labels in the form of simple identifiers surrounded by braces (I<{my_identifier}>), but you can easily use different markers (see L<"Redefine Markers">).
=item * Automatic or manual lookup of values
By default, Template::Magic compares any I
This is what you would see in a WYSIWYG editor: I<(you should be using a browser to see the example below this line)>
=for html
Name:
Surname:
=item template with placeholders
The placeholders "John" and "Smith" are included in blocks and will be replaced by the actual values of 'name' and 'surname' from your code.
Name: John
Surname: Smith
This is what you would see in a WYSIWYG editor: I<(you should be using a browser to see the example below this line)>
=for html
Name: John
Surname: Smith
=back
=head2 Setup simulated areas
If you want to include in your template some area only for design purpose I<(for example to see, right in the template, how could look a large nested loop)>, just transform it into a block and give it an identifier that will never be defined in your code.
{my_simulated_area} this block simulates a possible output
and it will never generate any output {/my_simulated_area}
=head2 Setup labeled areas
If you want to label some area in your template I<(for example to extract the area to mix with another template)>, just transform it into a block and give it an identifier that will always be defined in your code. A convenient way to do so is to define a reference to an empty hash. This will generate the output of the block and (since the hash does not contain any keys) the lookup will fallback to the I zones and the I locations.
=over
=item the code
$my_labeled_area = {} ; # a ref to an empty hash
=item the template
{my_labeled_area}
this block will always generate an output
{/my_labeled_area}
=back
=head2 Build a loop
=over
=item the template
A loop is represented by a block, usually containing labels:
A loop:
{my_loop}-------------------
Date: {date}
Operation: {operation}
{/my_loop}-------------------
=item the code
You should have some array of hashes (or a reference to) defined somewhere:
$my_loop = [
{
date => '8-2-02',
operation => 'purchase'
},
{
date => '9-3-02',
operation => 'payment'
}
] ;
=item the output
A loop:
-------------------
Date: 8-2-02
Operation: purchase
-------------------
Date: 9-3-02
Operation: payment
-------------------
=back
=head2 Build a nested loop
=over
=item the template
A nested loop is represented by a block nested into another block:
A nested loop:
{my_nested_loop}-------------------
Date: {date}
Operation: {operation}
Details:{details}
- {quantity} {item}{/details}
{/my_nested_loop}-------------------
Notice that the block I<'details'> is nested into the block I<'my_nested_loop'>.
=item the code
You should have some array nested into some other array, defined somewhere:
# a couple of nested "for" loops may produce this:
$my_nested_loop = [
{
date => '8-2-02',
operation => 'purchase',
details => [
{
quantity => 5,
item => 'balls'
},
{
quantity => 3,
item => 'cubes'
},
{
quantity => 6,
item => 'cones'
}
]
},
{
date => '9-3-02',
operation => 'payment',
details => [
{
quantity => 2,
item => 'cones'
},
{ quantity => 4,
item => 'cubes'}
]
}
] ;
Notice that the value of the keys I<'details'> are a reference to an array of hashes.
=item the output
A nested loop:
-------------------
Date: 8-2-02
Operation: purchase
Details:
- 5 balls
- 3 cubes
- 6 cones
-------------------
Date: 9-3-02
Operation: payment
Details:
- 2 cones
- 4 cubes
-------------------
=back
=head2 Build a simple loop
This is a new feature implemented in Template::Magic 1.32, that allows the direct handling of array items in loops (i.e. you can use an array of strings instead of an array of hashes containing a named string).
When the loop contains just a label, you can also directly use the items of any array, eventually using also the relative index number:
=over
=item the code
You should have some array defined somewhere:
$my_loop = [ qw( ball cube cone ) ] ;
=item the template
A loop is represented by a block, usually containing labels. This loop defines as 'product' the label representing each array item, the progressive count as 'line_number' and the starting count at 1:
A loop:
{my_loop OF product line_number 1}-------------------
{line_number} - Product: {product}
{/my_loop}-------------------
=item the output
A loop:
-------------------
1 - Product: ball
-------------------
2 - Product: cube
-------------------
3 - Product: cone
-------------------
=back
B: any loop that directly uses the values of any array, can be written as:
=over
=item {my_array}
this is used only when the array items are reference to hashes (see L<"Build a loop"> or L<"Build a nested loop">)
=item {my_array OF anything index 1}
this defines as 'anything' the label representing each array item, the progressive count as 'index' and the starting count will start at 1
=item {my_array OF anything index}
if you omit the starting count value, it will start at 0
=item {my_array OF anything}
if you don't use any progressive count inside the block you may omit it
=item {my_array anything}
you can also omit the 'OF' (case insensitive) keyword in all the above cases
=back
=head2 Process (huge) loops iteration by iteration
Usually a loop is built just by an array of hashes value (see L<"Build a loop">). This means that you have to fill an array with all the hashes BEFORE the process starts. In normal situations (i.e. the array contains just a few hashes) this is not a problem, but if the array is supposed to contain a lot of hashes, it could be more efficient by creating each hash just DURING the process and not BEFORE it (i.e. without storing it in any array).
For example imagine that in the L<"Build a loop"> example, the array comes from a huge file like this:
8-2-02|purchase
9-3-02|payment
... some hundred lines
You could generate the output line by line with a simple sub like this:
sub my_loop
{
my ($z) = @_ ;
open FILE, '/path/to/data/file' ;
while () # for each line of the file
{
chomp ;
my $line_hash ;
@$line_hash{'date', 'operation'} = split /\|/ ; # create line hash
$z->value = $line_hash ; # set the zone value
$z->value_process() ; # process the value
}
}
This way you don't waste memory to store the data for all the iteration into the array: you just use the memory needed for one iteration at a time.
=head2 Setup an if-else condition
=over
=item the template
An if-else condition is represented with 2 blocks
{OK_block}This is the OK block, containig {a_scalar}{/OK_block}
{NO_block}This is the NO block{/NO_block}
=item the code
Remember that a block will be deleted if the lookup of the identifier returns the UNDEF value, so your code will determine what block will generate output (defined identifier) and what not (undefined identifier).
if ($OK) { $OK_block = {a_scalar => 'A SCALAR VARIABLE'} }
else { $NO_block = {} }
Same thing here:
$a_scalar = 'A SCALAR VARIABLE';
$OK ? $OK_block={} : $NO_block={};
=item the output
A true C<$OK> would leave undefined C<$NO_block>, so it would produce this output:
This is the OK block, containig A SCALAR VARIABLE
A false $OK would leave undefined C<$OK_block>, so it would produce this output:
This is the NO block
Notice that C<$OK_block> and C<$NO_block> should not return a SCALAR value, that would replace the whole block with the value of the scalar.
=back
=head2 Use the NOT_* blocks
This is a new feature implemented in Template::Magic 1.2, that allows to simplify the if-else handling for any zone. It is intended to be used only in such case (if-else), and in such order (first the * block and next the NOT_* block); for any other use, please refer to L<"Setup an if-else condition">.
For any zone you can use a NOT_* zone (where '*' stands for the zone id) which will automatically be printed if the zone is not printed, or wiped out if the zone is printed.
The above example could be written also this way:
=over
=item the template
{OK_block}This is the OK block, containig {a_scalar}{/OK_block}
{NOT_OK_block}This is the NOT_OK_block, containig {a_scalar},
and printed automatically if the OK_block will not be printed
{/NOT_OK_block}
=item the code
$a_scalar = 'A SCALAR VARIABLE';
$OK_block = any_condition() ? {} : ''
=item the output
A true C whould set the C<$OK_block> to an empty hash reference, thus printing
This is the OK block, containig A SCALAR VARIABLE
While a false C whould wipe out the C, thus automatically printing the C.
This is the NOT_OK_block, containig A SCALAR VARIABLE,
and printed automatically if the OK_block will not be printed
=back
=head2 Setup a switch condition
=over
=item the template
A simple switch (if-elsif-elsif) condition is represented with multiple blocks:
{type_A}type A block with {a_scalar_1}{/type_A}
{type_B}type B block with {a_scalar_2}{/type_B}
{type_C}type C block with {a_scalar_1}{/type_C}
{type_D}type D block with {a_scalar_2}{/type_D}
=item the code
Your code will determine what block will generate output (defined identifier) and what not (undefined identifier). In the following example, value of C<$type> will determine what block will produce output, then the next line will define C<$type_C> using a symbolic reference:
$type = 'type_C';
$$type = { a_scalar_1 => 'THE SCALAR 1',
a_scalar_2 => 'THE SCALAR 2' };
Same thing yet but with a different programming style:
$a_scalar_1 = 'THE SCALAR 1';
$a_scalar_2 = 'THE SCALAR 2';
$type = 'type_D';
$$type = {};
Same thing without using any symbolic reference:
$type = 'type_D';
$my_hash{$type} = { a_scalar_1 => 'THE SCALAR 1',
a_scalar_2 => 'THE SCALAR 2' };
$tm = new Template::Magic
lookups => \%my_hash ;
=item the output
A C<$type> set to 'type_C' would produce this output:
type C block with THE SCALAR 1
A C<$type> set to 'type_D' would produce this output:
type D block with THE SCALAR 2
=back
=head2 Pass parameters to a subroutine
Template::Magic can execute subroutines from your code: when you use a zone identifier that matches with a subroutine identifier, the subroutine will receive the I as a parameters and will be executed. This is very useful when you want to return a modified copy of the template content itself, or if you want to allow the designer to pass parameter to the subroutines.
This example show you how to allow the designer to pass some parameters to a subroutine in your code. The 'matrix' sub, used in the example, receives the parameters written in the template and generates just a table filled of 'X'.
=over
=item the template
{matrix}5,3{/matrix}
The content of 'matrix' block ('5,3') is used as parameter
=item the code
sub matrix
{
my ($zone) = @_;
my ($column, $row) = split ',' , $zone->content; # split the parameters
my $out;
for (0..$row-1) {$out .= 'X' x $column. "\n"};
$out;
}
The sub 'matrix' receive the reference to the I, and return the output for the block
=item the output
XXXXX
XXXXX
XXXXX
=back
The same example with named parameters, could be written as follow:
=over
=item the template
{matrix columns => 5, rows => 3}
The attributes string of 'matrix' label (' columns => 5, rows => 3') is used as parameter
=item the code
sub matrix
{
my ($zone) = shift;
my $attributes = $zone->attributes;
$attributes =~ tr/ //d; # no spaces
my %attr = split /=>|,/, $attributes; # split the parameters
my $out;
for (0..$attr{rows}-1) {$out .= 'X' x $attr{columns} . "\n"};
$out;
}
The sub 'matrix' receive the reference to the I, and return the output for the block
=item the output
XXXXX
XXXXX
XXXXX
=back
=head2 Pass a structure to a subroutine
You can use the '_EVAL_ATTRIBUTES_' zone handler to pass complex named structures to a subroutine.
A simple example that use the '_EVAL_ATTRIBUTES_' zone handler could be:
$tm = new Template::Magic
markers => ['<<', '/', '>>'] , # to avoid conflict
zone_handlers => '_EVAL_ATTRIBUTES_' ;
This is a possible example of template:
text < 'red', quantity => 2}>> text
The '_EVAL_ATTRIBUTES_' zone handler set the C property to the evalued I C<< {color => 'red', quantity => 2} >> in the template, so you can use it directly in your sub:
sub my_sub
{
my ($z) = @_ ;
'The color is '. $z->param->{color}
. ' the quantity is '. $z->param->{quantity}
}
B: You should use '_EVAL_ATTRIBUTES_' handler ONLY if you are the programmer AND the designer.
=head2 Use subroutines to rewrite links
If you use a block identifier that matches with a subroutine identifier, the subroutine will receive the content of the block as a single parameter and will be executed. This is very useful when you want to return a modified copy of the template content itself.
A typical application of this capability is the template of a HTML table of content that point to several template files. You can use the capabilities of your favourite WYSIWYG editor to easily link each menu in the template with each template file. By doing so you will generate a static and working HTML file, linked with the other static and working HTML template files. This will allow you to easily check the integrity of your links, and preview how the links would work when utilized by your program.
Then a simple C subroutine - defined in your program - will return a self-pointing link that will be put in the output in place of the static link. See the example below:
=over
=item the template
Working links pointing to static templates files (useful for testing and preview purpose, without passing through the program)
=item the code
sub modify_link
{
my ($zone) = shift;
my ($content) = $zone->content;
$content =~ m|([^/]*).html$|;
return '/path/to/myprog.cgi?action='.$content;
}
=item the output
Working links pointing to your program, defining different query strings.
See also L<"Pass parameters to a subroutine">.
=back
=head2 Prepare the identifiers description list
If you have to pass to a webmaster the description of every identifier in your program utilized by any label or block, Template::Magic can help you by generating a pretty formatted list of all the identifiers (from labels and blocks) present in any output printed by your program. Just follow these steps:
=over
=item 1 Add the following line anywhere before printing the output:
$tm->ID_list;
=item 2 Capture the outputs of your program
Your program will run exactly the same way, but instead of print the regular outputs, it will print just a pretty formatted list of all the identifiers present in any output.
=item 3 Add the description
Add the description of each label and block to the captured output and give it to the webmaster.
=back
=head2 Allow untrustworthy people to edit the template
F does not use any eval() statement and the allowed characters for identifiers are only alphanumeric C<(\w+)>, so even dealing with tainted templates it should not raise any security problem that you wouldn't have in your program itself.
=head3 Avoid unwanted executions
This module can execute the subroutines of your code whenever it matches a label or block identifier with the subroutine identifier. Though unlikely, it is possible in principle that someone (only if allowed to edit the template) sneaks the correct identifier from your code, therefore, if you have any potentially dangerous subroutine in your code, you should restrict this capability. To do this, you can omit the C value handler, or pass only explicit locations to the C method.
=over
=item potentially unsafe code
sub my_potentially_dangerous_sub { unlink 'database_file' };
$name = 'John';
$surname = 'Smith';
# automatic lookup in __PACKAGE__ namespace
$tm = new Template::Magic ;
With this code, a malicious person allowed to edit the template could add the label I<{my_potentially_dangerous_sub}> in the template and that label would trigger the deletion of 'database_file'.
=item code with subs_execution disabled
Just explicitly omit the C value handler when you create the object, so no sub will be executed:
$tm = new Template::Magic
value_handler => [ qw( SCALAR REF ARRAY HASH ) ] ;
=item code with restricted lookups
sub my_potentially_dangerous_sub { unlink 'database_file' };
%my_restricted_hash = ( name => 'John', surname => 'Smith' );
# lookup in %my_restricted_hash only
$tm = new Template::Magic
lookups => \%my_restricted_hash ;
With this code the lookup is restricted to just the identifiers used in the template, thus the subroutine C is unavailable to the outside world.
=back
=head2 Embed perl into a template
This example represents the maximum degree of inclusion of perl code into a template: in this situation, virtually any code inside the '_EVAL_' block will be executed from the template.
B: For obvious reasons you should use this handler ONLY if you are the programmer AND the designer.
=over
=item the template
{_EVAL_}$char x ($num+1){/_EVAL_}
The content of '_EVAL_' block could be any perl expression
=item the code
$tm = new Template::Magic
zone_handlers => '_EVAL_' ;
$char = 'W';
$num = 5;
=item the output
The handler will generate as the output the evaluated content of the block.
WWWWWW
Since a block can contain any quantity of text, you could use this type of configuration as a cheap way to embed perl into (HTML) files.
Notice that the default syntax markers ({/}) could somehow clash with perl blocks, so if you want to embed perl into your templates, you should consider to redefine the syntax with some more appropriate marker (See L<"Redefine Markers">).
=back
=head2 Caching or not the template
Template::Magic cache the template structure by default if it is passed as a path to a file. You can avoid the caching either by passing a filehandler or a reference to a template content (not so memory efficient) or by using the 'cache/nocache' L<"options">:
$tm = new Template::Magic
options => 'no_cache' ;
=head1 EFFICIENCY
The system is very flexible, so you can use it in a variety of ways, but you have to know what is the best option for your needs.
=head2 Memory optimization
You can avoid waste of memory by avoiding the method L