The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# O NTS::Template foi baseado na estrutura de programacao
# do Template Toolkit
# By: Udlei Nattis <unattis@nattis.com>

package NTS::Template;

use strict;
no warnings;
our $VERSION = '2.1';

my (%my,@string);
my $func = {
    # Funcao printf
    PRINTF  => "my \$PRINTF = sub { my (\$x,\$y) = \@_; printf(\$x,\$y); };",

    # Funcao copiada do CGI::Util
    ESCAPE  => "my \$ESCAPE = sub { 
        my (\$toenc) = shift; 
        return undef unless defined (\$toenc);
        \$toenc = pack(\"C*\", unpack(\"C*\", \$toenc)); 
        \$toenc=~s/([^a-zA-Z0-9_.-])/uc sprintf(\"%%%02x\",ord(\$1))/eg;
        print(\$toenc); };",

    # Funcao copiada do CGI::Util
    UNESCAPE    => "my \$UNESCAPE = sub {
        my \$todec = shift;
        return undef unless defined(\$todec);
        \$todec =~ tr/+/ /;
        \$todec =~ s/%(?:([0-9a-fA-F]{2})|u([0-9a-fA-F]{4}))/
        defined(\$1)? chr hex(\$1) : utf8_chr(hex($\2))/ge;
        print(\$todec); };",
};

# Atalho
sub view_templ {
    my($templ_dir,$templ_file,$templ_vars,$templ_extra) = @_;
    
    my $templ = NTS::Template->new();
    my $r = $templ->process({ templ_dir => $templ_dir, templ_file => $templ_file, templ_vars => $templ_vars,
        templ_extra => $templ_extra });

    return $r;
}

# Cria modulo do template
sub new {
    my ($class,$vars) = @_;
    my $self = {};

    bless $self, ref $class || $class || "NTS::Template";

    return $self;
}

# Processa arquivo
sub process {
    my ($self,$vars) = @_;
    my ($data,$file,$_r);
    
    # Arquivo
    $file = $vars->{templ_dir}."/".$vars->{templ_file};
    
    # Executa parse
    $data .= $self->parse($self->load($file),$vars->{templ_vars},$vars->{templ_dir});

    # Retira quebra de linha
    $data =~ s/\s+|\t+/ /g if ($vars->{templ_extra}->{nospaces});

    my $my;
    foreach (keys %my) { 
        # Variavel com conteudo
        if (defined $vars->{templ_vars}->{$_}) { $my .= "my \$$_ = \$vars->{templ_vars}->{$_};\n"; }
       
        # Verifica se existe funcao
        elsif ($func->{$_}) { $my .= $func->{$_}."\n"; }
       
        # Variavel sem conteudo
        else { $my .= "my \$$_;\n"; };
    }

    $data = $my.$data;

    # Saida para variavel
    if (defined $vars->{templ_extra}->{oreturn}) {
        $data =~ s/[^s]print\(|[^s]printf\(/\$_r .= sprintf(/g;
    }
    
    # Verifica se deve alterar saida dos dados
    elsif ($ENV{'MOD_PERL'}) {
        $data =~ s/[^s]print\(/Apache->request->print(/g;
        $data =~ s/[^s]printf\(/Apache->request->printf(/g;
        Apache->request->print($data."\n") if $vars->{templ_extra}->{source};
    }

    # Printa sources
    elsif ($vars->{templ_extra}->{source}) {
        print($data."\n");
    }

#    print $data;
#    Apache->request->print($data);
    $_r = eval $data;
    if ($@) {
        if ($ENV{'MOD_PERL'}) {
            Apache->request->print($@);
        } else {
            print $@."\n";
        }
    }

    # Altera saida do source
    $_r = $data."\n".$_r
        if ($vars->{templ_extra}->{source} && $vars->{templ_extra}->{oreturn});

    return $_r if $vars->{templ_extra}->{oreturn};
    return 1;
}

# Abre arquivo
sub load {
    my ($self,$file) = @_;
    my ($data);
    
    # Printa todo o conteudo de uma vez
    local $/;

    if (open(FH,"<".$file)) { 
        $data = <FH>;
        close(FH);
        return $data;
    }

    # Termina com erro
    else { die "Can't open file $file: $!\n"; }
}

sub parse {
    my ($self,$data,$templ_vars,$templ_dir) = @_;
    
    # Adiciona \ em caracteres nao permitidos
    my $p = 0;
    my ($ndata,$i,$j);
    
    # Recupera condicoes
    while ($data =~ s/(.*?)?(?:\[\%\s?(.*?)\s?\%\])//sx) {
        
        # String padrao
        $ndata .= "print(\"".AddSlashes($1)."\"); ";

        my ($n,$c,$nc,$l,$tp,$fc);
        $l = ($2 =~ /^#/) ? "" : $2;
        
        # Verifica qual o tipo de condicao
        if ($l eq "ELSE") { $n = " } else { "; }
        elsif ($l eq "END" || $l eq "/IF" || $l eq "/FOREACH") { $n = " } "; }
       
        # include
        elsif ($l =~ /^INCLUDE\s+(\-[a-z]+)?\s?(.*)/) { #(\-[a-z]+)?\s+?([\w\.\/]+)/) {
            $i = $1; $j = $2;
           
            # Include em variavel
            if ($templ_vars->{$j} =~ /^(.*)\/([\w\-\.]+)$/) {
                $ndata .= $self->parse($self->load($1."/".$2),$templ_vars,$templ_dir)
                    if (!$i || ($i eq "-f" && -f $1."/".$2)); }

            # Include padrao
            else {
                $ndata .= $self->parse($self->load($templ_dir."/".$j),$templ_vars,$templ_dir)
                    if (!$i || ($i eq "-f" && -f $templ_dir."/".$j)); }
        }
       
        elsif ($l =~ /^IF\s?(.*)/) { $c = $1; $n = "if "; }
        
        elsif ($l =~ /^UNLESS\s?(.*)/) { $c = $1; $n = "unless "; }
        
        elsif ($l =~ /^FOREACH\s+(\w+)\s?=\s?(.*)/) { 
            # $tp (type) verifica tipo de $c e adiciona ele em keys %{}, @{} ou outra forma
            # quando necessario
            $tp = $2; 
            $c = $2; $n = "foreach \$$1 "; $my{$1} = 1; }
            
        elsif ($l =~ /^FOR\s?(.*)/) { $c = $1; $n = "for "; }
        
        elsif ($l =~ /^(ELSIF|^ELSEIF)\s?(.*)/) { $c = $2; $n = " } elsif "; }
       
        # Funcoes
        elsif ($l =~ /^\&(\w+)\((.*)\)/) {
            $c = $2;
            $nc = 1;

            # $fc = function
            $fc = $1;
            $my{$1} = 1;
        }
       
        else { 
            # $nc = no condition
            $nc = 1; 
            $c = $l; }

        # Monta condicao
        if ($c) { 

            # Recupera strings
            my (@string,$s);
            
            $s = 0;
            while ($c =~ s/([\-a-z]{1,2}?\s)?(\".*?[^\\]\")|([\-a-z]{1,2}?\s)?(\/.*?\/)/ .$s/) { 
          
                if ($4) { $string[$s] = $4; }
                elsif ($1) { $string[$s] = $1."\"".$templ_dir."/\".".$2; }
                else { $string[$s] = $2; }
               
                $s++; 
            }

            # Trata condicoes e variaveis
            my @n;
            
            #while ($c =~ s/\s?([^\(\)=!&<>\s]+)(\s+)?([\(\)=!&<>]{1,2})?//) {
            while ($c =~ s/\s?(^[\!])?([^\;\(\)=!&<>\s\%\,\~]+)(\s+)?([\~\,\;\(\)=!&<>\+\%\-\.\*\/]{1,2})?//) {
                my $i = $2;
                my $j = $4;
                my $k = $1;

                # Altera tudo para eq e ne
                if ($j eq "==") { $j = "eq"; }
                elsif ($j eq "!=") { $j = "ne"; }

               
                # Volta para == caso seja igual a 0
                if ($i eq "0") { 
                    if ($n[$#n] =~ /\s+?eq\s+?/) { $n[$#n] = " == "; }
                    elsif ($n[$#n] =~ /\s+?ne\s+?/) { $n[$#n] = " != "; }
                }
                
                # Recupera strings
                if ($i =~ /^\.(\d+)/) {
                    $i = $1;                  
                    push(@n,$string[$i]); }

                else { 
                    push(@n,$k.&parse_cond($i,$j)); }

                # Adiciona condicao
                if ($j) { 
                    push(@n," $j "); }
            }
           
            # Recupera variavel
            $i = join("",@n);
            
            # Var simples
            if (defined $nc) {
                # Verifica quando 'e para printar variavel
                if (!$#n && $i =~ /\w$|}$/) {
                    $n .= "print($i); "; }
                
                # Verifica se esta dentro de uma funcao
                elsif (defined $fc) {
                    $n .= "&\$".$fc."(".$i."); ";
                }
                
                else { $n .= $i."; "; }
                
            } 

            # Condicao
            else { 

                # Verifica se precisa tratar tipo
                if (defined $tp) {
                    $tp =~ s/\./}->{/g;
              
                    # Verifica se 'e array
                    $j = ref $templ_vars->{$tp};
                    if ($j eq "ARRAY") { $i = "\@{$i}"; }
                
                    # Verifica se 'e hash
                    elsif ($j eq "HASH") { $i = "keys \%{$i}"; }

                    # Else
                    else { $i = "\@{$i}"; }
               
                    undef $tp;
                }
                
                $n .= "($i) { "; }
        }

        $ndata .= $n; 
    }

    $ndata .= "print(\"".AddSlashes($data)."\"); " if $data;

    return $ndata;
}

# Parseia condicoes
sub parse_cond {
    my ($i,$j) = @_;
  
    # Verifica AND e OR
    if ($i eq "AND") { return " && "; }
    elsif ($i eq "OR") { return " || "; }

    # Valor numerico
    elsif ($i =~ /^(\d+)$/) { return "\'".$1."\'"; }

    # Trata variavel simples
    elsif ($i =~ /^(\w+)([\+\-]{2})?$/) { $my{$1} = 1; return "\$$1$2"; }

    # Variavel estilo hash
    elsif ($i =~ /^\w+[\.]{1}\w+/) {
    
        # Separa pela virgula para tratar
        my @r;
        while ($i =~ s/(\w+)([\+\-]{2})?//sx) {
            if (@r) { push(@r,"{$1}$2"); }
            else { push(@r,"\$$1"); $my{$1} = 1; }
        }
    
        return join("->",@r);
    }

    else {
        return $i; }
}

# Adiciona barras invertidas
sub AddSlashes {
    my($str,$oreturn) = @_;     

    $str =~ s/\%/%%/g if ($oreturn);
    #$str =~ s/\\/\\\\/g;

    $str =~ s/([\"\#\@\$\\])/\\$1/g;

    return $str;
}

1;

__END__

=head1 NAME

NTS::Template - Fast and small template system

=head1 Description

Formerly Ananke::Template, this Template System is based in Template ToolKit. Very small compared with Template Toolkit, and 100% compatible with mod_perl 2.

Speedy:

    $ cd /proc ; grep name cpuinfo
    model name      : AMD Athlon(tm) XP 1700+

    $ ./bench_templ
    Benchmark: timing 5000 iterations of NTS::Template, Template ToolKit...
    NTS::Template:  9 wallclock secs ( 9.48 usr +  0.11 sys =  9.59 CPU) @ 521.38/s (n=5000)
    Template ToolKit: 25 wallclock secs (24.84 usr +  0.16 sys = 25.00 CPU) @ 200.00/s (n=5000)

Memory:

    $ ./GTop -m NTS::Template

    NTS::Template
          Size     Shared       Diff
       2564096    1642496     114688 (bytes)

    $ ./GTop -m Template
    
    Template
          Size     Shared       Diff
       3960832    1683456    1470464 (bytes)

=head1 SYNOPSIS

no comment

=head1 TO DO

no comment

=head1 DIRECTIVE

=head2 IF, ELSIF OR ELSEIF, UNLESS

try:

    $vars->{test1} = "ok";
    $vars->{test2}->{test2} = "ok";
    $vars->{test3} = 1;

    [% IF test1 %] ok, test1 [% END %]
    [% IF test2.test2 %] ok, test2.test2 [% END %]
    [% IF test1 == test2.test2 %] ok, test1 == test2.test2 [% END %]
    [% IF test1 == "ok" %] ok, test1 == "ok" [% END %]
    [% IF test3 == 1 %] ok, test3 [% END %]
    [% IF test1 AND test2.test2 AND test3 %] ok, test1 AND test2.test2 AND test3 [% END %]
    [% IF test1 OR test3 %] ok, test1 OR test3 [% END %]
    [% IF test1 == test3 %] ok, test1 == test3 [% ELSE %] fail [% END %]
    [% IF test1 == test3 %] ok, test1 == test3 
    [% IF test1 != "fail" %] ok, test1 != "ok" [% ELSE %] fail [% END %]
    [% IF test2.test2 != "fail" %] ok, test2.test2 != "ok" [% ELSE %] fail [% END %]

    [% IF test1 == test3 %] ok, test1 == test3
    [% ELSIF test2.test2 == test3 %] ok, test2.test2 == test3 
    [% ELSE %] fail [% END %]

    [% UNLESS test1 == "fail" %] fail, test == "fail" [% ELSE %] ok [% END %]

return:

    ok, test1 
    ok, test2.test2 
    ok, test1 == test2.test2 
    ok, test1 == "ok" 
    ok, test3 
    ok, test1 AND test2.test2 AND test3 
    ok, test1 OR test3 
    fail
    ok, test1 != "ok" 
    ok, test2.test2 != "ok" 
    fail
    fail, test == "fail" 

=head2 FOREACH

Repeat the enclosed FOREACH ... END block for each value in the list.

    [% FOREACH variable = list %]                 
        content... 
        [% variable %]
    [% END %]

    # or

    [% FOREACH i = list_chn_grp %]
        [% count++ %]
        [% IF count % 2 %] [% bgcolor = "#FFFFFF" %]
        [% ELSE %] [% bgcolor = "#EEEEEE" %]
        [% END %]
    
        [% i.bgcolor %]
    [% END %]

=head2 FOR

    [% FOR i=1;i<=12;i++ %]
        [% i=1 %]
    [% END %]

=head2 VARIABLES

    [% var = 'text' %]
    [% var %]

=head2 &PRINTF

    [% var = 2 %]
    [% &PRINTF('%02d',var) %]

=head2 &ESCAPE/&UNESCAPE

    [% var = "http://www.nattis.com.br?a=b&c=d&e=f" %]
    [% &ESCAPE(var,'') %]
    [% &UNESCAPE(var,'') %]

=head1 Authors

=over

=item

    Udlei Nattis E<lt>unattis (at) nattis.comE<gt>
    http://www.nattis.com

=back

=cut