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