diff Lib/IMPL/DOM/Navigator.pm @ 24:7f00786f8210

Первая рабочая реазизация схемы и навигаторов
author Sergey
date Mon, 05 Oct 2009 00:48:49 +0400
parents 818c74b038ae
children a8086f85a571
line wrap: on
line diff
--- a/Lib/IMPL/DOM/Navigator.pm	Wed Sep 30 17:43:52 2009 +0400
+++ b/Lib/IMPL/DOM/Navigator.pm	Mon Oct 05 00:48:49 2009 +0400
@@ -6,55 +6,206 @@
 use IMPL::Class::Property;
 use IMPL::Class::Property::Direct;
 BEGIN {
-    public _direct property Path => prop_get | owner_set;
-    public _direct property Current => prop_get | owner_set;
+    private _direct property _path => prop_all;
+    private _direct property _state => prop_all;
+    private _direct property _savedstates => prop_all;
+    public property Current => {get => \&_getCurrent};
 }
 
 sub CTOR {
     my ($this,$CurrentNode) = @_;
     
-    $this->{$Current} = $CurrentNode or die IMPL::InvalidArgumentException("A starting node is a required paramater");
+    die IMPL::InvalidArgumentException("A starting node is a required paramater") unless $CurrentNode;
+    
+    $this->{$_state} = { alternatives => [ $CurrentNode ], current => 0 };
+}
+
+sub _getCurrent {
+    $_[0]->{$_state}{alternatives}[$_[0]->{$_state}{current}]
 }
 
 sub Navigate {
-    my ($this,$query) = @_;
+    my ($this,@path) = @_;
+    
+    return unless @path;
     
-    if ( my ($newNode) = $this->{$Current}->selectNodes($query) ) {
-        push @{$this->{$Path}}, $this->{$Current};
-        return $this->{$Current} = $newNode;
+    foreach my $query (@path) {
+        if (my $current = $this->Current) {
+            
+            my @alternatives = $this->Current->selectNodes($query);
+            
+            unless (@alternatives) {
+                $this->advanceNavigator or return undef;
+                @alternatives = $this->Current->selectNodes($query);
+            }
+            
+            push @{$this->{$_path}},$this->{$_state};
+            $this->{$_state} = {
+                alternatives => \@alternatives,
+                current => 0,
+                query => $query
+            }
+        } else {
+            return undef;
+        }
+    }
+    
+    return $this->Current;
+}
+
+sub selectNodes {
+    my ($this,@path) = @_;
+    
+    return internalSelectNodes($this->Current,@path);
+}
+
+sub internalSelectNodes {
+    my $node = shift;
+    my $query = shift;
+    
+    if (@_) {
+        return map internalSelectNodes($_,@_), $node->selectNodes($query);
     } else {
-        return undef;
+        return $node->selectNodes($query);
     }
 }
 
-sub _NavigateNode {
-    my ($this,$newNode) = @_;
-    push @{$this->{$Path}}, $this->{$Current};
-    return $this->{$Current} = $newNode;
+sub internalNavigateNodeSet {
+    my ($this,@nodeSet) = @_;
+    
+    push @{$this->{$_path}}, $this->{$_state};
+    
+    $this->{$_state} = {
+        alternatives => \@nodeSet,
+        current => 0
+    };
+    
+    return $this->Current;
+}
+
+sub fetch {
+    my ($this) = @_;
+    
+    my $result = $this->Current;
+    $this->advanceNavigator;
+    return $result;
 }
 
-sub _NavigateNodeStirct {
-    my ($this,$newNode) = @_;
+sub advanceNavigator {
+    my ($this) = @_;
+    
+    $this->{$_state}{current}++;
     
-    die new IMPL::InvalidOperationException("A newNode doesn't belongs to the current") unless $newNode->parentNode == $this->{$Current};
-    push @{$this->{$Path}}, $this->{$Current};
-    return $this->{$Current} = $newNode;
+    if (@{$this->{$_state}{alternatives}} <= $this->{$_state}{current}) {
+        if ( exists $this->{$_state}{query} ) {
+            my $query = $this->{$_state}{query};
+  
+            $this->Back or return 0; # that meams the end of the history
+
+            undef while ( $this->advanceNavigator and not $this->Navigate($query));
+
+            return $this->Current ? 1 : 0;
+        }
+        return 0;
+    }
+    
+    return 1;
+}
+
+sub doeach {
+    my ($this,$code) = @_;
+    local $_;
+    
+    do {
+        for (my $i = $this->{$_state}{current}; $i < @{$this->{$_state}{alternatives}}; $i++) {
+            $_ = $this->{$_state}{alternatives}[$i];
+            $code->();
+        }
+         $this->{$_state}{current} = @{$this->{$_state}{alternatives}};
+    } while ($this->advanceNavigator);
 }
 
 sub Back {
-    my ($this) = @_;
+    my ($this,$steps) = @_;
+    
+    $steps ||= 1;
     
-    if ( my $newNode = $this->{$Path} ? pop @{$this->{$Path}} : undef ) {
-        return $this->{$Current} = $newNode;
+    if ($this->{$_path} and @{$this->{$_path}}) {
+        
+        $steps = @{$this->{$_path}} - 1 if $steps >= @{$this->{$_path}};
+        
+        ($this->{$_state}) = splice @{$this->{$_path}},-$steps;
+        
+        $this->Current;
     } else {
         return undef;
     }
 }
 
 sub PathToString {
-    my $this = shift;
+    my ($this,$delim) = @_;
+    
+    $delim ||= '/';
+    
+    join($delim,map $_->{alternatives}[$_->{current}]->nodeName, $this->{$_path} ? (@{$this->{$_path}}, $this->{$_state}) : $this->{$_state});
+}
+
+sub clone {
+    my ($this) = @_;
+    
+    my $newNavi = __PACKAGE__->surrogate;
+    
+    $newNavi->{$_path} = [ map { { %{ $_ } }  } @{$this->{$_path}} ] if $this->{$_path};
+    $newNavi->{$_state} = { %{$this->{$_state}} };
+    
+    return $newNavi;
+    
+}
+
+sub saveState {
+    my ($this) = @_;
+    
+    my %state;
+    
+    $state{path} = [ map { { %{ $_ } }  } @{$this->{$_path}} ] if $this->{$_path};
+    $state{state} = { %{$this->{$_state}} };
     
-    join('/',map $_->nodeName, $this->{$Path} ? (@{$this->{$Path}}, $this->{$Current}) : $this->{$Current});
+    push @{$this->{$_savedstates}}, \%state;
+}
+
+sub restoreState {
+    my ($this) = @_;
+    
+    if ( my $state = pop @{$this->{$_savedstates}||[]} ) {
+        $this->{$_path} = $state->{path};
+        $this->{$_state} = $state->{state};
+    }
+}
+
+sub applyState {
+    my ($this) = @_;
+    
+    pop @{$this->{$_savedstates}||[]};
+}
+
+sub dosafe {
+    my ($this,$transaction) = @_;
+    
+    $this->saveState();
+    
+    my $result;
+    
+    eval {
+        $result = $transaction->();
+    };
+    
+    if ($@) {
+        $this->restoreState();
+        return undef;
+    } else {
+        $this->applyState();
+        return $result;
+    }
 }
 
 1;
@@ -66,6 +217,11 @@
 
      DOM .
 
+     ().
+
+     ,    ,
+        .
+
 =head1 METHODS
 
 =over
@@ -74,7 +230,7 @@
 
       .
 
-=item C<$obj->Navigate($query)>
+=item C<$obj->Navigate([$query,...])>
 
       C<$query>.     
          .