/[suikacvs]/messaging/manakai/lib/Message/DOM/Node.pm
Suika

Contents of /messaging/manakai/lib/Message/DOM/Node.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.11 - (show annotations) (download)
Sat Jul 7 15:05:01 2007 UTC (17 years, 4 months ago) by wakaba
Branch: MAIN
Changes since 1.10: +90 -26 lines
++ manakai/t/ChangeLog	7 Jul 2007 15:04:52 -0000
	* DOM-Node.t: Tests for |set_user_data| and |get_user_data|
	are added.  Tests for |get_feature| and |is_supported| are added.

2007-07-07  Wakaba  <wakaba@suika.fam.cx>

++ manakai/lib/Message/DOM/ChangeLog	7 Jul 2007 15:04:43 -0000
	* Node.pm (==, is_equal_node): Implemented.
	(is_same_node): Implemented.
	(get_feature, get_user_data, set_user_data): Implemented.
	(is_supported): Implemented.

2007-07-07  Wakaba  <wakaba@suika.fam.cx>

1 package Message::DOM::Node;
2 use strict;
3 our $VERSION=do{my @r=(q$Revision: 1.10 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};
4 push our @ISA, 'Message::IF::Node';
5 require Scalar::Util;
6 require Message::DOM::DOMException;
7
8 ## NOTE:
9 ## Node
10 ## + Attr (2)
11 ## + AttributeDefinition (81002)
12 ## + CharacterData
13 ## + Comment (8)
14 ## + Text (3)
15 ## + CDATASection (4)
16 ## + Document (9)
17 ## + DocumentFragment (11)
18 ## + DocumentType (10)
19 ## + Element (1)
20 ## + ElementTypeDefinition (81001)
21 ## + Entity (6)
22 ## + EntityReference (5)
23 ## + Notation (12)
24 ## + ProcessingInstruction (7)
25
26 use overload
27 '==' => 'is_equal_node',
28 '!=' => sub {
29 return not ($_[0] == $_[1]);
30 },
31 #eq => sub { $_[0] eq $_[1] }, ## is_same_node
32 #ne => sub { $_[0] ne $_[1] }, ## not is_same_node
33 fallback => 1;
34
35 ## The |Node| interface - constants
36
37 ## Definition group NodeType
38
39 ## NOTE: Numeric codes up to 200 are reserved by W3C [DOM1SE, DOM2, DOM3].
40
41 sub ELEMENT_NODE () { 1 }
42 sub ATTRIBUTE_NODE () { 2 }
43 sub TEXT_NODE () { 3 }
44 sub CDATA_SECTION_NODE () { 4 }
45 sub ENTITY_REFERENCE_NODE () { 5 }
46 sub ENTITY_NODE () { 6 }
47 sub PROCESSING_INSTRUCTION_NODE () { 7 }
48 sub COMMENT_NODE () { 8 }
49 sub DOCUMENT_NODE () { 9 }
50 sub DOCUMENT_TYPE_NODE () { 10 }
51 sub DOCUMENT_FRAGMENT_NODE () { 11 }
52 sub NOTATION_NODE () { 12 }
53 sub ELEMENT_TYPE_DEFINITION_NODE () { 81001 }
54 sub ATTRIBUTE_DEFINITION_NODE () { 81002 }
55
56 ## Definition group DocumentPosition
57
58 ## Spec:
59 ## <http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#DocumentPosition>
60
61 sub DOCUMENT_POSITION_DISCONNECTED () { 0x01 }
62 sub DOCUMENT_POSITION_PRECEDING () { 0x02 }
63 sub DOCUMENT_POSITION_FOLLOWING () { 0x04 }
64 sub DOCUMENT_POSITION_CONTAINS () { 0x08 }
65 sub DOCUMENT_POSITION_CONTAINED_BY () { 0x10 }
66 sub DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC () { 0x20 }
67
68 sub ____new ($$) {
69 my $self = bless \({}), shift;
70 $$self->{owner_document} = shift;
71 Scalar::Util::weaken ($$self->{owner_document});
72 return $self;
73 } # ____new
74
75 sub ___report_error ($$) {
76 $_[1]->throw;
77 } # ___report_error
78
79 sub AUTOLOAD {
80 my $method_name = our $AUTOLOAD;
81 $method_name =~ s/.*:://;
82 return if $method_name eq 'DESTROY';
83
84 if ({
85 ## Read-only attributes (trivial accessors)
86 owner_document => 1,
87 parent_node => 1,
88 manakai_read_only => 1,
89 }->{$method_name}) {
90 no strict 'refs';
91 eval qq{
92 sub $method_name (\$) {
93 return \${\$_[0]}->{$method_name};
94 }
95 };
96 goto &{ $AUTOLOAD };
97 } elsif ({
98 ## Read-write attributes (DOMString, trivial accessors)
99 }->{$method_name}) {
100 no strict 'refs';
101 eval qq{
102 sub $method_name (\$;\$) {
103 if (\@_ > 1) {
104 \${\$_[0]}->{$method_name} = ''.\$_[1];
105 }
106 return \${\$_[0]}->{$method_name};
107 }
108 };
109 goto &{ $AUTOLOAD };
110 } else {
111 require Carp;
112 Carp::croak (qq<Can't locate method "$AUTOLOAD">);
113 }
114 } # AUTOLOAD
115
116 ## |Node| attributes
117
118 ## NOTE: Overridden by |Element|.
119 sub attributes () { undef }
120
121 sub base_uri ($) {
122 ## NOTE: Overridden by |Attr|, |CharacterData|, |Document|, |DocumentType|,
123 ## |Element|, |EntityReference|, and |ProcessingInstruction|.
124
125 local $Error::Depth = $Error::Depth + 1;
126 return $_[0]->owner_document->base_uri;
127 } # base_uri
128
129 sub child_nodes ($) {
130 ## NOTE: Overridden by |CharacterData|, |ElementTypeDefinition|,
131 ## |Notation|, and |ProcessingInstruction|.
132 require Message::DOM::NodeList;
133 return bless \\($_[0]), 'Message::DOM::NodeList::ChildNodeList';
134 } # child_nodes
135
136 sub manakai_expanded_uri ($) {
137 my $self = shift;
138 local $Error::Depth = $Error::Depth + 1;
139 my $ln = $self->local_name;
140 if (defined $ln) {
141 my $nsuri = $self->namespace_uri;
142 if (defined $nsuri) {
143 return $nsuri . $ln;
144 } else {
145 return $ln;
146 }
147 } else {
148 return undef;
149 }
150 } # manakai_expanded_uri
151
152 sub first_child ($) {
153 my $self = shift;
154 return $$self->{child_nodes} ? $$self->{child_nodes}->[0] : undef;
155 } # first_child
156
157 sub last_child ($) {
158 my $self = shift;
159 return $$self->{child_nodes} && $$self->{child_nodes}->[0]
160 ? $$self->{child_nodes}->[-1] : undef;
161 } # last_child
162
163 sub local_name { undef }
164
165 sub manakai_local_name { undef }
166
167 sub namespace_uri { undef }
168
169 sub next_sibling ($) {
170 my $self = shift;
171 my $parent = $$self->{parent_node};
172 return undef unless defined $parent;
173 my $has_self;
174 for (@{$parent->child_nodes}) {
175 if ($_ eq $self) {
176 $has_self = 1;
177 } elsif ($has_self) {
178 return $_;
179 }
180 }
181 return undef;
182 } # next_sibling
183
184 ## NOTE: Overridden by subclasses.
185 sub node_name () { undef }
186
187 ## NOTE: Overridden by subclasses.
188 sub node_type () { }
189
190 ## NOTE: Overridden by |Attr|, |AttributeDefinition|,
191 ## |CharacterData|, and |ProcessingInstruction|.
192 sub node_value () { undef }
193
194 sub owner_document ($);
195
196 sub manakai_parent_element ($) {
197 my $self = shift;
198 my $parent = $$self->{parent_node};
199 while (defined $parent) {
200 if ($parent->node_type == ELEMENT_NODE) {
201 return $parent;
202 } else {
203 $parent = $$parent->{parent_node};
204 }
205 }
206 return undef;
207 } # manakai_parent_element
208
209 sub parent_node ($);
210
211 ## NOTE: Overridden by |Element| and |Attr|.
212 sub prefix ($;$) { undef }
213
214 sub previous_sibling ($) {
215 my $self = shift;
216 my $parent = $$self->{parent_node};
217 return undef unless defined $parent;
218 my $prev;
219 for (@{$parent->child_nodes}) {
220 if ($_ eq $self) {
221 return $prev;
222 } else {
223 $prev = $_;
224 }
225 }
226 return undef;
227 } # previous_sibling
228
229 sub manakai_read_only ($);
230
231 sub text_content ($;$) {
232 ## NOTE: For |Element|, |Attr|, |Entity|, |EntityReference|,
233 ## |DocumentFragment|, and |AttributeDefinition|. In addition,
234 ## |Document|'s |text_content| might call this attribute.
235
236 ## NOTE: Overridden by |Document|, |DocumentType|, |Notation|,
237 ## |CharacterData|, |ProcessingInstruction|, and |ElementTypeDefinition|.
238
239 my $self = $_[0];
240
241 if (@_ > 1) {
242 if (${$$self->{owner_document} or $self}->{strict_error_checking} and
243 $$self->{manakai_read_only}) {
244 report Message::DOM::DOMException
245 -object => $self,
246 -type => 'NO_MODIFICATION_ALLOWED_ERR',
247 -subtype => 'READ_ONLY_NODE_ERR';
248 }
249
250 local $Error::Depth = $Error::Depth + 1;
251 @{$self->child_nodes} = ();
252 if (defined $_[1] and length $_[1]) {
253 ## NOTE: |DocumentType| don't use this code.
254 my $text = ($$self->{owner_document} || $self)->create_text_node ($_[1]);
255 $self->append_child ($text);
256 }
257 }
258
259 if (defined wantarray) {
260 local $Error::Depth = $Error::Depth + 1;
261 my $r = '';
262 my @node = @{$self->child_nodes};
263 while (@node) {
264 my $child = shift @node;
265 my $child_nt = $child->node_type;
266 if ($child_nt == TEXT_NODE or $child_nt == CDATA_SECTION_NODE) {
267 $r .= $child->node_value unless $child->is_element_content_whitespace;
268 } elsif ($child_nt == COMMENT_NODE or
269 $child_nt == PROCESSING_INSTRUCTION_NODE or
270 $child_nt == DOCUMENT_TYPE_NODE) {
271 #
272 } else {
273 unshift @node, @{$child->child_nodes};
274 }
275 }
276 return $r;
277 }
278 } # text_content
279
280 ## |Node| methods
281
282 sub clone_node ($;$) {
283 my ($self, $deep) = @_;
284
285 ## ISSUE: Need definitions for the cloning operation
286 ## for ElementTypeDefinition, and AttributeDefinition nodes,
287 ## as well as new attributes introduced in DOM XML Document Type Definition
288 ## module.
289 ## ISSUE: Define if default attributes and attributedefinition are inconsistent
290
291 local $Error::Depth = $Error::Depth + 1;
292 my $od = $self->owner_document;
293 my $strict_check = $od->strict_error_checking;
294 $od->strict_error_checking (0);
295 my $cfg = $od->dom_config;
296 my $er_copy_asis
297 = $cfg->get_parameter
298 (q<http://suika.fam.cx/www/2006/dom-config/clone-entity-reference-subtree>);
299
300 my $r;
301 my @udh;
302 my @node = ([$self]);
303 while (@node) {
304 my ($node, $parent) = @{shift @node};
305 my $nt = $node->node_type;
306 my $clone;
307 if ($nt == ELEMENT_NODE) {
308 $clone = $od->create_element_ns
309 ($node->namespace_uri, [$node->prefix, $node->local_name]);
310 if ($parent) {
311 $parent->append_child ($clone);
312 } else {
313 $r = $clone;
314 }
315 my $attrs = $node->attributes;
316 my $attrsMax = @$attrs - 1;
317 for my $i (0..$attrsMax) {
318 my $attr = $attrs->[$i];
319 push @node, [$attr, $clone] if $attr->specified;
320 }
321 if ($deep) {
322 push @node, map {[$_, $clone]} @{$node->child_nodes};
323 }
324 } elsif ($nt == TEXT_NODE) {
325 $clone = $od->create_text_node ($node->data);
326 if ($parent) {
327 $parent->append_child ($clone);
328 } else {
329 $r = $clone;
330 }
331 $clone->is_element_content_whitespace (1)
332 if $node->is_element_content_whitespace;
333 } elsif ($nt == ATTRIBUTE_NODE) {
334 $clone = $od->create_attribute_ns
335 ($node->namespace_uri, [$node->prefix, $node->local_name]);
336 if ($parent) {
337 $parent->set_attribute_node_ns ($clone);
338 } else {
339 $r = $clone;
340 }
341 $clone->specified (1);
342 push @node, map {[$_, $clone]} @{$node->child_nodes};
343 } elsif ($nt == COMMENT_NODE) {
344 $clone = $od->create_comment ($node->data);
345 if ($parent) {
346 $parent->append_child ($clone);
347 } else {
348 $r = $clone;
349 }
350 } elsif ($nt == CDATA_SECTION_NODE) {
351 $clone = $od->create_cdata_section ($node->data);
352 if ($parent) {
353 $parent->append_child ($clone);
354 } else {
355 $r = $clone;
356 }
357 } elsif ($nt == PROCESSING_INSTRUCTION_NODE) {
358 $clone = $od->create_processing_instruction
359 ($node->target, $node->data);
360 if ($parent) {
361 $parent->append_child ($clone);
362 } else {
363 $r = $clone;
364 }
365 } elsif ($nt == ENTITY_REFERENCE_NODE) {
366 $clone = $od->create_entity_reference ($node->node_name);
367 if ($er_copy_asis) {
368 $clone->manakai_set_read_only (0);
369 $clone->text_content (0);
370 for (@{$node->child_nodes}) {
371 $clone->append_child ($_->clone_node (1));
372 }
373 $clone->manakai_expanded ($node->manakai_expanded);
374 $clone->manakai_set_read_only (1, 1);
375 } # copy asis
376 if ($parent) {
377 $parent->append_child ($clone);
378 } else {
379 $r = $clone;
380 }
381 } elsif ($nt == DOCUMENT_FRAGMENT_NODE) {
382 $clone = $od->create_document_fragment;
383 $r = $clone;
384 push @node, map {[$_, $clone]} @{$node->child_nodes};
385 } elsif ($nt == DOCUMENT_NODE) {
386 $od->strict_error_checking ($strict_check);
387 report Message::DOM::DOMException
388 -object => $self,
389 -type => 'NOT_SUPPORTED_ERR',
390 -subtype => 'CLONE_NODE_NOT_SUPPORTED_ERR';
391 } elsif ($nt == DOCUMENT_TYPE_NODE) {
392 $od->strict_error_checking ($strict_check);
393 report Message::DOM::DOMException
394 -object => $self,
395 -type => 'NOT_SUPPORTED_ERR',
396 -subtype => 'CLONE_NODE_NOT_SUPPORTED_ERR';
397 } elsif ($nt == ENTITY_NODE) {
398 $od->strict_error_checking ($strict_check);
399 report Message::DOM::DOMException
400 -object => $self,
401 -type => 'NOT_SUPPORTED_ERR',
402 -subtype => 'CLONE_NODE_NOT_SUPPORTED_ERR';
403 } elsif ($nt == NOTATION_NODE) {
404 $od->strict_error_checking ($strict_check);
405 report Message::DOM::DOMException
406 -object => $self,
407 -type => 'NOT_SUPPORTED_ERR',
408 -subtype => 'CLONE_NODE_NOT_SUPPORTED_ERR';
409 } else {
410 $od->strict_error_checking ($strict_check);
411 report Message::DOM::DOMException
412 -object => $self,
413 -type => 'NOT_SUPPORTED_ERR',
414 -subtype => 'CLONE_NODE_NOT_SUPPORTED_ERR';
415 }
416
417 my $udhs = $$self->{user_data};
418 push @udh, [$node => $clone, $udhs] if $udhs and %$udhs;
419 } # @node
420 $od->strict_error_checking (1) if $strict_check;
421
422 ## Calling user data handlers if any
423 for my $sd (@udh) {
424 my $src = $sd->[0];
425 my $src_ud = $sd->[2];
426 for my $key (keys %{$src_ud}) {
427 my $dh = $src_ud->{$key}->[1];
428 if ($dh) { ## NODE_CLONED
429 $dh->handle (1, $key, $src_ud->{$key}->[0], $src, $sd->[1]);
430 ## ISSUE: |handler| method? CODE?
431 }
432 }
433 }
434
435 return $r;
436 } # clone_node
437
438 sub compare_document_position ($$) {
439 ## ISSUE: There are implementation specifics
440 ## (see what Gecko does if it implement this method...)
441
442 ## ISSUE: Maybe we should overload <=> or cmp
443
444 ## TODO: Too long method name! Too long constant names!
445 ## Too many thing to be done by a method!
446 ## Maybe we should import simpler method implemented by IE.
447
448 ## ISSUE: Need documentation for ElementTypeDefinition and AttributeDefinition
449 ## concerns
450
451 my @acontainer = ($_[0]);
452 my @bcontainer = ($_[1]);
453 F: {
454 A: while (1) {
455 if ($acontainer[-1] eq $bcontainer[-1]) {
456 last F;
457 } else {
458 my $ap;
459 my $atype = $acontainer[-1]->node_type;
460 if ($atype == ATTRIBUTE_NODE) {
461 $ap = $acontainer[-1]->owner_element;
462 } elsif ($atype == ENTITY_NODE or $atype == NOTATION_NODE or
463 $atype == ELEMENT_TYPE_DEFINITION_NODE) {
464 $ap = $acontainer[-1]->owner_document_type_definition;
465 } elsif ($atype == ATTRIBUTE_DEFINITION_NODE) {
466 $ap = $acontainer[-1]->owner_element_type_definition;
467 } else {
468 $ap = $acontainer[-1]->parent_node;
469 }
470 if (defined $ap) {
471 push @acontainer, $ap;
472 } else {
473 last A;
474 }
475 }
476 } # A
477
478 B: while (1) {
479 if ($acontainer[-1] eq $bcontainer[-1]) {
480 last F;
481 } else {
482 my $bp;
483 my $btype = $bcontainer[-1]->node_type;
484 if ($btype == ATTRIBUTE_NODE) {
485 $bp = $bcontainer[-1]->owner_element;
486 } elsif ($btype == ENTITY_NODE or $btype == NOTATION_NODE or
487 $btype == ELEMENT_TYPE_DEFINITION_NODE) {
488 $bp = $bcontainer[-1]->owner_document_type_definition;
489 } elsif ($btype == ATTRIBUTE_DEFINITION_NODE) {
490 $bp = $bcontainer[-1]->owner_element_type_definition;
491 } else {
492 $bp = $bcontainer[-1]->parent_node;
493 }
494 if (defined $bp) {
495 push @bcontainer, $bp;
496 } else {
497 last B;
498 }
499 }
500 } # B
501
502 ## Disconnected
503 if ($bcontainer[-1]->isa ('Message::IF::Node')) {
504 ## ISSUE: Document this in manakai's DOM Perl Binding?
505 return DOCUMENT_POSITION_DISCONNECTED
506 | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
507 | ((${$acontainer[-1]} cmp ${$bcontainer[-1]}) > 0
508 ? DOCUMENT_POSITION_FOLLOWING
509 : DOCUMENT_POSITION_PRECEDING);
510 } else {
511 ## TODO: Is there test cases for this?
512 return DOCUMENT_POSITION_DISCONNECTED
513 | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
514 | DOCUMENT_POSITION_FOLLOWING;
515 }
516 } # F
517
518 ## Common container found
519 if (@acontainer >= 2) {
520 if (@bcontainer >= 2) {
521 my $acnt = $acontainer[-2]->node_type;
522 my $bcnt = $bcontainer[-2]->node_type;
523 if ($acnt == ATTRIBUTE_NODE or
524 $acnt == NOTATION_NODE or
525 $acnt == ELEMENT_TYPE_DEFINITION_NODE or
526 $acnt == ATTRIBUTE_DEFINITION_NODE) {
527 if ($acnt == $bcnt) {
528 return DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
529 | (($acontainer[-2]->node_name cmp
530 $bcontainer[-2]->node_name) > 0
531 ? DOCUMENT_POSITION_FOLLOWING
532 : DOCUMENT_POSITION_PRECEDING);
533 } elsif ($bcnt == ATTRIBUTE_NODE or
534 $bcnt == NOTATION_NODE or
535 $bcnt == ELEMENT_TYPE_DEFINITION_NODE or
536 $bcnt == ATTRIBUTE_DEFINITION_NODE) {
537 return (($acnt < $bcnt)
538 ? DOCUMENT_POSITION_FOLLOWING
539 : DOCUMENT_POSITION_PRECEDING);
540 } else {
541 ## A: Non-child and B: child
542 return DOCUMENT_POSITION_FOLLOWING;
543 }
544 } elsif ($bcnt == ATTRIBUTE_NODE or
545 $bcnt == NOTATION_NODE or
546 $bcnt == ELEMENT_TYPE_DEFINITION_NODE or
547 $bcnt == ATTRIBUTE_DEFINITION_NODE) {
548 ## A: Child and B: non-child
549 return DOCUMENT_POSITION_PRECEDING;
550 } else {
551 ## A and B are both children
552 for my $cn (@{$acontainer[-1]->child_nodes}) {
553 if ($cn eq $acontainer[-2]) {
554 return DOCUMENT_POSITION_FOLLOWING;
555 } elsif ($cn eq $bcontainer[-2]) {
556 return DOCUMENT_POSITION_PRECEDING;
557 }
558 }
559 die "compare_document_position: Something wrong (1)";
560 }
561 } else {
562 ## B contains A
563 return DOCUMENT_POSITION_CONTAINS
564 | DOCUMENT_POSITION_PRECEDING;
565 }
566 } else {
567 if (@bcontainer >= 2) {
568 ## A contains B
569 return DOCUMENT_POSITION_CONTAINED_BY
570 | DOCUMENT_POSITION_FOLLOWING;
571 } else {
572 ## A eq B
573 return 0;
574 }
575 }
576 die "compare_document_position: Something wrong (2)";
577 } # compare_document_position
578
579 sub get_feature ($$;$) {
580 my $feature = lc $_[1]; ## TODO: |lc|?
581 $feature =~ s/^\+//;
582 my $version = defined $_[2] ? $_[2] : '';
583 if ($Message::DOM::DOMImplementation::HasFeature->{$feature}->{$version}) {
584 return $_[0];
585 } else {
586 return undef;
587 }
588 } # get_feature
589
590 sub get_user_data ($$) {
591 if (${$_[0]}->{user_data}->{$_[1]}) {
592 return ${$_[0]}->{user_data}->{$_[1]}->[0];
593 } else {
594 return undef;
595 }
596 } # get_user_data
597
598 sub has_attributes ($) {
599 for (values %{${$_[0]}->{attributes} or {}}) {
600 return 1 if keys %$_;
601 }
602 return 0;
603 } # has_attributes
604
605 sub has_child_nodes ($) {
606 return (@{${$_[0]}->{child_nodes} or []} > 0);
607 } # has_child_nodes
608
609 sub is_equal_node ($$) {
610 local $Error::Depth = $Error::Depth + 1;
611
612 return 0 unless UNIVERSAL::isa ($_[1], 'Message::IF::Node');
613
614 my $nt = $_[0]->node_type;
615 return 0 unless $nt == $_[1]->node_type;
616
617 my @str_attr = qw/node_name local_name namespace_uri
618 prefix node_value/;
619 push @str_attr, qw/public_id system_id internal_subset/
620 if $nt == DOCUMENT_TYPE_NODE;
621 for my $attr_name (@str_attr) {
622 my $v1 = $_[0]->can ($attr_name) ? $_[0]->$attr_name : undef;
623 my $v2 = $_[1]->can ($attr_name) ? $_[1]->$attr_name : undef;
624 if (defined $v1 and defined $v2) {
625 return 0 unless ''.$v1 eq ''.$v2;
626 } elsif (defined $v1 or defined $v2) {
627 return 0;
628 }
629 }
630
631 my @num_eq_attr = qw/child_nodes attributes/;
632 push @num_eq_attr, qw/entities notations element_types/
633 if $nt == DOCUMENT_TYPE_NODE;
634 push @num_eq_attr, qw/attribute_definitions/
635 if $nt == ELEMENT_TYPE_DEFINITION_NODE;
636 push @num_eq_attr, qw/declared_type default_type allowed_tokens/
637 if $nt == ATTRIBUTE_DEFINITION_NODE;
638 for my $attr_name (@num_eq_attr) {
639 my $v1 = $_[0]->can ($attr_name) ? $_[0]->$attr_name : undef;
640 my $v2 = $_[1]->can ($attr_name) ? $_[1]->$attr_name : undef;
641 if (defined $v1 and defined $v2) {
642 return 0 unless $v1 == $v2;
643 } elsif (defined $v1 or defined $v2) {
644 return 0;
645 }
646 }
647
648 return 1;
649 } # is_equal_node
650
651 sub is_same_node ($$) { $_[0] eq $_[1] }
652
653 sub is_supported ($$;$) {
654 my $feature = lc $_[1]; ## TODO: |lc|?
655 my $plus = ($feature =~ s/^\+//);
656 my $version = defined $_[2] ? $_[2] : '';
657 return $Message::DOM::DOMImplementation::HasFeature->{$feature}->{$version};
658 } # is_supported;
659
660 ## NOTE: Only applied to Elements and Documents
661 sub append_child ($$) {
662 my ($self, $new_child) = @_;
663 if (defined $$new_child->{parent_node}) {
664 my $parent_list = ${$$new_child->{parent_node}}->{child_nodes};
665 for (0..$#$parent_list) {
666 if ($parent_list->[$_] eq $new_child) {
667 splice @$parent_list, $_, 1;
668 last;
669 }
670 }
671 }
672 push @{$$self->{child_nodes}}, $new_child;
673 $$new_child->{parent_node} = $self;
674 Scalar::Util::weaken ($$new_child->{parent_node});
675 ## TODO:
676 $$new_child->{owner_document} = $self if $self->node_type == DOCUMENT_NODE;
677 return $new_child;
678 } # append_child
679
680 sub manakai_append_text ($$) {
681 ## NOTE: For |Element|, |Attr|, |Entity|, |EntityReference|,
682 ## |DocumentFragment|, and |AttributeDefinition|. In addition,
683 ## |Document|'s |text_content| might call this attribute.
684
685 ## NOTE: Overridden by |Document|, |DocumentType|, |CharacterData|,
686 ## |ElementTypeDefinition|, |Notation|, and |ProcessingInstruction|.
687
688 my $self = $_[0];
689 local $Error::Depth = $Error::Depth + 1;
690 if (@{$$self->{child_nodes}} and
691 $$self->{child_nodes}->[-1]->node_type == TEXT_NODE) {
692 $$self->{child_nodes}->[-1]->manakai_append_text ($_[1]);
693 } else {
694 my $text = ($$self->{owner_document} or $self)->create_text_node ($_[1]);
695 $self->append_child ($text);
696 }
697 } # manakai_append_text
698
699 ## NOTE: Only applied to Elements and Documents
700 sub insert_before ($$;$) {
701 my ($self, $new_child, $ref_child) = @_;
702 if (defined $$new_child->{parent_node}) {
703 my $parent_list = ${$$new_child->{parent_node}}->{child_nodes};
704 for (0..$#$parent_list) {
705 if ($parent_list->[$_] eq $new_child) {
706 splice @$parent_list, $_, 1;
707 last;
708 }
709 }
710 }
711 my $i = @{$$self->{child_nodes}};
712 if (defined $ref_child) {
713 for (0..$#{$$self->{child_nodes}}) {
714 if ($$self->{child_nodes}->[$_] eq $ref_child) {
715 $i = $_;
716 last;
717 }
718 }
719 }
720 splice @{$$self->{child_nodes}}, $i, 0, $new_child;
721 $$new_child->{parent_node} = $self;
722 Scalar::Util::weaken ($$new_child->{parent_node});
723 return $new_child;
724 } # insert_before
725
726 sub is_default_namespace ($$) {
727 ## TODO: Document that ElementTypeDefinition and AttributeDefinition
728 ## are same as DocumentType
729
730 local $Error::Depth = $Error::Depth + 1;
731 my $namespace_uri = defined $_[1] ? $_[1] : '';
732 my $nt = $_[0]->node_type;
733 if ($nt == ELEMENT_NODE) {
734 my $el = $_[0];
735 EL: {
736 unless (defined $el->prefix) {
737 my $elns = $el->namespace_uri;
738 if ($namespace_uri ne '' and defined $elns) {
739 return $namespace_uri eq $elns;
740 } else {
741 return not ($namespace_uri eq '' or defined $elns);
742 }
743 }
744 my $xmlns = $el->get_attribute_ns
745 ('http://www.w3.org/2000/xmlns/', 'xmlns');
746 if (defined $xmlns) {
747 if ($namespace_uri ne '') {
748 return ($namespace_uri eq $xmlns);
749 } else {
750 return ($xmlns eq '');
751 }
752 }
753 $el = $el->manakai_parent_element;
754 redo EL if defined $el;
755 return 0;
756 } # EL;
757 } else {
758 my $el = $nt == DOCUMENT_NODE
759 ? $_[0]->document_element
760 : $nt == ATTRIBUTE_NODE
761 ? $_[0]->owner_element
762 : $_[0]->manakai_parent_element;
763 if (defined $el) {
764 return $el->is_default_namespace ($_[1]);
765 } else {
766 return 0;
767 }
768 }
769 } # is_default_namespace
770
771 sub lookup_namespace_uri ($$) {
772 ## TODO: Need definition for ElementTypeDefinition and AttributeDefinition
773
774 my ($self, $prefix) = @_;
775 $prefix = undef if defined $prefix and $prefix eq '';
776 ## NOTE: Implementation dependent.
777 ## TODO: Check what Gecko does.
778 local $Error::Depth = $Error::Depth + 1;
779 my $nt = $self->node_type;
780 if ($nt == ELEMENT_NODE) {
781 my $el = $self;
782 EL: {
783 my $elns = $el->namespace_uri;
784 if (defined $elns) {
785 my $elpfx = $el->prefix;
786 if ((not defined $prefix and not defined $elpfx) or
787 (defined $prefix and defined $elpfx and $prefix eq $elpfx)) {
788 return $elns;
789 }
790 }
791 AT: for my $attr (@{$el->attributes}) {
792 my $attrns = $attr->namespace_uri;
793 next AT if not defined $attrns or
794 $attrns ne 'http://www.w3.org/2000/xmlns/';
795 my $attrpfx = $attr->prefix;
796 if (not defined $prefix) {
797 my $attrln = $attr->local_name;
798 if ($attrln eq 'xmlns') {
799 my $attrval = $attr->value;
800 return length $attrval ? $attrval : undef;
801 }
802 } elsif (defined $prefix and
803 defined $attrpfx and $attrpfx eq 'xmlns') {
804 my $attrln = $attr->local_name;
805 if ($attrln eq $prefix) {
806 my $attrval = $attr->value;
807 return length $attrval ? $attrval : undef;
808 }
809 }
810 } # AT
811 $el = $el->manakai_parent_element;
812 redo EL if defined $el;
813 return undef;
814 } # EL;
815 } else {
816 my $el = $nt == DOCUMENT_NODE
817 ? $self->document_element
818 : $nt == ATTRIBUTE_NODE
819 ? $self->owner_element
820 : $self->manakai_parent_element;
821 if (defined $el) {
822 return $el->lookup_namespace_uri ($prefix);
823 } else {
824 return undef;
825 }
826 }
827 } # lookup_namespace_uri
828
829 sub lookup_prefix ($$) {
830 ## ISSUE: Document ElementTypeDefinition and AttributeDefinition
831 ## behavior (i.e. same as DocumentType)
832
833 my $namespace_uri = defined $_[1] ? $_[1] : '';
834 if ($namespace_uri eq '') {
835 return undef;
836 }
837
838 local $Error::Depth = $Error::Depth + 1;
839 my $nt = $_[0]->node_type;
840 if ($nt == ELEMENT_NODE) {
841 my $el = $_[0];
842 EL: {
843 my $elns = $el->namespace_uri;
844 if (defined $elns and $elns eq $namespace_uri) {
845 my $elpfx = $el->prefix;
846 if (defined $elpfx) {
847 my $oeluri = $_[0]->lookup_namespace_uri ($elpfx);
848 if (defined $oeluri and $oeluri eq $namespace_uri) {
849 return $elpfx;
850 }
851 }
852 }
853 AT: for my $attr (@{$el->attributes}) {
854 my $attrpfx = $attr->prefix;
855 next AT if not defined $attrpfx or $attrpfx ne 'xmlns';
856 my $attrns = $attr->namespace_uri;
857 next AT if not defined $attrns or
858 $attrns ne 'http://www.w3.org/2000/xmlns/';
859 next AT unless $attr->value eq $namespace_uri;
860 my $attrln = $attr->local_name;
861 my $oeluri = $el->lookup_namespace_uri ($attrln);
862 next AT unless defined $oeluri;
863 if ($oeluri eq $namespace_uri) {
864 return $attrln;
865 }
866 }
867 $el = $el->manakai_parent_element;
868 redo EL if defined $el;
869 return undef;
870 } # EL
871 } else {
872 my $el = $nt == DOCUMENT_NODE
873 ? $_[0]->document_element
874 : $nt == ATTRIBUTE_NODE
875 ? $_[0]->owner_element
876 : $_[0]->manakai_parent_element;
877 if (defined $el) {
878 return $el->lookup_prefix ($_[1]);
879 } else {
880 return undef;
881 }
882 }
883 } # lookup_prefix
884
885 sub normalize ($) {
886 my $self = shift;
887 my $ptext;
888 local $Error::Depth = $Error::Depth + 1;
889
890 ## Children
891 my @remove;
892 for my $cn (@{$self->child_nodes}) {
893 if ($cn->node_type == TEXT_NODE) {
894 my $nv = $cn->node_value;
895 if (length $nv) {
896 if (defined $ptext) {
897 $ptext->manakai_append_text ($nv);
898 $ptext->is_element_content_whitespace (1)
899 if $cn->is_element_content_whitespace and
900 $ptext->is_element_content_whitespace;
901 push @remove, $cn;
902 } else {
903 $ptext = $cn;
904 }
905 } else {
906 push @remove, $cn;
907 }
908 } else {
909 $cn->normalize;
910 undef $ptext;
911 }
912 }
913 $self->remove_child ($_) for @remove;
914
915 my $nt = $self->node_type;
916 if ($nt == ELEMENT_NODE) {
917 ## Attributes
918 $_->normalize for @{$self->attributes};
919 } elsif ($nt == DOCUMENT_TYPE_NODE) {
920 ## ISSUE: Document these explicitly in DOM XML Document Type Definitions spec
921 ## Element type definitions
922 $_->normalize for @{$self->element_types};
923 ## General entities
924 $_->normalize for @{$self->general_entities};
925 } elsif ($nt == ELEMENT_TYPE_DEFINITION_NODE) {
926 ## Attribute definitions
927 $_->normalize for @{$self->attribute_definitions};
928 }
929 ## TODO: normalize-characters
930
931 ## TODO: In this implementation, if a modification raises a
932 ## |NO_MODIFICATION_ALLOWED_ERR|, then any modification before it
933 ## is not reverted.
934 } # normalize
935
936 ## NOTE: Only applied to Elements and Documents
937 sub remove_child ($$) {
938 my ($self, $old_child) = @_;
939 my $parent_list = $$self->{child_nodes};
940 for (0..$#$parent_list) {
941 if ($parent_list->[$_] eq $old_child) {
942 splice @$parent_list, $_, 1;
943 last;
944 }
945 }
946 delete $$old_child->{parent_node};
947 return $old_child;
948 } # remove_child
949
950 sub manakai_set_read_only ($;$$) {
951 my $value = 1 if $_[1];
952 if ($_[2]) {
953 my @target = ($_[0]);
954 while (@target) {
955 my $target = shift @target;
956 if ($value) {
957 $$target->{manakai_read_only} = 1;
958 } else {
959 delete $$target->{manakai_read_only};
960 }
961 push @target, @{$target->child_nodes};
962
963 my $nt = $target->node_type;
964 if ($nt == ELEMENT_NODE) {
965 push @target, @{$target->attributes};
966 } elsif ($nt == ELEMENT_TYPE_DEFINITION_NODE) {
967 push @target, @{$target->attribute_definitions};
968 } elsif ($nt == DOCUMENT_TYPE_NODE) {
969 push @target, @{$target->element_types};
970 push @target, @{$target->general_entities};
971 push @target, @{$target->notations};
972 }
973 }
974 } else { # not deep
975 if ($value) {
976 ${$_[0]}->{manakai_read_only} = 1;
977 } else {
978 delete ${$_[0]}->{manakai_read_only};
979 }
980 }
981 } # manakai_set_read_only
982
983 # {NOTE:: Perl application developers are advised to be careful
984 # to include direct or indirect references to the node
985 # itself as user data or in user data handlers.
986 # They would result in memory leak problems unless
987 # the circular references are removed later.
988 #
989 # It would be a good practive to eusure that every user data
990 # registered to a node is later unregistered by setting
991 # <DOM::null> as a data for the same key.
992 #
993 sub set_user_data ($$$;$) {
994 my ($self, $key, $data, $handler) = @_;
995
996 my $v = ($$self->{user_data} ||= {});
997 my $r = $v->{$key}->[0];
998
999 if (defined $data) {
1000 $v->{$key} = [$data, $handler];
1001
1002 if (defined $handler) {
1003 eval q{
1004 sub DESTROY {
1005 my $uds = ${$_[0]}->{user_data};
1006 for my $key (keys %$uds) {
1007 if (defined $uds->{$key}->[1]) {
1008 local $Error::Depth = $Error::Depth + 1;
1009 $uds->{$key}->[1]->(3, $key, $uds->{$key}->[0]); # NODE_DELETED
1010 }
1011 }
1012 }
1013 };
1014 }
1015 } else {
1016 delete $v->{$key};
1017 }
1018 return $r;
1019 } # set_user_data
1020
1021 package Message::IF::Node;
1022
1023 =head1 LICENSE
1024
1025 Copyright 2007 Wakaba <w@suika.fam.cx>
1026
1027 This program is free software; you can redistribute it and/or
1028 modify it under the same terms as Perl itself.
1029
1030 =cut
1031
1032 1;
1033 ## $Date: 2007/07/07 11:11:34 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24