comparison lib/IMPL/DOM/Node.pm @ 407:c6e90e02dd17 ref20150831

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