NAME
Text::MagicTemplate - magic merger of runtime values with template
SYNOPSIS
- the template
-
The template file 'my_template_file'... (this example uses plain text for clarity, but MagicTemplate 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: {'my_included_file'}
... and another template file 'my_included_file' that will be included...
this is the included file 'my_included_file' that contains a label: {a_scalar}
- the code
-
... some variables and subroutines already defined somewhere in your code...
$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 3 magic lines...
use Text::MagicTemplate; $mt = new Text::MagicTemplate; $mt->print( 'my_template_file' );
- the output
-
(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.
DESCRIPTION
Text::MagicTemplate 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. Usually no need to assign values to the object. Template outputs are linked to runtime values by their identifiers, which are added to the template in the form of simple labels or blocks 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 what value and where to put it. Nothing else is required, no complicated new syntax to learn!
On the other side, the programmer has just to define variables and subroutines as usual and their values will appear in the right place with 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 (see "How it works").
Features
There are several "similar" modules, so why yet another one? These features are the answer:
Simple, flexible and powerful to use
In most cases, you will have just to use
new()
andprint(template)
methods, without having to pass any other value to the object: it will do the right job for you.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 Text::MagicTemplate recognizes labels in the form of simple identifiers surrounded by braces ({my_identifier}), but you can easily use a different template syntax (see "Redefine Syntax").
Automatic or manual lookup of values
By default, Text::MagicTemplate compares any label identifier defined in your template with any variable or subroutine identifier defined in the caller namespace. However, you can explicitly define the lookup otherwise, by passing to the
new()
method a list of package namespaces and/or hash references.Support for unlimited nested included templates
Sometimes it can be useful to split a template into differents files. No nesting limit when including files into files. (see "Include a file")
Branching support
You can easily create simple or complex if-elsif-else conditions to print just the blocks linked with the true conditions (see "Setup an if-else condition" and "Setup a switch condition")
Unlimited nested loops support
When you need complex outputs you can build any immaginable nested loop, even mixed with control switches and included templates (see "Build a loop" and "Build a nested loop")
Placeholders and simulated areas support
Placeholders and simulated areas can help in designing the template for a more consistent preview of the final output. (see "Setup placeholders" and "Setup simulated areas")
Template block management
When you need complex management of templates files, you have a couple of static methods to extract, mix and set blocks inside any template. (see
get_block()
andset_block()
static methods)Simple to maintain
Change your code and Text::MagicTemplate will change its behaviour accordingly. In most cases you will not have to reconfigure, either the object, or the template.
Very lightweight
MagicTemplate.pm doesn't use any other module and its code is just about 100 lines (easier to write that this documentation :-) )
How it works
The Text::MagicTemplate object is a blessed array containing locations that it uses to look up identifiers. This array can store package namespaces or references to hashes. If no parameter is passed to the constructor, the package namespace of the caller will be used by default.
Text::MagicTemplate compares any label identifier defined in your template with any variable or subroutine identifier (for package locations) or key (for hash locations) defined in each stored location. When it find a match, it looks for the value. If the value represents a CODE, it will be executed to achieve a returned value; if the value represents a reference, it will be dereferenced until the actual value will be achieved.
The TYPE of the found value will determine the behaviour of MagicTemplate:
An UNDEF value will delete label or block.
A SCALAR type will replace label or block with the scalar value.
A HASH type will set that HASH as a temporary location for the lookup of the block. Text::MagicTemplate first uses that location to look up the identifiers contained in the block; then, if unsuccessful, it will search into the stored locations.
An ARRAY type will generate a loop, merging each value in the array with the the block and replacing the block with the sequence of the outputs.
Examples:
The same template: '{block}|before-{label}-after|{/block}'
... with these values... ...produce these outputs
---------------------------------------------------------------------------
$label = 'THE VALUE'; >
$block = undef;
---------------------------------------------------------------------------
$label = 'THE VALUE'; > NEW CONTENT
$block = 'NEW CONTENT';
---------------------------------------------------------------------------
$label = 'THE VALUE'; > |before-THE VALUE-after|
$block = {};
---------------------------------------------------------------------------
$label = undef; > |before--after|
$block = {};
---------------------------------------------------------------------------
$label = 'THE VALUE'; > |before-NEW VALUE-after|
%block = (label=>'NEW VALUE');
---------------------------------------------------------------------------
$label = 'THE VALUE'; > |before-NEW VALUE-after|
$block = {label=>'NEW VALUE'};
---------------------------------------------------------------------------
$label = 'THE VALUE'; > NEW CONTENT|before-THE VALUE-after|
@block = ('NEW CONTENT', |before-NEW VALUE-after|
{},
{label=>'NEW VALUE'});
---------------------------------------------------------------------------
$label = 'THE VALUE'; > NEW CONTENT|before-THE VALUE-after|
$block = ['NEW CONTENT', |before-NEW VALUE-after|
{},
{label=>'NEW VALUE'}];
---------------------------------------------------------------------------
Different combinations of values, labels and blocks can easily produce complex ouputs. See the "HOW TO..." section in this documentation.
METHODS
- new ( [lookup_list] )
-
The constructor method creates a blessed array reference. This array contains locations where the methods will look up the identifiers present in the template. Locations are packages or hashes: with packages locations, the lookup is done with all the IDENTIFIERS defined in the package namespace, with hash locations it is done with the KEYS existing in the hash.
If you want to make available all the identifiers of your current package, just call the constructor without parameters:
# default lookup in the caller package $mt = new Text::MagicTemplate ; # same thing but explicit $mt = new Text::MagicTemplate __PACKAGE__ ;
If you want to keep unavailable some variable or subroutine from the template, you can pass just the reference of some hash containing just the identifiers used in the template. This is the best method to use the module IF you allow untrustly people to edit the template AND if you have any potentially dangerous subroutine in your code. (see "CAVEATS").
# lookup in %my_hash only $mt = new Text::MagicTemplate \%my_hash ;
You can also define an arbitrary list of packages and/or references to hashes as the lookup: the precedence of the lookup will be inherited from the order of the items passed, and the first found mach will return the value.
# lookup in several location $mt = new Text::MagicTemplate \%my_hash, 'main', \%my_other_hash ;
In this example, the lookup will be done in
%my_hash
first - if unsuccessful - it will be done in the'main' package
and - if unsuccessful - it will be done in%my_other_hash
. - output ( template [, identifier] )
-
This method merges the runtime values with the template and returns a reference to the output. It accept one template parameter that can be a string or a reference. If it is a string it is considered to be a path to a template file; if it is a reference, it is considered to be a reference to a template content. If any identifier is passed, it returns a reference to the output of just that block.
# template is a path $output = $mt->output( '/temp/template_file.html' ); # template is a reference $output = $mt->output( $tpl_content ); # this limits the output to just 'my_block_identifier' $my_block_output = $mt->output( $tpl_content, 'my_block_identifier' );
- print ( template [, identifier] )
-
This method merges the runtime values with the template and prints the output. The template can be a string or a reference. If it is a string it is considered to be a path to a template file; if it is a reference, it is considered to be a reference to a template content. If any identifier is passed, it prints the output of just that block.
# template is a path $mt->print( '/temp/template_file.html' ); # template is a reference $mt->print( $tpl_content ); # this limits the output to just 'my_block_identifier' $mt->print( $tpl_content, 'my_block_identifier' );
STATIC METHODS
To use any of the static method in this section you must load the module:
use Text::MagicTemplate;
and use them directly by using the class name:
CLASS_NAME->static_method_identifier(parameters)
Examples
... Text::MagicTemplate->get_block ( ....... )
... Text::MagicTemplate->set_block ( ....... )
... Text::MagicTemplate::HTML->get_block ( ....... )
... myCustomSyntax->set_block ( ....... )
... Text::MagicTemplate::HTML->no_code_execution
...
- syntax ( START_MARKER, END_MARKER_ID, END_MARKER )
-
This static method redefine the syntax for the class. This is the basic structure of a generic label:
+--------------+---------------+------------+------------+ | START_MARKER | END_MARKER_ID | IDENTIFIER | END_MARKER | +--------------+---------------+------------+------------+
where END_MARKER_ID is exclusively used to produce an end label, and is omitted in every other case.
These are the default values of the markers that define the label.
START_MARKER: { END_MARKER_ID: / END_MARKER: }
You can redefine them using this static method:
Text::MagicTemplate->syntax qw|__ END_ __|;
where '__' is the START_MARKER, 'END_' is the END_MARKER_ID and '__' is the END_MARKER;
See "Redefine Syntax" to have more details.
- no_code_execution ()
-
This static method disables the execution of subroutines from your code. (see "CAVEATS" for details).
- code_execution ()
-
This static method enables the execution of subroutines from your code. Since this feature is enabled by default, you need it only if you have used no_code_execution() static method before.
- get_block ( template [, identifier] )
-
This static method returns a reference to the template content or to a block inside the template, without merging values. The template can be a string or a reference. If it is a string it is considered to be a path to a template file; if it is a reference, it is considered to be a reference to a template content. If any identifier is passed, it returns just that block.
# this returns a ref to the whole template content $tpl_content = Text::MagicTemplate->get_block ( '/temp/template_file.html' ); # this return a ref to the 'my_block_identifier' block $tpl_block = Text::MagicTemplate->get_block ( '/temp/template_file.html', 'my_block_identifier' ); # same thing passing a reference $tpl_block = Text::MagicTemplate->get_block ( $tpl_content, 'my_block_identifier' );
- set_block ( template, identifier, new_content )
-
This static method sets the content of the block (or blocks) identifier inside a template - without merging values - and returns a reference to the changed template. The template can be a string or a reference. If it is a string it is considered to be a path to a template file; if it is a reference, it is considered to be a reference to a template content. New_content can be a reference to the content or the content itself.
# this return a ref to the 'my_block' block $new_content = Text::MagicTemplate->get_block ( '/temp/template_file_2.html', 'my_block' ); # this returns a ref to the changed template content, $changed_content = Text::MagicTemplate->set_block ( '/temp/template_file.html', 'my_old_block', $new_content );
CONSTANTS
Text::MagicTemplates utilizes constants to change some behaviour.
- $Text::MagicTemplate::ID_OUTPUT
-
A true value of this constant will generate a pretty formatted output of only the identifiers present in the template. Thus the programmer can pass a description of each label and block within a template to a designer.
To activate this behaviour you must set the constant to true at compile time and before the use of the module. Example:
BEGIN { $Text::MagicTemplate::ID_OUTPUT++ } use Text::MagicTemplate::HTML;
HOW TO...
Please, carefully read and understand section "How it works", before reading this section.
Include a file
To include a file in a template just set a label with the pathname of the file as identifier, surrounded by quotes:
{'/temp/footer.html'}
The file will be included in place of the label and if it is a template, it will be processed as usual.
Redefine Syntax
- by using a prebuilt syntax module
-
The standard installation comes with a HTML friendly syntax module that implements a HTML-comment-like syntax. If your output is an HTML text - or just because you prefer that particular look - you can use it instead of using the standard module. Read the documentation of Text::MagicTemplate::HTML to know the details.
- by adding one line to your code
-
To redefine syntax use the
syntax()
static method, directly in your code:use Text::MagicTemplate; Text::MagicTemplate->syntax qw|{ / }|; # redefine the markers as needed
- by subclassing Text::MagicTemplate class
-
If you need some custom and permanent solution you can subclass the
Text::MagicTemplate
class. As an added benefit, this method allows you to use different syntaxes in the same script.This is an example that explains you how to write and use a custom syntax module myCustomSyntax.pm (obviously you can use the class name you prefer).
Redefine the markers and save this code as file myCustomSyntax.pm
package myCustomSyntax; # choose a meaningful package namespace :-) use Text::MagicTemplate; push @ISA, qw(Text::MagicTemplate); __PACKAGE__->syntax qw|__ END_ __|; # redefine these values as needed 1;
Use it by loading the module as usual:
use myCustomSyntax;
and use methods as usual...
$mt = new myCustomSyntax; $mt->print ( 'my_custom_template_file' );
This sintax would work with this block labeled 'my_identifier':
__my_identifier__ content of block __END_my_identifier__
If you write some custom syntax module - useful for any particular output - please, let me know.
Setup a template
A quick way to setup a template in 4 simple steps is the following:
- 1 Prepare an output
-
Prepare a complete output as your code could print. Place all the static items of your output where they should go, place placeholders (any runtime value that your code would supply) where they should go and format everything as you want
- 1 Choose names
-
Choose meaningful names (or variables and subroutines names if you already have a code) for labels and blocks
- 1 Insert single labels
-
Find the dynamic items in the template and replace them with a label, or if you want to keep them as visible placeholders, transform each one of them into a block
- 1 Define blocks
-
If you have any area that will be repeated by a loop or that will be printed just under certain conditions transform it into a block.
Setup placeholders
These are a couple of templates that use a HTML friendly sintax (implemented in Text::MagicTemplate::HTML). The output will be the same for both templates, with or without placeholders: the difference is the way you can look at the template.
- template without placeholders
-
<p><hr>Name: <b style="color:blue"><!--{name}--></b><br> Surname: <b style="color:blue"><!--{surname}--></b><hr></p>
This is what you would see in a WYSIWYG editor: (you should be using a browser to see the example below this line)
- 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
This is what you would see in a WYSIWYG editor: I<(you should be using a browser to see the example below this line)> Setup simulated areas
Surname: SmithIf 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} Setup labeled areas
Build a loop
- the template
- A loop is represented by a block, usually containing labels: A loop: {my_loop}------------------- Date: {date} Operation: {operation} {/my_loop}------------------- 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' } ] ; the output
Build a nested loop
- 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}------------------- Note that the block I<'details'> is nested into the block I<'my_nested_loop'>. 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'} ] } ] ; Note that the value of the keys I<'details'> are a reference to an array of hashes. the output
Setup an if-else condition
- 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} 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={}; the output
Setup a switch condition
- 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} 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' }; $mt = new Text::MagicTemplate \%my_hash; the output
Prepare the identifiers description list
- 1 Add the following BEGIN block anywhere before the use statement:
- BEGIN { $Text::MagicTemplate::ID_OUTPUT++ } use Text::MagicTemplate::HTML; 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. 3 Add the description
CAVEATS
Read this section just in case you are planning to allow untrustworthy people to edit the template. Allowing untrustworthy people to edit the template
F does not use any eval() statement, it just do a recursive search and replace with the content of the template. Besides, the allowed characters for identifiers are only alphanumeric C<(\w+)>, so even dealing with tainted templates should not raise any security problem that you wouldn't have in your program itself. However, since the module is just about 100 lines of code, you should consider to analise it directly. If you do this, please send me some feedback. Avoid unwanted executions
- potentially unsafe code
- sub my_potentially_dangerous_sub { unlink 'database_file' }; $name = 'John'; $surname = 'Smith'; $mt = new Text::MagicTemplate ; # automatic lookup in __PACKAGE__ namespace 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'. code with no_code_execution
- Use the same code, but add this line: Text::MagicTemplate->no_code_execution; ... so Text::MagicTemplate will execute no subroutines Note that if you use a subclass - as C - you must use the correct subclass name: Text::MagicTemplate::HTML->no_code_execution; # or myCustomSyntax->no_code_execution; code with restricted lookups
GLOSSARY
- block
- A I is a chunk of text in a I delimited by (and including) a I and an I: +-------+-------------------+------------+ | LABEL | CONTENT | END_LABEL | +-------+-------------------+------------+ Example: B<{my_identifier} content of the block {/my_identifier}> where C<'{my_identifier}'> is the LABEL, C<' content of the block '> is the CONTENT and C<'{/my_identifier}'> is the END_LABEL. end label
- An I is a string in the form of: +--------------+---------------+------------+------------+ | START_MARKER | END_MARKER_ID | IDENTIFIER | END_MARKER | +--------------+---------------+------------+------------+ Example of end label : B<{/my_identifier}> where C<'{'> is the START_MARKER, C<'/'> is the END_MARKER_ID, C<'my_identifier'> is the IDENTIFIER, and C<'}'> is the END_MARKER. identifier
- A I is a alphanumeric name C<(\w+)> that represents (and usually matches) a variable or a subroutine identifier of your code. include label
- An I is a I used to include a I file. The I must be surrounded by single or double quotes and should be a valid path. Example: B<{'/templates/temp_file.html'}> label
- A I is a string in the form of: +--------------+------------+------------+ | START_MARKER | IDENTIFIER | END_MARKER | +--------------+------------+------------+ Example: B<{my_identifier}> where C<'{'> is the START_MARKER, C<'my_identifier'> is the IDENTIFIER and C<'}'> is the END_MARKER. locations
- I are packages namespaces or hashes used to perform lookups. lookup
- The action performed by a method to compare label I with code identifier (variable identifiers, subroutine identifiers and hash keys). nested block
- A I is a I contained in another I: +----------------------+ | CONTAINER_BLOCK | | +----------------+ | | | NESTED_BLOCK | | | +----------------+ | +----------------------+ Example: {my_container_identifier} B<{my_nested_identifier} content of the block {/my_nested_identifier}> {/my_container_identifier} where all the above is the CONTAINER_BLOCK and C<'{my_nested_identifier} content of the block {/my_nested_identifier}'> is the NESTED_BLOCK. output
- The I is the result of the merger of runtimes data with a template template
SUPPORT and FEEDBACK
I would like to have just a line of feedback from everybody who tries or actually uses this module. Feel free to write me any comment, suggestion or request. AUTHOR
Domizio Demichelis, . COPYRIGHT
Copyright (c)2002 Domizio Demichelis. All Rights Reserved. This module is free software; it may be used freely and redistributed for free providing this copyright header remains part of the module. You may not charge for the redistribution of this module. Selling this code without Domizio Demichelis' written permission is expressly forbidden. This module may not be modified without first notifying the author (this is to enable me to track modifications). In all cases the copyright header should remain fully intact in all modifications. This code is provided on an "As Is'' basis, without warranty, expressed or implied. The author disclaims all warranties with regard to this software, including all implied warranties of merchantability and fitness, in no event shall the author, Domizio Demichelis be liable for any special, indirect or consequential damages or any damages whatsoever including but not limited to loss of use, data or profits. By using this module you agree to indemnify Domizio Demichelis from any liability that might arise from it is use. Should this code prove defective, you assume the cost of any and all necessary repairs, servicing, correction and any other costs arising directly or indrectly from it is use. The copyright notice must remain fully intact at all times. Use of this program or its output constitutes acceptance of these terms.
6 POD Errors
The following errors were encountered while parsing the POD:
- Around line 367:
You forgot a '=back' before '=head2'
- Around line 437:
You forgot a '=back' before '=head2'
- Around line 441:
=over without closing =back
- Around line 454:
'=end' without a target? (Should be "=end html")
- Around line 469:
'=end' without a target? (Should be "=end html")
- Around line 471:
=back without =over