diff options
Diffstat (limited to 'lib/Template/Context.pm')
-rw-r--r-- | lib/Template/Context.pm | 1549 |
1 files changed, 1549 insertions, 0 deletions
diff --git a/lib/Template/Context.pm b/lib/Template/Context.pm new file mode 100644 index 0000000..6fb29ca --- /dev/null +++ b/lib/Template/Context.pm @@ -0,0 +1,1549 @@ +#============================================================= -*-Perl-*- +# +# Template::Context +# +# DESCRIPTION +# Module defining a context in which a template document is processed. +# This is the runtime processing interface through which templates +# can access the functionality of the Template Toolkit. +# +# AUTHOR +# Andy Wardley <abw@kfs.org> +# +# COPYRIGHT +# Copyright (C) 1996-2000 Andy Wardley. All Rights Reserved. +# Copyright (C) 1998-2000 Canon Research Centre Europe Ltd. +# +# This module is free software; you can redistribute it and/or +# modify it under the same terms as Perl itself. +# +# REVISION +# $Id: Context.pm,v 2.81 2003/07/24 11:32:35 abw Exp $ +# +#============================================================================ + +package Template::Context; + +require 5.004; + +use strict; +use vars qw( $VERSION $DEBUG $AUTOLOAD $DEBUG_FORMAT ); +use base qw( Template::Base ); + +use Template::Base; +use Template::Config; +use Template::Constants; +use Template::Exception; + +$VERSION = sprintf("%d.%02d", q$Revision: 2.81 $ =~ /(\d+)\.(\d+)/); +$DEBUG_FORMAT = "\n## \$file line \$line : [% \$text %] ##\n"; + + +#======================================================================== +# ----- PUBLIC METHODS ----- +#======================================================================== + +#------------------------------------------------------------------------ +# template($name) +# +# General purpose method to fetch a template and return it in compiled +# form. In the usual case, the $name parameter will be a simple string +# containing the name of a template (e.g. 'header'). It may also be +# a reference to Template::Document object (or sub-class) or a Perl +# sub-routine. These are considered to be compiled templates and are +# returned intact. Finally, it may be a reference to any other kind +# of valid input source accepted by Template::Provider (e.g. scalar +# ref, glob, IO handle, etc). +# +# Templates may be cached at one of 3 different levels. The internal +# BLOCKS member is a local cache which holds references to all +# template blocks used or imported via PROCESS since the context's +# reset() method was last called. This is checked first and if the +# template is not found, the method then walks down the BLOCKSTACK +# list. This contains references to the block definition tables in +# any enclosing Template::Documents that we're visiting (e.g. we've +# been called via an INCLUDE and we want to access a BLOCK defined in +# the template that INCLUDE'd us). If nothing is defined, then we +# iterate through the LOAD_TEMPLATES providers list as a 'chain of +# responsibility' (see Design Patterns) asking each object to fetch() +# the template if it can. +# +# Returns the compiled template. On error, undef is returned and +# the internal ERROR value (read via error()) is set to contain an +# error message of the form "$name: $error". +#------------------------------------------------------------------------ + +sub template { + my ($self, $name) = @_; + my ($prefix, $blocks, $defblocks, $provider, $template, $error); + my ($shortname, $blockname, $providers); + + $self->debug("template($name)") if $self->{ DEBUG }; + + # references to Template::Document (or sub-class) objects objects, or + # CODE references are assumed to be pre-compiled templates and are + # returned intact + return $name + if UNIVERSAL::isa($name, 'Template::Document') + || ref($name) eq 'CODE'; + + $shortname = $name; + + unless (ref $name) { + + $self->debug("looking for block [$name]") if $self->{ DEBUG }; + + # we first look in the BLOCKS hash for a BLOCK that may have + # been imported from a template (via PROCESS) + return $template + if ($template = $self->{ BLOCKS }->{ $name }); + + # then we iterate through the BLKSTACK list to see if any of the + # Template::Documents we're visiting define this BLOCK + foreach $blocks (@{ $self->{ BLKSTACK } }) { + return $template + if $blocks && ($template = $blocks->{ $name }); + } + + # now it's time to ask the providers, so we look to see if any + # prefix is specified to indicate the desired provider set. + if ($^O eq 'MSWin32') { + # let C:/foo through + $prefix = $1 if $shortname =~ s/^(\w{2,})://o; + } + else { + $prefix = $1 if $shortname =~ s/^(\w+)://; + } + + if (defined $prefix) { + $providers = $self->{ PREFIX_MAP }->{ $prefix } + || return $self->throw(Template::Constants::ERROR_FILE, + "no providers for template prefix '$prefix'"); + } + } + $providers = $self->{ PREFIX_MAP }->{ default } + || $self->{ LOAD_TEMPLATES } + unless $providers; + + + # Finally we try the regular template providers which will + # handle references to files, text, etc., as well as templates + # reference by name. If + + $blockname = ''; + while ($shortname) { + $self->debug("asking providers for [$shortname] [$blockname]") + if $self->{ DEBUG }; + + foreach my $provider (@$providers) { + ($template, $error) = $provider->fetch($shortname, $prefix); + if ($error) { + if ($error == Template::Constants::STATUS_ERROR) { + # $template contains exception object + if (UNIVERSAL::isa($template, 'Template::Exception') + && $template->type() eq Template::Constants::ERROR_FILE) { + $self->throw($template); + } + else { + $self->throw( Template::Constants::ERROR_FILE, $template ); + } + } + # DECLINE is ok, carry on + } + elsif (length $blockname) { + return $template + if $template = $template->blocks->{ $blockname }; + } + else { + return $template; + } + } + + last if ref $shortname || ! $self->{ EXPOSE_BLOCKS }; + $shortname =~ s{/([^/]+)$}{} || last; + $blockname = length $blockname ? "$1/$blockname" : $1; + } + + $self->throw(Template::Constants::ERROR_FILE, "$name: not found"); +} + + +#------------------------------------------------------------------------ +# plugin($name, \@args) +# +# Calls on each of the LOAD_PLUGINS providers in turn to fetch() (i.e. load +# and instantiate) a plugin of the specified name. Additional parameters +# passed are propagated to the new() constructor for the plugin. +# Returns a reference to a new plugin object or other reference. On +# error, undef is returned and the appropriate error message is set for +# subsequent retrieval via error(). +#------------------------------------------------------------------------ + +sub plugin { + my ($self, $name, $args) = @_; + my ($provider, $plugin, $error); + + $self->debug("plugin($name, ", defined $args ? @$args : '[ ]', ')') + if $self->{ DEBUG }; + + # request the named plugin from each of the LOAD_PLUGINS providers in turn + foreach my $provider (@{ $self->{ LOAD_PLUGINS } }) { + ($plugin, $error) = $provider->fetch($name, $args, $self); + return $plugin unless $error; + if ($error == Template::Constants::STATUS_ERROR) { + $self->throw($plugin) if ref $plugin; + $self->throw(Template::Constants::ERROR_PLUGIN, $plugin); + } + } + + $self->throw(Template::Constants::ERROR_PLUGIN, "$name: plugin not found"); +} + + +#------------------------------------------------------------------------ +# filter($name, \@args, $alias) +# +# Similar to plugin() above, but querying the LOAD_FILTERS providers to +# return filter instances. An alias may be provided which is used to +# save the returned filter in a local cache. +#------------------------------------------------------------------------ + +sub filter { + my ($self, $name, $args, $alias) = @_; + my ($provider, $filter, $error); + + $self->debug("filter($name, ", + defined $args ? @$args : '[ ]', + defined $alias ? $alias : '<no alias>', ')') + if $self->{ DEBUG }; + + # use any cached version of the filter if no params provided + return $filter + if ! $args && ! ref $name + && ($filter = $self->{ FILTER_CACHE }->{ $name }); + + # request the named filter from each of the FILTERS providers in turn + foreach my $provider (@{ $self->{ LOAD_FILTERS } }) { + ($filter, $error) = $provider->fetch($name, $args, $self); + last unless $error; + if ($error == Template::Constants::STATUS_ERROR) { + $self->throw($filter) if ref $filter; + $self->throw(Template::Constants::ERROR_FILTER, $filter); + } + # return $self->error($filter) + # if $error == &Template::Constants::STATUS_ERROR; + } + + return $self->error("$name: filter not found") + unless $filter; + + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + # commented out by abw on 19 Nov 2001 to fix problem with xmlstyle + # plugin which may re-define a filter by calling define_filter() + # multiple times. With the automatic aliasing/caching below, any + # new filter definition isn't seen. Don't think this will cause + # any problems as filters explicitly supplied with aliases will + # still work as expected. + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + # alias defaults to name if undefined + # $alias = $name + # unless defined($alias) or ref($name) or $args; + + # cache FILTER if alias is valid + $self->{ FILTER_CACHE }->{ $alias } = $filter + if $alias; + + return $filter; +} + + +#------------------------------------------------------------------------ +# view(\%config) +# +# Create a new Template::View bound to this context. +#------------------------------------------------------------------------ + +sub view { + my $self = shift; + require Template::View; + return Template::View->new($self, @_) + || $self->throw(&Template::Constants::ERROR_VIEW, + $Template::View::ERROR); +} + + +#------------------------------------------------------------------------ +# process($template, \%params) [% PROCESS template var=val ... %] +# process($template, \%params, $local) [% INCLUDE template var=val ... %] +# +# Processes the template named or referenced by the first parameter. +# The optional second parameter may reference a hash array of variable +# definitions. These are set before the template is processed by +# calling update() on the stash. Note that, unless the third parameter +# is true, the context is not localised and these, and any other +# variables set in the template will retain their new values after this +# method returns. The third parameter is in place so that this method +# can handle INCLUDE calls: the stash will be localized. +# +# Returns the output of processing the template. Errors are thrown +# as Template::Exception objects via die(). +#------------------------------------------------------------------------ + +sub process { + my ($self, $template, $params, $localize) = @_; + my ($trim, $blocks) = @$self{ qw( TRIM BLOCKS ) }; + my (@compiled, $name, $compiled); + my ($stash, $tblocks, $error, $tmpout); + my $output = ''; + + $template = [ $template ] unless ref $template eq 'ARRAY'; + + $self->debug("process([ ", join(', '), @$template, ' ], ', + defined $params ? $params : '<no params>', ', ', + $localize ? '<localized>' : '<unlocalized>', ')') + if $self->{ DEBUG }; + + # fetch compiled template for each name specified + foreach $name (@$template) { + push(@compiled, $self->template($name)); + } + + if ($localize) { + # localise the variable stash with any parameters passed + $stash = $self->{ STASH } = $self->{ STASH }->clone($params); + } else { + # update stash with any new parameters passed + $self->{ STASH }->update($params); + $stash = $self->{ STASH }; + } + + eval { + foreach $name (@$template) { + $compiled = shift @compiled; + my $element = ref $compiled eq 'CODE' + ? { (name => (ref $name ? '' : $name), modtime => time()) } + : $compiled; + $stash->set('component', $element); + + unless ($localize) { + # merge any local blocks defined in the Template::Document + # into our local BLOCKS cache + @$blocks{ keys %$tblocks } = values %$tblocks + if UNIVERSAL::isa($compiled, 'Template::Document') + && ($tblocks = $compiled->blocks()); + } + + if (ref $compiled eq 'CODE') { + $tmpout = &$compiled($self); + } + elsif (ref $compiled) { + $tmpout = $compiled->process($self); + } + else { + $self->throw('file', + "invalid template reference: $compiled"); + } + + if ($trim) { + for ($tmpout) { + s/^\s+//; + s/\s+$//; + } + } + $output .= $tmpout; + } + }; + $error = $@; + + if ($localize) { + # ensure stash is delocalised before dying + $self->{ STASH } = $self->{ STASH }->declone(); + } + + $self->throw(ref $error + ? $error : (Template::Constants::ERROR_FILE, $error)) + if $error; + + return $output; +} + + +#------------------------------------------------------------------------ +# include($template, \%params) [% INCLUDE template var = val, ... %] +# +# Similar to process() above but processing the template in a local +# context. Any variables passed by reference to a hash as the second +# parameter will be set before the template is processed and then +# revert to their original values before the method returns. Similarly, +# any changes made to non-global variables within the template will +# persist only until the template is processed. +# +# Returns the output of processing the template. Errors are thrown +# as Template::Exception objects via die(). +#------------------------------------------------------------------------ + +sub include { + my ($self, $template, $params) = @_; + return $self->process($template, $params, 'localize me!'); +} + +#------------------------------------------------------------------------ +# insert($file) +# +# Insert the contents of a file without parsing. +#------------------------------------------------------------------------ + +sub insert { + my ($self, $file) = @_; + my ($prefix, $providers, $text, $error); + my $output = ''; + + my $files = ref $file eq 'ARRAY' ? $file : [ $file ]; + + $self->debug("insert([ ", join(', '), @$files, " ])") + if $self->{ DEBUG }; + + + FILE: foreach $file (@$files) { + my $name = $file; + + if ($^O eq 'MSWin32') { + # let C:/foo through + $prefix = $1 if $name =~ s/^(\w{2,})://o; + } + else { + $prefix = $1 if $name =~ s/^(\w+)://; + } + + if (defined $prefix) { + $providers = $self->{ PREFIX_MAP }->{ $prefix } + || return $self->throw(Template::Constants::ERROR_FILE, + "no providers for file prefix '$prefix'"); + } + else { + $providers = $self->{ PREFIX_MAP }->{ default } + || $self->{ LOAD_TEMPLATES }; + } + + foreach my $provider (@$providers) { + ($text, $error) = $provider->load($name, $prefix); + next FILE unless $error; + if ($error == Template::Constants::STATUS_ERROR) { + $self->throw($text) if ref $text; + $self->throw(Template::Constants::ERROR_FILE, $text); + } + } + $self->throw(Template::Constants::ERROR_FILE, "$file: not found"); + } + continue { + $output .= $text; + } + return $output; +} + + +#------------------------------------------------------------------------ +# throw($type, $info, \$output) [% THROW errtype "Error info" %] +# +# Throws a Template::Exception object by calling die(). This method +# may be passed a reference to an existing Template::Exception object; +# a single value containing an error message which is used to +# instantiate a Template::Exception of type 'undef'; or a pair of +# values representing the exception type and info from which a +# Template::Exception object is instantiated. e.g. +# +# $context->throw($exception); +# $context->throw("I'm sorry Dave, I can't do that"); +# $context->throw('denied', "I'm sorry Dave, I can't do that"); +# +# An optional third parameter can be supplied in the last case which +# is a reference to the current output buffer containing the results +# of processing the template up to the point at which the exception +# was thrown. The RETURN and STOP directives, for example, use this +# to propagate output back to the user, but it can safely be ignored +# in most cases. +# +# This method rides on a one-way ticket to die() oblivion. It does not +# return in any real sense of the word, but should get caught by a +# surrounding eval { } block (e.g. a BLOCK or TRY) and handled +# accordingly, or returned to the caller as an uncaught exception. +#------------------------------------------------------------------------ + +sub throw { + my ($self, $error, $info, $output) = @_; + local $" = ', '; + + # die! die! die! + if (UNIVERSAL::isa($error, 'Template::Exception')) { + die $error; + } + elsif (defined $info) { + die (Template::Exception->new($error, $info, $output)); + } + else { + $error ||= ''; + die (Template::Exception->new('undef', $error, $output)); + } + + # not reached +} + + +#------------------------------------------------------------------------ +# catch($error, \$output) +# +# Called by various directives after catching an error thrown via die() +# from within an eval { } block. The first parameter contains the errror +# which may be a sanitized reference to a Template::Exception object +# (such as that raised by the throw() method above, a plugin object, +# and so on) or an error message thrown via die from somewhere in user +# code. The latter are coerced into 'undef' Template::Exception objects. +# Like throw() above, a reference to a scalar may be passed as an +# additional parameter to represent the current output buffer +# localised within the eval block. As exceptions are thrown upwards +# and outwards from nested blocks, the catch() method reconstructs the +# correct output buffer from these fragments, storing it in the +# exception object for passing further onwards and upwards. +# +# Returns a reference to a Template::Exception object.. +#------------------------------------------------------------------------ + +sub catch { + my ($self, $error, $output) = @_; + + if (UNIVERSAL::isa($error, 'Template::Exception')) { + $error->text($output) if $output; + return $error; + } + else { + return Template::Exception->new('undef', $error, $output); + } +} + + +#------------------------------------------------------------------------ +# localise(\%params) +# delocalise() +# +# The localise() method creates a local copy of the current stash, +# allowing the existing state of variables to be saved and later +# restored via delocalise(). +# +# A reference to a hash array may be passed containing local variable +# definitions which should be added to the cloned namespace. These +# values persist until delocalisation. +#------------------------------------------------------------------------ + +sub localise { + my $self = shift; + $self->{ STASH } = $self->{ STASH }->clone(@_); +} + +sub delocalise { + my $self = shift; + $self->{ STASH } = $self->{ STASH }->declone(); +} + + +#------------------------------------------------------------------------ +# visit($blocks) +# +# Each Template::Document calls the visit() method on the context +# before processing itself. It passes a reference to the hash array +# of named BLOCKs defined within the document, allowing them to be +# added to the internal BLKSTACK list which is subsequently used by +# template() to resolve templates. +# from a provider. +#------------------------------------------------------------------------ + +sub visit { + my ($self, $blocks) = @_; + unshift(@{ $self->{ BLKSTACK } }, $blocks) +} + + +#------------------------------------------------------------------------ +# leave() +# +# The leave() method is called when the document has finished +# processing itself. This removes the entry from the BLKSTACK list +# that was added visit() above. For persistance of BLOCK definitions, +# the process() method (i.e. the PROCESS directive) does some extra +# magic to copy BLOCKs into a shared hash. +#------------------------------------------------------------------------ + +sub leave { + my $self = shift; + shift(@{ $self->{ BLKSTACK } }); +} + + +#------------------------------------------------------------------------ +# define_block($name, $block) +# +# Adds a new BLOCK definition to the local BLOCKS cache. $block may +# be specified as a reference to a sub-routine or Template::Document +# object or as text which is compiled into a template. Returns a true +# value (the $block reference or compiled block reference) if +# succesful or undef on failure. Call error() to retrieve the +# relevent error message (i.e. compilation failure). +#------------------------------------------------------------------------ + +sub define_block { + my ($self, $name, $block) = @_; + $block = $self->template(\$block) + || return undef + unless ref $block; + $self->{ BLOCKS }->{ $name } = $block; +} + + +#------------------------------------------------------------------------ +# define_filter($name, $filter, $is_dynamic) +# +# Adds a new FILTER definition to the local FILTER_CACHE. +#------------------------------------------------------------------------ + +sub define_filter { + my ($self, $name, $filter, $is_dynamic) = @_; + my ($result, $error); + $filter = [ $filter, 1 ] if $is_dynamic; + + foreach my $provider (@{ $self->{ LOAD_FILTERS } }) { + ($result, $error) = $provider->store($name, $filter); + return 1 unless $error; + $self->throw(&Template::Constants::ERROR_FILTER, $result) + if $error == &Template::Constants::STATUS_ERROR; + } + $self->throw(&Template::Constants::ERROR_FILTER, + "FILTER providers declined to store filter $name"); +} + + +#------------------------------------------------------------------------ +# reset() +# +# Reset the state of the internal BLOCKS hash to clear any BLOCK +# definitions imported via the PROCESS directive. Any original +# BLOCKS definitions passed to the constructor will be restored. +#------------------------------------------------------------------------ + +sub reset { + my ($self, $blocks) = @_; + $self->{ BLKSTACK } = [ ]; + $self->{ BLOCKS } = { %{ $self->{ INIT_BLOCKS } } }; +} + + +#------------------------------------------------------------------------ +# stash() +# +# Simple accessor methods to return the STASH values. This is likely +# to be called quite often so we provide a direct method rather than +# relying on the slower AUTOLOAD. +#------------------------------------------------------------------------ + +sub stash { + return $_[0]->{ STASH }; +} + + +#------------------------------------------------------------------------ +# define_vmethod($type, $name, \&sub) +# +# Passes $type, $name, and &sub on to stash->define_vmethod(). +#------------------------------------------------------------------------ +sub define_vmethod { + my $self = shift; + $self->stash->define_vmethod(@_); +} + + +#------------------------------------------------------------------------ +# debugging($command, @args, \%params) +# +# Method for controlling the debugging status of the context. The first +# argument can be 'on' or 'off' to enable/disable debugging, 'format' +# to define the format of the debug message, or 'msg' to generate a +# debugging message reporting the file, line, message text, etc., +# according to the current debug format. +#------------------------------------------------------------------------ + +sub debugging { + my $self = shift; + my $hash = ref $_[-1] eq 'HASH' ? pop : { }; + my @args = @_; + +# print "*** debug(@args)\n"; + if (@args) { + if ($args[0] =~ /^on|1$/i) { + $self->{ DEBUG_DIRS } = 1; + shift(@args); + } + elsif ($args[0] =~ /^off|0$/i) { + $self->{ DEBUG_DIRS } = 0; + shift(@args); + } + } + + if (@args) { + if ($args[0] =~ /^msg$/i) { + return unless $self->{ DEBUG_DIRS }; + my $format = $self->{ DEBUG_FORMAT }; + $format = $DEBUG_FORMAT unless defined $format; + $format =~ s/\$(\w+)/$hash->{ $1 }/ge; + return $format; + } + elsif ($args[0] =~ /^format$/i) { + $self->{ DEBUG_FORMAT } = $args[1]; + } + # else ignore + } + + return ''; +} + + +#------------------------------------------------------------------------ +# AUTOLOAD +# +# Provides pseudo-methods for read-only access to various internal +# members. For example, templates(), plugins(), filters(), +# eval_perl(), load_perl(), etc. These aren't called very often, or +# may never be called at all. +#------------------------------------------------------------------------ + +sub AUTOLOAD { + my $self = shift; + my $method = $AUTOLOAD; + my $result; + + $method =~ s/.*:://; + return if $method eq 'DESTROY'; + + warn "no such context method/member: $method\n" + unless defined ($result = $self->{ uc $method }); + + return $result; +} + + +#------------------------------------------------------------------------ +# DESTROY +# +# Stash may contain references back to the Context via macro closures, +# etc. This breaks the circular references. +#------------------------------------------------------------------------ + +sub DESTROY { + my $self = shift; + undef $self->{ STASH }; +} + + + +#======================================================================== +# -- PRIVATE METHODS -- +#======================================================================== + +#------------------------------------------------------------------------ +# _init(\%config) +# +# Initialisation method called by Template::Base::new() +#------------------------------------------------------------------------ + +sub _init { + my ($self, $config) = @_; + my ($name, $item, $method, $block, $blocks); + my @itemlut = ( + LOAD_TEMPLATES => 'provider', + LOAD_PLUGINS => 'plugins', + LOAD_FILTERS => 'filters' + ); + + # LOAD_TEMPLATE, LOAD_PLUGINS, LOAD_FILTERS - lists of providers + while (($name, $method) = splice(@itemlut, 0, 2)) { + $item = $config->{ $name } + || Template::Config->$method($config) + || return $self->error($Template::Config::ERROR); + $self->{ $name } = ref $item eq 'ARRAY' ? $item : [ $item ]; + } + + my $providers = $self->{ LOAD_TEMPLATES }; + my $prefix_map = $self->{ PREFIX_MAP } = $config->{ PREFIX_MAP } || { }; + while (my ($key, $val) = each %$prefix_map) { + $prefix_map->{ $key } = [ ref $val ? $val : + map { $providers->[$_] } + split(/\D+/, $val) ] + unless ref $val eq 'ARRAY'; +# print(STDERR "prefix $key => $val => [", +# join(', ', @{ $prefix_map->{ $key } }), "]\n"); + } + + # STASH + $self->{ STASH } = $config->{ STASH } || do { + my $predefs = $config->{ VARIABLES } + || $config->{ PRE_DEFINE } + || { }; + + # hack to get stash to know about debug mode + $predefs->{ _DEBUG } = ( ($config->{ DEBUG } || 0) + & &Template::Constants::DEBUG_UNDEF ) ? 1 : 0 + unless defined $predefs->{ _DEBUG }; + + Template::Config->stash($predefs) + || return $self->error($Template::Config::ERROR); + }; + + # compile any template BLOCKS specified as text + $blocks = $config->{ BLOCKS } || { }; + $self->{ INIT_BLOCKS } = $self->{ BLOCKS } = { + map { + $block = $blocks->{ $_ }; + $block = $self->template(\$block) + || return undef + unless ref $block; + ($_ => $block); + } + keys %$blocks + }; + + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + # RECURSION - flag indicating is recursion into templates is supported + # EVAL_PERL - flag indicating if PERL blocks should be processed + # TRIM - flag to remove leading and trailing whitespace from output + # BLKSTACK - list of hashes of BLOCKs defined in current template(s) + # CONFIG - original configuration hash + # EXPOSE_BLOCKS - make blocks visible as pseudo-files + # DEBUG_FORMAT - format for generating template runtime debugging messages + # DEBUG - format for generating template runtime debugging messages + + $self->{ RECURSION } = $config->{ RECURSION } || 0; + $self->{ EVAL_PERL } = $config->{ EVAL_PERL } || 0; + $self->{ TRIM } = $config->{ TRIM } || 0; + $self->{ BLKSTACK } = [ ]; + $self->{ CONFIG } = $config; + $self->{ EXPOSE_BLOCKS } = defined $config->{ EXPOSE_BLOCKS } + ? $config->{ EXPOSE_BLOCKS } + : 0; + + $self->{ DEBUG_FORMAT } = $config->{ DEBUG_FORMAT }; + $self->{ DEBUG_DIRS } = ($config->{ DEBUG } || 0) + & Template::Constants::DEBUG_DIRS; + $self->{ DEBUG } = defined $config->{ DEBUG } + ? $config->{ DEBUG } & ( Template::Constants::DEBUG_CONTEXT + | Template::Constants::DEBUG_FLAGS ) + : $DEBUG; + + return $self; +} + + +#------------------------------------------------------------------------ +# _dump() +# +# Debug method which returns a string representing the internal state +# of the context object. +#------------------------------------------------------------------------ + +sub _dump { + my $self = shift; + my $output = "[Template::Context] {\n"; + my $format = " %-16s => %s\n"; + my $key; + + foreach $key (qw( RECURSION EVAL_PERL TRIM )) { + $output .= sprintf($format, $key, $self->{ $key }); + } + foreach my $pname (qw( LOAD_TEMPLATES LOAD_PLUGINS LOAD_FILTERS )) { + my $provtext = "[\n"; + foreach my $prov (@{ $self->{ $pname } }) { + $provtext .= $prov->_dump(); +# $provtext .= ",\n"; + } + $provtext =~ s/\n/\n /g; + $provtext =~ s/\s+$//; + $provtext .= ",\n ]"; + $output .= sprintf($format, $pname, $provtext); + } + $output .= sprintf($format, STASH => $self->{ STASH }->_dump()); + $output .= '}'; + return $output; +} + + +1; + +__END__ + + +#------------------------------------------------------------------------ +# IMPORTANT NOTE +# This documentation is generated automatically from source +# templates. Any changes you make here may be lost. +# +# The 'docsrc' documentation source bundle is available for download +# from http://www.template-toolkit.org/docs.html and contains all +# the source templates, XML files, scripts, etc., from which the +# documentation for the Template Toolkit is built. +#------------------------------------------------------------------------ + +=head1 NAME + +Template::Context - Runtime context in which templates are processed + +=head1 SYNOPSIS + + use Template::Context; + + # constructor + $context = Template::Context->new(\%config) + || die $Template::Context::ERROR; + + # fetch (load and compile) a template + $template = $context->template($template_name); + + # fetch (load and instantiate) a plugin object + $plugin = $context->plugin($name, \@args); + + # fetch (return or create) a filter subroutine + $filter = $context->filter($name, \@args, $alias); + + # process/include a template, errors are thrown via die() + $output = $context->process($template, \%vars); + $output = $context->include($template, \%vars); + + # raise an exception via die() + $context->throw($error_type, $error_message, \$output_buffer); + + # catch an exception, clean it up and fix output buffer + $exception = $context->catch($exception, \$output_buffer); + + # save/restore the stash to effect variable localisation + $new_stash = $context->localise(\%vars); + $old_stash = $context->delocalise(); + + # add new BLOCK or FILTER definitions + $context->define_block($name, $block); + $context->define_filter($name, \&filtersub, $is_dynamic); + + # reset context, clearing any imported BLOCK definitions + $context->reset(); + + # methods for accessing internal items + $stash = $context->stash(); + $tflag = $context->trim(); + $epflag = $context->eval_perl(); + $providers = $context->templates(); + $providers = $context->plugins(); + $providers = $context->filters(); + ... + +=head1 DESCRIPTION + +The Template::Context module defines an object class for representing +a runtime context in which templates are processed. It provides an +interface to the fundamental operations of the Template Toolkit +processing engine through which compiled templates (i.e. Perl code +constructed from the template source) can process templates, load +plugins and filters, raise exceptions and so on. + +A default Template::Context object is created by the Template module. +Any Template::Context options may be passed to the Template new() +constructor method and will be forwarded to the Template::Context +constructor. + + use Template; + + my $template = Template->new({ + TRIM => 1, + EVAL_PERL => 1, + BLOCKS => { + header => 'This is the header', + footer => 'This is the footer', + }, + }); + +Similarly, the Template::Context constructor will forward all configuration +parameters onto other default objects (e.g. Template::Provider, Template::Plugins, +Template::Filters, etc.) that it may need to instantiate. + + $context = Template::Context->new({ + INCLUDE_PATH => '/home/abw/templates', # provider option + TAG_STYLE => 'html', # parser option + }); + +A Template::Context object (or subclass/derivative) can be explicitly +instantiated and passed to the Template new() constructor method as +the CONTEXT item. + + use Template; + use Template::Context; + + my $context = Template::Context->new({ TRIM => 1 }); + my $template = Template->new({ CONTEXT => $context }); + +The Template module uses the Template::Config context() factory method +to create a default context object when required. The +$Template::Config::CONTEXT package variable may be set to specify an +alternate context module. This will be loaded automatically and its +new() constructor method called by the context() factory method when +a default context object is required. + + use Template; + + $Template::Config::CONTEXT = 'MyOrg::Template::Context'; + + my $template = Template->new({ + EVAL_PERL => 1, + EXTRA_MAGIC => 'red hot', # your extra config items + ... + }); + +=head1 METHODS + +=head2 new(\%params) + +The new() constructor method is called to instantiate a Template::Context +object. Configuration parameters may be specified as a HASH reference or +as a list of (name =E<gt> value) pairs. + + my $context = Template::Context->new({ + INCLUDE_PATH => 'header', + POST_PROCESS => 'footer', + }); + + my $context = Template::Context->new( EVAL_PERL => 1 ); + +The new() method returns a Template::Context object (or sub-class) or +undef on error. In the latter case, a relevant error message can be +retrieved by the error() class method or directly from the +$Template::Context::ERROR package variable. + + my $context = Template::Context->new(\%config) + || die Template::Context->error(); + + my $context = Template::Context->new(\%config) + || die $Template::Context::ERROR; + +The following configuration items may be specified. + +=over 4 + + +=item VARIABLES, PRE_DEFINE + +The VARIABLES option (or PRE_DEFINE - they're equivalent) can be used +to specify a hash array of template variables that should be used to +pre-initialise the stash when it is created. These items are ignored +if the STASH item is defined. + + my $context = Template::Context->new({ + VARIABLES => { + title => 'A Demo Page', + author => 'Joe Random Hacker', + version => 3.14, + }, + }; + +or + + my $context = Template::Context->new({ + PRE_DEFINE => { + title => 'A Demo Page', + author => 'Joe Random Hacker', + version => 3.14, + }, + }; + + + + + +=item BLOCKS + +The BLOCKS option can be used to pre-define a default set of template +blocks. These should be specified as a reference to a hash array +mapping template names to template text, subroutines or Template::Document +objects. + + my $context = Template::Context->new({ + BLOCKS => { + header => 'The Header. [% title %]', + footer => sub { return $some_output_text }, + another => Template::Document->new({ ... }), + }, + }); + + + + + +=item TRIM + +The TRIM option can be set to have any leading and trailing whitespace +automatically removed from the output of all template files and BLOCKs. + +By example, the following BLOCK definition + + [% BLOCK foo %] + Line 1 of foo + [% END %] + +will be processed is as "\nLine 1 of foo\n". When INCLUDEd, the surrounding +newlines will also be introduced. + + before + [% INCLUDE foo %] + after + +output: + before + + Line 1 of foo + + after + +With the TRIM option set to any true value, the leading and trailing +newlines (which count as whitespace) will be removed from the output +of the BLOCK. + + before + Line 1 of foo + after + +The TRIM option is disabled (0) by default. + + + + + + +=item EVAL_PERL + +This flag is used to indicate if PERL and/or RAWPERL blocks should be +evaluated. By default, it is disabled and any PERL or RAWPERL blocks +encountered will raise exceptions of type 'perl' with the message +'EVAL_PERL not set'. Note however that any RAWPERL blocks should +always contain valid Perl code, regardless of the EVAL_PERL flag. The +parser will fail to compile templates that contain invalid Perl code +in RAWPERL blocks and will throw a 'file' exception. + +When using compiled templates (see +L<COMPILE_EXT|Template::Manual::Config/Caching_and_Compiling_Options> and +L<COMPILE_DIR|Template::Manual::Config/Caching_and_Compiling_Options>), +the EVAL_PERL has an affect when the template is compiled, and again +when the templates is subsequently processed, possibly in a different +context to the one that compiled it. + +If the EVAL_PERL is set when a template is compiled, then all PERL and +RAWPERL blocks will be included in the compiled template. If the +EVAL_PERL option isn't set, then Perl code will be generated which +B<always> throws a 'perl' exception with the message 'EVAL_PERL not +set' B<whenever> the compiled template code is run. + +Thus, you must have EVAL_PERL set if you want your compiled templates +to include PERL and RAWPERL blocks. + +At some point in the future, using a different invocation of the +Template Toolkit, you may come to process such a pre-compiled +template. Assuming the EVAL_PERL option was set at the time the +template was compiled, then the output of any RAWPERL blocks will be +included in the compiled template and will get executed when the +template is processed. This will happen regardless of the runtime +EVAL_PERL status. + +Regular PERL blocks are a little more cautious, however. If the +EVAL_PERL flag isn't set for the I<current> context, that is, the +one which is trying to process it, then it will throw the familiar 'perl' +exception with the message, 'EVAL_PERL not set'. + +Thus you can compile templates to include PERL blocks, but optionally +disable them when you process them later. Note however that it is +possible for a PERL block to contain a Perl "BEGIN { # some code }" +block which will always get run regardless of the runtime EVAL_PERL +status. Thus, if you set EVAL_PERL when compiling templates, it is +assumed that you trust the templates to Do The Right Thing. Otherwise +you must accept the fact that there's no bulletproof way to prevent +any included code from trampling around in the living room of the +runtime environment, making a real nuisance of itself if it really +wants to. If you don't like the idea of such uninvited guests causing +a bother, then you can accept the default and keep EVAL_PERL disabled. + + + + + + + +=item RECURSION + +The template processor will raise a file exception if it detects +direct or indirect recursion into a template. Setting this option to +any true value will allow templates to include each other recursively. + + + +=item LOAD_TEMPLATES + +The LOAD_TEMPLATE option can be used to provide a reference to a list +of Template::Provider objects or sub-classes thereof which will take +responsibility for loading and compiling templates. + + my $context = Template::Context->new({ + LOAD_TEMPLATES => [ + MyOrg::Template::Provider->new({ ... }), + Template::Provider->new({ ... }), + ], + }); + +When a PROCESS, INCLUDE or WRAPPER directive is encountered, the named +template may refer to a locally defined BLOCK or a file relative to +the INCLUDE_PATH (or an absolute or relative path if the appropriate +ABSOLUTE or RELATIVE options are set). If a BLOCK definition can't be +found (see the Template::Context template() method for a discussion of +BLOCK locality) then each of the LOAD_TEMPLATES provider objects is +queried in turn via the fetch() method to see if it can supply the +required template. Each provider can return a compiled template, an +error, or decline to service the request in which case the +responsibility is passed to the next provider. If none of the +providers can service the request then a 'not found' error is +returned. The same basic provider mechanism is also used for the +INSERT directive but it bypasses any BLOCK definitions and doesn't +attempt is to parse or process the contents of the template file. + +This is an implementation of the 'Chain of Responsibility' +design pattern as described in +"Design Patterns", Erich Gamma, Richard Helm, Ralph Johnson, John +Vlissides), Addision-Wesley, ISBN 0-201-63361-2, page 223 +. + +If LOAD_TEMPLATES is undefined, a single default provider will be +instantiated using the current configuration parameters. For example, +the Template::Provider INCLUDE_PATH option can be specified in the Template::Context configuration and will be correctly passed to the provider's +constructor method. + + my $context = Template::Context->new({ + INCLUDE_PATH => '/here:/there', + }); + + + + + +=item LOAD_PLUGINS + +The LOAD_PLUGINS options can be used to specify a list of provider +objects (i.e. they implement the fetch() method) which are responsible +for loading and instantiating template plugin objects. The +Template::Content plugin() method queries each provider in turn in a +"Chain of Responsibility" as per the template() and filter() methods. + + my $context = Template::Context->new({ + LOAD_PLUGINS => [ + MyOrg::Template::Plugins->new({ ... }), + Template::Plugins->new({ ... }), + ], + }); + +By default, a single Template::Plugins object is created using the +current configuration hash. Configuration items destined for the +Template::Plugins constructor may be added to the Template::Context +constructor. + + my $context = Template::Context->new({ + PLUGIN_BASE => 'MyOrg::Template::Plugins', + LOAD_PERL => 1, + }); + + + + + +=item LOAD_FILTERS + +The LOAD_FILTERS option can be used to specify a list of provider +objects (i.e. they implement the fetch() method) which are responsible +for returning and/or creating filter subroutines. The +Template::Context filter() method queries each provider in turn in a +"Chain of Responsibility" as per the template() and plugin() methods. + + my $context = Template::Context->new({ + LOAD_FILTERS => [ + MyTemplate::Filters->new(), + Template::Filters->new(), + ], + }); + +By default, a single Template::Filters object is created for the +LOAD_FILTERS list. + + + +=item STASH + +A reference to a Template::Stash object or sub-class which will take +responsibility for managing template variables. + + my $stash = MyOrg::Template::Stash->new({ ... }); + my $context = Template::Context->new({ + STASH => $stash, + }); + +If unspecified, a default stash object is created using the VARIABLES +configuration item to initialise the stash variables. These may also +be specified as the PRE_DEFINE option for backwards compatibility with +version 1. + + my $context = Template::Context->new({ + VARIABLES => { + id => 'abw', + name => 'Andy Wardley', + }, + }; + + + +=item DEBUG + +The DEBUG option can be used to enable various debugging features +of the Template::Context module. + + use Template::Constants qw( :debug ); + + my $template = Template->new({ + DEBUG => DEBUG_CONTEXT | DEBUG_DIRS, + }); + +The DEBUG value can include any of the following. Multiple values +should be combined using the logical OR operator, '|'. + +=over 4 + +=item DEBUG_CONTEXT + +Enables general debugging messages for the +L<Template::Context|Template::Context> module. + +=item DEBUG_DIRS + +This option causes the Template Toolkit to generate comments +indicating the source file, line and original text of each directive +in the template. These comments are embedded in the template output +using the format defined in the DEBUG_FORMAT configuration item, or a +simple default format if unspecified. + +For example, the following template fragment: + + + Hello World + +would generate this output: + + ## input text line 1 : ## + Hello + ## input text line 2 : World ## + World + + +=back + + + + + +=back + +=head2 template($name) + +Returns a compiled template by querying each of the LOAD_TEMPLATES providers +(instances of Template::Provider, or sub-class) in turn. + + $template = $context->template('header'); + +On error, a Template::Exception object of type 'file' is thrown via +die(). This can be caught by enclosing the call to template() in an +eval block and examining $@. + + eval { + $template = $context->template('header'); + }; + if ($@) { + print "failed to fetch template: $@\n"; + } + +=head2 plugin($name, \@args) + +Instantiates a plugin object by querying each of the LOAD_PLUGINS +providers. The default LOAD_PLUGINS provider is a Template::Plugins +object which attempts to load plugin modules, according the various +configuration items such as PLUGIN_BASE, LOAD_PERL, etc., and then +instantiate an object via new(). A reference to a list of constructor +arguments may be passed as the second parameter. These are forwarded +to the plugin constructor. + +Returns a reference to a plugin (which is generally an object, but +doesn't have to be). Errors are thrown as Template::Exception objects +of type 'plugin'. + + $plugin = $context->plugin('DBI', 'dbi:msql:mydbname'); + +=head2 filter($name, \@args, $alias) + +Instantiates a filter subroutine by querying the LOAD_FILTERS providers. +The default LOAD_FILTERS providers is a Template::Filters object. +Additional arguments may be passed by list reference along with an +optional alias under which the filter will be cached for subsequent +use. The filter is cached under its own $name if $alias is undefined. +Subsequent calls to filter($name) will return the cached entry, if +defined. Specifying arguments bypasses the caching mechanism and +always creates a new filter. Errors are thrown as Template::Exception +objects of typre 'filter'. + + # static filter (no args) + $filter = $context->filter('html'); + + # dynamic filter (args) aliased to 'padright' + $filter = $context->filter('format', '%60s', 'padright'); + + # retrieve previous filter via 'padright' alias + $filter = $context->filter('padright'); + +=head2 process($template, \%vars) + +Processes a template named or referenced by the first parameter and returns +the output generated. An optional reference to a hash array may be passed +as the second parameter, containing variable definitions which will be set +before the template is processed. The template is processed in the current +context, with no localisation of variables performed. Errors are thrown +as Template::Exception objects via die(). + + $output = $context->process('header', { title => 'Hello World' }); + +=head2 include($template, \%vars) + +Similar to process() above, but using localised variables. Changes made to +any variables will only persist until the include() method completes. + + $output = $context->include('header', { title => 'Hello World' }); + +=head2 throw($error_type, $error_message, \$output) + +Raises an exception in the form of a Template::Exception object by +calling die(). This method may be passed a reference to an existing +Template::Exception object; a single value containing an error message +which is used to instantiate a Template::Exception of type 'undef'; or +a pair of values representing the exception type and info from which a +Template::Exception object is instantiated. e.g. + + $context->throw($exception); + $context->throw("I'm sorry Dave, I can't do that"); + $context->throw('denied', "I'm sorry Dave, I can't do that"); + +The optional third parameter may be a reference to the current output +buffer. This is then stored in the exception object when created, +allowing the catcher to examine and use the output up to the point at +which the exception was raised. + + $output .= 'blah blah blah'; + $output .= 'more rhubarb'; + $context->throw('yack', 'Too much yacking', \$output); + +=head2 catch($exception, \$output) + +Catches an exception thrown, either as a reference to a +Template::Exception object or some other value. In the latter case, +the error string is promoted to a Template::Exception object of +'undef' type. This method also accepts a reference to the current +output buffer which is passed to the Template::Exception constructor, +or is appended to the output buffer stored in an existing +Template::Exception object, if unique (i.e. not the same reference). +By this process, the correct state of the output buffer can be +reconstructed for simple or nested throws. + +=head2 define_block($name, $block) + +Adds a new block definition to the internal BLOCKS cache. The first +argument should contain the name of the block and the second a reference +to a Template::Document object or template sub-routine, or template text +which is automatically compiled into a template sub-routine. Returns +a true value (the sub-routine or Template::Document reference) on +success or undef on failure. The relevant error message can be +retrieved by calling the error() method. + +=head2 define_filter($name, \&filter, $is_dynamic) + +Adds a new filter definition by calling the store() method on each of +the LOAD_FILTERS providers until accepted (in the usual case, this is +accepted straight away by the one and only Template::Filters +provider). The first argument should contain the name of the filter +and the second a reference to a filter subroutine. The optional +third argument can be set to any true value to indicate that the +subroutine is a dynamic filter factory. Returns a true value or +throws a 'filter' exception on error. + +=head2 localise(\%vars) + +Clones the stash to create a context with localised variables. Returns a +reference to the newly cloned stash object which is also stored +internally. + + $stash = $context->localise(); + +=head2 delocalise() + +Restore the stash to its state prior to localisation. + + $stash = $context->delocalise(); + +=head2 visit(\%blocks) + +This method is called by Template::Document objects immediately before +they process their content. It is called to register any local BLOCK +definitions with the context object so that they may be subsequently +delivered on request. + +=head2 leave() + +Compliment to visit(), above. Called by Template::Document objects +immediately after they process their content. + +=head2 reset() + +Clears the local BLOCKS cache of any BLOCK definitions. Any initial set of +BLOCKS specified as a configuration item to the constructor will be reinstated. + +=head2 AUTOLOAD + +An AUTOLOAD method provides access to context configuration items. + + $stash = $context->stash(); + $tflag = $context->trim(); + $epflag = $context->eval_perl(); + ... + +=head1 AUTHOR + +Andy Wardley E<lt>abw@andywardley.comE<gt> + +L<http://www.andywardley.com/|http://www.andywardley.com/> + + + + +=head1 VERSION + +2.81, distributed as part of the +Template Toolkit version 2.10, released on 24 July 2003. + +=head1 COPYRIGHT + + Copyright (C) 1996-2003 Andy Wardley. All Rights Reserved. + Copyright (C) 1998-2002 Canon Research Centre Europe Ltd. + +This module is free software; you can redistribute it and/or +modify it under the same terms as Perl itself. + +=head1 SEE ALSO + +L<Template|Template>, L<Template::Document|Template::Document>, L<Template::Exception|Template::Exception>, L<Template::Filters|Template::Filters>, L<Template::Plugins|Template::Plugins>, L<Template::Provider|Template::Provider>, L<Template::Service|Template::Service>, L<Template::Stash|Template::Stash> |