The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl

package Ananke::Template;
use strict;

my $VERSION = '1.2'; 
my @my;

# Processo para facilitar o print do template
sub view_template {
	my ($template_dir,$template_file,$vars,$to_file) = @_;

	my $template = new Ananke::Template($template_dir);
	$template->process($template_file,$vars,$to_file);

	undef $template_dir; undef $template_file; undef $vars;
	undef $template; undef $to_file;
}

# Inicia modulo
sub new {
	my($self,$templ_dir,$to_file) = @_;

	# Grava dados
	bless {
		'TEMPL_DIR' => $templ_dir,
	}, $self;
}

# Processa página
sub process {
	my($self,$file,$vars,$to_file) = @_;
	my($fdata,$output,$my);
	$self->{TEMPL_FILE} = $file;
	$self->{TO_FILE} = $to_file;
	@my = ();

	$fdata = $self->load();
	$output = $self->parse($fdata,$vars);
	
	foreach (@my) {
		$my .=  $_->{value};
	}

	$output = $my.$output;

	#print $output;
	eval $output;	
	print $@;

	#open (FH,">/tmp/filexx");
	#syswrite(FH,$output);
	#close(FH);
}

# Trata arquivo
sub parse {
	my($self,$fdata,$vars) = @_;
	my(@t,$ndata,$output);
	my $outype;
	
	if ($self->{TO_FILE}) {
		$output .= "open(OUTFILE,\">".$self->{TO_FILE}."\");";
		$outype = "OUTFILE";
	} else {
		$outype = "STDOUT";
	}

	# Transfere dados para vars
	foreach (keys %{$vars}) {
		push(@my,{
			var	=> "\$T$_",
			value => "my \$T$_ = \$vars->{$_};\n"
		});
	}
	
	# Adiciona \ em caracteres nao permitidos
	my $Tstart = quotemeta("[%");
	my $Tend = quotemeta("%]");

	# Faz o primeiro parse
	while ($fdata) {

		# Verifica parse
		if ($fdata =~ s/^(.*?)?(?:$Tstart(.*?)$Tend)//sx) {
	
			$t[1] = $1; $t[2] = $2;
			$t[1] =~ s/[\n|\s]//g if ($t[1] =~ /^[\n\s]+$/);

			# Nao executa linhas comentadas e espacos desnecessarios
			$t[2] =~ s/^\s+?\#\s+(.*?)\s+?$//g;
			$t[2] =~ s/^\s+?(.*?)\s+?$/$1/g;

			if ($t[1]) {
				$t[1] = "\nsyswrite($outype,\"".&AddSlashes($t[1])."\");";
			}
		
			# Retira espaços em branco no começo e final da var
			$t[2] =~ s/^[ ]+?(.*)[ ]+?$/$1/s;
			
			# Trata if e elsif
			if ($t[2] =~ /^(IF|ELSIF|UNLESS)\s+(.*)$/i) {
				$t[3] = lc($1);
				$t[4] = $2;
				$t[4] =~ s/AND/\&\&/g; $t[4] =~ s/OR/\|\|/g;
			
				$t[3] = "} ".$t[3] if ($t[3] eq "elsif");
			
				# Trata todos os tipos de vars
				while ($t[4] =~ /([\&\|\s\>\<\=\%\!]+)?([\w\"\'\.\+\-]+)([\&\|\s\>\<\=\%\!]+)?/g) {
					$t[5] = $1; $t[6] = $2; $t[7] = $3;
					
					$t[5] =~ s/\=\=/eq/g; $t[5] =~ s/\!\=/ne/g;
					$t[7] =~ s/\=\=/eq/g; $t[7] =~ s/\!\=/ne/g;

					# vars scalares
					if ($t[6] =~ /^(\w+)\.(\w+)$/) {
						$t[6] = "\$T".$1."->{$2}";
						$self->my("\$T".$1."->{$2}");
					}
				
					# Numeros
					elsif ($t[6] =~ /^([\d]+)$/) {
						$t[6] = $1;
					}
				
					# Demais variaveis
					elsif ($t[6] =~ /^(\w+)$/) {
						$self->my("\$T".$t[6]);
						$t[6] = "\$T".$t[6];
					}
					
					# String
					elsif ($t[6] =~ /^([\w\"\']+)$/) {
						$t[6] = $1;
					}
					
					# vars normais
					#else {
					#}

					$t[8] .= $t[5].$t[6].$t[7];
				}

				$t[2] = "\n".$t[3]." (".$t[8].") {";

				undef $t[3]; undef $t[4]; undef $t[5];
				undef $t[6]; undef $t[7]; undef $t[8];
			}

			# Trata for
			elsif ($t[2] =~ /(FOR)\s(.*)/) {
				$t[8] = $1;
				$t[3] = $2;
	
				# Trata opcoes do for
				while ($t[3] =~ /([\;])?([\w\.\+\-]+)([\<\=\>\!]+)?/g) {
					$t[4] = $2; $t[5] = $3; $t[6] = $1;
					$t[6] =~ s/\=\=/eq/g; $t[6] =~ s/\!\=/ne/g;
					
					# Trata numeros
					if ($t[4] =~ /^[0-9]+$/) {
						$t[4] = $t[4];
					}
					
					# Trata hash
					elsif ($t[4] =~ /^(\w+)\.(\w+)$/) {
						$self->my("\$T".$1."->{$2}");
						$t[4] = "\$T".$1."->{$2}";
					} 
					
					# Trata vars
					else {
						$self->my("\$T".$t[4]);
						$t[4] = "\$T".$t[4];
					}

					$t[7] .= "$t[6]$t[4]$t[5]";
				}

				$t[2] = "\n".lc($t[8])." (".$t[7].") {";
				
				undef $t[3]; undef $t[4]; undef $t[5];
				undef $t[6]; undef $t[7]; undef $t[8];
			}

			# Trata foreach
			elsif ($t[2] =~ /(FOREACH) (.*) = (.*)/i) {
				
				# Seta vars do if
				$t[3] = $1; $t[4] = $2; $t[5] = $3;

				# Verifica se é hash
				if (ref $vars->{$t[5]} eq "ARRAY") {
					$t[2] = "\n".lc($1)." my \$T$2 (\@{\$T$3}) {";
					$self->my("\@T$3");
				}

				# Caso nao exista array
				else {
					$t[2] = "\n".lc($1)." my \$T$2 (\@\{0\}) {";
				}

				# apaga vars do if
				undef $t[3]; undef $t[4]; undef $t[5];
			}

			# Fecha sintaxy
			elsif ($t[2] eq "END") {
				$t[2] = "\n}";
			}

			# Else
			elsif ($t[2] eq "ELSE") {
				$t[2] = "\n} else {";
			}

			# Adiciona include
			elsif ($t[2] =~ /^INCLUDE\s+(.*)$/) {
			   $ndata = $self->load($1);
				$t[2] = $self->parse($ndata,$vars);
			}

			# Trata hash
			elsif ($t[2] =~ /(\w+)\.(\w+)/) {
				$t[2] = "\nsyswrite($outype,\$T".$1."->{".$2."});";
				$self->my("\$T".$1."->{".$2."}");
			}

			# Trata string
			elsif ($t[2] =~ /^\w$/) {
				$self->my("\$T".$t[2]);
				$t[2] = "\nsyswrite($outype,\$T".$t[2].");";
			}

			# Seta vars
			elsif ($t[2] =~ /^([\w\+\-]+)\s?([\=\>\<\!]+)?\s?[\"]?(.*)?[\"]?$/) {
				$t[3] = $1; $t[4] = $2; $t[5] = $3;
				$t[4] =~ s/\=\=/eq/g; $t[4] =~ s/\!\=/ne/g;

				$t[5] =~ s/"$//g if ($t[5] =~ /"$/);

				# Trata variaveis unica
				if ($t[3] && !$t[5]) {
					
					# Variaveis
					if ($t[3] =~ /^\w+$/) {
						$self->my("\$T".$t[3]);
						$t[2] = "\nsyswrite($outype,\$T".$t[3].");";
					}
					
					# Variaveis especiais
					elsif ($t[3] =~ /^[\w\+\-]+$/) {
						$self->my("\$T".$t[3]);
						$t[2] = "\n\$T".$t[3].";";
					}
				}
				
				# Seta variaveis
				elsif ($t[3] && $t[5]) {
					$self->my("\$T".$t[3]);
					$t[2] = "\n\$T".$t[3]." $t[4] \"".&AddSlashes($t[5])."\";";
				}
			}
	
			$output .= $t[1].$t[2];
		}

		# Outros
		elsif ($fdata =~ s/^(.*)$//sx) {
			$output .= "\nsyswrite($outype,\"".&AddSlashes($1)."\");\n";
		}
	}

	$output .= "close(OUTFILE);\n" if ($self->{TO_FILE});
	return $output;
}

# Verifica se adicionou no array
sub my {
	my($self,$var) = @_;
	my (@t,$t);

	if ($var =~ /^([\$\@\%])(.*)?$/) {
		$t[1] = $1; $t[2] = $2;
	
		# Trata array
		if ($t[1] eq "\@") {
			# Verifica se ja esta no array
			$t = 1;
			foreach (@my) { if ($_->{var} eq "\@".$t[2]) { undef $t } }
			
			# Adiciona no array
			push(@my,{
				var	=> "\@".$t[2],
				value	=> "my \@".$t[2].";\n",
			}) if ($t);

			undef $t;
		}

		# Trata var
		elsif ($t[1] eq "\$" && $t[2] =~ /^([\w\+]+)([\-\>]+)?([\w\{\}]+)?/g) {
			$t[3] = $1;
			$t[3] =~ s/\+//g; $t[3] =~ s/\-//g;
	
			# Verifica se ja esta no array
			$t = 1;
			foreach (@my) { 
				if ($_->{var} eq "\$".$t[3]) { 
					undef $t;
					last;
				}
			}
			
			# Adiciona no array
			push(@my,{
				var	=> "\$".$t[3],
				value	=> "my \$".$t[3].";\n",
			}) if ($t);
			
			undef $t;
		}
	}
}

# Abre aquivo
sub load {
	my($self,$templ_file) = @_;
	my($r,$fdata);
	my $path;
	my $file = $templ_file || $self->{TEMPL_FILE};
	my $templ_path = $self->{TEMPL_DIR}."/".$file;

	local $/ = undef;
	#local *FH;

	# Abre arquivo
	if (open(FH,$templ_path)) {
		$fdata = <FH>;
		
		#open(FH2,">>/tmp/filexx");
		#syswrite(FH2,$fdata);
		#close(FH2);

		# Fecha arquivo
		close(FH);
	}

	# Retorna erro
	else {
		die "Erro abrindo arquivo $templ_path: $!\n";
	}

	# Retorna dados
	return $fdata;
}

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

	$str =~ s/\\/\\\\/g;
	$str =~ s/\#/\\#/g;
	$str =~ s/\@/\\@/g;
	$str =~ s/\"/\\"/g;
	
	return $str;
}

1;
__END__

=head1 NAME

Ananke::Template - Front-end module to the Ananke::Template

=head1 DESCRIPTION

Based in Template ToolKit
This documentation describes the Template module which is the direct
Perl interface into the Ananke::Template.

=head1 SYNOPSIS 

=head2 Template.pl:

	use Ananke::Template;

	# Vars
	my @array;
	push(@array,{ name => 'Udlei', last => 'Nattis' });
	push(@array,{ name => 'Ananke', last => 'IT' });

	my $var = {
		id => 1,
		title => 'no title',
		text  => 'no text',
	};

	# Template Directory and File
	my $template_dir = "./";
	my $template_file = "template.html";
	my $template_vars = {
		'hello'  => "\nhello world",
		'scalar' => $var,
		'array'  => ['v1','v2','v3','v4'],
		'register' => \@array,
	};
	$template_vars->{SCRIPT_NAME} = "file.pl";

	# Method 1 - print
	# Create template object
	my $template = new Ananke::Template($template_dir);

	# Run Template
	$template->process($template_file,$template_vars);

	# Method 2 - print
	&Ananke::Template::view_template($template_dir,$template_file,$template_vars);

	# Method 3 - write in file
	&Ananke::Template::view_template($template_dir,$template_file,$template_vars,"/tmp/file.html");

=head2 template.html:

	[% hello %]

	[% IF scalar %]
		ID: [% scalar.id %]
		Title: [% scalar.title %]
		Text: [% scalar.text %]
	[% END %]

	[% FOREACH i = array %]
		value = [% i %]
	[% END %]

	[% FOREACH i = register %]
		Nome = [% i.name %], Last = [% i.last %]
	[% END %]

=head1 DIRECTIVE

=head2 INCLUDE

Process another template file or block and include the output.  Variables are localised.

	[% INCLUDE template %]
	[% INCLUDE ../template.html %]

=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 IF / UNLESS / ELSIF / ELSE

Enclosed block is processed if the condition is true / false.

	[% IF condition %]
		content
	[% ELSIF condition %]
		content
	[% ELSE %]
		content
	[% END %]

	[% UNLESS condition %]
		content
	[% # ELSIF/ELSE as per IF, above %]
		content
	[% END %]

=head2 FOR

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

=head2 VARIABLES

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

=head1 AUTHOR

	Udlei D. R. Nattis
	nattis@anankeit.com.br
	http://www.nobol.com.br
	http://www.anankeit.com.br

=cut

# Data inicio: Thu Feb 21 16:19:18 BRT 2002
# Desenvolvido por: Udlei Nattis <nattis@anankeit.com.br>