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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.22 - (show annotations) (download)
Mon Sep 24 10:16:14 2007 UTC (17 years, 2 months ago) by wakaba
Branch: MAIN
CVS Tags: manakai-release-0-4-0
Changes since 1.21: +6 -2 lines
++ manakai/lib/Message/DOM/ChangeLog	24 Sep 2007 10:15:28 -0000
2007-09-24  Wakaba  <wakaba@suika.fam.cx>

	* DOMException.pm (SYNTAX_ERR): New subtype is defined.
	(UNDECLARED_PREFIX_ERR): New subtype is defined.

	* Document.pm (Document): Implements the |DocumentSelector|
	interface.

	* Element.pm (Element): Implements the |ElementSelector|
	interface.

	* Node.pm (Node): Implements the |NSResolver| interface.

	* SelectorsAPI.pm: Now (hopefully) conform to the Selectors
	API Editor's Draft (only |query_selector_all| on |Document|,
	with limited selectors syntax support, though).

++ manakai/t/ChangeLog	24 Sep 2007 10:16:05 -0000
2007-09-24  Wakaba  <wakaba@suika.fam.cx>

	* selectors-test-1.dat: New tests for pseudo-elements
	are added.

1 ## NOTE: This module will be renamed as Document.pm.
2
3 package Message::DOM::Document;
4 use strict;
5 our $VERSION=do{my @r=(q$Revision: 1.21 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};
6 push our @ISA, 'Message::DOM::Node', 'Message::IF::Document',
7 'Message::IF::DocumentTraversal', 'Message::IF::DocumentXDoctype',
8 'Message::IF::DocumentSelector', # MUST in Selectors API spec
9 'Message::IF::HTMLDocument';
10 require Message::DOM::Node;
11 use Char::Class::XML
12 qw/
13 InXML_NameStartChar10 InXMLNameStartChar11
14 InXMLNameChar10 InXMLNameChar11
15 InXML_NCNameStartChar10 InXMLNCNameStartChar11
16 InXMLNCNameChar10 InXMLNCNameChar11
17 /;
18
19 sub ____new ($$) {
20 my $self = shift->SUPER::____new (undef);
21 $$self->{implementation} = $_[0];
22 $$self->{strict_error_checking} = 1;
23 $$self->{child_nodes} = [];
24 $$self->{'http://suika.fam.cx/www/2006/dom-config/strict-document-children'} = 1;
25 $$self->{'http://suika.fam.cx/www/2006/dom-config/dtd-default-attribute'} = 1;
26 $$self->{'http://suika.fam.cx/www/2006/dom-config/clone-entity-reference-subtree'} = 1;
27 $$self->{'error-handler'} = sub ($) {
28 ## NOTE: Same as one set by |setParameter| with |undef| value.
29 warn $_[0];
30 return $_[0]->severity != 3; # SEVERITY_FATAL_ERROR
31 };
32 return $self;
33 } # ____new
34
35 sub AUTOLOAD {
36 my $method_name = our $AUTOLOAD;
37 $method_name =~ s/.*:://;
38 return if $method_name eq 'DESTROY';
39
40 if ({
41 ## Read-only attributes (trivial accessors)
42 implementation => 1,
43 }->{$method_name}) {
44 no strict 'refs';
45 eval qq{
46 sub $method_name (\$) {
47 return \${\$_[0]}->{$method_name};
48 }
49 };
50 goto &{ $AUTOLOAD };
51 } elsif ({
52 ## Read-write attributes (DOMString, trivial accessors)
53 document_uri => 1,
54 input_encoding => 1,
55 }->{$method_name}) {
56 no strict 'refs';
57 eval qq{
58 sub $method_name (\$;\$) {
59 if (\@_ > 1) {
60 if (\${\$_[0]}->{strict_error_checking} and
61 \${\$_[0]}->{manakai_read_only}) {
62 report Message::DOM::DOMException
63 -object => \$_[0],
64 -type => 'NO_MODIFICATION_ALLOWED_ERR',
65 -subtype => 'READ_ONLY_NODE_ERR';
66 }
67 if (defined \$_[1]) {
68 \${\$_[0]}->{$method_name} = ''.\$_[1];
69 } else {
70 delete \${\$_[0]}->{$method_name};
71 }
72 }
73 return \${\$_[0]}->{$method_name};
74 }
75 };
76 goto &{ $AUTOLOAD };
77 } elsif ({
78 ## Read-write attributes (boolean, trivial accessors)
79 all_declarations_processed => 1,
80 }->{$method_name}) {
81 no strict 'refs';
82 eval qq{
83 sub $method_name (\$;\$) {
84 if (\@_ > 1) {
85 if (\${\$_[0]}->{manakai_strict_error_checking} and
86 \${\$_[0]}->{manakai_read_only}) {
87 report Message::DOM::DOMException
88 -object => \$_[0],
89 -type => 'NO_MODIFICATION_ALLOWED_ERR',
90 -subtype => 'READ_ONLY_NODE_ERR';
91 }
92 if (\$_[1]) {
93 \${\$_[0]}->{$method_name} = 1;
94 } else {
95 delete \${\$_[0]}->{$method_name};
96 }
97 }
98 return \${\$_[0]}->{$method_name};
99 }
100 };
101 goto &{ $AUTOLOAD };
102 } elsif (my $module_name = {
103 create_attribute => 'Message::DOM::Attr',
104 create_attribute_ns => 'Message::DOM::Attr',
105 create_attribute_definition => 'Message::DOM::AttributeDefinition',
106 create_cdata_section => 'Message::DOM::Text',
107 create_comment => 'Message::DOM::CharacterData',
108 create_document_fragment => 'Message::DOM::DocumentFragment',
109 create_document_type_definition => 'Message::DOM::DocumentType',
110 create_element => 'Message::DOM::Element',
111 create_element_ns => 'Message::DOM::Element',
112 create_element_type_definition => 'Message::DOM::ElementTypeDefinition',
113 create_entity_reference => 'Message::DOM::EntityReference',
114 create_general_entity => 'Message::DOM::Entity',
115 create_notation => 'Message::DOM::Notation',
116 create_processing_instruction => 'Message::DOM::ProcessingInstruction',
117 manakai_create_serial_walker => 'Message::DOM::SerialWalker',
118 create_text_node => 'Message::DOM::Text',
119 create_tree_walker => 'Message::DOM::TreeWalker',
120 query_selector => 'Message::DOM::SelectorsAPI',
121 query_selector_all => 'Message::DOM::SelectorsAPI',
122 }->{$method_name}) {
123 eval qq{ require $module_name } or die $@;
124 goto &{ $AUTOLOAD };
125 } else {
126 require Carp;
127 Carp::croak (qq<Can't locate method "$AUTOLOAD">);
128 }
129 } # AUTOLOAD
130 sub implementation ($);
131 sub create_attribute ($$);
132 sub create_attribute_ns ($$$);
133 sub create_attribute_definition ($$);
134 sub create_cdata_section ($$);
135 sub create_comment ($$);
136 sub create_document_fragment ($);
137 sub create_document_type_definition ($$);
138 sub create_element ($$);
139 sub create_element_ns ($$$);
140 sub create_element_type_definition ($$);
141 sub create_entity_reference ($$);
142 sub create_general_entity ($$);
143 sub create_notation ($$);
144 sub create_processing_instruction ($$$);
145 sub create_text_node ($$);
146
147 ## |Node| attributes
148
149 sub base_uri ($) {
150 my $v = ${$_[0]}->{manakai_entity_base_uri};
151 if (defined $v) {
152 return $v;
153 } else {
154 return ${$_[0]}->{document_uri};
155 }
156 ## TODO: HTML5 <base>
157 } # base_uri
158
159 sub node_name () { '#document' }
160
161 sub node_type () { 9 } # DOCUMENT_NODE
162
163 sub text_content ($;$) {
164 my $self = shift;
165 if ($$self->{'http://suika.fam.cx/www/2006/dom-config/strict-document-children'}) {
166 return undef;
167 } else {
168 local $Error::Depth = $Error::Depth + 1;
169 return $self->SUPER::text_content (@_);
170 }
171 } # text_content
172
173 ## |Node| methods
174
175 sub adopt_node ($$) {
176 my ($self, $source) = @_;
177 ## TODO: Should we apply |copy-asis| configuration parameter to this method?
178
179 return undef unless UNIVERSAL::isa ($source, 'Message::DOM::Node');
180
181 my $strict = $self->strict_error_checking;
182 if ($strict and $$self->{manakai_read_only}) {
183 report Message::DOM::DOMException
184 -object => $self,
185 -type => 'NO_MODIFICATION_ALLOWED_ERR',
186 -subtype => 'READ_ONLY_NODE_ERR';
187 }
188
189 my $parent = $source->parent_node;
190 if ($strict and defined $parent and $$parent->{manakai_read_only}) {
191 report Message::DOM::DOMException
192 -object => $self,
193 -type => 'NO_MODIFICATION_ALLOWED_ERR',
194 -subtype => 'READ_ONLY_NODE_ERR';
195 }
196
197 my $nt = $source->node_type;
198 my $oe;
199 if ($nt == 2) { # ATTRIBUTE_NODE
200 $oe = $source->owner_element;
201 if ($strict and defined $oe and $$oe->{manakai_read_only}) {
202 report Message::DOM::DOMException
203 -object => $self,
204 -type => 'NO_MODIFICATION_ALLOWED_ERR',
205 -subtype => 'READ_ONLY_NODE_ERR';
206 }
207 } elsif ($nt == 9 or $nt == 10 or $nt == 6 or $nt == 12 or
208 $nt == 81001 or $nt == 81002) {
209 # DOCUMENT_NODE, DOCUMENT_TYPE_NODE, ENTITY_NODE, NOTATION_NODE,
210 # ELEMENT_TYPE_DEFINITION_NODE, ATTRIBUTE_DEFINITION_NODE
211 report Message::DOM::DOMException
212 -object => $self,
213 -type => 'NOT_SUPPORTED_ERR',
214 -subtype => 'ADOPT_NODE_TYPE_NOT_SUPPORTED_ERR';
215 ## ISSUE: Define ELEMENT_TYPE_DEFINITION_NODE and ATTRIBUTE_DEFINITION_NODE
216 }
217
218 my @change_od;
219 my @nodes = ($source);
220 while (@nodes) {
221 my $node = shift @nodes;
222 my $nt = $node->node_type;
223 if ($strict and $$node->{manakai_read_only}) {
224 report Message::DOM::DOMException
225 -object => $self,
226 -type => 'NO_MODIFICATION_ALLOWED_ERR',
227 -subtype => 'READ_ONLY_NODE_ERR';
228 }
229
230 push @change_od, $node;
231 push @nodes, @{$node->child_nodes}, @{$node->attributes or []};
232 } # @nodes
233
234 local $Error::Depth = $Error::Depth + 1;
235
236 if (defined $parent) {
237 $parent->remove_child ($source);
238 } elsif (defined $oe) {
239 $oe->remove_attribute_node ($source);
240 }
241
242 return $source if $self eq $change_od[0]->owner_document;
243 ## NOTE: The array must have more than zero
244 ## nodes by definition. In addition,
245 ## it cannot contain document or document
246 ## type nodes in current implementation.
247
248 my @ud_node;
249 for my $n (@change_od) {
250 $$n->{owner_document} = $self;
251 Scalar::Util::weaken ($$n->{owner_document});
252 if ($$n->{user_data}) {
253 push @ud_node, $n;
254 }
255 }
256
257 for my $src (@ud_node) {
258 my $src_ud = $$src->{user_data};
259 for my $key (keys %{$src_ud}) {
260 my $dh = $src_ud->{$key}->[1];
261 if ($dh) {
262 $dh->(5, $key, $src_ud->{$key}->[0], $src, undef); # NODE_ADOPTED
263 }
264 }
265 }
266
267 return $source;
268 } # adopt_node
269
270 sub append_child ($$) {
271 ## NOTE: Overrides |Node|'s implementation.
272 my $self = $_[0];
273
274 ## NOTE: |$self_od| code here in some $self->node_type.
275
276 ## -- Node Type check
277 my @new_child;
278 my $new_child_parent;
279 if ($_[1]->node_type == 11) { # DOCUMENT_FRAGMENT_NODE
280 push @new_child, @{$_[1]->child_nodes};
281 $new_child_parent = $_[1];
282 } else {
283 @new_child = ($_[1]);
284 $new_child_parent = $_[1]->parent_node;
285 }
286
287 ## NOTE: Depends on $self->node_type:
288 if ($$self->{strict_error_checking}) {
289 my $child_od = $_[1]->owner_document || $_[1]; # might be DocumentType
290 if ($self ne $child_od and $child_od->node_type != 10) {
291 report Message::DOM::DOMException # DOCUMENT_TYPE_NODE
292 -object => $self,
293 -type => 'WRONG_DOCUMENT_ERR',
294 -subtype => 'EXTERNAL_OBJECT_ERR';
295 }
296
297 if ($$self->{manakai_read_only} or
298 (@new_child and defined $new_child_parent and
299 $$new_child_parent->{manakai_read_only})) {
300 report Message::DOM::DOMException
301 -object => $self,
302 -type => 'NO_MODIFICATION_ALLOWED_ERR',
303 -subtype => 'READ_ONLY_NODE_ERR';
304 }
305
306 ## NOTE: Only in |Document|:
307 my $strict_children = $self->dom_config->get_parameter
308 (q<http://suika.fam.cx/www/2006/dom-config/strict-document-children>);
309 if ($strict_children) {
310 my $has_el;
311 my $has_dt;
312 my $child_nt = $_[1]->node_type;
313 if ($child_nt == 1) { # ELEMENT_NODE
314 $has_el = 1;
315 } elsif ($child_nt == 10) { # DOCUMENT_TYPE_NODE
316 $has_dt = 1;
317 } elsif ($child_nt == 11) { # DOCUMENT_FRAGMENT_NODE
318 for my $cn (@{$_[1]->child_nodes}) {
319 my $cnt = $cn->node_type;
320 if ($cnt == 1) { # ELEMENT_NODE
321 if ($has_el) {
322 report Message::DOM::DOMException
323 -object => $self,
324 -type => 'HIERARCHY_REQUEST_ERR',
325 -subtype => 'CHILD_NODE_TYPE_ERR';
326 }
327 $has_el = 1;
328 } elsif ($cnt == 10) { # DOCUMENT_TYPE_NODE
329 ## NOTE: |DocumentType| node cannot be contained in
330 ## |DocumentFragment| in strict mode.
331 if ($has_dt) {
332 report Message::DOM::DOMException
333 -object => $self,
334 -type => 'HIERARCHY_REQUEST_ERR',
335 -subtype => 'CHILD_NODE_TYPE_ERR';
336 }
337 $has_dt = 1;
338 }
339 }
340 }
341
342 if ($has_el) {
343 my $anode = $self->last_child;
344 while (defined $anode) {
345 if ($anode->node_type == 1) { # ELEMENT_NODE
346 report Message::DOM::DOMException
347 -object => $self,
348 -type => 'HIERARCHY_REQUEST_ERR',
349 -subtype => 'CHILD_NODE_TYPE_ERR';
350 }
351 $anode = $anode->previous_sibling;
352 }
353 } # has_el
354 if ($has_dt) {
355 my $anode = $self->last_child;
356 while (defined $anode) {
357 my $ant = $anode->node_type;
358 if ($ant == 1 or $ant == 10) { # ELEMENT_NODE or DOCUMENT_TYPE_NODE
359 report Message::DOM::DOMException
360 -object => $self,
361 -type => 'HIERARCHY_REQUEST_ERR',
362 -subtype => 'CHILD_NODE_TYPE_ERR';
363 }
364 $anode = $anode->previous_sibling;
365 }
366 } # has_dt
367 }
368
369 for my $cn (@new_child) {
370 unless ({
371 3, (not $strict_children), # TEXT_NODE
372 5, (not $strict_children), # ENTITY_REFERENCE_NODE
373 1, 1, # ELEMENT_NODE
374 4, (not $strict_children), # CDATA_SECTION_NODE
375 7, 1, # PROCESSING_INSTRUCTION_NODE
376 8, 1, # COMMENT_NODE
377 10, 1, # DOCUMENT_TYPE_NODE
378 }->{$cn->node_type}) {
379 report Message::DOM::DOMException
380 -object => $self,
381 -type => 'HIERARCHY_REQUEST_ERR',
382 -subtype => 'CHILD_NODE_TYPE_ERR';
383 }
384 }
385
386 ## NOTE: Ancestor check here in |Node|.
387 }
388
389 ## NOTE: "Insert at" code only in insert_before and replace_child
390
391 ## -- Removes from parent
392 if ($new_child_parent) {
393 if (@new_child == 1) {
394 my $v = $$new_child_parent->{child_nodes};
395 RP: for my $i (0..$#$v) {
396 if ($v->[$i] eq $new_child[0]) {
397 splice @$v, $i, 1, ();
398 last RP;
399 }
400 } # RP
401 } else {
402 @{$$new_child_parent->{child_nodes}} = ();
403 }
404 }
405
406 ## -- Rewrite the |parentNode| properties
407 for my $nc (@new_child) {
408 $$nc->{parent_node} = $self;
409 Scalar::Util::weaken ($$nc->{parent_node});
410 }
411
412 ## NOTE: Depends on method:
413 push @{$$self->{child_nodes}}, @new_child;
414
415 ## NOTE: Only in |Document|.
416 for (@new_child) {
417 delete $$_->{implementation};
418 $$_->{owner_document} = $self;
419 Scalar::Util::weaken ($$_->{owner_document});
420 }
421
422 return $_[1];
423 } # apepnd_child
424
425 sub manakai_append_text ($$) {
426 my $self = shift;
427 if ($$self->{'http://suika.fam.cx/www/2006/dom-config/strict-document-children'}) {
428 #
429 } else {
430 local $Error::Depth = $Error::Depth + 1;
431 return $self->SUPER::manakai_append_text (@_);
432 }
433 } # manakai_append_text
434
435 sub insert_before ($$) {
436 ## NOTE: Overrides |Node|'s implementation.
437 my $self = $_[0];
438
439 ## NOTE: |$self_od| code here depending on $self->node_type.
440
441 ## -- Node Type check
442 my @new_child;
443 my $new_child_parent;
444 if ($_[1]->node_type == 11) { # DOCUMENT_FRAGMENT_NODE
445 push @new_child, @{$_[1]->child_nodes};
446 $new_child_parent = $_[1];
447 } else {
448 @new_child = ($_[1]);
449 $new_child_parent = $_[1]->parent_node;
450 }
451
452 ## NOTE: Depends on $self->node_type:
453 if ($$self->{strict_error_checking}) {
454 my $child_od = $_[1]->owner_document || $_[1]; # might be DocumentType
455 if ($self ne $child_od and $child_od->node_type != 10) {
456 report Message::DOM::DOMException # DOCUMENT_TYPE_NODE
457 -object => $self,
458 -type => 'WRONG_DOCUMENT_ERR',
459 -subtype => 'EXTERNAL_OBJECT_ERR';
460 }
461
462 if ($$self->{manakai_read_only} or
463 (@new_child and defined $new_child_parent and
464 $$new_child_parent->{manakai_read_only})) {
465 report Message::DOM::DOMException
466 -object => $self,
467 -type => 'NO_MODIFICATION_ALLOWED_ERR',
468 -subtype => 'READ_ONLY_NODE_ERR';
469 }
470
471 ## NOTE: Only in |Document|:
472 my $strict_children = $self->dom_config->get_parameter
473 (q<http://suika.fam.cx/www/2006/dom-config/strict-document-children>);
474 if ($strict_children) {
475 my $has_el;
476 my $has_dt;
477 my $child_nt = $_[1]->node_type;
478 if ($child_nt == 1) { # ELEMENT_NODE
479 $has_el = 1;
480 } elsif ($child_nt == 10) { # DOCUMENT_TYPE_NODE
481 $has_dt = 1;
482 } elsif ($child_nt == 11) { # DOCUMENT_FRAGMENT_NODE
483 for my $cn (@{$_[1]->child_nodes}) {
484 my $cnt = $cn->node_type;
485 if ($cnt == 1) { # ELEMENT_NODE
486 if ($has_el) {
487 report Message::DOM::DOMException
488 -object => $self,
489 -type => 'HIERARCHY_REQUEST_ERR',
490 -subtype => 'CHILD_NODE_TYPE_ERR';
491 }
492 $has_el = 1;
493 } elsif ($cnt == 10) { # DOCUMENT_TYPE_NODE
494 ## NOTE: |DocumentType| node cannot be contained in
495 ## |DocumentFragment| in strict mode.
496 if ($has_dt) {
497 report Message::DOM::DOMException
498 -object => $self,
499 -type => 'HIERARCHY_REQUEST_ERR',
500 -subtype => 'CHILD_NODE_TYPE_ERR';
501 }
502 $has_dt = 1;
503 }
504 }
505 }
506
507 ## ISSUE: This code is wrong. Old manakai's implementation
508 ## is better, but it is also wrong in some edge cases.
509 ## Maybe we should remove these code entirely. DOM3Core
510 ## conformance is not important for this bit. It only makes
511 ## things too complex. Same for replace_child's code.
512 if ($has_el) {
513 my $anode = $self->last_child;
514 while (defined $anode) {
515 if ($anode->node_type == 1) { # ELEMENT_NODE
516 report Message::DOM::DOMException
517 -object => $self,
518 -type => 'HIERARCHY_REQUEST_ERR',
519 -subtype => 'CHILD_NODE_TYPE_ERR';
520 }
521 $anode = $anode->previous_sibling;
522 }
523 } # has_el
524 if ($has_dt) {
525 my $anode = $self->last_child;
526 while (defined $anode) {
527 my $ant = $anode->node_type;
528 if ($ant == 1 or $ant == 10) { # ELEMENT_NODE or DOCUMENT_TYPE_NODE
529 report Message::DOM::DOMException
530 -object => $self,
531 -type => 'HIERARCHY_REQUEST_ERR',
532 -subtype => 'CHILD_NODE_TYPE_ERR';
533 }
534 $anode = $anode->previous_sibling;
535 }
536 } # has_dt
537 }
538
539 for my $cn (@new_child) {
540 unless ({
541 3, (not $strict_children), # TEXT_NODE
542 5, (not $strict_children), # ENTITY_REFERENCE_NODE
543 1, 1, # ELEMENT_NODE
544 4, (not $strict_children), # CDATA_SECTION_NODE
545 7, 1, # PROCESSING_INSTRUCTION_NODE
546 8, 1, # COMMENT_NODE
547 10, 1, # DOCUMENT_TYPE_NODE
548 }->{$cn->node_type}) {
549 report Message::DOM::DOMException
550 -object => $self,
551 -type => 'HIERARCHY_REQUEST_ERR',
552 -subtype => 'CHILD_NODE_TYPE_ERR';
553 }
554 }
555
556 ## NOTE: Ancestor check here in |Node|.
557 }
558
559 ## -- Insert at... ## NOTE: Only in insert_before and replace_child
560 my $index = -1; # last
561 if (defined $_[2]) {
562 ## error if $_[1] eq $_[2];
563
564 my $cns = $self->child_nodes;
565 my $cnsl = @$cns;
566 C: {
567 $index = 0;
568 for my $i (0..($cnsl-1)) {
569 my $cn = $cns->[$i];
570 if ($cn eq $_[2]) {
571 $index += $i;
572 last C;
573 } elsif ($cn eq $_[1]) {
574 $index = -1; # offset
575 }
576 }
577
578 report Message::DOM::DOMException
579 -object => $self,
580 -type => 'NOT_FOUND_ERR',
581 -subtype => 'NOT_CHILD_ERR';
582 } # C
583 }
584 ## NOTE: "else" only in replace_child
585
586 ## -- Removes from parent
587 if ($new_child_parent) {
588 if (@new_child == 1) {
589 my $v = $$new_child_parent->{child_nodes};
590 RP: for my $i (0..$#$v) {
591 if ($v->[$i] eq $new_child[0]) {
592 splice @$v, $i, 1, ();
593 last RP;
594 }
595 } # RP
596 } else {
597 @{$$new_child_parent->{child_nodes}} = ();
598 }
599 }
600
601 ## -- Rewrite the |parentNode| properties
602 for my $nc (@new_child) {
603 $$nc->{parent_node} = $self;
604 Scalar::Util::weaken ($$nc->{parent_node});
605 }
606
607 ## NOTE: Depends on method:
608 if ($index == -1) {
609 push @{$$self->{child_nodes}}, @new_child;
610 } else {
611 splice @{$$self->{child_nodes}}, $index, 0, @new_child;
612 }
613
614 ## NOTE: Only in |Document|.
615 for (@new_child) {
616 delete $$_->{implementation};
617 $$_->{owner_document} = $self;
618 Scalar::Util::weaken ($$_->{owner_document});
619 }
620
621 return $_[1];
622 } # insert_before
623
624 sub replace_child ($$) {
625 ## NOTE: Overrides |Node|'s implementation.
626 my $self = $_[0];
627
628 ## NOTE: |$self_od| code here depending on $self->node_type.
629
630 ## -- Node Type check
631 my @new_child;
632 my $new_child_parent;
633 if ($_[1]->node_type == 11) { # DOCUMENT_FRAGMENT_NODE
634 push @new_child, @{$_[1]->child_nodes};
635 $new_child_parent = $_[1];
636 } else {
637 @new_child = ($_[1]);
638 $new_child_parent = $_[1]->parent_node;
639 }
640
641 ## NOTE: Depends on $self->node_type:
642 if ($$self->{strict_error_checking}) {
643 my $child_od = $_[1]->owner_document || $_[1]; # might be DocumentType
644 if ($self ne $child_od and $child_od->node_type != 10) {
645 report Message::DOM::DOMException # DOCUMENT_TYPE_NODE
646 -object => $self,
647 -type => 'WRONG_DOCUMENT_ERR',
648 -subtype => 'EXTERNAL_OBJECT_ERR';
649 }
650
651 if ($$self->{manakai_read_only} or
652 (@new_child and defined $new_child_parent and
653 $$new_child_parent->{manakai_read_only})) {
654 report Message::DOM::DOMException
655 -object => $self,
656 -type => 'NO_MODIFICATION_ALLOWED_ERR',
657 -subtype => 'READ_ONLY_NODE_ERR';
658 }
659
660 ## NOTE: Only in |Document|:
661 my $strict_children = $self->dom_config->get_parameter
662 (q<http://suika.fam.cx/www/2006/dom-config/strict-document-children>);
663 if ($strict_children) {
664 my $has_el;
665 my $has_dt;
666 my $child_nt = $_[1]->node_type;
667 if ($child_nt == 1) { # ELEMENT_NODE
668 $has_el = 1;
669 } elsif ($child_nt == 10) { # DOCUMENT_TYPE_NODE
670 $has_dt = 1;
671 } elsif ($child_nt == 11) { # DOCUMENT_FRAGMENT_NODE
672 for my $cn (@{$_[1]->child_nodes}) {
673 my $cnt = $cn->node_type;
674 if ($cnt == 1) { # ELEMENT_NODE
675 if ($has_el) {
676 report Message::DOM::DOMException
677 -object => $self,
678 -type => 'HIERARCHY_REQUEST_ERR',
679 -subtype => 'CHILD_NODE_TYPE_ERR';
680 }
681 $has_el = 1;
682 } elsif ($cnt == 10) { # DOCUMENT_TYPE_NODE
683 ## NOTE: |DocumentType| node cannot be contained in
684 ## |DocumentFragment| in strict mode.
685 if ($has_dt) {
686 report Message::DOM::DOMException
687 -object => $self,
688 -type => 'HIERARCHY_REQUEST_ERR',
689 -subtype => 'CHILD_NODE_TYPE_ERR';
690 }
691 $has_dt = 1;
692 }
693 }
694 }
695
696 if ($has_el) {
697 my $anode = $self->last_child;
698 while (defined $anode) {
699 if ($anode->node_type == 1) { # ELEMENT_NODE
700 report Message::DOM::DOMException
701 -object => $self,
702 -type => 'HIERARCHY_REQUEST_ERR',
703 -subtype => 'CHILD_NODE_TYPE_ERR';
704 }
705 $anode = $anode->previous_sibling;
706 }
707 } # has_el
708 if ($has_dt) {
709 my $anode = $self->last_child;
710 while (defined $anode) {
711 my $ant = $anode->node_type;
712 if ($ant == 1 or $ant == 10) { # ELEMENT_NODE or DOCUMENT_TYPE_NODE
713 report Message::DOM::DOMException
714 -object => $self,
715 -type => 'HIERARCHY_REQUEST_ERR',
716 -subtype => 'CHILD_NODE_TYPE_ERR';
717 }
718 $anode = $anode->previous_sibling;
719 }
720 } # has_dt
721 }
722
723 for my $cn (@new_child) {
724 unless ({
725 3, (not $strict_children), # TEXT_NODE
726 5, (not $strict_children), # ENTITY_REFERENCE_NODE
727 1, 1, # ELEMENT_NODE
728 4, (not $strict_children), # CDATA_SECTION_NODE
729 7, 1, # PROCESSING_INSTRUCTION_NODE
730 8, 1, # COMMENT_NODE
731 10, 1, # DOCUMENT_TYPE_NODE
732 }->{$cn->node_type}) {
733 report Message::DOM::DOMException
734 -object => $self,
735 -type => 'HIERARCHY_REQUEST_ERR',
736 -subtype => 'CHILD_NODE_TYPE_ERR';
737 }
738 }
739
740 ## NOTE: Ancestor check here in |Node|.
741 }
742
743 ## -- Insert at... ## NOTE: Only in insertBefore and replaceChild
744 my $index = -1; # last
745 if (defined $_[2]) {
746 ## error if $_[1] eq $_[2];
747
748 my $cns = $self->child_nodes;
749 my $cnsl = @$cns;
750 C: {
751 $index = 0;
752 for my $i (0..($cnsl-1)) {
753 my $cn = $cns->[$i];
754 if ($cn eq $_[2]) {
755 $index += $i;
756 last C;
757 } elsif ($cn eq $_[1]) {
758 $index = -1; # offset
759 }
760 }
761
762 report Message::DOM::DOMException
763 -object => $self,
764 -type => 'NOT_FOUND_ERR',
765 -subtype => 'NOT_CHILD_ERR';
766 } # C
767 } else {
768 ## NOTE: Only in replaceChild
769 report Message::DOM::DOMException
770 -object => $self,
771 -type => 'NOT_FOUND_ERR',
772 -subtype => 'NOT_CHILD_ERR';
773 }
774
775 ## -- Removes from parent
776 if ($new_child_parent) {
777 if (@new_child == 1) {
778 my $v = $$new_child_parent->{child_nodes};
779 RP: for my $i (0..$#$v) {
780 if ($v->[$i] eq $new_child[0]) {
781 splice @$v, $i, 1, ();
782 last RP;
783 }
784 } # RP
785 } else {
786 @{$$new_child_parent->{child_nodes}} = ();
787 }
788 }
789
790 ## -- Rewrite the |parentNode| properties
791 for my $nc (@new_child) {
792 $$nc->{parent_node} = $self;
793 Scalar::Util::weaken ($$nc->{parent_node});
794 }
795
796 ## NOTE: Depends on method:
797 splice @{$$self->{child_nodes}}, $index, 1, @new_child;
798 delete ${$_[2]}->{parent_node};
799
800 ## NOTE: Only in |Document|.
801 for (@new_child) {
802 delete $$_->{implementation};
803 $$_->{owner_document} = $self;
804 Scalar::Util::weaken ($$_->{owner_document});
805 }
806
807 return $_[2];
808 } # replace_child
809
810 ## |Document| attributes
811
812 ## NOTE: A manakai extension.
813 sub all_declarations_processed ($;$);
814
815 sub doctype ($) {
816 my $self = $_[0];
817 for (@{$self->child_nodes}) {
818 if ($_->node_type == 10) { # DOCUMENT_TYPE_NODE
819 return $_;
820 }
821 }
822 return undef;
823 } # doctype
824
825 sub document_element ($) {
826 my $self = shift;
827 for (@{$self->child_nodes}) {
828 if ($_->node_type == 1) { # ELEMENT_NODE
829 return $_;
830 }
831 }
832 return undef;
833 } # document_element
834
835 sub document_uri ($;$);
836
837 sub dom_config ($) {
838 require Message::DOM::DOMConfiguration;
839 return bless \\($_[0]), 'Message::DOM::DOMConfiguration';
840 } # dom_config
841
842 sub manakai_entity_base_uri ($;$) {
843 my $self = $_[0];
844 if (@_ > 1) {
845 if ($$self->{strict_error_checking}) {
846 if ($$self->{manakai_read_only}) {
847 report Message::DOM::DOMException
848 -object => $self,
849 -type => 'NO_MODIFICATION_ALLOWED_ERR',
850 -subtype => 'READ_ONLY_NODE_ERR';
851 }
852 }
853 if (defined $_[1]) {
854 $$self->{manakai_entity_base_uri} = ''.$_[1];
855 } else {
856 delete $$self->{manakai_entity_base_uri};
857 }
858 }
859
860 if (defined $$self->{manakai_entity_base_uri}) {
861 return $$self->{manakai_entity_base_uri};
862 } else {
863 return $$self->{document_uri};
864 }
865 } # manakai_entity_base_uri
866
867 sub input_encoding ($;$);
868
869 sub strict_error_checking ($;$) {
870 ## NOTE: Same as trivial boolean accessor, except no read-only checking.
871 if (@_ > 1) {
872 if ($_[1]) {
873 ${$_[0]}->{strict_error_checking} = 1;
874 } else {
875 delete ${$_[0]}->{strict_error_checking};
876 }
877 }
878 return ${$_[0]}->{strict_error_checking};
879 } # strict_error_checking
880
881 ## ISSUE: Setting manakai_is_html true shadows
882 ## xml_* properties. Is this desired?
883
884 sub xml_encoding ($;$) {
885 my $self = $_[0];
886 if (@_ > 1) {
887 ## NOTE: A manakai extension.
888 if ($$self->{strict_error_checking}) {
889 if ($$self->{manakai_is_html}) {
890 report Message::DOM::DOMException
891 -object => $self,
892 -type => 'NOT_SUPPORTED_ERR',
893 -subtype => 'NON_HTML_OPERATION_ERR';
894 }
895 if ($$self->{manakai_read_only}) {
896 report Message::DOM::DOMException
897 -object => $self,
898 -type => 'NO_MODIFICATION_ALLOWED_ERR',
899 -subtype => 'READ_ONLY_NODE_ERR';
900 }
901 }
902 if (defined $_[1]) {
903 $$self->{xml_encoding} = ''.$_[1];
904 } else {
905 delete $$self->{xml_encoding};
906 }
907 }
908
909 if ($$self->{manakai_is_html}) {
910 return undef;
911 } else {
912 return $$self->{xml_encoding};
913 }
914 } # xml_encoding
915
916 sub xml_standalone ($;$) {
917 my $self = $_[0];
918 if (@_ > 1) {
919 if ($$self->{strict_error_checking}) {
920 if ($$self->{manakai_is_html}) {
921 report Message::DOM::DOMException
922 -object => $self,
923 -type => 'NOT_SUPPORTED_ERR',
924 -subtype => 'NON_HTML_OPERATION_ERR';
925 }
926 ## NOTE: Not in DOM3.
927 if ($$self->{manakai_read_only}) {
928 report Message::DOM::DOMException
929 -object => $self,
930 -type => 'NO_MODIFICATION_ALLOWED_ERR',
931 -subtype => 'READ_ONLY_NODE_ERR';
932 }
933 }
934 if ($_[1]) {
935 $$self->{xml_standalone} = 1;
936 } else {
937 delete $$self->{xml_standalone};
938 }
939 }
940
941 if ($$self->{manakai_is_html}) {
942 return 0;
943 } else {
944 return $$self->{xml_standalone};
945 }
946 } # xml_standalone
947
948 sub xml_version ($;$) {
949 my $self = $_[0];
950 if (@_ > 1) {
951 my $v = ''.$_[1];
952 if ($$self->{strict_error_checking}) {
953 if ($$self->{manakai_is_html}) {
954 report Message::DOM::DOMException
955 -object => $self,
956 -type => 'NOT_SUPPORTED_ERR',
957 -subtype => 'NON_HTML_OPERATION_ERR';
958 }
959 if ($v ne '1.0' and $v ne '1.1') {
960 report Message::DOM::DOMException
961 -object => $self,
962 -type => 'NOT_SUPPORTED_ERR',
963 -subtype => 'UNKNOWN_XML_VERSION_ERR';
964 }
965 if ($$self->{manakai_read_only}) {
966 ## ISSUE: Not in DOM3.
967 report Message::DOM::DOMException
968 -object => $self,
969 -type => 'NO_MODIFICATION_ALLOWED_ERR',
970 -subtype => 'READ_ONLY_NODE_ERR';
971 }
972 }
973 $$self->{xml_version} = $v;
974 }
975
976 if (defined wantarray) {
977 if ($$self->{manakai_is_html}) {
978 return undef;
979 } elsif (defined $$self->{xml_version}) {
980 return $$self->{xml_version};
981 } else {
982 return '1.0';
983 }
984 }
985 } # xml_version
986
987 ## |Document| methods
988
989 sub get_element_by_id ($$) {
990 local $Error::Depth = $Error::Depth + 1;
991 my @nodes = @{$_[0]->child_nodes};
992 N: while (@nodes) {
993 my $node = shift @nodes;
994 next N unless $node->node_type == 1; # ELEMENT_NODE
995 for my $attr (@{$node->attributes}) {
996 if ($attr->is_id and $attr->value eq $_[1]) {
997 return $node;
998 }
999 }
1000 unshift @nodes, @{$node->child_nodes};
1001 } # N
1002 return undef;
1003 } # get_element_by_id
1004
1005 ## TODO: HTML5 case normalization
1006 sub get_elements_by_tag_name ($$) {
1007 my $name = ''.$_[1];
1008 my $chk;
1009 if ($name eq '*') {
1010 $chk = sub () { 1 };
1011 } else {
1012 $chk = sub ($) {
1013 return $_[0]->manakai_tag_name eq $name;
1014 };
1015 }
1016
1017 require Message::DOM::NodeList;
1018 return bless \[$_[0], $chk], 'Message::DOM::NodeList::GetElementsList';
1019 } # get_elements_by_tag_name
1020
1021 sub get_elements_by_tag_name_ns ($$$) {
1022 my $nsuri = defined $_[1] ? ''.$_[1] : '';
1023 my $lname = ''.$_[2];
1024 my $chk;
1025 if ($nsuri eq '*') {
1026 if ($lname eq '*') {
1027 $chk = sub () { 1 };
1028 } else {
1029 $chk = sub ($) {
1030 return $_[0]->manakai_local_name eq $lname;
1031 };
1032 }
1033 } elsif ($nsuri eq '') {
1034 if ($lname eq '*') {
1035 $chk = sub ($) {
1036 return not defined $_[0]->namespace_uri;
1037 };
1038 } else {
1039 $chk = sub ($) {
1040 return (not defined $_[0]->namespace_uri and
1041 $_[0]->manakai_local_name eq $lname);
1042 };
1043 }
1044 } else {
1045 if ($lname eq '*') {
1046 $chk = sub ($) {
1047 my $ns = $_[0]->namespace_uri;
1048 return (defined $ns and $ns eq $nsuri);
1049 };
1050 } else {
1051 $chk = sub ($) {
1052 my $ns = $_[0]->namespace_uri;
1053 return (defined $ns and $ns eq $nsuri and
1054 $_[0]->manakai_local_name eq $lname);
1055 };
1056 }
1057 }
1058
1059 require Message::DOM::NodeList;
1060 return bless \[$_[0], $chk], 'Message::DOM::NodeList::GetElementsList';
1061 } # get_elements_by_tag_name
1062
1063 ## TODO: import_node
1064
1065 ## TODO: normalize_document
1066
1067 ## TODO: rename_node
1068
1069 ## |DocumentTraversal| methods
1070
1071 ## TODO: create_node_iterator
1072
1073 sub manakai_create_serial_walker ($$;$$$);
1074
1075 sub create_tree_walker ($$;$$$);
1076
1077 ## |HTMLDocument| attributes
1078
1079 sub compat_mode ($) {
1080 if (${$_[0]}->{manakai_is_html}) {
1081 if (defined ${$_[0]}->{manakai_compat_mode} and
1082 ${$_[0]}->{manakai_compat_mode} eq 'quirks') {
1083 return 'BackCompat';
1084 }
1085 }
1086 return 'CSS1Compat';
1087 } # compat_mode
1088
1089 sub manakai_compat_mode ($;$) {
1090 if (${$_[0]}->{manakai_is_html}) {
1091 if (@_ > 1 and defined $_[1] and
1092 {'no quirks' => 1, 'limited quirks' => 1, 'quirks' => 1}->{$_[1]}) {
1093 ${$_[0]}->{manakai_compat_mode} = $_[1];
1094 }
1095 return ${$_[0]}->{manakai_compat_mode} || 'no quirks';
1096 } else {
1097 return 'no quirks';
1098 }
1099 } # manakai_compat_mode
1100
1101 sub inner_html ($;$) {
1102 my $self = $_[0];
1103 local $Error::Depth = $Error::Depth + 1;
1104
1105 if ($$self->{manakai_is_html}) {
1106 require Whatpm::HTML;
1107 if (@_ > 1) {
1108 ## Step 1
1109 ## TODO: Stop parsing and ...
1110
1111 ## Step 2
1112 my @cn = @{$self->child_nodes};
1113 for (@cn) { ## NOTE: Might throw a |NO_MODIFICATION_ALLOWED_ERR|.
1114 $self->remove_child ($_); #
1115 }
1116
1117 ## Step 3, 4, 5
1118 Whatpm::HTML->parse_string ($_[1] => $self);
1119
1120 ## TODO:
1121 ## <script>var input = function_to_do_xmlhttprequest (location.href);
1122 ## document.innerHTML = input</script>
1123
1124 return unless defined wantarray;
1125 }
1126
1127 return ${ Whatpm::HTML->get_inner_html ($self) };
1128 } else {
1129 if (@_ > 1) {
1130 ## Step 1
1131 require Whatpm::XMLParser; # MUST
1132 my $doc = $self->implementation->create_document;
1133
1134 ## Step 2
1135 #
1136
1137 ## Step 3
1138 $doc = Whatpm::XMLParser->parse_string ($_[1] => $doc); # MUST
1139
1140 ## Step 4
1141 #
1142
1143 ## Step 5
1144 ## TODO: ill-formed -> SYNTAX_ERR # MUST
1145
1146 ## Step 6 # MUST
1147 my @cn = @{$self->child_nodes}; ## TODO: If read-only
1148 for (@cn) {
1149 $self->remove_child ($_);
1150 }
1151
1152 ## Step 7, 8, 9, 10
1153 for my $node (@{$doc->child_nodes}) {
1154 $self->append_child ($self->adopt_node ($node));
1155 }
1156
1157 return unless defined wantarray;
1158 }
1159
1160 ## TODO: This serializer is currently not conformant to HTML5 spec.
1161 require Whatpm::XMLSerializer;
1162 my $r = '';
1163 for (@{$self->child_nodes}) {
1164 $r .= ${ Whatpm::XMLSerializer->get_outer_xml ($_, sub {
1165 ## TODO: INVALID_STATE_ERR
1166 }) };
1167 }
1168 return $r;
1169 }
1170 } # inner_html
1171
1172 sub manakai_is_html ($;$) {
1173 if (@_ > 1) {
1174 if ($_[1]) {
1175 ${$_[0]}->{manakai_is_html} = 1;
1176 } else {
1177 delete ${$_[0]}->{manakai_is_html};
1178 delete ${$_[0]}->{manakai_compat_mode};
1179 }
1180 }
1181 return ${$_[0]}->{manakai_is_html};
1182 } # manakai_is_html
1183
1184 package Message::IF::Document;
1185 package Message::IF::DocumentTraversal;
1186 package Message::IF::DocumentXDoctype;
1187 package Message::IF::DocumentSelector;
1188 package Message::IF::HTMLDocument;
1189
1190 package Message::DOM::DOMImplementation;
1191
1192 sub create_document ($;$$$) {
1193 my $r = Message::DOM::Document->____new ($_[0]);
1194
1195 if (defined $_[2]) {
1196 local $Error::Depth = $Error::Depth + 1;
1197 $r->append_child ($r->create_element_ns ($_[1], $_[2])); # NAMESPACE_ERR
1198 ## NOTE: manakai might raise DOMExceptions in cases not defined
1199 ## in DOM3Core spec: XMLNSPREFIX_NONXMLNSNS_ERR,
1200 ## XMLNS_NONXMLNSNS_ERR, and NONXMLNSPREFIX_XMLNSNS_ERR.
1201 } elsif (defined $_[1]) {
1202 report Message::DOM::DOMException
1203 -object => $_[0],
1204 -type => 'NAMESPACE_ERR',
1205 -subtype => 'QNAME_NULLNS_ERR';
1206 }
1207
1208 if (defined $_[3]) {
1209 if ($_[3]->parent_node) {
1210 report Message::DOM::DOMException
1211 -object => $_[0],
1212 -type => 'WRONG_DOCUMENT_ERR',
1213 -subtype => 'INUSE_DOCTYPE_ERR';
1214 }
1215 local $Error::Depth = $Error::Depth + 1;
1216 $r->insert_before ($_[3], $r->first_child); # EXTERNAL_OBJECT_ERR
1217 }
1218
1219 return $r;
1220 } # create_document
1221
1222 =head1 LICENSE
1223
1224 Copyright 2007 Wakaba <w@suika.fam.cx>
1225
1226 This program is free software; you can redistribute it and/or
1227 modify it under the same terms as Perl itself.
1228
1229 =cut
1230
1231 1;
1232 ## $Date: 2007/07/29 08:31:14 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24