view Lib/DOM/Providers/Page.pm @ 72:eac47fa4f262

docs
author wizard
date Fri, 26 Mar 2010 16:26:31 +0300
parents 16ada169ca75
children
line wrap: on
line source

use strict;

package DOM::Providers::Page;
use Template::Provider;
#use PerfCounter;
use DOM::Page;
use Common;
use Encode;

our @ISA= qw(Object Exporter);

our $UseIndexPage;  #optional
our $PagesPath;     #required
our $IncludesPath;  #optional
our $CacheSize;     #optional
our $CachePath;     #optional
our $Encoding;      #optional
our $AllowExtPath;  #optional
our $PageResolver;  #optional


BEGIN {
    DeclareProperty('PageResolver');
    DeclareProperty('PagesBase');
    DeclareProperty('IndexPage');
    DeclareProperty('TemplatesProvider');
    DeclareProperty('PageEnc');
}

sub as_list {
    return( map { UNIVERSAL::isa($_,'ARRAY') ? @{$_} : defined $_ ? $_ : () } @_ );
}

sub GetProviderInfo {
    return {
        Name => 'Page',
        Host => 'DOM::Site',
        Methods => {
            LoadPage => \&SiteLoadPage,
            ReleasePage => \&SiteReleasePage,
        }
    }
}

sub CTOR {
    my ($this,%args) = @_;
    
    $this->{$PageResolver} = $args{'PageResolver'};
    $this->{$PagesBase} = $args{'TemplatesPath'};
    $this->{$IndexPage} = $args{'IndexPage'} || 'index.html';
    $this->{$PageEnc} = $args{'Encoding'};
    $this->{$TemplatesProvider} = new Template::Provider( INCLUDE_PATH => [$this->{$PagesBase}, as_list($args{'IncludePath'}) ], COMPILE_DIR => $args{'CachePath'}, CACHE_SIZE => $args{'CacheSize'}, ENCODING => $args{'Encoding'}, ABSOLUTE => $AllowExtPath, RELATIVE => $AllowExtPath, INTERPOLATE => 1, PRE_CHOMP => 3);
}

sub ResolveId {
    my ($this,$pageId) = @_;
    
    if ($this->{$PageResolver} && UNIVERSAL::can($this->{$PageResolver},'ResolveId')) {
        return $this->{$PageResolver}->ResolveId($pageId);
    } else {
        return grep { $_ } split /\//,$pageId;
    }
}

sub MakePageId {
    my ($this,$raPath) = @_;
    
    if ($this->{$PageResolver} && UNIVERSAL::can($this->{$PageResolver},'MakeId')) {
        return $this->{$PageResolver}->MakeId($raPath);
    } else {
        return join '/',@$raPath;
    }
}

sub PageIdToURL {
    my ($this,$pageId) = @_;
    
    if ($this->{$PageResolver} && UNIVERSAL::can($this->{$PageResolver},'PageIdToURL')) {
        return $this->{$PageResolver}->PageIdToURL($pageId);
    } else {
        return '/'.$pageId;
    }
}

sub SiteLoadPage {
    my ($this,$site,$pageId) = @_;
    
    return $site->RegisterObject('Page', $this->LoadPage($pageId, Site => $site));
}
sub LoadPage {
    my ($this,$pageId,%args) = @_;
    
    #StartTimeCounter('LoadPageTime');

    my @pathPage = $this->ResolveId($pageId);

    my $pageNode = $this->LoadNode(\@pathPage);
    
    pop @pathPage;
    
    my @pathNode;
    
    # поскольку путь указан относительно корневого контейнера, то нужно его добавить в начало
    my @NavChain = map { push @pathNode, $_; $this->LoadNode(\@pathNode); } ('.',@pathPage);
    
    if ($pageNode->{'Type'} eq 'Section') {
        push @NavChain,$pageNode;
        $pageNode = $this->LoadNode($pageNode->{'pathIndexPage'});
    }
    
    # формируем меню страницы
    my %PageMenus;
    foreach my $MenuSet (map { $_->{'Menus'}} @NavChain, $pageNode->{'Menus'} ) {
        foreach my $menuName (keys %$MenuSet) {
            if ($PageMenus{$menuName}) {
                $PageMenus{$menuName}->Merge($MenuSet->{$menuName});
            } else {
                $PageMenus{$menuName} = $MenuSet->{$menuName};
            }
        }
    }
    
    # формируем ключевые слова и свойства
    my @keywords;
    my %Props;
    foreach my $PropSet ( (map { $_->{'Props'}} @NavChain), $pageNode->{'Props'} ) {
        if(ref $PropSet->{'Keywords'} eq 'ARRAY') {
            push @keywords, @{$PropSet->{'Keywords'}};
        } elsif (not ref $PropSet->{'Keywords'} and exists $PropSet->{'Keywords'}) {
            push @keywords, $PropSet->{'Keywords'};
        }
        
        while (my ($prop,$value) = each %$PropSet) {
            next if $prop eq 'Keywords';
            $Props{$prop} = $value;
        }
    }
        
    #StopTimeCounter('LoadPageTime');
    # загружаем шаблон
    
    #StartTimeCounter('FetchTime');
    my ($Template,$error) = $this->{$TemplatesProvider}->fetch($pageNode->{'TemplateFileName'});
    die new Exception("Failed to load page $pageId",$Template ? $Template->as_string : 'Failed to parse') if $error;
    #StopTimeCounter('FetchTime');
    
    my $page = new DOM::Page(TemplatesProvider => $this->{$TemplatesProvider}, Properties => \%Props, Menus => \%PageMenus, NavChain => \@NavChain, Template => $Template, %args);
    $page->Properties->{url} = $this->PageIdToURL($pageId);
    return $page;
}

sub LoadNode {
    my ($this,$refNodePath) = @_;
    
    my $fileNameNode = $this->{$PagesBase} . join('/',grep $_, @$refNodePath);
    my $fileNameMenus;
    my $fileNameProps; 
    
    my %Node;

    if ( -d $fileNameNode ) {
        $Node{'Type'} = 'Section';
        $fileNameMenus = $fileNameNode . '/.menu.pl';
        $fileNameProps = $fileNameNode . '/.prop.pl';
    } elsif ( -e $fileNameNode ) {
        $Node{'Type'} = 'Page';
        $Node{'TemplateFileName'} = join('/',@$refNodePath);;
        $fileNameMenus = $fileNameNode . '.menu.pl';
        $fileNameProps = $fileNameNode . '.prop.pl';
    } else {
        die new Exception("Page not found: $fileNameNode");
    }
    
    if ( -f $fileNameProps ) {
        local ${^ENCODING};
        my $dummy = '';
        open my $hnull,'>>',\$dummy;
        local (*STDOUT,*STDIN) = ($hnull,$hnull);
        $Node{'Props'} = do $fileNameProps or warn "can't parse $fileNameProps: $@";
    }
    
    if ( -f $fileNameMenus ) {
        local ${^ENCODING};
        my $dummy = '';
        open my $hnull,'>>',\$dummy;
        local (*STDOUT,*STDIN) = ($hnull,$hnull);
        $Node{'Menus'} = do $fileNameMenus or warn "can't parse $fileNameMenus: $@";
    }
    
    if ($Node{'Menus'}) {
        my %Menus;
        foreach my $menu (keys %{$Node{'Menus'}}) {
            $Menus{$menu} = new DOM::PageMenu( DATA => $Node{'Menus'}->{$menu} );
        }
        $Node{'Menus'} = \%Menus;
    }
    
    $Node{'pathIndexPage'} = [@$refNodePath, $Node{'Props'}->{'IndexPage'} || $this->{$IndexPage}] if $Node{'Type'} eq 'Section';
    
    return \%Node;
}

sub SiteReleasePage {
    my ($this,$site) = @_;
    
    my $page = $site->Objects()->{'Page'};
    $page->Release() if $page;
    
    return 1;
}

sub construct {
    my $self = shift;
    
    return new DOM::Providers::Page(TemplatesPath => $PagesPath, IncludePath => $IncludesPath, IndexPage => $UseIndexPage, CachePath => $CachePath, CacheSize => $CacheSize, Encoding => $Encoding);
}

sub DecodeData {
    my ($Encoding, $data) = @_;
    
    if (ref $data) {
        if (ref $data eq 'SCALAR') {
            my $decoded = Encode::decode($Encoding,$$data,Encode::LEAVE_SRC);
            return \$decoded;
        } elsif (UNIVERSAL::isa($data, 'HASH')) {
            return {map {Encode::decode($Encoding,$_,Encode::LEAVE_SRC),DecodeData($Encoding,$data->{$_})} keys %$data };
        } elsif (UNIVERSAL::isa($data, 'ARRAY')) {
            return [map {DecodeData($Encoding,$_)} @$data];
        } elsif (ref $data eq 'REF') {
            my $decoded = DecodeData($Encoding,$$data);
            return \$decoded;
        } else {
            die new Exception('Cant decode data type', ref $data);
        }
    } else {
        return Encode::decode($Encoding,$data,Encode::LEAVE_SRC);
    }
}

1;

=pod
Хранилище шаблонов на основе файловой системы.

Хранилище состоит из разделов, каждый раздел имеет набор свойств и меню
Специальны свойства разделов
    Keywords Ключевые слова
    Name Название
    IndexPage страница по умолчанию

В разделах находятся страницы, каждая страница имеет набор свойств и меню

При загрузке страницы полностью загружаются все родительские контейнеры,
При этом одноименные меню сливаются,
Свойства keywords объеъединяются,
Если имя страницы не задано, то используется имя раздела

=cut