# -------------------------------------------------------------------------------------
# lib::Template
# -------------------------------------------------------------------------------------
#
#       Author : Jean-Michel Hiver (jhiver@cisedi.com).
#    Copyright : (c) Jean-Michel Hiver, 2001.
# 
#      Unauthorized modification, use, reuse, distribution or redistribution
#      of this module is stricly forbidden.
#
#    Description:
#
#      An extensible yet simple module to enable the use of templates in CGI
#      scripts and other programs. This module defines no tags, but is used as
#      a tokenizer and launches appropriates tags when necessary.
#
#      See the POD documentation for more details.
#
# -------------------------------------------------------------------------------------
package lib::Template;
use strict;
use lib::Config;
use lib::Exception;
use vars qw /$CONFIG $LEFT_TOKEN $RIGHT_TOKEN $TOKEN_REGEX $STASH $BASE_DIR/;


##
# $class->init;
# -------------
#   Initializes the lib::Template class. Defines LEFT_TOKEN, RIGHT_TOKEN,
#   and BASE_DIR according to the config file.
##
sub init
{
    my $class = shift;
    $CONFIG      = new lib::Config ('lib.Template');
    $LEFT_TOKEN  = quotemeta ($CONFIG->get ('LEFT_TOKEN'));
    $RIGHT_TOKEN = quotemeta ($CONFIG->get ('RIGHT_TOKEN'));
    $BASE_DIR    = $CONFIG->get ('BASE_DIR');
    $TOKEN_REGEX = $LEFT_TOKEN . '.*?' . $RIGHT_TOKEN;
    $STASH = {};
}


##
# $class->new ( file => 'myfile.tmpl', hash => $myhash );
# -------------------------------------------------------
#   Returns a new lib::Template object.
#   file is the template to parse, and hash is the structure
#   to apply to the template.
##
sub new
{
    my $class = shift;
    $class    = ref $class || $class;
    my $self  = bless { @_ }, $class;
    unless (exists $self->{data})
    {
	if (exists $self->{file})
	{
	    my $file = $BASE_DIR . '/' . $self->{file};
	    open FP, "<$file" or throw (new lib::Exception ( code => 'CANNOT_OPEN_FILE', info => $file ) );
	    $self->{data} = join '', <FP>;
	    close FP;
	}
	else
	{
	    throw (new lib::Exception ( code => 'TEMPLATE_HAS_NO_DATA', info => $self ) );
	}
    }
    return $self;
}


##
# $self->parse;
# -------------
#   Returns the current lib::Template data replaced with a correct set of values.
##
sub parse
{
    my $self = shift;
    my $tokens = $self->_tokenize;
    my $res = [];
    
    while (@{$tokens})
    {
	my $token = $tokens->[0];
	if ($token =~ /$TOKEN_REGEX/os)
	{
	    my ($token_name) = $token =~ /$LEFT_TOKEN\s*([A-Za-z0-9_-]+)\s*.*$RIGHT_TOKEN/os;
	    my $token_class  = $CONFIG->get ($token_name);
	    if (defined $token_class)
	    {
		my $token_file = $token_class;
		$token_file =~ s/::/\//g;
		$token_file .= '.pm';
		
		require $token_file;
		import  $token_class;
		my $object = $token_class->new ( hash   => $self->{hash},
						 result => $res,
						 tokens => $tokens,
						 class  => ref $self );
		$object->main;
	    }
	    else
	    {
		my $tmp = shift (@{$tokens});
		push @{$res}, $tmp if (defined $tmp);
	    }
	}
	else
	{
	    my $tmp = shift (@{$tokens});
	    push @{$res}, $tmp if (defined $tmp);
	}
    }
    return join '', @{$res};
}


sub out { print shift->parse }


##
# $self->_tokenize;
# -----------------
#   Returns the data to parse as a list of tokens:
#   ( 'some text', '<% a_tag %>', 'some more text', '<% end-a_tag %>' etc.
##
sub _tokenize
{
    my $self  = shift;
    my $data  = $self->{data};
    
    my @tags  = ();
    (defined $data) and (@tags = $data =~ /($TOKEN_REGEX)/gs);
    
    my @split = ();
    (defined $data) and (@split = split /$TOKEN_REGEX/s, $data);
    
    my $tokens = [];
    while (@split)
    {
	push @{$tokens}, shift @split;
	push @{$tokens}, shift @tags if (@tags);
    }
    
    return $tokens;
}


1;





=head1 SYNOPSIS

------------------------------------------------------------------------------------
lib::Template
------------------------------------------------------------------------------------
      Author : Jean-Michel Hiver (jhiver@cisedi.com).
   Copyright : (c) Jean-Michel Hiver, 2001.

     Unauthorized modification, use, reuse, distribution or redistribution
     of this module is stricly forbidden.

   Description:

     An extensible yet simple module to enable the use of templates in CGI
     scripts and other programs. This module defines no tags, but is used as
     a tokenizer and launches appropriates tags when necessary.
------------------------------------------------------------------------------------


=head1 HOW TO USE IT

To create a new lib::Template object:

    my $tmpl = new lib::Template ( file => 'mytemplate.tmpl', hash => $hashref );

To get parsed data:

    my $data = $tmpl->parse;

If you are just interested in printing the data:

    $tmpl->out;

Which lets you naturally write, when you are just interested in printing out the
output of a template:

    new lib::Template ( file => 'mytemplate.tmpl', hash => $hashref )->out;


=head1 TAGS WHICH CAN BE USED

By default, tag delimiters are ASP-ish. These can be set in the lib.Template
config file which is stored in the configuration directory (see lib::Config
for more information about this).

By convention is this documentation we will use ASP-like syntax. The names
of the tags themselves can be changed in the config file, although this is
not recommended for portability's sake.

Now, let's go thru the available tags:


=head2 VAR tag (lib::Template::Var)

How it would look in your Perl code (modulo Perl style):

    new lib::Template ( file => $file,
                        hash => {
                                  foo => 'this is foo',
                                  bar => 'this is bar',
                                } )->out;

How you can use the tag in the template file:

    <% var name="foo" %>
    <% var name="bar" encode_html="1" %>

Please note that you CAN NOT type:

    <% var foo %>                    <!-- name="foo" ! --> 
    <% var name=foo encode_html=1 %> <!-- double quotes ! -->


=head2 IF tag (lib::Template::If) 

The "if" tag can be used like this:

    <% if name="foo" %>
      FOO had a true value
    <% end-if %>

Naturally, the notion of truth is the same as Perl's: undefined, empty string or
0 are false, everything else is true. Optionnaly, you can use an <% else %> tag:

    <% if name="foo" %>
      "foo" had a true value.
    <% else %>
      <% if name="bar" %>
        "foo" wasn't true but "bar" was.
      <% else %>
        neither "foo" nor "bar" were true.
      <% end-if %>
    <% end-if %>


=head2 INCLUDE tag (lib::Template::Include)

The include tag lets you include another file in the current template.

   <% include file="myfile.tmpl" %>
   <!-- replaces the tag with the contents of 'myfile.tmpl' -->

   <% include file="my_variable" from_variable="1" %>
   <!-- same thing, but the file name is taken from my_variable rather
        than hard-coded -->


ATTENTION! WARNING! ARTUNG! ATENCION!

For the moment there is NO performed check to ensure that the Template
module doesn't infinitely "includes". Be careful or your server may go
out of memory in no time!

=head2 LOOP tag (lib::Template::Loop)

This tags lets you loop thru a list of elements which are hashrefs. As a Perl
programmer, you'd write:

    my $array_ref = [ { foo => 'foo1', bar => 'bar1' },
                      { foo => 'foo2', bar => 'bar2' },
                      { foo => 'foo3', bar => 'bar3' } ].

    new lib::Template ( file => $file,
                        hash => { my_loop => $array_ref } )->out;

On each iteration of the loop, a few properties are added to the hashrefs such as:

    __count__    : Starts at 1. Iteration number
    __is_first__ : true if it's the first iteration
    __is_last__  : true if it's the last iteration
    __even__     : true if the current iteration is even
    __odd__      : true if the current iteration is odd

So you could actually write:

  <table>
    <tr>
     <td><strong>Number:</strong></td>
     <td><strong>Foos:</strong></td>
     <td><strong>Bars:</strong></td>
    </tr>
  
    <% loop name="array_ref" %>
      <tr>
        <% if name="__odd__" %>
          <td bgcolor="FFFFFF"><% var name="__count__" %></td>
          <td bgcolor="FFFFFF"><% var name="foo" encode_html="1" %></td>
          <td bgcolor="FFFFFF"><% var name="foo" encode_html="1" %></td>
        <% else %>
          <td bgcolor="CCCCCC"><% var name="__count__" %></td>
          <td bgcolor="CCCCCC"><% var name="foo" encode_html="1" %></td>
          <td bgcolor="CCCCCC"><% var name="foo" encode_html="1" %></td>
        <% end-if %>
      </tr>
    <% end-loop %>
  </table>

Indeed, loops can be nested into all tags that support nesting.


=head2 PERL tag (lib::Template::Include)

Those who would like to program in an ASP fashion can use the following:

    <% perl
      my $cgi   = lib::cgi::CGI->get;                # see lib::cgi::CGI
      my $table = lib::sql::Table->table ('User');   # see lib::sql::Table
      my $query = $table->search ( $cgi );           # lib::sql::Table magic
      
      my @result = ();
      push @result, $h while (my $h = $query->next);

      $GLOBAL->{query} = \@result;
    %>

    <table>
    <% loop name="query" %>
      <tr>
       <td><% var name="ID" %></td>
       <td><% var name="Age" %></td>
       <td><% var name="Date_Of_Birth" %></td>
      </tr>
    <% end-loop %>
    </table>

Note that using 'print' statements will not do what you expect it to, as it will
print things BEFORE the template is completely parsed. Instead, you should use the
$GLOBAL hashref to save your data, and then use presentation tags to display it.


Enjoy!
