Mercurial > pub > Impl
annotate Lib/IMPL/DOM/Navigator.pm @ 104:196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
Minor and major fixes almost for everything.
A 'Source' property of the ValidationErrors generated from a NodeSet or a NodeList is subject to change in the future.
author | wizard |
---|---|
date | Tue, 11 May 2010 02:42:59 +0400 |
parents | 16ada169ca75 |
children | a7efb3117295 |
rev | line source |
---|---|
49 | 1 package IMPL::DOM::Navigator; |
2 use strict; | |
3 use warnings; | |
4 | |
5 use base qw(IMPL::Object); | |
6 use IMPL::Class::Property; | |
7 use IMPL::Class::Property::Direct; | |
8 BEGIN { | |
9 private _direct property _path => prop_all; | |
10 private _direct property _state => prop_all; | |
11 private _direct property _savedstates => prop_all; | |
12 public property Current => {get => \&_getCurrent}; | |
13 } | |
14 | |
15 sub CTOR { | |
16 my ($this,$CurrentNode) = @_; | |
17 | |
18 die IMPL::InvalidArgumentException("A starting node is a required paramater") unless $CurrentNode; | |
19 | |
20 $this->{$_state} = { alternatives => [ $CurrentNode ], current => 0 }; | |
21 } | |
22 | |
23 sub _initNavigator { | |
24 my ($this,$CurrentNode) = @_; | |
25 | |
26 die IMPL::InvalidArgumentException("A starting node is a required paramater") unless $CurrentNode; | |
27 | |
28 $this->{$_state} = { alternatives => [ $CurrentNode ], current => 0 }; | |
29 delete $this->{$_path}; | |
30 delete $this->{$_savedstates}; | |
31 } | |
32 | |
33 sub _getCurrent { | |
34 $_[0]->{$_state}{alternatives}[$_[0]->{$_state}{current}] | |
35 } | |
36 | |
37 sub Navigate { | |
38 my ($this,@path) = @_; | |
39 | |
40 return unless @path; | |
41 | |
42 my $node; | |
43 | |
44 foreach my $query (@path) { | |
45 if (my $current = $this->Current) { | |
46 | |
47 my @alternatives = $current->selectNodes($query); | |
48 | |
49 unless (@alternatives) { | |
50 $current = $this->advanceNavigator or return undef; | |
51 @alternatives = $current->selectNodes($query); | |
52 } | |
53 | |
54 push @{$this->{$_path}},$this->{$_state}; | |
55 $this->{$_state} = { | |
56 alternatives => \@alternatives, | |
57 current => 0, | |
58 query => $query | |
59 }; | |
60 | |
61 $node = $alternatives[0]; | |
62 } else { | |
63 return undef; | |
64 } | |
65 } | |
66 | |
67 $node; | |
68 } | |
69 | |
70 sub selectNodes { | |
71 my ($this,@path) = @_; | |
72 | |
73 return internalSelectNodes($this->Current,@path); | |
74 } | |
75 | |
76 sub internalSelectNodes { | |
77 my $node = shift; | |
78 my $query = shift; | |
79 | |
80 if (@_) { | |
81 return map internalSelectNodes($_,@_), $node->selectNodes($query); | |
82 } else { | |
83 return $node->selectNodes($query); | |
84 } | |
85 } | |
86 | |
87 sub internalNavigateNodeSet { | |
88 my ($this,@nodeSet) = @_; | |
89 | |
90 push @{$this->{$_path}}, $this->{$_state}; | |
91 | |
92 $this->{$_state} = { | |
93 alternatives => \@nodeSet, | |
94 current => 0 | |
95 }; | |
96 | |
97 $nodeSet[0]; | |
98 } | |
99 | |
100 sub fetch { | |
101 my ($this) = @_; | |
102 | |
103 my $result = $this->Current; | |
104 $this->advanceNavigator; | |
105 return $result; | |
106 } | |
107 | |
108 sub advanceNavigator { | |
109 my ($this) = @_; | |
110 | |
111 $this->{$_state}{current}++; | |
112 | |
113 if (@{$this->{$_state}{alternatives}} <= $this->{$_state}{current}) { | |
114 if ( exists $this->{$_state}{query} ) { | |
115 my $query = $this->{$_state}{query}; | |
116 | |
117 $this->Back or return undef; # that meams the end of the history | |
118 | |
119 undef while ( $this->advanceNavigator and not $this->Navigate($query)); | |
120 | |
121 return $this->Current; | |
122 } | |
123 return undef; | |
124 } | |
125 | |
126 return $this->Current; | |
127 } | |
128 | |
129 sub doeach { | |
130 my ($this,$code) = @_; | |
131 local $_; | |
132 | |
133 do { | |
134 for (my $i = $this->{$_state}{current}; $i < @{$this->{$_state}{alternatives}}; $i++) { | |
135 $_ = $this->{$_state}{alternatives}[$i]; | |
136 $code->(); | |
137 } | |
138 $this->{$_state}{current} = @{$this->{$_state}{alternatives}}; | |
139 } while ($this->advanceNavigator); | |
140 } | |
141 | |
142 sub Back { | |
143 my ($this,$steps) = @_; | |
144 if ($this->{$_path} and @{$this->{$_path}}) { | |
145 if ( (not $steps) || $steps == 1) { | |
146 $this->{$_state} = pop @{$this->{$_path}}; | |
147 } else { | |
148 $steps ||= 1; | |
149 | |
150 $steps = @{$this->{$_path}} - 1 if $steps >= @{$this->{$_path}}; | |
151 | |
152 $this->{$_state} = (splice @{$this->{$_path}},-$steps)[0]; | |
153 } | |
154 $this->Current if defined wantarray; | |
155 } else { | |
156 return undef; | |
157 } | |
158 } | |
159 | |
160 sub PathToString { | |
161 my ($this,$delim) = @_; | |
162 | |
163 $delim ||= '/'; | |
164 | |
165 join($delim,map $_->{alternatives}[$_->{current}]->nodeName, $this->{$_path} ? (@{$this->{$_path}}, $this->{$_state}) : $this->{$_state}); | |
166 } | |
167 | |
104
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
49
diff
changeset
|
168 sub pathLength { |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
49
diff
changeset
|
169 my ($this) = @_; |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
49
diff
changeset
|
170 $this->{$_path} ? scalar @{$this->{$_path}} : 0; |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
49
diff
changeset
|
171 } |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
49
diff
changeset
|
172 |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
49
diff
changeset
|
173 sub GetNodeFromHistory { |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
49
diff
changeset
|
174 my ($this,$index) = @_; |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
49
diff
changeset
|
175 |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
49
diff
changeset
|
176 if (my $state = $this->{$_path} ? $this->{$_path}->[$index] : undef ) { |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
49
diff
changeset
|
177 return $state->{alternatives}[$state->{current}] |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
49
diff
changeset
|
178 } else { |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
49
diff
changeset
|
179 return undef; |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
49
diff
changeset
|
180 } |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
49
diff
changeset
|
181 } |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
49
diff
changeset
|
182 |
49 | 183 sub clone { |
184 my ($this) = @_; | |
185 | |
186 my $newNavi = __PACKAGE__->surrogate; | |
187 | |
188 $newNavi->{$_path} = [ map { { %{ $_ } } } @{$this->{$_path}} ] if $this->{$_path}; | |
189 $newNavi->{$_state} = { %{$this->{$_state}} }; | |
190 | |
191 return $newNavi; | |
192 | |
193 } | |
194 | |
195 sub saveState { | |
196 my ($this) = @_; | |
197 | |
198 my %state; | |
199 | |
200 $state{path} = [ map { { %{ $_ } } } @{$this->{$_path}} ] if $this->{$_path}; | |
201 $state{state} = { %{$this->{$_state}} }; | |
202 | |
203 push @{$this->{$_savedstates}}, \%state; | |
204 } | |
205 | |
206 sub restoreState { | |
207 my ($this) = @_; | |
208 | |
209 if ( my $state = pop @{$this->{$_savedstates}||[]} ) { | |
210 $this->{$_path} = $state->{path}; | |
211 $this->{$_state} = $state->{state}; | |
212 } | |
213 } | |
214 | |
215 sub applyState { | |
216 my ($this) = @_; | |
217 | |
218 pop @{$this->{$_savedstates}||[]}; | |
219 } | |
220 | |
221 sub dosafe { | |
222 my ($this,$transaction) = @_; | |
223 | |
224 $this->saveState(); | |
225 | |
226 my $result; | |
227 | |
228 eval { | |
229 $result = $transaction->(); | |
230 }; | |
231 | |
232 if ($@) { | |
233 $this->restoreState(); | |
234 return undef; | |
235 } else { | |
236 $this->applyState(); | |
237 return $result; | |
238 } | |
239 } | |
240 | |
241 1; | |
242 | |
243 __END__ | |
244 =pod | |
245 | |
246 =head1 DESCRIPTION | |
247 | |
248 Объект для хождения по дереву DOM объектов. | |
249 | |
250 Результатом навигации является множество узлов (альтернатив). | |
251 | |
252 Состоянием навигатора является текущий набор узлов, позиция в данном наборе, | |
253 а также запрос по которому были получены данные результаты. | |
254 | |
255 Если при навигации указан путь сосящий из нескольких фильтров, то он разбивается | |
256 этапы простой навигации по кадой из частей пути. На каждом элементарном этапе | |
257 навигации образуется ряд альтернатив, и при каждом следующем этапе навигации | |
258 альтернативы предыдущих этапов могут перебираться, до получения положительного | |
259 результата навигации, в противном случае навигация считается невозможной. | |
260 | |
261 =head1 METHODS | |
262 | |
263 =over | |
264 | |
265 =item C<<$obj->new($nodeStart)>> | |
266 | |
267 Создает объект навигатора с указанной начальной позицией. | |
268 | |
269 =item C<<$obj->Navigate([$query,...])>> | |
270 | |
271 Перейти в новый узел используя запрос C<$query>. На данный момент запросом может | |
272 быть только имя узла и будет взят только первый узел. Если по запросу ничего не | |
273 найдено, переход не будет осуществлен. | |
274 | |
275 Возвращает либо новый узел в который перешли, либо C<undef>. | |
276 | |
277 =item C<<$obj->Back()>> | |
278 | |
279 Возвращается в предыдущий узел, если таковой есть. | |
280 | |
281 Возвращает либо узел в который перешли, либо C<undef>. | |
282 | |
283 =item C<<$obj->advanceNavigator()>> | |
284 | |
285 Переходит в следующую альтернативу, соответствующую текущему запросу. | |
286 | |
287 =back | |
288 | |
289 =cut |