diff Lib/IMPL/Web/Handler/TTView.pm @ 285:546957c50a36

*IMPL::Web::Handler::TTView Reworked template selection mechanism *IMPL::Web::Application: refactoring -Removed obsolete IMPL::Text modules
author cin
date Mon, 18 Feb 2013 02:55:59 +0400
parents a8dbddf491dd
children 5d14baa35790
line wrap: on
line diff
--- a/Lib/IMPL/Web/Handler/TTView.pm	Thu Feb 14 19:14:02 2013 +0400
+++ b/Lib/IMPL/Web/Handler/TTView.pm	Mon Feb 18 02:55:59 2013 +0400
@@ -2,6 +2,7 @@
 use strict;
 
 use List::Util qw(first);
+use IMPL::lang;
 use IMPL::Const qw(:prop);
 use IMPL::declare {
     require => {
@@ -20,18 +21,16 @@
         contentType     => PROP_RO,
         contentCharset  => PROP_RO,
         loader          => PROP_RO,
-        selectors       => PROP_RO | PROP_LIST,
+        selectors       => PROP_RO,
         defaultDocument => PROP_RW,
-        indexResource   => PROP_RW,
-        _selectorsCache => PROP_RW,
-        _classTemplates => PROP_RW
+        _selectorsCache => PROP_RW
       ]
 };
 
 sub CTOR {
     my ($this) = @_;
 
-    $this->indexResource('index') unless $this->indexResource;
+    $this->_selectorsCache([ map $this->ParseRule($_), @{$this->selectors || []} ]);
 }
 
 sub Invoke {
@@ -53,6 +52,7 @@
         model       => $model,
         action      => $action,
         app         => $action->application,
+        env         => _cached($action->context->{environment}),
         ImportClass => sub {
             my $class = shift;
 
@@ -83,176 +83,158 @@
     );
 }
 
-sub SelectView {
-    my ( $this, $action, $class ) = @_;
-
-    my @path = split /\//, $action->query->path_info(), -1;
+sub _cached {
+    my $arg = shift;
     
-    shift @path;    # remove always empty leading segment
-
-    $this->BuildCache unless $this->_selectorsCache;
-    my $cache = $this->_selectorsCache;
-
-    @path = reverse @path;
-
-    foreach
-      my $subclass ( $class ? ( _GetHierarchy($class), '-default' ) : '-default' )
-    {
-        my @results;
-        push @results,
-          { result => $this->_classTemplates->{$subclass}, level => 0 }
-          if $this->_classTemplates->{$subclass};
-          
-        if ( $cache->{$subclass} ) {
-            my $alternatives =
-              [ { selector => $cache->{$subclass}, immediate => 1 } ];
-            $alternatives =
-              $this->MatchAlternatives( $_, $alternatives, \@results )
-              foreach @path;
-        }
-
-        if (@results) {
-            @results = sort { $b->{level} <=> $a->{level} } @results;
-            return ( shift @results )->{result};
-        }
-    }
-
-    return $this->defaultDocument;
-}
-
-sub _GetHierarchy {
-    my ($class) = @_;
-    return unless $class;
-
-    no strict 'refs';
-
-    return $class, map { _GetHierarchy($_) } @{"${class}::ISA"};
-}
-
-sub BuildCache {
-    my ($this) = @_;
-
-    my @selectors;
-
-    my $cache = $this->_selectorsCache( {} );
-    $this->_classTemplates( {} );
-
-    foreach my $selector ( $this->selectors ) {
-        if ( not ref $selector ) {
-
-            my ( $path, $data ) = split( /\s*=>\s*/, $selector );
-
-            my @path = split( /\s+/, $path );
-
-            my $class;
-
-            # if this selector has a class part
-            if ( $path[$#path] =~ m/^\@(.*)/ ) {
-                $class = $1;
-                pop @path;
-            }
-            else {
-                $class = '-default';
-            }
-
-            #if this selector has a path
-            if (@path) {
-                @path = reverse @path;
-                my $last  = pop @path;
-                my $t     = ( $cache->{$class} ||= {} );
-                my $level = 1;
-                foreach my $prim (@path) {
-                    $t = ( $t->{$prim}->{next} ||= {} );
-                    $level++;
-                }
-                $t->{$last}->{level} = $level;
-                $t->{$last}->{data}  = $data;
-
-            }
-            else {
-
-                # we dont have a selector, only class
-
-                $this->_classTemplates->{$class} = $data;
-            }
-
-        }
+    return $arg unless ref $arg eq 'CODE';
+    
+    return sub {
+        ref $arg eq 'CODE' ? $arg = &$arg() : $arg;
     }
 }
 
-sub MatchAlternatives {
-    my ( $this, $segment, $alternatives, $results ) = @_;
+sub SelectView {
+    my ($this,$action) = @_;
+    
+    my @path;
+    
+    for(my $r = $action->context->{resource}; $r ; $r = $r->parent ) {
+        unshift @path, {
+            name => $r->id,
+            class => typeof($r->model)
+        };
+    }
+    
+    @path = map { name => $_}, split /\/+/, $action->query->path_info()
+        unless (@path);
+    
+    return $this->MatchPath(\@path,$this->_selectorsCache) || $this->defaultDocument;
+}
 
+sub ParseRule {
+    my ($this, $rule) = @_;
+    
+    my ($selector,$data) = split /\s+=>\s+/, $rule;
+    
+    my @parts;
+    my $first = 1;
+    my $weight = 0;
+    foreach my $part ( split /\//, $selector ) {
+        # если первым символом является /
+        # значит путь в селекторе абсолютный и не нужно
+        # добавлять "любой" элемент в начало
+        
+        if($part) {
+            $weight ++;
+            push @parts,{ any => 1 } if $first;
+        } else {
+            push @parts,{ any => 1 } unless $first;
+            next;
+        }        
+
+        my ($name,$class) = split /@/, $part;
+        
+        if ( my ( $varName, $rx ) = ( $name =~ m/^\{(?:(\w+)\:)?(.*)\}$/ ) ) {
+            #this is a regexp
+
+            push @parts, {
+                rx => $rx,
+                var => $varName,
+                class => $class,
+            };
+        } else {
+            push @parts, {
+                name => length($name) ? $name : undef,
+                class => $class,
+            };
+        }        
+    } continue {
+        $first = 0;
+    }
+     
+    return { selector => \@parts, data => $data, weight => $weight };
+}
+
+sub MatchPath {
+    my ($this,$path,$rules) = @_;
+    
+    $path ||= [];
+    $rules ||= [];
+    
     my @next;
     
-    foreach my $alt (@$alternatives) {
-        while ( my ( $selector, $match ) = each %{ $alt->{selector} } ) {
+    foreach my $segment (@$path) {
+        foreach my $rule (@$rules) {
+            my @selector = @{$rule->{selector}};
+            
+            my $part = shift @selector;
 
-            my $context = {
-                vars => \%{ $alt->{vars} || {} },
-                selector => $match->{next}
-            };
-
-            if ( $selector =~ s/^>// ) {
-                $context->{immediate} = 1;
-            }
+            if ($part->{any}) {
+                #keep the rule for the next try
+                push @next, $rule;
 
-            if ( my ( $name, $rx ) =
-                ( $selector =~ m/^\{(?:(\w+)\:)?(.*)\}$/ ) )
-            {
-
-                #this is a regexp
-
-                if ( my @captures = ( $segment =~ m/($rx)/ ) ) {
-                    $context->{success} = 1;
-
-                    if ($name) {
-                        $context->{vars}->{$name} = \@captures;
-                    }
+                $part = shift @selector while $part->{any};
+            }
+            
+            # if this rule doesn't have a selector
+            next unless $part;
+            
+            my $newRule = {
+                selector => \@selector,
+                data => $rule->{data},
+                weight => $rule->{weight},
+                vars => { %{$rule->{vars} || {}} }
+            };
+            
+            my $success = 1;
+            if (my $class = $part->{class}) {
+                $success = isclass($segment->{class},$class);
+            }
+            
+            if($success && (my $name = $part->{name})) {
+                $success = $segment->{name} eq $name;
+            } elsif ($success && (my $rx = $part->{rx})) {
+                if( my @captures = ($segment->{name} =~ m/($rx)/) ) {
+                    $newRule->{vars}->{$part->{var}} = \@captures
+                        if $part->{var};
+                } else {
+                    $success = 0;
                 }
             }
-            else {
-
-                #this is a segment name
-                if ( $segment eq $selector ) {
-                    $context->{success} = 1;
-                }
+            
+            push @next, $newRule if $success;
+            
+        }
+        $rules = [@next];
+        undef @next;
+    }
+    
+    my $result = (
+        sort {
+            $b->{weight} <=> $a->{weight}
+        }
+        grep {
+            scalar(@{$_->{selector}}) == 0
+        }
+        @$rules
+    )[0];
+    
+    if($result) {
+        my $data = $result->{data};
+        $data =~ s/{(\w+)(?:\:(\d+))?}/
+            my ($name,$index) = ($1,$2 || 0);
+            
+            if ($result->{vars}{$name}) {
+                $result->{vars}{$name}[$index];
+            } else {
+                "";
             }
-
-            # test if there were a match
-            if ( delete $context->{success} ) {
-                if ( my $data = $match->{data} ) {
-
-                    # interpolate data
-                    $data =~ s/{(\w+)(?:\:(\d+))?}/
-                        my ($name,$index) = ($1,$2 || 0);
-                        
-                        if ($context->{vars}{$name}) {
-                            $context->{vars}{$name}[$index];
-                        } else {
-                            "";
-                        }
-                    /gex;
-
-                    push @$results,
-                      { level => $match->{level}, result => $data };
-                }
-                push @next, $context if $context->{selector};
-            }
-            else {
-
-                #repeat current alternative if it's not required to be immediate
-                push @next,
-                  {
-                    selector => { $selector, $match },
-                    vars     => $alt->{vars}
-                  }
-                  unless $alt->{immediate};
-            }
-        }
+        /gex;
+        
+        return $data;
+    } else {
+        return;
     }
-
-    return \@next;
 }
 
 1;
@@ -301,14 +283,47 @@
 выборе используется принцип похожий на селекторы C<CSS>, основывающийся на именах ресурсов и их типах
 данных.
 
+Данный обработчик понимает определенные свойства контекста:
+
+=over
+
+=item * C<resourceLocation>
+
+В данном свойстве может быть передана информация о текущем расположении ресурса,
+для которого строится представление. Эта информация будет доступна в шаблоне
+через свойство документа C<location>.
+
+=item * C<environment>
+
+В данном совойстве контекста передается дополнительная информация об окружении
+ресурса, например, которую задали родительские ресурсы. Использование данного
+свойства позволяет не загромождать ресурс реализацией функциональности по
+поддержке окружения. Это свойство может быть ссылкой на функцию, что позволяет
+формировать контекст только по необходимости, при этом указанная функция будет
+выполнена только один раз, при первом обращении.
+
+=back 
+
 =head1 SELECTORS
 
 =begin text
 
-[url-template] [class] => template
+syntax::= selector => template
+
+selector::= ([>]segment-template[@class-name])
+
+segment-template::= {'{'name:regular-expr'}'|segment-name}
+
+name::= \w+
+
+segment-name::= \S+
+
+class-name::= name[(::name)]
+
+url-template@class => template
 
 shoes => product/list
-{action:*.} @My::Data::Product => product/{action}
+/shop//{action:*.}@My::Data::Product => product/{action}
 
 stuff >list => product/list
 details => product/details