2007-05-21 03:34:53 +04:00
|
|
|
|
#!/usr/bin/perl
|
2009-06-29 15:40:21 +04:00
|
|
|
|
# Простой шаблонный движок.
|
|
|
|
|
# Когда-то inspired by phpBB templates, которые в свою очередь inspired by
|
|
|
|
|
# phplib templates. Однако уже далеко ушедши от них обоих.
|
2007-05-21 03:34:53 +04:00
|
|
|
|
|
|
|
|
|
package VMX::Template;
|
|
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
|
use VMX::Common qw(:all);
|
2009-03-13 20:47:24 +03:00
|
|
|
|
use Digest::MD5 qw(md5_hex);
|
2008-08-15 21:31:24 +04:00
|
|
|
|
use Hash::Merge;
|
2007-05-21 03:34:53 +04:00
|
|
|
|
|
2009-01-06 01:39:48 +03:00
|
|
|
|
my $mtimes = {}; # время изменения файлов
|
|
|
|
|
my $uncompiled_code = {}; # нескомпилированный код
|
|
|
|
|
my $compiled_code = {}; # скомпилированный код (sub'ы)
|
|
|
|
|
my $langhashes = {}; # хеши ленгпаков
|
2009-06-29 15:40:21 +04:00
|
|
|
|
my %assigncache = {}; # кэш eval'ов присвоений
|
2007-10-28 19:16:50 +03:00
|
|
|
|
|
2009-01-06 01:39:48 +03:00
|
|
|
|
# Конструктор
|
|
|
|
|
# $obj = new VMX::Template, %params
|
2008-08-15 21:31:24 +04:00
|
|
|
|
sub new
|
|
|
|
|
{
|
2007-05-21 03:34:53 +04:00
|
|
|
|
my $class = shift;
|
|
|
|
|
$class = ref ($class) || $class;
|
2008-08-15 21:31:24 +04:00
|
|
|
|
my $self =
|
|
|
|
|
{
|
2009-06-29 15:40:21 +04:00
|
|
|
|
conv =>
|
|
|
|
|
{
|
|
|
|
|
# char => func_name | \&sub_ref
|
|
|
|
|
T => 'strip_tags',
|
|
|
|
|
i => 'int',
|
|
|
|
|
s => 'htmlspecialchars',
|
|
|
|
|
l => 'lc',
|
|
|
|
|
u => 'uc',
|
|
|
|
|
q => 'quotequote',
|
|
|
|
|
H => 'strip_unsafe_tags',
|
|
|
|
|
L => \&language_ref,
|
|
|
|
|
Lz => \&language_refnull,
|
|
|
|
|
},
|
|
|
|
|
tests =>
|
|
|
|
|
{
|
|
|
|
|
'!' => [ '!', 0 ],
|
|
|
|
|
odd => [ 'test_odd', 0 ],
|
|
|
|
|
even => [ 'test_even', 0 ],
|
|
|
|
|
mod => [ 'test_mod', 1 ],
|
|
|
|
|
eq => [ 'test_eq', 1 ],
|
|
|
|
|
},
|
2007-10-27 23:44:31 +04:00
|
|
|
|
root => '.', # каталог с шаблонами
|
2009-01-06 01:39:48 +03:00
|
|
|
|
reload => 1, # если 0, шаблоны не будут перечитываться с диска, и вызовов stat() происходить не будет
|
2007-10-28 19:16:50 +03:00
|
|
|
|
wrapper => undef, # фильтр, вызываемый перед выдачей результата parse
|
2009-01-06 01:39:48 +03:00
|
|
|
|
tpldata => {}, # сюда будут сохранены: данные
|
2009-02-01 19:37:33 +03:00
|
|
|
|
lang => {}, # ~ : языковые данные
|
2009-06-29 15:40:21 +04:00
|
|
|
|
tpldata_stack => [], # стек tpldata-ы для datapush и datapop
|
2009-03-13 20:47:24 +03:00
|
|
|
|
cache_dir => undef, # необязательный кэш, ускоряющий работу только в случае частых инициализаций интерпретатора
|
2009-01-03 14:56:08 +03:00
|
|
|
|
use_utf8 => undef, # шаблоны в UTF-8 и с флагом UTF-8
|
2009-01-06 01:39:48 +03:00
|
|
|
|
@_,
|
2007-10-27 23:44:31 +04:00
|
|
|
|
};
|
2009-03-13 20:47:24 +03:00
|
|
|
|
$self->{cache_dir} =~ s!/*$!/!so if $self->{cache_dir};
|
2009-01-06 01:39:48 +03:00
|
|
|
|
$self->{root} =~ s!/*$!/!so;
|
2007-10-27 23:44:31 +04:00
|
|
|
|
bless $self, $class;
|
2007-05-21 03:34:53 +04:00
|
|
|
|
}
|
|
|
|
|
|
2009-01-06 01:39:48 +03:00
|
|
|
|
# Функция задаёт имена файлов для хэндлов
|
|
|
|
|
# $obj->set_filenames (handle1 => 'template1.tpl', handle2 => \'{CODE} - Template code', ...)
|
2008-08-15 21:31:24 +04:00
|
|
|
|
sub set_filenames
|
|
|
|
|
{
|
2007-05-21 03:34:53 +04:00
|
|
|
|
my $self = shift;
|
|
|
|
|
my %fns = @_;
|
2009-01-06 01:39:48 +03:00
|
|
|
|
while (my ($k, $v) = each %fns)
|
2008-08-15 21:31:24 +04:00
|
|
|
|
{
|
2009-01-06 01:39:48 +03:00
|
|
|
|
if (ref $v && ref $v ne 'SCALAR')
|
|
|
|
|
{
|
|
|
|
|
$v = "$v";
|
|
|
|
|
}
|
|
|
|
|
$self->{filenames}->{$k} = $v;
|
2007-05-21 03:34:53 +04:00
|
|
|
|
}
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2009-01-06 01:39:48 +03:00
|
|
|
|
# Функция загружает файлы переводов (внутри хеши)
|
|
|
|
|
# $obj->load_lang ($filename, $filename, ...);
|
2008-08-15 21:31:24 +04:00
|
|
|
|
sub load_lang
|
|
|
|
|
{
|
2009-02-01 19:37:33 +03:00
|
|
|
|
my $self = shift;
|
|
|
|
|
return $self->load_lang_hashes(map
|
2009-01-06 01:39:48 +03:00
|
|
|
|
{
|
|
|
|
|
my $load = 0;
|
|
|
|
|
my $mtime;
|
|
|
|
|
if (!defined($mtimes->{$_}) || $self->{reload})
|
|
|
|
|
{
|
|
|
|
|
$mtime = [ stat($_) ] -> [ 9 ];
|
|
|
|
|
$load = 1 if !defined($mtimes->{$_}) || $mtime > $mtimes->{$_};
|
|
|
|
|
}
|
|
|
|
|
if ($load)
|
2008-08-15 21:31:24 +04:00
|
|
|
|
{
|
2007-11-07 03:12:06 +03:00
|
|
|
|
$mtimes->{$_} = $mtime;
|
2009-01-06 01:39:48 +03:00
|
|
|
|
$langhashes->{$_} = do $_;
|
2007-11-07 03:12:06 +03:00
|
|
|
|
}
|
2008-08-15 21:31:24 +04:00
|
|
|
|
$langhashes->{$_};
|
2007-11-07 03:12:06 +03:00
|
|
|
|
} @_);
|
2007-09-12 00:56:37 +04:00
|
|
|
|
}
|
|
|
|
|
|
2009-01-06 01:39:48 +03:00
|
|
|
|
# Функция загружает хеши переводов
|
|
|
|
|
# $obj->load_lang_hashes ($hash, $hash, ...);
|
2008-08-15 21:31:24 +04:00
|
|
|
|
sub load_lang_hashes
|
|
|
|
|
{
|
2009-02-01 19:37:33 +03:00
|
|
|
|
my $self = shift;
|
|
|
|
|
my $i = 0;
|
2008-08-15 21:31:24 +04:00
|
|
|
|
Hash::Merge::set_behavior('RIGHT_PRECEDENT');
|
|
|
|
|
$self->{lang} = Hash::Merge::merge ($self->{lang}, $_) foreach @_;
|
2009-02-01 19:37:33 +03:00
|
|
|
|
return $i;
|
2007-09-11 02:46:55 +04:00
|
|
|
|
}
|
|
|
|
|
|
2009-01-06 01:39:48 +03:00
|
|
|
|
# Функция уничтожает данные шаблона
|
|
|
|
|
# $obj->clear()
|
2008-10-16 19:27:36 +04:00
|
|
|
|
sub clear
|
2008-08-15 21:31:24 +04:00
|
|
|
|
{
|
2009-06-29 15:40:21 +04:00
|
|
|
|
shift->{tpldata} = {};
|
2007-05-21 03:34:53 +04:00
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2009-06-29 15:40:21 +04:00
|
|
|
|
# Функция сохраняет текущие данные шаблона в стек и уничтожает их
|
|
|
|
|
# $obj->datapush ()
|
|
|
|
|
sub datapush
|
2009-06-27 17:21:50 +04:00
|
|
|
|
{
|
2009-06-27 17:45:22 +04:00
|
|
|
|
my $self = shift;
|
2009-06-29 15:40:21 +04:00
|
|
|
|
push (@{$self->{tpldata_stack}}, \$self->{tpldata});
|
|
|
|
|
$self->clear;
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Функция восстанавливает данные шаблона из стека
|
|
|
|
|
# $obj->datapop()
|
|
|
|
|
sub datapop
|
|
|
|
|
{
|
|
|
|
|
my $self = shift;
|
|
|
|
|
return 0 if (@{$self->{tpldata_stack}} <= 0);
|
|
|
|
|
$self->{tpldata} = pop @{$self->{tpldata_stack}};
|
|
|
|
|
return 1;
|
2009-06-27 17:21:50 +04:00
|
|
|
|
}
|
|
|
|
|
|
2009-01-06 01:39:48 +03:00
|
|
|
|
# Функция загружает, компилирует и возвращает результат для хэндла
|
|
|
|
|
# $obj->parse('handle')
|
2008-08-15 21:31:24 +04:00
|
|
|
|
sub parse
|
|
|
|
|
{
|
2007-06-02 02:30:09 +04:00
|
|
|
|
my $self = shift;
|
2008-10-18 17:56:52 +04:00
|
|
|
|
my ($handle) = @_;
|
2009-01-06 01:39:48 +03:00
|
|
|
|
my $fn = $self->{filenames}->{$handle};
|
|
|
|
|
my $textref;
|
|
|
|
|
unless (ref $fn)
|
|
|
|
|
{
|
|
|
|
|
die "[Template] unknown handle '$handle'"
|
|
|
|
|
unless $fn;
|
|
|
|
|
$fn = $self->{root}.$fn
|
|
|
|
|
if $fn !~ m!^/!so;
|
|
|
|
|
die "[Template] couldn't load template file '$fn' for handle '$handle'"
|
|
|
|
|
unless $textref = $self->loadfile($fn);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
$textref = $fn;
|
|
|
|
|
$fn = undef;
|
|
|
|
|
}
|
|
|
|
|
my $sub = $self->compile($textref, $handle, $fn);
|
|
|
|
|
my $str = eval { &$sub($self) };
|
|
|
|
|
die "[Template] error running '$handle': $@" if $@;
|
2009-02-07 16:16:04 +03:00
|
|
|
|
&{$self->{wrapper}} ($str) if $self->{wrapper};
|
2007-06-02 02:30:09 +04:00
|
|
|
|
return $str;
|
2007-05-21 03:34:53 +04:00
|
|
|
|
}
|
|
|
|
|
|
2009-01-06 01:39:48 +03:00
|
|
|
|
# Функция загружает файл с кэшированием
|
|
|
|
|
# $textref = $obj->loadfile($file)
|
|
|
|
|
sub loadfile
|
|
|
|
|
{
|
2009-02-01 19:37:33 +03:00
|
|
|
|
my $self = shift;
|
2009-01-06 01:39:48 +03:00
|
|
|
|
my ($fn) = @_;
|
|
|
|
|
my $load = 0;
|
|
|
|
|
my $mtime;
|
|
|
|
|
if (!$uncompiled_code->{$fn} || $self->{reload})
|
|
|
|
|
{
|
|
|
|
|
$mtime = [ stat($fn) ] -> [ 9 ];
|
|
|
|
|
$load = 1 if !$uncompiled_code->{$fn} || $mtime > $mtimes->{$fn};
|
|
|
|
|
}
|
|
|
|
|
if ($load)
|
|
|
|
|
{
|
|
|
|
|
# если файл изменился - перезасасываем
|
|
|
|
|
my ($fd, $text);
|
|
|
|
|
if (open $fd, "<", $fn)
|
|
|
|
|
{
|
|
|
|
|
local $/ = undef;
|
|
|
|
|
$text = <$fd>;
|
|
|
|
|
close $fd;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return undef;
|
|
|
|
|
}
|
|
|
|
|
# удаляем старый скомпилированный код
|
|
|
|
|
delete $compiled_code->{$uncompiled_code->{$fn}}
|
|
|
|
|
if $uncompiled_code->{$fn};
|
|
|
|
|
$uncompiled_code->{$fn} = \$text;
|
|
|
|
|
$mtimes->{$fn} = $mtime;
|
|
|
|
|
}
|
|
|
|
|
return $uncompiled_code->{$fn};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Функция присваивает переменные блока в новую итерацию
|
|
|
|
|
# $obj->assign_block_vars ($block, varname1 => value1, varname2 => value2, ...)
|
2009-02-01 19:37:33 +03:00
|
|
|
|
# Так тоже можно (при этом избежим лишнего копирования хеша!):
|
|
|
|
|
# $obj->assign_block_vars ($block, { varname1 => value1, varname2 => value2, ... })
|
2008-08-15 21:31:24 +04:00
|
|
|
|
sub assign_block_vars
|
|
|
|
|
{
|
2007-05-21 03:34:53 +04:00
|
|
|
|
my $self = shift;
|
|
|
|
|
my $block = shift;
|
2009-02-01 19:37:33 +03:00
|
|
|
|
my $vararray;
|
|
|
|
|
if (@_ > 1)
|
|
|
|
|
{
|
|
|
|
|
# копирование хеша, да...
|
|
|
|
|
$vararray = { @_ };
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
# а так можно и не копировать
|
|
|
|
|
($vararray) = @_;
|
|
|
|
|
}
|
2008-03-03 14:29:35 +03:00
|
|
|
|
$block =~ s/^\.+//so;
|
|
|
|
|
$block =~ s/\.+$//so;
|
2008-08-15 21:31:24 +04:00
|
|
|
|
if (!$block)
|
|
|
|
|
{
|
|
|
|
|
# если не блок, а корневой уровень
|
2009-02-01 19:37:33 +03:00
|
|
|
|
$self->assign_vars($vararray);
|
2008-08-15 21:31:24 +04:00
|
|
|
|
}
|
2009-02-01 19:37:33 +03:00
|
|
|
|
elsif ($block !~ /\.[^\.]/so)
|
2008-08-15 21:31:24 +04:00
|
|
|
|
{
|
|
|
|
|
# если блок, но не вложенный
|
2009-02-01 19:37:33 +03:00
|
|
|
|
$block =~ s/\.*$/./so; # добавляем . в конец, если надо
|
|
|
|
|
$self->{tpldata}->{$block} ||= [];
|
2009-01-06 01:39:48 +03:00
|
|
|
|
push @{$self->{tpldata}->{$block}}, $vararray;
|
2008-08-15 21:31:24 +04:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
# если вложенный блок
|
2009-02-01 19:37:33 +03:00
|
|
|
|
my $ev;
|
|
|
|
|
$block =~ s/\.+$//so; # обрезаем точки в конце (хоть их 10 там)
|
2009-06-29 15:40:21 +04:00
|
|
|
|
unless ($ev = $assigncache{"=$block"})
|
2008-08-15 21:31:24 +04:00
|
|
|
|
{
|
2009-02-01 19:37:33 +03:00
|
|
|
|
$ev = '$_[0]';
|
|
|
|
|
my @blocks = split /\./, $block;
|
|
|
|
|
my $lastblock = pop @blocks;
|
|
|
|
|
foreach (@blocks)
|
|
|
|
|
{
|
2009-06-29 15:40:21 +04:00
|
|
|
|
$ev .= "{'$_.'}";
|
2009-02-01 19:37:33 +03:00
|
|
|
|
$ev .= "[\$\#\{$ev\}]";
|
|
|
|
|
}
|
2009-06-29 15:40:21 +04:00
|
|
|
|
$ev .= "{'$lastblock.'}";
|
2009-02-01 23:01:45 +03:00
|
|
|
|
$ev = "return sub { $ev ||= []; push \@\{$ev\}, \$_[1]; }";
|
2009-06-29 15:40:21 +04:00
|
|
|
|
$ev = $assigncache{"=$block"} = eval $ev;
|
2007-05-21 03:34:53 +04:00
|
|
|
|
}
|
2009-02-01 19:37:33 +03:00
|
|
|
|
&$ev($self->{tpldata}, $vararray);
|
2007-05-21 03:34:53 +04:00
|
|
|
|
}
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2009-01-06 01:39:48 +03:00
|
|
|
|
# Функция добавляет переменные к текущей итерации блока
|
|
|
|
|
# $obj->append_block_vars ($block, varname1 => value1, varname2 => value2, ...)
|
2008-08-15 21:31:24 +04:00
|
|
|
|
sub append_block_vars
|
|
|
|
|
{
|
2007-05-21 03:34:53 +04:00
|
|
|
|
my $self = shift;
|
|
|
|
|
my $block = shift;
|
|
|
|
|
my %vararray = @_;
|
|
|
|
|
my $lastit;
|
2008-08-15 21:31:24 +04:00
|
|
|
|
if (!$block || $block eq '.')
|
|
|
|
|
{
|
|
|
|
|
# если не блок, а корневой уровень
|
2009-02-01 19:37:33 +03:00
|
|
|
|
$self->assign_vars(@_);
|
2008-08-15 21:31:24 +04:00
|
|
|
|
}
|
2009-02-01 19:37:33 +03:00
|
|
|
|
elsif ($block !~ /\../so)
|
2008-08-15 21:31:24 +04:00
|
|
|
|
{
|
|
|
|
|
# если блок, но не вложенный
|
2009-02-01 19:37:33 +03:00
|
|
|
|
$block =~ s/\.*$/./so; # добавляем . в конец, если надо
|
2009-01-06 01:39:48 +03:00
|
|
|
|
$self->{tpldata}{$block} ||= [];
|
2009-02-01 19:37:33 +03:00
|
|
|
|
$lastit = $#{$self->{tpldata}{$block}};
|
2008-12-27 02:40:58 +03:00
|
|
|
|
$lastit = 0 if $lastit < 0;
|
2009-01-06 01:39:48 +03:00
|
|
|
|
$self->{tpldata}{$block}[$lastit]{$_} = $vararray{$_}
|
2009-02-01 19:37:33 +03:00
|
|
|
|
for keys %vararray;
|
2008-08-15 21:31:24 +04:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
# если вложенный блок
|
2009-02-01 19:37:33 +03:00
|
|
|
|
my $ev;
|
|
|
|
|
$block =~ s/\.+$//so; # обрезаем точки в конце (хоть их 10 там)
|
2009-06-29 15:40:21 +04:00
|
|
|
|
unless ($ev = $assigncache{"+$block"})
|
2008-08-15 21:31:24 +04:00
|
|
|
|
{
|
2009-02-01 19:37:33 +03:00
|
|
|
|
$ev = '$_[0]';
|
|
|
|
|
my @blocks = split /\.+/, $block;
|
|
|
|
|
foreach (@blocks)
|
|
|
|
|
{
|
2009-06-29 15:40:21 +04:00
|
|
|
|
$ev .= "{'$_.'}";
|
2009-02-01 19:37:33 +03:00
|
|
|
|
$ev .= "[\$#\{$ev\}]";
|
|
|
|
|
}
|
2009-02-01 23:01:45 +03:00
|
|
|
|
$ev = 'return sub { for my $k (keys %{$_[1]}) { '.$ev.'{$k} = $_[1]->{$k}; } }';
|
2009-06-29 15:40:21 +04:00
|
|
|
|
$ev = $assigncache{"+$block"} = eval $ev;
|
2007-05-21 03:34:53 +04:00
|
|
|
|
}
|
2009-02-01 19:37:33 +03:00
|
|
|
|
&$ev($self->{tpldata}, \%vararray);
|
2007-05-21 03:34:53 +04:00
|
|
|
|
}
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2009-01-06 01:39:48 +03:00
|
|
|
|
# Функция присваивает переменные корневого уровня
|
|
|
|
|
# $obj->assign_vars (varname1 => value1, varname2 => value2, ...)
|
2008-08-15 21:31:24 +04:00
|
|
|
|
sub assign_vars
|
|
|
|
|
{
|
2007-05-21 03:34:53 +04:00
|
|
|
|
my $self = shift;
|
2009-06-29 15:40:21 +04:00
|
|
|
|
my %h;
|
|
|
|
|
if (@_ > 1 || !ref($_[0]))
|
2009-02-01 19:37:33 +03:00
|
|
|
|
{
|
2009-06-29 15:40:21 +04:00
|
|
|
|
%h = @_;
|
2009-02-01 19:37:33 +03:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2009-06-29 15:40:21 +04:00
|
|
|
|
%h = %{$_[0]};
|
2009-02-01 19:37:33 +03:00
|
|
|
|
}
|
2009-06-29 15:40:21 +04:00
|
|
|
|
$self->{tpldata}{'.'}[0] ||= {};
|
|
|
|
|
$self->{tpldata}{'.'}[0]{$_} = $h{$_} for keys %h;
|
2007-05-21 03:34:53 +04:00
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2009-06-29 15:40:21 +04:00
|
|
|
|
# Аналог assign_vars, но преобразует имена переменных
|
|
|
|
|
sub tr_assign_vars
|
|
|
|
|
{
|
|
|
|
|
my $self = shift;
|
|
|
|
|
$self->assign_vars($self->tr_vars(@_));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Аналог assign_block_vars, но преобразует имена переменных
|
|
|
|
|
sub tr_assign_block_vars
|
|
|
|
|
{
|
|
|
|
|
my $self = shift;
|
|
|
|
|
my $block = shift;
|
|
|
|
|
$self->assign_block_vars($block, $self->tr_vars(@_));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Аналог append_block_vars, но преобразует имена переменных
|
|
|
|
|
sub tr_append_block_vars
|
|
|
|
|
{
|
|
|
|
|
my $self = shift;
|
|
|
|
|
my $block = shift;
|
|
|
|
|
$self->append_block_vars($block, $self->tr_vars(@_));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Собственно функция, которая преобразует имена переменных
|
|
|
|
|
sub tr_vars
|
|
|
|
|
{
|
|
|
|
|
my $self = shift;
|
|
|
|
|
my $tr = shift;
|
|
|
|
|
my $prefix = shift;
|
|
|
|
|
my %h = ();
|
|
|
|
|
my ($k, $v);
|
|
|
|
|
if ($tr && !ref($tr))
|
|
|
|
|
{
|
|
|
|
|
unless ($self->{_tr_subroutine_cache}->{$tr})
|
|
|
|
|
{
|
|
|
|
|
# делаем так, чтобы всякие uc, lc и т.п работали
|
|
|
|
|
$self->{_tr_subroutine_cache}->{$tr} = eval 'sub { '.$tr.'($_[0]) }';
|
|
|
|
|
}
|
|
|
|
|
$tr = $self->{_tr_subroutine_cache}->{$tr};
|
|
|
|
|
}
|
|
|
|
|
while(@_)
|
|
|
|
|
{
|
|
|
|
|
$k = shift;
|
|
|
|
|
$v = shift;
|
|
|
|
|
$k = &$tr($k) if $tr;
|
|
|
|
|
$k = $prefix.$k if $prefix;
|
|
|
|
|
$h{$k} = $v;
|
|
|
|
|
}
|
|
|
|
|
return %h;
|
|
|
|
|
}
|
|
|
|
|
|
2009-01-06 01:39:48 +03:00
|
|
|
|
# Функция компилирует код
|
|
|
|
|
# $sub = $self->compile(\$code, $handle, $fn);
|
|
|
|
|
# print &$sub($self);
|
2008-08-15 21:31:24 +04:00
|
|
|
|
sub compile
|
|
|
|
|
{
|
2007-10-28 19:16:50 +03:00
|
|
|
|
my $self = shift;
|
2009-01-06 01:39:48 +03:00
|
|
|
|
my ($coderef, $handle, $fn) = @_;
|
|
|
|
|
return $compiled_code->{$coderef} if $compiled_code->{$coderef};
|
2009-03-13 20:47:24 +03:00
|
|
|
|
my $h;
|
|
|
|
|
if ($self->{cache_dir})
|
|
|
|
|
{
|
|
|
|
|
$h = $self->{cache_dir}.md5_hex($$coderef).'.pl';
|
|
|
|
|
if (-e $h)
|
|
|
|
|
{
|
|
|
|
|
$compiled_code->{$coderef} = do $h;
|
|
|
|
|
if ($@)
|
|
|
|
|
{
|
|
|
|
|
warn "[Template] error compiling '$handle': [$@] in FILE: $h";
|
|
|
|
|
unlink $h;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return $compiled_code->{$coderef};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2007-05-21 03:34:53 +04:00
|
|
|
|
|
2008-08-15 21:31:24 +04:00
|
|
|
|
$self->{cur_template_path} = $self->{cur_template} = '';
|
2009-01-06 01:39:48 +03:00
|
|
|
|
if ($fn)
|
2008-08-15 21:31:24 +04:00
|
|
|
|
{
|
2009-01-06 01:39:48 +03:00
|
|
|
|
$self->{cur_template} = $fn;
|
2009-06-29 15:40:21 +04:00
|
|
|
|
$self->{cur_template} = substr($self->{cur_template}, length($self->{root}))
|
|
|
|
|
if substr($self->{cur_template}, 0, length($self->{root})) eq $self->{root};
|
2008-08-15 21:31:24 +04:00
|
|
|
|
$self->{cur_template} =~ s/\.[^\.]+$//iso;
|
|
|
|
|
$self->{cur_template} =~ s/:+//gso;
|
|
|
|
|
$self->{cur_template} =~ s!/+!:!gso;
|
|
|
|
|
$self->{cur_template} =~ s/[^\w_:]+//gso;
|
2009-01-06 01:39:48 +03:00
|
|
|
|
$self->{cur_template_path} = '->{"' . join('"}->{"',
|
|
|
|
|
map { lc } split /:/, $self->{cur_template}) . '"}';
|
2008-08-15 21:31:24 +04:00
|
|
|
|
}
|
|
|
|
|
|
2009-06-29 15:40:21 +04:00
|
|
|
|
my $nesting = 0;
|
|
|
|
|
my $included = {};
|
|
|
|
|
my @code_lines = ();
|
|
|
|
|
my @block_names = ('.');
|
|
|
|
|
my ($cbstart, $cbcount, $cbplus, $mm);
|
|
|
|
|
|
2009-01-06 01:39:48 +03:00
|
|
|
|
my $code = $$coderef;
|
2009-02-23 22:33:41 +03:00
|
|
|
|
Encode::_utf8_on($code) if $self->{use_utf8};
|
2009-01-25 19:56:18 +03:00
|
|
|
|
|
2009-06-29 15:40:21 +04:00
|
|
|
|
# комментарии <!--# ... #-->
|
2007-05-21 03:34:53 +04:00
|
|
|
|
$code =~ s/\s*<!--#.*?#-->//gos;
|
2009-06-29 15:40:21 +04:00
|
|
|
|
# форматирование кода для красоты
|
|
|
|
|
$code =~ s/(?:^|\n)\s*(<!--\s*(?:BEGIN|END|IF\S*|ELSE\S*|INCLUDE|SET|ENDSET)\s+.*?-->)\s*(?:$|\n)/\x01$1\x01\n/gos;
|
|
|
|
|
1 while $code =~ s/(?<!\x01)<!--\s*(?:BEGIN|END|IF\S*|ELSE\S*|INCLUDE|SET|ENDSET)\s+.*?-->/\x01$&/gom;
|
|
|
|
|
1 while $code =~ s/<!--\s*(?:BEGIN|END|IF\S*|ELSE\S*|INCLUDE|SET|ENDSET)\s+.*?-->(?!\x01)/$&\x01/gom;
|
|
|
|
|
|
|
|
|
|
# ' и \ -> \' и \\
|
|
|
|
|
$code =~ s/\'|\\/\\$&/gos;
|
2008-02-13 18:10:23 +03:00
|
|
|
|
|
2009-06-29 15:40:21 +04:00
|
|
|
|
# "первая замена"
|
|
|
|
|
$code =~
|
|
|
|
|
s%(?>\%+) *\w+[\w ]*?(?>\%+)|(?>\%+)|\{[a-z0-9\-_]+\.\#\}|\{((?:[a-z0-9\-_]+\.)*)([a-z0-9\-_]+)((?:->[a-z0-9\-_]+)*)(?:\/([a-z0-9\-_]+))?\}%
|
|
|
|
|
$self->generate_xx_ref($&,$1,$2,$3,$4)
|
|
|
|
|
%goise;
|
2007-05-21 03:34:53 +04:00
|
|
|
|
|
2009-06-29 15:40:21 +04:00
|
|
|
|
# \n -> \n\x01
|
|
|
|
|
$code =~ s/\n/\n\x01/gos;
|
2007-09-11 02:46:55 +04:00
|
|
|
|
|
2009-06-29 15:40:21 +04:00
|
|
|
|
# разбиваем код на строки
|
|
|
|
|
@code_lines = split /\x01/, $code;
|
|
|
|
|
foreach (@code_lines)
|
2008-08-15 21:31:24 +04:00
|
|
|
|
{
|
2009-06-29 15:40:21 +04:00
|
|
|
|
next unless $_;
|
|
|
|
|
if (/^\s*<!--\s*BEGIN\s+([a-z0-9\-_]+?)\s+([a-z \t\-_0-9]*)-->\s*$/iso)
|
2009-06-27 17:45:22 +04:00
|
|
|
|
{
|
2009-06-29 15:40:21 +04:00
|
|
|
|
# начало блока
|
|
|
|
|
$nesting++;
|
|
|
|
|
$block_names[$nesting] = $1;
|
|
|
|
|
$self->{current_namespace} = join '.', @block_names;
|
|
|
|
|
$cbstart = 0; $cbcount = ''; $cbplus = '++';
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
my $o2 = $2;
|
|
|
|
|
if ($o2 =~ /^[ \t]*AT ([0-9]+)[ \t]*(?:([0-9]+)[ \t]*)?$/)
|
|
|
|
|
{
|
|
|
|
|
$cbstart = $1;
|
|
|
|
|
$cbcount = $2 ? $1+$2 : 0;
|
|
|
|
|
}
|
|
|
|
|
elsif ($o2 =~ /^[ \t]*MOD ([1-9][0-9]*) ([0-9]+)[ \t]*$/)
|
|
|
|
|
{
|
|
|
|
|
$cbstart = $2;
|
|
|
|
|
$cbplus = '+='.$1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# либо min (N, $cbcount) если $cbcount задано
|
|
|
|
|
# либо просто N если нет
|
|
|
|
|
if ($nesting < 2)
|
|
|
|
|
{
|
|
|
|
|
# блок не вложенный
|
|
|
|
|
if ($cbcount) { $_ = "my \$_${1}_count = min (scalar(\@\{\$self->{tpldata}{'$1.'} || []\}), " . $cbcount . ');'; }
|
|
|
|
|
else { $_ = "my \$_${1}_count = scalar(\@{\$self->{tpldata}{'$1.'} || []});"; }
|
|
|
|
|
# начало цикла for
|
|
|
|
|
$_ .= "\nfor (my \$_${1}_i = $cbstart; \$_${1}_i < \$_${1}_count; \$_${1}_i$cbplus)\n{";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
# блок вложенный
|
|
|
|
|
my $namespace = substr (join ('.', @block_names), 2);
|
|
|
|
|
my $varref = $self->generate_block_data_ref ($namespace);
|
|
|
|
|
if ($cbcount) { $_ = "my \$_${1}_count = min (scalar(\@\{$varref || []\}), $cbcount);"; }
|
|
|
|
|
else { $_ = "my \$_${1}_count = ($varref && \@\{$varref\}) ? scalar(\@\{$varref || []\}) : 0;"; }
|
|
|
|
|
$_ .= "\nfor (my \$_${1}_i = $cbstart; \$_${1}_i < \$_${1}_count; \$_${1}_i$cbplus)\n{";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
elsif (/^\s*<!--\s*END\s+(.*?)-->\s*$/so)
|
|
|
|
|
{
|
|
|
|
|
# чётко проверяем: блок нельзя завершать чем попало
|
|
|
|
|
delete $block_names[$nesting--] if ($nesting > 0 && trim ($1) eq $block_names[$nesting]);
|
|
|
|
|
$self->{current_namespace} = join '.', @block_names;
|
|
|
|
|
$_ = "} # END $1";
|
|
|
|
|
}
|
|
|
|
|
elsif (/^\s*<!--\s*(ELS(?:E\s*)?)?IF(\S*)\s+((?:[a-z0-9\-_]+\.)*)([a-z0-9\-_]+|#)((?:->[a-z0-9\-_]+)*)(?:\/([a-z0-9\-_]+))?\s*-->\s*$/iso)
|
|
|
|
|
{
|
|
|
|
|
my ($elsif, $varref, $t, $ta) = (
|
|
|
|
|
($1 ? "} elsif" : "if"),
|
|
|
|
|
$self->generate_block_varref($3, $4, $5, $6, 1),
|
|
|
|
|
split /:/, $2, 2
|
|
|
|
|
);
|
|
|
|
|
if ($ta && $t && $self->{tests}->{lc $t}->[1])
|
|
|
|
|
{
|
|
|
|
|
$ta =~ s/\'|\\/\\$&/gso;
|
|
|
|
|
$ta = ", '$ta'";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
$ta = "";
|
|
|
|
|
}
|
|
|
|
|
$t = $self->{tests}->{lc $t}->[0] || '' if $t;
|
|
|
|
|
$_ = "$elsif ($t($varref$ta)) {";
|
|
|
|
|
}
|
|
|
|
|
elsif (/^\s*<!--\s*ELSE\s*-->\s*$/so)
|
|
|
|
|
{
|
|
|
|
|
$_ = "} else {";
|
|
|
|
|
}
|
|
|
|
|
elsif (/^\s*<!--\s*INCLUDE\s*([^'\s]+)\s*-->\s*$/so)
|
|
|
|
|
{
|
|
|
|
|
my $n = $1;
|
|
|
|
|
$_ = "\$t .= \$self->parse('_INCLUDE$n');";
|
|
|
|
|
unless ($included->{$n})
|
|
|
|
|
{
|
|
|
|
|
$_ = "\$self->set_filenames('_INCLUDE$n' => '$n');\n $_";
|
|
|
|
|
$included->{$n} = 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
elsif (/^\s*<!--\s*SET\s+((?:[a-z0-9\-_]+\.)*)([a-z0-9\-_\/]+)\s*-->\s*$/iso)
|
|
|
|
|
{
|
|
|
|
|
my $varref = $self->generate_block_data_ref($1, 1)."{'$2'}";
|
|
|
|
|
$_ = "$varref = eval {\nmy \$t = '';";
|
|
|
|
|
}
|
|
|
|
|
elsif (/^\s*<!--\s*ENDSET\s*-->\s*$/so)
|
|
|
|
|
{
|
|
|
|
|
$_ = "return \$t;\n};";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
$_ = "\$t .= '$_';";
|
2007-05-21 03:34:53 +04:00
|
|
|
|
}
|
|
|
|
|
}
|
2009-02-01 19:37:33 +03:00
|
|
|
|
|
2009-06-29 15:40:21 +04:00
|
|
|
|
# собираем код в строку
|
2009-01-06 01:39:48 +03:00
|
|
|
|
$code = ($self->{use_utf8} ? "\nuse utf8;\n" : "").
|
|
|
|
|
'sub {
|
|
|
|
|
my $self = shift;
|
|
|
|
|
my $t = "";
|
|
|
|
|
my $_current_template = [ split /:/, \'' . $self->{cur_template} . '\' ];
|
2009-06-29 15:40:21 +04:00
|
|
|
|
' . join("\n", @code_lines) . '
|
2009-01-06 01:39:48 +03:00
|
|
|
|
return $t;
|
|
|
|
|
}';
|
2009-03-13 20:47:24 +03:00
|
|
|
|
if ($h)
|
|
|
|
|
{
|
|
|
|
|
my $fd;
|
|
|
|
|
if (open $fd, ">$h")
|
|
|
|
|
{
|
|
|
|
|
print $fd $code;
|
|
|
|
|
close $fd;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
warn "[Template] error caching '$handle': $! while opening $h";
|
|
|
|
|
}
|
|
|
|
|
}
|
2009-01-06 01:39:48 +03:00
|
|
|
|
|
|
|
|
|
$compiled_code->{$coderef} = eval $code;
|
|
|
|
|
die "[Template] error compiling '$handle': [$@] in CODE:\n$code" if $@;
|
|
|
|
|
|
|
|
|
|
return $compiled_code->{$coderef};
|
2007-05-21 03:34:53 +04:00
|
|
|
|
}
|
|
|
|
|
|
2009-06-29 15:40:21 +04:00
|
|
|
|
# Функция для "первой замены"
|
|
|
|
|
sub generate_xx_ref
|
2008-08-15 21:31:24 +04:00
|
|
|
|
{
|
2008-02-13 18:10:23 +03:00
|
|
|
|
my $self = shift;
|
2009-06-29 15:40:21 +04:00
|
|
|
|
my @a = @_;
|
|
|
|
|
my $a = shift @a;
|
|
|
|
|
if ($a =~ /^\%\%|\%\%$/so)
|
|
|
|
|
{
|
|
|
|
|
my $r = $a;
|
|
|
|
|
$r =~ s/^\%\%/\%/so;
|
|
|
|
|
$r =~ s/\%\%$/\%/so;
|
|
|
|
|
return $r;
|
2008-08-15 21:31:24 +04:00
|
|
|
|
}
|
2009-06-29 15:40:21 +04:00
|
|
|
|
elsif ($a =~ /^\%(.+)\%$/so)
|
2008-08-15 21:31:24 +04:00
|
|
|
|
{
|
2009-06-29 15:40:21 +04:00
|
|
|
|
return $self->language_xform($self->{current_namespace}, $1);
|
2008-08-15 21:31:24 +04:00
|
|
|
|
}
|
2009-06-29 15:40:21 +04:00
|
|
|
|
elsif ($a =~ /^\%\%+$/so)
|
2008-08-15 21:31:24 +04:00
|
|
|
|
{
|
2009-06-29 15:40:21 +04:00
|
|
|
|
return substr($a, 1);
|
2008-08-15 21:31:24 +04:00
|
|
|
|
}
|
2009-06-29 15:40:21 +04:00
|
|
|
|
elsif ($a =~ /^\{([a-z0-9\-_]+)\.\#\}$/iso)
|
2008-08-15 21:31:24 +04:00
|
|
|
|
{
|
2009-06-29 15:40:21 +04:00
|
|
|
|
return '\'.(1+$_'.$1.'_i).\'';
|
2008-08-15 21:31:24 +04:00
|
|
|
|
}
|
2009-06-29 15:40:21 +04:00
|
|
|
|
elsif ($a =~ /^\{.*\}$/so)
|
2008-08-15 21:31:24 +04:00
|
|
|
|
{
|
2009-06-29 15:40:21 +04:00
|
|
|
|
return "' . " . $self->generate_block_varref(@a) . " . '";
|
2008-02-13 18:10:23 +03:00
|
|
|
|
}
|
2009-06-29 15:40:21 +04:00
|
|
|
|
return $a;
|
2008-02-13 18:10:23 +03:00
|
|
|
|
}
|
|
|
|
|
|
2009-06-29 15:40:21 +04:00
|
|
|
|
# Функция генерирует подстановку переменной шаблона
|
|
|
|
|
# $varref = $obj->generate_block_varref ($namespace, $varname, $varhash)
|
|
|
|
|
sub generate_block_varref
|
2008-08-15 21:31:24 +04:00
|
|
|
|
{
|
2007-05-21 03:34:53 +04:00
|
|
|
|
my $self = shift;
|
2009-06-29 15:40:21 +04:00
|
|
|
|
my ($namespace, $varname, $varhash, $varconv) = @_;
|
|
|
|
|
my $varref;
|
2009-06-27 17:45:22 +04:00
|
|
|
|
|
2009-06-29 15:40:21 +04:00
|
|
|
|
$varconv = undef unless $self->{conv}->{$varconv};
|
|
|
|
|
# обрезаем точки в конце
|
|
|
|
|
$namespace =~ s/\.*$//o;
|
|
|
|
|
|
|
|
|
|
$varref = $self->generate_block_data_ref ($namespace, 1);
|
|
|
|
|
# добавляем имя переменной
|
|
|
|
|
if ($varname ne '#')
|
2009-06-27 17:45:22 +04:00
|
|
|
|
{
|
2009-06-29 15:40:21 +04:00
|
|
|
|
$varref .= "{'$varname'}";
|
2009-01-06 01:39:48 +03:00
|
|
|
|
}
|
2009-06-29 15:40:21 +04:00
|
|
|
|
else
|
2008-09-02 00:19:55 +04:00
|
|
|
|
{
|
2009-06-29 15:40:21 +04:00
|
|
|
|
$varref = $namespace;
|
|
|
|
|
$varref =~ s/^(?:.*\.)?([^\.]+)\.*$/$1/;
|
|
|
|
|
$varref = '(1+$_'.$varref.'_i)';
|
2009-06-27 17:45:22 +04:00
|
|
|
|
}
|
2009-06-29 15:40:21 +04:00
|
|
|
|
|
|
|
|
|
# добавляем путь по вложенным хешам/массивам
|
|
|
|
|
if ($varhash)
|
2009-06-27 17:45:22 +04:00
|
|
|
|
{
|
2009-06-29 15:40:21 +04:00
|
|
|
|
$varhash = [ split /->/, $varhash ];
|
|
|
|
|
foreach (@$varhash)
|
2009-06-27 17:46:44 +04:00
|
|
|
|
{
|
2009-06-29 15:40:21 +04:00
|
|
|
|
if (/^\d+$/so)
|
|
|
|
|
{
|
|
|
|
|
$varref .= "[$_]";
|
|
|
|
|
}
|
|
|
|
|
elsif ($_)
|
2008-09-02 00:19:55 +04:00
|
|
|
|
{
|
2009-06-29 15:40:21 +04:00
|
|
|
|
$varref .= "{'$_'}";
|
2008-09-02 00:19:55 +04:00
|
|
|
|
}
|
|
|
|
|
}
|
2009-06-27 17:20:21 +04:00
|
|
|
|
}
|
2007-05-21 03:34:53 +04:00
|
|
|
|
|
2009-06-29 15:40:21 +04:00
|
|
|
|
# генерируем преобразование
|
|
|
|
|
if ($varconv)
|
2009-06-27 17:46:44 +04:00
|
|
|
|
{
|
2009-06-29 15:40:21 +04:00
|
|
|
|
unless (ref $self->{conv}->{$varconv})
|
2009-06-27 17:46:44 +04:00
|
|
|
|
{
|
2009-06-29 15:40:21 +04:00
|
|
|
|
$varref = "(" . $self->{conv}->{$varconv} . "($varref))";
|
2009-06-27 17:46:44 +04:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2009-06-29 15:40:21 +04:00
|
|
|
|
my $f = $self->{conv}->{$varconv};
|
|
|
|
|
unless ($namespace)
|
|
|
|
|
{
|
|
|
|
|
$f = &$f($self, $varname, $varref);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
$f = &$f($self, "$namespace.$varname", $varref);
|
|
|
|
|
}
|
|
|
|
|
$varref = "($f)";
|
2009-06-27 17:46:44 +04:00
|
|
|
|
}
|
|
|
|
|
}
|
2008-08-15 21:31:24 +04:00
|
|
|
|
|
2009-06-29 15:40:21 +04:00
|
|
|
|
return $varref;
|
2007-05-21 03:34:53 +04:00
|
|
|
|
}
|
|
|
|
|
|
2009-06-29 15:40:21 +04:00
|
|
|
|
# Функция генерирует обращение к массиву переменных блока
|
|
|
|
|
# $blockref = $obj->generate_block_data_ref ($block, $include_last_iterator)
|
|
|
|
|
sub generate_block_data_ref
|
2008-08-15 21:31:24 +04:00
|
|
|
|
{
|
2007-05-21 03:34:53 +04:00
|
|
|
|
my $self = shift;
|
2009-06-29 15:40:21 +04:00
|
|
|
|
my $blockref = '$self->{tpldata}';
|
|
|
|
|
my ($block, $withlastit) = @_;
|
2009-06-27 17:45:22 +04:00
|
|
|
|
|
2009-06-29 15:40:21 +04:00
|
|
|
|
# для корневого блока
|
|
|
|
|
return '$self->{tpldata}{\'.\'}' . ($withlastit ? '[0]' : '')
|
|
|
|
|
if $block =~ /^\.*$/so;
|
|
|
|
|
|
|
|
|
|
# строим цепочку блоков
|
|
|
|
|
$block =~ s/\.+$//so;
|
|
|
|
|
my @blocks = split (/\.+/, $block);
|
|
|
|
|
my $lastblock = pop (@blocks);
|
|
|
|
|
$blockref .= "{'$_.'}[\$_${_}_i]" foreach @blocks;
|
|
|
|
|
$blockref .= "{'$lastblock.'}";
|
|
|
|
|
|
|
|
|
|
# добавляем последний итератор, если надо
|
|
|
|
|
$blockref .= "[\$_${lastblock}_i]" if ($withlastit);
|
|
|
|
|
return $blockref;
|
2007-05-21 03:34:53 +04:00
|
|
|
|
}
|
|
|
|
|
|
2009-01-06 01:39:48 +03:00
|
|
|
|
# Функция компилирует ссылку на данные ленгпака
|
2008-08-15 21:31:24 +04:00
|
|
|
|
sub language_ref
|
|
|
|
|
{
|
|
|
|
|
my $self = shift;
|
2009-06-29 15:40:21 +04:00
|
|
|
|
my ($var, $varref, $value, $ifnull) = @_;
|
2008-08-15 21:31:24 +04:00
|
|
|
|
my $code = '';
|
|
|
|
|
$code .= '->{' . lc($_) . '}' foreach split /\.+/, $var;
|
|
|
|
|
$code .= '->{' . $varref . '}';
|
2009-01-28 02:32:22 +03:00
|
|
|
|
$code = ($self->{cur_template_path} ?
|
2008-08-15 21:31:24 +04:00
|
|
|
|
'(($self->{lang}' . $self->{cur_template_path} . $code . ') || ' : '') .
|
2009-01-28 02:32:22 +03:00
|
|
|
|
'($self->{lang}' . $code . ')';
|
2009-06-29 15:40:21 +04:00
|
|
|
|
$code .= ' || (' . $varref . ')' unless $ifnull;
|
|
|
|
|
$code .= ')';
|
2008-08-15 21:31:24 +04:00
|
|
|
|
return $code;
|
|
|
|
|
}
|
|
|
|
|
|
2009-06-29 15:40:21 +04:00
|
|
|
|
# Функция компилирует ссылку на данные ленгпака
|
|
|
|
|
sub language_refnull { language_ref($_[0], $_[1], $_[2], $_[3], 1) }
|
|
|
|
|
|
2009-01-06 01:39:48 +03:00
|
|
|
|
# Compile-time вычисление language_ref
|
2008-08-15 21:31:24 +04:00
|
|
|
|
sub language_xform
|
|
|
|
|
{
|
|
|
|
|
my $self = shift;
|
2009-06-29 15:40:21 +04:00
|
|
|
|
my ($ns, $value) = @_;
|
2008-08-15 21:31:24 +04:00
|
|
|
|
my ($ca, $cb) = ($self->{lang}, $self->{lang});
|
|
|
|
|
foreach (split /:/, $self->{cur_template})
|
|
|
|
|
{
|
|
|
|
|
$cb = $cb->{lc $_} if $cb;
|
|
|
|
|
}
|
2009-06-29 15:40:21 +04:00
|
|
|
|
if ($ns)
|
2008-08-15 21:31:24 +04:00
|
|
|
|
{
|
2009-06-29 15:40:21 +04:00
|
|
|
|
foreach (split /\./, $ns)
|
2008-08-15 21:31:24 +04:00
|
|
|
|
{
|
|
|
|
|
$ca = $ca->{lc $_} if $ca;
|
|
|
|
|
$cb = $cb->{lc $_} if $cb;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$ca = $ca->{$value} if $ca;
|
|
|
|
|
$cb = $cb->{$value} if $cb;
|
|
|
|
|
return $ca || $cb;
|
|
|
|
|
}
|
|
|
|
|
|
2009-06-29 15:40:21 +04:00
|
|
|
|
# Тесты
|
|
|
|
|
|
|
|
|
|
sub test_even { !($_[0] & 1) }
|
|
|
|
|
sub test_odd { ($_[0] & 1 ? 1 : 0) }
|
|
|
|
|
sub test_eq { $_[0] eq $_[1] }
|
|
|
|
|
|
|
|
|
|
sub test_mod
|
|
|
|
|
{
|
|
|
|
|
my ($div, $mod) = split /\s*,\s*/, $_[1], 2;
|
|
|
|
|
$mod ||= 0;
|
|
|
|
|
return ($_[0] % $div) == $mod;
|
|
|
|
|
}
|
|
|
|
|
|
2007-05-21 03:34:53 +04:00
|
|
|
|
1;
|
2008-08-15 21:31:24 +04:00
|
|
|
|
__END__
|
2009-06-29 15:40:21 +04:00
|
|
|
|
|
|
|
|
|
=head1 Шаблонизатор VMX::Template, старая версия
|
|
|
|
|
|
|
|
|
|
Sway::Template - простой и быстрый шаблонизатор, выросший из шаблонизатора phpBB2.
|
|
|
|
|
|
|
|
|
|
Legacy-версия. Новая стабильная версия входит в Solstice Homepage System (см. ветку svn branches/solstice).
|
|
|
|
|
|
|
|
|
|
=head1 Программный интерфейс
|
|
|
|
|
|
|
|
|
|
# Конструктор
|
|
|
|
|
$template = VMX::Template->new(
|
|
|
|
|
root => '.', # каталог с шаблонами
|
|
|
|
|
reload => 1, # если 0, шаблоны не будут перечитываться с диска, и вызовов stat() происходить не будет
|
|
|
|
|
wrapper => undef, # фильтр, вызываемый перед выдачей результата parse
|
|
|
|
|
cache_dir => undef, # необязательный кэш, ускоряющий работу только в случае частых инициализаций интерпретатора
|
|
|
|
|
use_utf8 => undef, # шаблоны в UTF-8 и с флагом UTF-8
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
# Выбор файла шаблона:
|
|
|
|
|
$template->set_filenames('alias' => 'file-name.tpl');
|
|
|
|
|
|
|
|
|
|
# Задание кода шаблона "на месте":
|
|
|
|
|
$template->set_filenames('alias' => \ 'код шаблона');
|
|
|
|
|
|
|
|
|
|
# Загрузка файлов автоматических переводов:
|
|
|
|
|
# Каждый такой файл содержит код на языке Perl, возвращающий ссылку на хеш.
|
|
|
|
|
# Загрузка происходит с автоматическим пересчитыванием с диска при изменениях.
|
|
|
|
|
$template->load_lang("файл1.pl", "файл2.pl");
|
|
|
|
|
|
|
|
|
|
# Загрузка хешей в качестве языковых данных:
|
|
|
|
|
$template->load_lang_hashes({ block => { var => 'перевод' } });
|
|
|
|
|
|
|
|
|
|
# Присвоение переменных корневого уровня:
|
|
|
|
|
$template->assign_vars(KEY => "значение");
|
|
|
|
|
|
|
|
|
|
# Присвоение переменных блока-цикла в новую итерацию блока:
|
|
|
|
|
$template->assign_block_vars("block", KEY => "значение");
|
|
|
|
|
# Или то же, но без копирования готового хешрефа:
|
|
|
|
|
$template->assign_block_vars("block", { KEY => "значение" });
|
|
|
|
|
# Или то же, но в последнюю заданную итерацию блока, а не в новую:
|
|
|
|
|
$template->append_block_vars("block", KEY => "значение");
|
|
|
|
|
|
|
|
|
|
# Выполнение шаблона и получение результатов (страницы):
|
|
|
|
|
# Выбранные файлы автоматически перезачитываются с диска при изменениях.
|
|
|
|
|
# Код кэшируется на 2-х уровнях: на диске в виде perl-кода и в памяти в скомпилированном виде.
|
|
|
|
|
$page = $template->parse('alias');
|
|
|
|
|
|
|
|
|
|
# Очистка сохранённых данных для генерации новой страницы:
|
|
|
|
|
$template->clear;
|
|
|
|
|
|
|
|
|
|
=head1 Синтаксис
|
|
|
|
|
|
|
|
|
|
Идея синтаксиса шаблонов - быть максимально простым и не поганить HTML-код.
|
|
|
|
|
|
|
|
|
|
=head2 Комментарии
|
|
|
|
|
|
|
|
|
|
<!--# Комментарий, не попадающий в результат исполнения #-->
|
|
|
|
|
|
|
|
|
|
=head2 Включение (INCLUDE)
|
|
|
|
|
|
|
|
|
|
Включение шаблона с именем filename.tpl:
|
|
|
|
|
<!-- INCLUDE filename.tpl -->
|
|
|
|
|
|
|
|
|
|
=head2 Подстановки переменных
|
|
|
|
|
|
|
|
|
|
{переменная} или {переменная->путь} или {переменная/преобразование}
|
|
|
|
|
|
|
|
|
|
Где переменная - цепочка идентификаторов, разделённая точками.
|
|
|
|
|
Все, кроме последнего - имена блоков, последний - имя переменной.
|
|
|
|
|
После имени переменной может идти путь по ней, если она представляет собой хеш или массив.
|
|
|
|
|
Путь - цепочка идентификаторов или чисел, разделённая стрелками ->
|
|
|
|
|
Например, {DOMAIN} - подстановка корневой переменной DOMAIN.
|
|
|
|
|
А {site.url.DOMAIN} - подстановка переменной DOMAIN блока url, вложенного в блок site.
|
|
|
|
|
А {site.url.CONFIG->rules->0/s} - подстановка первого элемента массива, вложенного
|
|
|
|
|
в переменную CONFIG блока url, вложенного в блок site, по ключу rules, в
|
|
|
|
|
HTML-безопасном виде (т.е. с преобразованием s, см.ниже).
|
|
|
|
|
|
|
|
|
|
А преобразование - одно из:
|
|
|
|
|
|
|
|
|
|
T - удаление всех HTML тегов из значения
|
|
|
|
|
H - удаление всех HTML тегов из значения, кроме разрешённых в VMX::Common
|
|
|
|
|
i - преобразование значения к целому числу
|
|
|
|
|
s - замена всех символов < > & " ' на HTML сущности < > & " '
|
|
|
|
|
l - перевод значения в нижний регистр
|
|
|
|
|
u - перевод значения в верхний регистр
|
|
|
|
|
q - замена всех символов " ' \ на \" \' \\
|
|
|
|
|
L - замена значения на его контекстный перевод, если таковой имеется
|
|
|
|
|
Lz - замена значения на его контекстный перевод, если таковой имеется, и на пустое значение, если нет
|
|
|
|
|
|
|
|
|
|
=head2 Условный вывод (IF)
|
|
|
|
|
|
|
|
|
|
<!-- IF[тест] подстановка -->
|
|
|
|
|
<!-- END -->
|
|
|
|
|
|
|
|
|
|
Где подстановка - любая допустимая в {фигурных.скобках} подстановка.
|
|
|
|
|
|
|
|
|
|
Тесты:
|
|
|
|
|
|
|
|
|
|
! - логическое отрицание
|
|
|
|
|
EVEN - истина в случае, если значение - чётное число
|
|
|
|
|
ODD - истина в случае, если значение - нечётное число
|
|
|
|
|
EQ:строка - истина в случае, если значение переменной совпадает с "строка"
|
|
|
|
|
MOD:делитель,остаток - истина в случае, если остаток от деления значения переменной на "делитель" равен "остатку"
|
|
|
|
|
|
|
|
|
|
Например:
|
|
|
|
|
|
|
|
|
|
<!-- IF! site.url.CONFIG->rules->0/Lz -->
|
|
|
|
|
<!-- END -->
|
|
|
|
|
|
|
|
|
|
=head2 Циклический вывод (BEGIN block)
|
|
|
|
|
|
|
|
|
|
Циклический вывод, начать с итерации start (считаются с нуля), и вывести максимум по итерацию count.
|
|
|
|
|
Либо обработать все итерации с номерами, дающими остаток от деления на "div" равный "mod".
|
|
|
|
|
Имя блока состоит из латинских символов, цифр и символа '_' (подчёркивания).
|
|
|
|
|
|
|
|
|
|
Итак, варианты начала:
|
|
|
|
|
|
|
|
|
|
<!-- BEGIN block -->
|
|
|
|
|
<!-- BEGIN block AT start -->
|
|
|
|
|
<!-- BEGIN block AT start count -->
|
|
|
|
|
<!-- BEGIN block MOD div mod -->
|
|
|
|
|
|
|
|
|
|
Конец блока - всегда <!-- END block -->.
|
|
|
|
|
|
|
|
|
|
Пример:
|
|
|
|
|
|
|
|
|
|
<!-- BEGIN block -->
|
|
|
|
|
<!-- BEGIN inner_block -->
|
|
|
|
|
{block.inner_block.VARIABLE}
|
|
|
|
|
{inner_block.#}
|
|
|
|
|
<!-- END inner_block -->
|
|
|
|
|
<!-- END block -->
|
|
|
|
|
|
|
|
|
|
И специальный случай: внутри внутреннего блока {inner_block.#} имеет значением
|
|
|
|
|
номер текущей итерации внутреннего блока, считающийся с 1. Именно {inner_block.#},
|
|
|
|
|
а не {block.inner_block.#}.
|
|
|
|
|
|
|
|
|
|
=head2 Автоматические контекстные переводы
|
|
|
|
|
|
|
|
|
|
Они доступны в двух вариантах: %Переводимая строка% и {переменная/L}.
|
|
|
|
|
|
|
|
|
|
Идея в том, чтобы автоматически заменять строки на другие строки в зависимости
|
|
|
|
|
от их расположения в файле шаблона (контекста).
|
|
|
|
|
|
|
|
|
|
Контекст включает в себя:
|
|
|
|
|
|
|
|
|
|
- путь к файлу шаблона.
|
|
|
|
|
- путь к текущему блоку-циклу.
|
|
|
|
|
- в случае перевода переменной - имя переменной.
|
|
|
|
|
|
|
|
|
|
Данные перевода представляют собой вложенный хеш. Подставляемые строки
|
|
|
|
|
берутся по наиболее детализированному доступному пути в нём. Т.е. если есть
|
|
|
|
|
->1->2->3, берётся ->1->2->3, если нет ->1->2->3, но есть ->2->3, берётся ->2->3,
|
|
|
|
|
иначе берётся просто ->3.
|
|
|
|
|
|
|
|
|
|
Соответственно и загружаемые файлы переводов должны содержать просто код,
|
|
|
|
|
возвращающий хешреф. Т.е. в простейшем случае - просто хешреф.
|
|
|
|
|
|
|
|
|
|
=cut
|