Mercurial > pub > Impl
annotate Lib/IMPL/DOM/Node.pm @ 278:4ddb27ff4a0b
core refactoring
author | cin |
---|---|
date | Mon, 04 Feb 2013 02:10:37 +0400 |
parents | 6253872024a4 |
children | c6d0f889ef87 |
rev | line source |
---|---|
49 | 1 package IMPL::DOM::Node; |
2 use strict; | |
3 use warnings; | |
4 | |
165 | 5 use parent qw(IMPL::Object); |
49 | 6 |
7 use IMPL::Object::List; | |
8 use IMPL::Class::Property; | |
9 use Scalar::Util qw(weaken); | |
10 | |
11 use IMPL::Exception; | |
12 | |
13 BEGIN { | |
14 public _direct property nodeName => prop_get; | |
15 public _direct property document => prop_get; | |
16 public _direct property isComplex => { get => \&_getIsComplex } ; | |
17 public _direct property nodeValue => prop_all; | |
102 | 18 public _direct property childNodes => { get => \&_getChildNodes }; # prop_list |
148
e6447ad85cb4
DOM objects now have a schema and schemaSource properties
wizard
parents:
124
diff
changeset
|
19 public _direct property parentNode => prop_get | owner_set; |
e6447ad85cb4
DOM objects now have a schema and schemaSource properties
wizard
parents:
124
diff
changeset
|
20 public _direct property schema => prop_get | owner_set; |
e6447ad85cb4
DOM objects now have a schema and schemaSource properties
wizard
parents:
124
diff
changeset
|
21 public _direct property schemaSource => prop_get | owner_set; |
49 | 22 private _direct property _propertyMap => prop_all ; |
116 | 23 |
49 | 24 } |
25 | |
104
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
102
diff
changeset
|
26 our %Axes = ( |
194 | 27 parent => \&selectParent, |
28 siblings => \&selectSiblings, | |
29 child => \&childNodes, | |
30 document => \&selectDocument, | |
31 ancestor => \&selectAncestors, | |
32 descendant => \&selectDescendant | |
104
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
102
diff
changeset
|
33 ); |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
102
diff
changeset
|
34 |
49 | 35 sub CTOR { |
36 my ($this,%args) = @_; | |
37 | |
38 $this->{$nodeName} = delete $args{nodeName} or die new IMPL::InvalidArgumentException("A name is required"); | |
39 $this->{$nodeValue} = delete $args{nodeValue} if exists $args{nodeValue}; | |
40 if ( exists $args{document} ) { | |
41 $this->{$document} = delete $args{document}; | |
42 weaken($this->{$document}); | |
43 } | |
44 | |
124 | 45 while ( my ($key,$value) = each %args ) { |
194 | 46 $this->nodeProperty($key,$value); |
124 | 47 } |
49 | 48 } |
49 | |
50 sub insertNode { | |
51 my ($this,$node,$pos) = @_; | |
52 | |
53 die new IMPL::InvalidOperationException("You can't insert the node to itselft") if $this == $node; | |
54 | |
55 $node->{$parentNode}->removeNode($node) if ($node->{$parentNode}); | |
56 | |
57 $this->childNodes->InsertAt($pos,$node); | |
58 | |
59 $node->_setParent( $this ); | |
60 | |
61 return $node; | |
62 } | |
63 | |
64 sub appendChild { | |
65 my ($this,$node) = @_; | |
66 | |
67 die new IMPL::InvalidOperationException("You can't insert the node to itselft") if $this == $node; | |
68 | |
69 $node->{$parentNode}->removeNode($node) if ($node->{$parentNode}); | |
70 | |
71 my $children = $this->childNodes; | |
72 $children->Append($node); | |
73 | |
74 $node->_setParent( $this ); | |
75 | |
76 return $node; | |
77 } | |
78 | |
79 sub appendNode { | |
80 goto &appendChild; | |
81 } | |
82 | |
83 sub appendRange { | |
84 my ($this,@range) = @_; | |
85 | |
86 die new IMPL::InvalidOperationException("You can't insert the node to itselft") if grep $_ == $this, @range; | |
87 | |
88 foreach my $node (@range) { | |
89 $node->{$parentNode}->removeNode($node) if ($node->{$parentNode}); | |
90 $node->_setParent( $this ); | |
91 } | |
92 | |
93 $this->childNodes->Append(@range); | |
94 | |
95 return $this; | |
96 } | |
97 | |
98 sub _getChildNodes { | |
99 my ($this) = @_; | |
100 | |
101 $this->{$childNodes} = new IMPL::Object::List() unless $this->{$childNodes}; | |
102 | 102 return wantarray ? @{ $this->{$childNodes} } : $this->{$childNodes}; |
49 | 103 } |
104 | |
109
ddf0f037d460
IMPL::DOM::Node updated to support TT, (added childNodesRef and selectNodesRef for using from TT)
wizard
parents:
108
diff
changeset
|
105 sub childNodesRef { |
194 | 106 my ($this) = @_; |
107 return scalar $this->_getChildNodes; | |
109
ddf0f037d460
IMPL::DOM::Node updated to support TT, (added childNodesRef and selectNodesRef for using from TT)
wizard
parents:
108
diff
changeset
|
108 } |
ddf0f037d460
IMPL::DOM::Node updated to support TT, (added childNodesRef and selectNodesRef for using from TT)
wizard
parents:
108
diff
changeset
|
109 |
49 | 110 sub removeNode { |
111 my ($this,$node) = @_; | |
112 | |
113 if ($this == $node->{$parentNode}) { | |
114 $this->childNodes->RemoveItem($node); | |
115 $node->_setParent(undef); | |
116 return $node; | |
117 } else { | |
118 die new IMPL::InvalidOperationException("The specified node isn't belong to this node"); | |
119 } | |
120 } | |
121 | |
122 sub replaceNodeAt { | |
123 my ($this,$index,$node) = @_; | |
124 | |
125 my $nodeOld = $this->childNodes->[$index]; | |
126 | |
127 die new IMPL::InvalidOperationException("You can't insert the node to itselft") if $this == $node; | |
128 | |
129 # unlink node from previous parent | |
130 $node->{$parentNode}->removeNode($node) if ($node->{$parentNode}); | |
131 | |
132 # replace (or set) old node | |
133 $this->childNodes->[$index] = $node; | |
134 | |
135 # set new parent | |
136 $node->_setParent( $this ); | |
137 | |
138 # unlink old node if we have one | |
139 $nodeOld->_setParent(undef) if $nodeOld; | |
140 | |
141 # return old node | |
142 return $nodeOld; | |
143 } | |
144 | |
145 sub removeAt { | |
146 my ($this,$pos) = @_; | |
147 | |
148 if ( my $node = $this->childNodes->RemoveAt($pos) ) { | |
149 $node->_setParent(undef); | |
150 return $node; | |
151 } else { | |
152 return undef; | |
153 } | |
154 } | |
155 | |
156 sub removeLast { | |
157 my ($this) = @_; | |
158 | |
159 if ( my $node = $this->{$childNodes} ? $this->{$childNodes}->RemoveLast() : undef) { | |
160 $node->_setParent(undef); | |
161 return $node; | |
162 } else { | |
163 return undef; | |
164 } | |
165 } | |
166 | |
167 sub removeSelected { | |
168 my ($this,$query) = @_; | |
169 | |
170 my @newSet; | |
171 my @result; | |
172 | |
173 if (ref $query eq 'CODE') { | |
174 &$query($_) ? push @result, $_ : push @newSet, $_ foreach @{$this->childNodes}; | |
175 } elsif (defined $query) { | |
176 $_->nodeName eq $query ? push @result, $_ : push @newSet, $_ foreach @{$this->childNodes}; | |
177 } else { | |
178 my $children = $this->childNodes; | |
179 $_->_setParent(undef) foreach @$children; | |
180 delete $this->{$childNodes}; | |
181 return wantarray ? @$children : $children; | |
182 } | |
183 | |
184 $_->_setParent(undef) foreach @result; | |
185 | |
186 $this->{$childNodes} = @newSet ? bless \@newSet ,'IMPL::Object::List' : undef; | |
187 | |
188 return wantarray ? @result : \@result; | |
189 } | |
190 | |
104
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
102
diff
changeset
|
191 sub resolveAxis { |
194 | 192 my ($this,$axis) = @_; |
193 return $Axes{$axis}->($this) | |
104
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
102
diff
changeset
|
194 } |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
102
diff
changeset
|
195 |
49 | 196 sub selectNodes { |
194 | 197 my $this = shift; |
198 my $path; | |
199 | |
200 if (@_ == 1) { | |
201 $path = $this->translatePath($_[0]); | |
202 } else { | |
203 $path = [@_]; | |
204 } | |
205 | |
206 my @set = ($this); | |
207 | |
208 while (@$path) { | |
209 my $query = shift @$path; | |
210 @set = map $_->selectNodesAxis($query), @set; | |
211 } | |
212 | |
213 return wantarray ? @set : \@set; | |
108 | 214 } |
215 | |
113 | 216 sub selectSingleNode { |
194 | 217 my $this = shift; |
218 my @result = $this->selectNodes(@_); | |
219 return $result[0]; | |
113 | 220 } |
221 | |
109
ddf0f037d460
IMPL::DOM::Node updated to support TT, (added childNodesRef and selectNodesRef for using from TT)
wizard
parents:
108
diff
changeset
|
222 sub selectNodesRef { |
194 | 223 my $this = shift; |
224 | |
225 my @result = $this->selectNodes(@_); | |
226 return \@result; | |
109
ddf0f037d460
IMPL::DOM::Node updated to support TT, (added childNodesRef and selectNodesRef for using from TT)
wizard
parents:
108
diff
changeset
|
227 } |
ddf0f037d460
IMPL::DOM::Node updated to support TT, (added childNodesRef and selectNodesRef for using from TT)
wizard
parents:
108
diff
changeset
|
228 |
108 | 229 sub translatePath { |
194 | 230 my ($this,$path) = @_; |
231 | |
232 # TODO: Move path compilation here from IMPL::DOM::Schema::Validator::Compare | |
233 return [$path]; | |
108 | 234 } |
235 | |
236 sub selectNodesAxis { | |
194 | 237 my ($this,$query,$axis) = @_; |
104
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
102
diff
changeset
|
238 |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
102
diff
changeset
|
239 $axis ||= 'child'; |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
102
diff
changeset
|
240 |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
102
diff
changeset
|
241 die new IMPL::InvalidOperationException('Unknown axis',$axis) unless exists $Axes{$axis}; |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
102
diff
changeset
|
242 |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
102
diff
changeset
|
243 my $nodes = $this->resolveAxis($axis); |
49 | 244 |
245 my @result; | |
246 | |
247 if (ref $query eq 'CODE') { | |
104
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
102
diff
changeset
|
248 @result = grep &$query($_), @{$nodes}; |
49 | 249 } elsif (ref $query eq 'ARRAY' ) { |
250 my %keys = map (($_,1),@$query); | |
104
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
102
diff
changeset
|
251 @result = grep $keys{$_->nodeName}, @{$nodes}; |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
102
diff
changeset
|
252 } elsif (ref $query eq 'HASH') { |
194 | 253 while( my ($axis,$filter) = each %$query ) { |
254 push @result, $this->selectNodesAxis($filter,$axis); | |
255 } | |
49 | 256 } elsif (defined $query) { |
104
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
102
diff
changeset
|
257 @result = grep $_->nodeName eq $query, @{$nodes}; |
49 | 258 } else { |
104
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
102
diff
changeset
|
259 return wantarray ? @{$nodes} : $nodes; |
49 | 260 } |
261 | |
262 return wantarray ? @result : \@result; | |
263 } | |
264 | |
104
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
102
diff
changeset
|
265 sub selectParent { |
194 | 266 my ($this) = @_; |
267 | |
268 if ($this->parentNode) { | |
269 return wantarray ? $this->parentNode : [$this->parentNode]; | |
270 } else { | |
271 return wantarray ? () : []; | |
272 } | |
104
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
102
diff
changeset
|
273 } |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
102
diff
changeset
|
274 |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
102
diff
changeset
|
275 sub selectSiblings { |
194 | 276 my ($this) = @_; |
277 | |
278 if ($this->parentNode) { | |
279 return $this->parentNode->selectNodes( sub { $_ != $this } ); | |
280 } else { | |
281 return wantarray ? () : []; | |
282 } | |
104
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
102
diff
changeset
|
283 } |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
102
diff
changeset
|
284 |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
102
diff
changeset
|
285 sub selectDocument { |
194 | 286 my ($this) = @_; |
287 | |
288 if ($this->document) { | |
289 return wantarray ? $this->document : [$this->document]; | |
290 } else { | |
291 return wantarray ? () : []; | |
292 } | |
104
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
102
diff
changeset
|
293 } |
196bf443b5e1
DOM::Schema RC0 inflators support, validation and some other things,
wizard
parents:
102
diff
changeset
|
294 |
148
e6447ad85cb4
DOM objects now have a schema and schemaSource properties
wizard
parents:
124
diff
changeset
|
295 sub selectDescendant { |
194 | 296 wantarray ? |
297 map $_->selectAll(), $_[0]->childNodes : | |
298 [map $_->selectAll(), $_[0]->childNodes] | |
148
e6447ad85cb4
DOM objects now have a schema and schemaSource properties
wizard
parents:
124
diff
changeset
|
299 } |
e6447ad85cb4
DOM objects now have a schema and schemaSource properties
wizard
parents:
124
diff
changeset
|
300 |
e6447ad85cb4
DOM objects now have a schema and schemaSource properties
wizard
parents:
124
diff
changeset
|
301 sub selectAll { |
e6447ad85cb4
DOM objects now have a schema and schemaSource properties
wizard
parents:
124
diff
changeset
|
302 map(selectAll($_),@{$_[0]->childNodes}) , $_[0] |
e6447ad85cb4
DOM objects now have a schema and schemaSource properties
wizard
parents:
124
diff
changeset
|
303 } |
e6447ad85cb4
DOM objects now have a schema and schemaSource properties
wizard
parents:
124
diff
changeset
|
304 |
e6447ad85cb4
DOM objects now have a schema and schemaSource properties
wizard
parents:
124
diff
changeset
|
305 sub selectAncestors { |
194 | 306 my $parent = $_[0]->parentNode; |
307 | |
308 wantarray ? | |
309 ($parent ? ($parent->selectAncestors,$parent) : ()) : | |
310 [$parent ? ($parent->selectAncestors,$parent) : ()] | |
148
e6447ad85cb4
DOM objects now have a schema and schemaSource properties
wizard
parents:
124
diff
changeset
|
311 } |
e6447ad85cb4
DOM objects now have a schema and schemaSource properties
wizard
parents:
124
diff
changeset
|
312 |
49 | 313 sub firstChild { |
314 @_ >=2 ? $_[0]->replaceNodeAt(0,$_[1]) : $_[0]->childNodes->[0]; | |
315 } | |
316 | |
317 sub _getIsComplex { | |
152 | 318 ($_[0]->{$childNodes} and $_[0]->{$childNodes}->Count) ? 1 : 0; |
49 | 319 } |
320 | |
321 sub _updateDocRefs { | |
322 my ($this) = @_; | |
323 | |
324 # this method is called by the parent node on his children, so we need no to check parent | |
325 $this->{$document} = $this->{$parentNode}->document; | |
326 | |
327 # prevent cyclic | |
328 weaken($this->{$document}) if $this->{$document}; | |
329 | |
173 | 330 map $_->_updateDocRefs, @{$this->{$childNodes}} if $this->{$childNodes}; |
49 | 331 } |
332 | |
333 sub _setParent { | |
334 my ($this,$node) = @_; | |
335 | |
336 | |
337 if (($node || 0) != ($this->{$parentNode} || 0)) { | |
338 my $newOwner; | |
339 if ($node) { | |
340 $this->{$parentNode} = $node; | |
341 $newOwner = $node->document || 0; | |
342 | |
343 # prevent from creating cyclicreferences | |
344 weaken($this->{$parentNode}); | |
345 | |
346 } else { | |
347 delete $this->{$parentNode}; | |
75 | 348 |
349 #keep document | |
350 $newOwner = $this->{$document}; | |
49 | 351 } |
352 | |
353 if (($this->{$document}||0) != $newOwner) { | |
354 $this->{$document} = $newOwner; | |
355 weaken($this->{$document}) if $newOwner; | |
356 $_->_updateDocRefs foreach @{$this->childNodes}; | |
357 } | |
358 } | |
359 } | |
360 | |
361 sub text { | |
362 my ($this) = @_; | |
363 | |
364 join ('', $this->nodeValue || '', map ($_->text || '', @{$this->childNodes})); | |
365 } | |
366 | |
367 sub nodeProperty { | |
368 my $this = shift; | |
369 my $name = shift; | |
370 | |
188 | 371 return unless defined $name; |
372 | |
122 | 373 if (my $method = $this->can($name)) { |
194 | 374 unshift @_,$this; |
375 # use goto to preserve calling context | |
376 goto &$method; | |
122 | 377 } |
148
e6447ad85cb4
DOM objects now have a schema and schemaSource properties
wizard
parents:
124
diff
changeset
|
378 # dynamic property |
49 | 379 if (@_) { |
194 | 380 # set |
381 return $this->{$_propertyMap}{$name} = shift; | |
382 } else { | |
383 return $this->{$_propertyMap}{$name}; | |
49 | 384 } |
385 } | |
386 | |
122 | 387 sub listProperties { |
194 | 388 my ($this) = @_; |
389 | |
275 | 390 my %props = map {$_->name, 1} $this->GetMeta(typeof IMPL::Class::PropertyInfo, sub { $_->attributes->{domProperty}},1); |
194 | 391 |
392 return (keys %props,keys %{$this->{$_propertyMap}}); | |
122 | 393 } |
394 | |
152 | 395 sub save { |
194 | 396 my ($this,$writer) = @_; |
397 | |
398 if ( not ( $this->isComplex or defined $this->{$nodeValue} ) ) { | |
399 $writer->emptyTag( | |
400 $this->{$nodeName}, | |
401 map { | |
402 $_, | |
403 $this->nodeProperty($_) | |
404 } grep defined $this->nodeProperty($_), $this->listProperties | |
405 ); | |
406 } else { | |
407 $writer->startTag( | |
408 $this->{$nodeName}, | |
409 map { | |
410 $_, | |
411 $this->nodeProperty($_) | |
412 } grep defined $this->nodeProperty($_), $this->listProperties | |
413 ); | |
414 $writer->characters($this->{$nodeValue}) if $this->{$nodeValue}; | |
415 | |
416 $_->save($writer) foreach $this->childNodes; | |
417 | |
418 $writer->endTag($this->{$nodeName}); | |
419 } | |
152 | 420 } |
421 | |
49 | 422 sub qname { |
423 $_[0]->{$nodeName}; | |
424 } | |
425 | |
426 sub path { | |
427 my ($this) = @_; | |
428 | |
429 if ($this->parentNode) { | |
430 return $this->parentNode->path.'.'.$this->qname; | |
431 } else { | |
432 return $this->qname; | |
433 } | |
434 } | |
435 | |
436 1; | |
75 | 437 |
438 __END__ | |
439 | |
440 =pod | |
441 | |
442 =head1 NAME | |
443 | |
180 | 444 C<IMPL::DOM::Node> Элемент DOM модели |
75 | 445 |
446 =head1 DESCRIPTION | |
447 | |
180 | 448 Базовый узел DOM модели. От него можно наследовать другие элементы DOM модели. |
75 | 449 |
450 =head1 MEMBERS | |
451 | |
452 =head2 PROPERTIES | |
453 | |
454 =over | |
455 | |
456 =item C<[get] nodeName> | |
457 | |
180 | 458 Имя узла. Задается при создании. |
75 | 459 |
460 =item C<[get] document> | |
461 | |
180 | 462 Документ к которому принадлежит узел. Задается при поздании узла. |
75 | 463 |
464 =item C<[get] isComplex> | |
465 | |
180 | 466 Определяет является ли узел сложным (тоесть есть ли дети). |
75 | 467 |
180 | 468 C<true> - есть, C<false> - нет. |
75 | 469 |
470 =item C<[get,set] nodeValue> | |
471 | |
180 | 472 Значение узла, обычно простой скаляр, но ничто не мешает туда |
473 устанавливать любое значение. | |
75 | 474 |
475 =item C<[get,list] childNodes> | |
476 | |
180 | 477 Список детей, является списокм C<IMPL::Object::List>. |
75 | 478 |
479 =item C<[get] parentNode> | |
480 | |
180 | 481 Ссылка на родительский элемент, если таковой имеется. |
75 | 482 |
148
e6447ad85cb4
DOM objects now have a schema and schemaSource properties
wizard
parents:
124
diff
changeset
|
483 =item C<[get] schema> |
e6447ad85cb4
DOM objects now have a schema and schemaSource properties
wizard
parents:
124
diff
changeset
|
484 |
180 | 485 Ссылка на узел из C<IMPL::DOM::Schema>, представляющий схему данных текущего узла. Может быть C<undef>. |
148
e6447ad85cb4
DOM objects now have a schema and schemaSource properties
wizard
parents:
124
diff
changeset
|
486 |
e6447ad85cb4
DOM objects now have a schema and schemaSource properties
wizard
parents:
124
diff
changeset
|
487 =item C<[get] schema> |
e6447ad85cb4
DOM objects now have a schema and schemaSource properties
wizard
parents:
124
diff
changeset
|
488 |
180 | 489 Ссылка на узел из C<IMPL::DOM::Schema>, представляющий элемент схемы, объявляющий данный узел. Может быть C<undef>. |
148
e6447ad85cb4
DOM objects now have a schema and schemaSource properties
wizard
parents:
124
diff
changeset
|
490 |
180 | 491 Отличается от свойства C<schema> тем, что узел в случае ссылки на тип узла, данной свойство будет содержать |
492 описание ссылки C<IMPL::DOM::Schema::Node>, а свойство C<schema> например будет ссылаться на | |
148
e6447ad85cb4
DOM objects now have a schema and schemaSource properties
wizard
parents:
124
diff
changeset
|
493 C<IMPL::DOM::Schema::ComplexType>. |
e6447ad85cb4
DOM objects now have a schema and schemaSource properties
wizard
parents:
124
diff
changeset
|
494 |
e6447ad85cb4
DOM objects now have a schema and schemaSource properties
wizard
parents:
124
diff
changeset
|
495 =back |
e6447ad85cb4
DOM objects now have a schema and schemaSource properties
wizard
parents:
124
diff
changeset
|
496 |
75 | 497 =head2 METHODS |
498 | |
180 | 499 =cut |