/[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.8 - (hide annotations) (download)
Wed Jun 20 13:41:16 2007 UTC (17 years, 5 months ago) by wakaba
Branch: MAIN
Changes since 1.7: +547 -20 lines
++ manakai/t/ChangeLog	20 Jun 2007 13:41:02 -0000
2007-06-20  Wakaba  <wakaba@suika.fam.cx>

	* DOM-Node.t: Tests for DOCUMENT_POSITION_* constants
	and new features are added.

++ manakai/lib/Message/DOM/ChangeLog	20 Jun 2007 13:40:42 -0000
2007-06-20  Wakaba  <wakaba@suika.fam.cx>

	* Node.pm (manakai_expanded_uri, manakai_parent_element,
	clone_node, compare_document_position, has_attributes,
	has_child_nodes, is_default_namespace, lookup_namespace_uri,
	lookup_prefix, normalize): Implemented.

	* DOMElement.pm (remove_attribute, set_attribute): Alpha version.

	* DOMException.pm (CLONE_NODE_TYPE_NOT_SUPPORTED_ERR): New error.

1 wakaba 1.1 package Message::DOM::Node;
2     use strict;
3 wakaba 1.8 our $VERSION=do{my @r=(q$Revision: 1.7 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};
4 wakaba 1.1 push our @ISA, 'Message::IF::Node';
5     require Scalar::Util;
6 wakaba 1.6 require Message::DOM::DOMException;
7 wakaba 1.1
8 wakaba 1.4 ## 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     '==' => sub {
28     return 0 unless UNIVERSAL::isa ($_[0], 'Message::IF::Node');
29     ## TODO: implement is_equal_node
30     return $_[0]->is_equal_node ($_[1]);
31     },
32     '!=' => sub {
33     return not ($_[0] == $_[1]);
34     },
35     fallback => 1;
36    
37 wakaba 1.2 ## The |Node| interface - constants
38    
39     ## Definition group NodeType
40    
41     ## NOTE: Numeric codes up to 200 are reserved by W3C [DOM1SE, DOM2, DOM3].
42    
43     sub ELEMENT_NODE () { 1 }
44     sub ATTRIBUTE_NODE () { 2 }
45     sub TEXT_NODE () { 3 }
46     sub CDATA_SECTION_NODE () { 4 }
47     sub ENTITY_REFERENCE_NODE () { 5 }
48     sub ENTITY_NODE () { 6 }
49     sub PROCESSING_INSTRUCTION_NODE () { 7 }
50     sub COMMENT_NODE () { 8 }
51     sub DOCUMENT_NODE () { 9 }
52     sub DOCUMENT_TYPE_NODE () { 10 }
53     sub DOCUMENT_FRAGMENT_NODE () { 11 }
54     sub NOTATION_NODE () { 12 }
55     sub ELEMENT_TYPE_DEFINITION_NODE () { 81001 }
56     sub ATTRIBUTE_DEFINITION_NODE () { 81002 }
57    
58     ## Definition group DocumentPosition
59    
60     ## Spec:
61     ## <http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#DocumentPosition>
62    
63     sub DOCUMENT_POSITION_DISCONNECTED () { 0x01 }
64     sub DOCUMENT_POSITION_PRECEDING () { 0x02 }
65     sub DOCUMENT_POSITION_FOLLOWING () { 0x04 }
66     sub DOCUMENT_POSITION_CONTAINS () { 0x08 }
67     sub DOCUMENT_POSITION_CONTAINED_BY () { 0x10 }
68     sub DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC () { 0x20 }
69    
70 wakaba 1.1 sub ____new ($$) {
71     my $self = bless \({}), shift;
72     $$self->{owner_document} = shift;
73     Scalar::Util::weaken ($$self->{owner_document});
74     return $self;
75     } # ____new
76    
77 wakaba 1.6 sub ___report_error ($$) {
78     $_[1]->throw;
79     } # ___report_error
80    
81 wakaba 1.1 sub AUTOLOAD {
82     my $method_name = our $AUTOLOAD;
83     $method_name =~ s/.*:://;
84     return if $method_name eq 'DESTROY';
85    
86     if ({
87     ## Read-only attributes (trivial accessors)
88     owner_document => 1,
89     parent_node => 1,
90 wakaba 1.4 manakai_read_only => 1,
91 wakaba 1.1 }->{$method_name}) {
92     no strict 'refs';
93     eval qq{
94     sub $method_name (\$) {
95     return \${\$_[0]}->{$method_name};
96     }
97     };
98     goto &{ $AUTOLOAD };
99     } elsif ({
100     ## Read-write attributes (DOMString, trivial accessors)
101     }->{$method_name}) {
102     no strict 'refs';
103     eval qq{
104 wakaba 1.2 sub $method_name (\$;\$) {
105 wakaba 1.1 if (\@_ > 1) {
106 wakaba 1.2 \${\$_[0]}->{$method_name} = ''.\$_[1];
107 wakaba 1.1 }
108     return \${\$_[0]}->{$method_name};
109     }
110     };
111     goto &{ $AUTOLOAD };
112     } else {
113     require Carp;
114     Carp::croak (qq<Can't locate method "$AUTOLOAD">);
115     }
116     } # AUTOLOAD
117    
118 wakaba 1.6 ## |Node| attributes
119 wakaba 1.1
120 wakaba 1.6 ## NOTE: Overridden by |Element|.
121     sub attributes () { undef }
122 wakaba 1.2
123 wakaba 1.7 sub base_uri ($) {
124     ## NOTE: Overridden by |Attr|, |CharacterData|, |Document|, |DocumentType|,
125     ## |Element|, |EntityReference|, and |ProcessingInstruction|.
126    
127     local $Error::Depth = $Error::Depth + 1;
128     return $_[0]->owner_document->base_uri;
129     } # base_uri
130 wakaba 1.3
131     sub child_nodes ($) {
132 wakaba 1.6 ## NOTE: Overridden by |CharacterData|, |ElementTypeDefinition|,
133     ## |Notation|, and |ProcessingInstruction|.
134 wakaba 1.4 require Message::DOM::NodeList;
135     return bless \\($_[0]), 'Message::DOM::NodeList::ChildNodeList';
136 wakaba 1.3 } # child_nodes
137    
138 wakaba 1.8 sub manakai_expanded_uri ($) {
139     my $self = shift;
140     local $Error::Depth = $Error::Depth + 1;
141     my $ln = $self->local_name;
142     if (defined $ln) {
143     my $nsuri = $self->namespace_uri;
144     if (defined $nsuri) {
145     return $nsuri . $ln;
146     } else {
147     return $ln;
148     }
149     } else {
150     return undef;
151     }
152     } # manakai_expanded_uri
153    
154 wakaba 1.3 sub first_child ($) {
155     my $self = shift;
156     return $$self->{child_nodes} ? $$self->{child_nodes}->[0] : undef;
157     } # first_child
158    
159     sub last_child ($) {
160     my $self = shift;
161     return $$self->{child_nodes} && $$self->{child_nodes}->[0]
162     ? $$self->{child_nodes}->[-1] : undef;
163     } # last_child
164    
165 wakaba 1.6 sub local_name { undef }
166 wakaba 1.3
167 wakaba 1.6 sub manakai_local_name { undef }
168    
169     sub namespace_uri { undef }
170 wakaba 1.3
171     sub next_sibling ($) {
172     my $self = shift;
173     my $parent = $$self->{parent_node};
174     return undef unless defined $parent;
175     my $has_self;
176     for (@{$parent->child_nodes}) {
177     if ($_ eq $self) {
178     $has_self = 1;
179     } elsif ($has_self) {
180     return $_;
181     }
182     }
183     return undef;
184     } # next_sibling
185 wakaba 1.2
186 wakaba 1.6 ## NOTE: Overridden by subclasses.
187     sub node_name () { undef }
188 wakaba 1.2
189 wakaba 1.6 ## NOTE: Overridden by subclasses.
190     sub node_type () { }
191 wakaba 1.2
192 wakaba 1.6 ## NOTE: Overridden by |Attr|, |AttributeDefinition|,
193     ## |CharacterData|, and |ProcessingInstruction|.
194     sub node_value () { undef }
195 wakaba 1.3
196     sub owner_document ($);
197    
198 wakaba 1.8 sub manakai_parent_element ($) {
199     my $self = shift;
200     my $parent = $$self->{parent_node};
201     while (defined $parent) {
202     if ($parent->node_type == ELEMENT_NODE) {
203     return $parent;
204     } else {
205     $parent = $$parent->{parent_node};
206     }
207     }
208     return undef;
209     } # manakai_parent_element
210    
211 wakaba 1.3 sub parent_node ($);
212    
213 wakaba 1.6 ## NOTE: Overridden by |Element| and |Attr|.
214 wakaba 1.3 sub prefix ($;$) { undef }
215    
216     sub previous_sibling ($) {
217     my $self = shift;
218     my $parent = $$self->{parent_node};
219     return undef unless defined $parent;
220     my $prev;
221     for (@{$parent->child_nodes}) {
222     if ($_ eq $self) {
223     return $prev;
224     } else {
225     $prev = $_;
226     }
227     }
228     return undef;
229     } # previous_sibling
230    
231 wakaba 1.4 sub manakai_read_only ($);
232    
233 wakaba 1.3 sub text_content ($;$) {
234 wakaba 1.6 ## NOTE: For |Element|, |Attr|, |Entity|, |EntityReference|,
235     ## |DocumentFragment|, and |AttributeDefinition|. In addition,
236     ## |Document|'s |text_content| might call this attribute.
237    
238     ## NOTE: Overridden by |Document|, |DocumentType|, |Notation|,
239     ## |CharacterData|, |ProcessingInstruction|, and |ElementTypeDefinition|.
240    
241     my $self = $_[0];
242    
243     if (@_ > 1) {
244 wakaba 1.7 if (${$$self->{owner_document} or $self}->{strict_error_checking} and
245     $$self->{manakai_read_only}) {
246 wakaba 1.6 report Message::DOM::DOMException
247     -object => $self,
248     -type => 'NO_MODIFICATION_ALLOWED_ERR',
249     -subtype => 'READ_ONLY_NODE_ERR';
250     }
251    
252     local $Error::Depth = $Error::Depth + 1;
253     @{$self->child_nodes} = ();
254     if (defined $_[1] and length $_[1]) {
255     ## NOTE: |DocumentType| don't use this code.
256     my $text = ($$self->{owner_document} || $self)->create_text_node ($_[1]);
257     $self->append_child ($text);
258     }
259     }
260    
261     if (defined wantarray) {
262     local $Error::Depth = $Error::Depth + 1;
263     my $r = '';
264     my @node = @{$self->child_nodes};
265     while (@node) {
266     my $child = shift @node;
267     my $child_nt = $child->node_type;
268     if ($child_nt == TEXT_NODE or $child_nt == CDATA_SECTION_NODE) {
269     $r .= $child->node_value unless $child->is_element_content_whitespace;
270     } elsif ($child_nt == COMMENT_NODE or
271     $child_nt == PROCESSING_INSTRUCTION_NODE or
272     $child_nt == DOCUMENT_TYPE_NODE) {
273     #
274     } else {
275     unshift @node, @{$child->child_nodes};
276     }
277     }
278     return $r;
279     }
280 wakaba 1.3 } # text_content
281    
282 wakaba 1.7 ## |Node| methods
283    
284 wakaba 1.8 sub clone_node ($;$) {
285     my ($self, $deep) = @_;
286    
287     ## ISSUE: Need definitions for the cloning operation
288     ## for ElementTypeDefinition, and AttributeDefinition nodes,
289     ## as well as new attributes introduced in DOM XML Document Type Definition
290     ## module.
291     ## ISSUE: Define if default attributes and attributedefinition are inconsistent
292    
293     local $Error::Depth = $Error::Depth + 1;
294     my $od = $self->owner_document;
295     my $strict_check = $od->strict_error_checking;
296     $od->strict_error_checking (0);
297     my $cfg = $od->dom_config;
298     my $er_copy_asis = $cfg->{'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->element_content_whitespace (1)
332     if $node->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 has_attributes ($) {
580     for (values %{${$_[0]}->{attributes} or {}}) {
581     return 1 if keys %$_;
582     }
583     return 0;
584     } # has_attributes
585    
586     sub has_child_nodes ($) {
587     return (@{${$_[0]}->{child_nodes} or []} > 0);
588     } # has_child_nodes
589    
590 wakaba 1.4 ## TODO:
591     sub is_same_node ($$) {
592     return $_[0] eq $_[1];
593     } # is_same_node
594    
595     ## TODO:
596 wakaba 1.1 sub is_equal_node ($$) {
597 wakaba 1.4 return $_[0]->node_name eq $_[1]->node_name &&
598     $_[0]->node_value eq $_[1]->node_value;
599 wakaba 1.1 } # is_equal_node
600    
601     ## NOTE: Only applied to Elements and Documents
602     sub append_child ($$) {
603     my ($self, $new_child) = @_;
604     if (defined $$new_child->{parent_node}) {
605 wakaba 1.5 my $parent_list = ${$$new_child->{parent_node}}->{child_nodes};
606 wakaba 1.1 for (0..$#$parent_list) {
607     if ($parent_list->[$_] eq $new_child) {
608     splice @$parent_list, $_, 1;
609 wakaba 1.7 last;
610 wakaba 1.1 }
611     }
612     }
613     push @{$$self->{child_nodes}}, $new_child;
614     $$new_child->{parent_node} = $self;
615     Scalar::Util::weaken ($$new_child->{parent_node});
616 wakaba 1.7 ## TODO:
617     $$new_child->{owner_document} = $self if $self->node_type == DOCUMENT_NODE;
618 wakaba 1.1 return $new_child;
619     } # append_child
620    
621 wakaba 1.7 sub manakai_append_text ($$) {
622     ## NOTE: For |Element|, |Attr|, |Entity|, |EntityReference|,
623     ## |DocumentFragment|, and |AttributeDefinition|. In addition,
624     ## |Document|'s |text_content| might call this attribute.
625    
626     ## NOTE: Overridden by |Document|, |DocumentType|, |CharacterData|,
627     ## |ElementTypeDefinition|, |Notation|, and |ProcessingInstruction|.
628    
629     my $self = $_[0];
630     local $Error::Depth = $Error::Depth + 1;
631     if (@{$$self->{child_nodes}} and
632     $$self->{child_nodes}->[-1]->node_type == TEXT_NODE) {
633     $$self->{child_nodes}->[-1]->manakai_append_text ($_[1]);
634     } else {
635     my $text = ($$self->{owner_document} or $self)->create_text_node ($_[1]);
636     $self->append_child ($text);
637     }
638     } # manakai_append_text
639    
640     sub get_feature {
641     ## TODO:
642     return $_[0];
643     }
644    
645 wakaba 1.1 ## NOTE: Only applied to Elements and Documents
646     sub insert_before ($$;$) {
647     my ($self, $new_child, $ref_child) = @_;
648     if (defined $$new_child->{parent_node}) {
649 wakaba 1.5 my $parent_list = ${$$new_child->{parent_node}}->{child_nodes};
650 wakaba 1.1 for (0..$#$parent_list) {
651     if ($parent_list->[$_] eq $new_child) {
652     splice @$parent_list, $_, 1;
653 wakaba 1.7 last;
654 wakaba 1.1 }
655     }
656     }
657     my $i = @{$$self->{child_nodes}};
658     if (defined $ref_child) {
659     for (0..$#{$$self->{child_nodes}}) {
660     if ($$self->{child_nodes}->[$_] eq $ref_child) {
661     $i = $_;
662     last;
663     }
664     }
665     }
666     splice @{$$self->{child_nodes}}, $i, 0, $new_child;
667     $$new_child->{parent_node} = $self;
668     Scalar::Util::weaken ($$new_child->{parent_node});
669     return $new_child;
670     } # insert_before
671    
672 wakaba 1.8 sub is_default_namespace ($$) {
673     ## TODO: Document that ElementTypeDefinition and AttributeDefinition
674     ## are same as DocumentType
675    
676     local $Error::Depth = $Error::Depth + 1;
677     my $namespace_uri = defined $_[1] ? $_[1] : '';
678     my $nt = $_[0]->node_type;
679     if ($nt == ELEMENT_NODE) {
680     my $el = $_[0];
681     EL: {
682     unless (defined $el->prefix) {
683     my $elns = $el->namespace_uri;
684     if ($namespace_uri ne '' and defined $elns) {
685     return $namespace_uri eq $elns;
686     } else {
687     return not ($namespace_uri eq '' or defined $elns);
688     }
689     }
690     my $xmlns = $el->get_attribute_ns
691     ('http://www.w3.org/2000/xmlns/', 'xmlns');
692     if (defined $xmlns) {
693     if ($namespace_uri ne '') {
694     return ($namespace_uri eq $xmlns);
695     } else {
696     return ($xmlns eq '');
697     }
698     }
699     $el = $el->manakai_parent_element;
700     redo EL if defined $el;
701     return 0;
702     } # EL;
703     } else {
704     my $el = $nt == DOCUMENT_NODE
705     ? $_[0]->document_element
706     : $nt == ATTRIBUTE_NODE
707     ? $_[0]->owner_element
708     : $_[0]->manakai_parent_element;
709     if (defined $el) {
710     return $el->is_default_namespace ($_[1]);
711     } else {
712     return 0;
713     }
714     }
715     } # is_default_namespace
716    
717     sub lookup_namespace_uri ($$) {
718     ## TODO: Need definition for ElementTypeDefinition and AttributeDefinition
719    
720     my ($self, $prefix) = @_;
721     $prefix = undef if defined $prefix and $prefix eq '';
722     ## NOTE: Implementation dependent.
723     ## TODO: Check what Gecko does.
724     local $Error::Depth = $Error::Depth + 1;
725     my $nt = $self->node_type;
726     if ($nt == ELEMENT_NODE) {
727     my $el = $self;
728     EL: {
729     my $elns = $el->namespace_uri;
730     if (defined $elns) {
731     my $elpfx = $el->prefix;
732     if ((not defined $prefix and not defined $elpfx) or
733     (defined $prefix and defined $elpfx and $prefix eq $elpfx)) {
734     return $elns;
735     }
736     }
737     AT: for my $attr (@{$el->attributes}) {
738     my $attrns = $attr->namespace_uri;
739     next AT if not defined $attrns or
740     $attrns ne 'http://www.w3.org/2000/xmlns/';
741     my $attrpfx = $attr->prefix;
742     if (not defined $prefix) {
743     my $attrln = $attr->local_name;
744     if ($attrln eq 'xmlns') {
745     my $attrval = $attr->value;
746     return length $attrval ? $attrval : undef;
747     }
748     } elsif (defined $prefix and
749     defined $attrpfx and $attrpfx eq 'xmlns') {
750     my $attrln = $attr->local_name;
751     if ($attrln eq $prefix) {
752     my $attrval = $attr->value;
753     return length $attrval ? $attrval : undef;
754     }
755     }
756     } # AT
757     $el = $el->manakai_parent_element;
758     redo EL if defined $el;
759     return undef;
760     } # EL;
761     } else {
762     my $el = $nt == DOCUMENT_NODE
763     ? $self->document_element
764     : $nt == ATTRIBUTE_NODE
765     ? $self->owner_element
766     : $self->manakai_parent_element;
767     if (defined $el) {
768     return $el->lookup_namespace_uri ($prefix);
769     } else {
770     return undef;
771     }
772     }
773     } # lookup_namespace_uri
774    
775     sub lookup_prefix ($$) {
776     ## ISSUE: Document ElementTypeDefinition and AttributeDefinition
777     ## behavior (i.e. same as DocumentType)
778    
779     my $namespace_uri = defined $_[1] ? $_[1] : '';
780     if ($namespace_uri eq '') {
781     return undef;
782     }
783    
784     local $Error::Depth = $Error::Depth + 1;
785     my $nt = $_[0]->node_type;
786     if ($nt == ELEMENT_NODE) {
787     my $el = $_[0];
788     EL: {
789     my $elns = $el->namespace_uri;
790     if (defined $elns and $elns eq $namespace_uri) {
791     my $elpfx = $el->prefix;
792     if (defined $elpfx) {
793     my $oeluri = $_[0]->lookup_namespace_uri ($elpfx);
794     if (defined $oeluri and $oeluri eq $namespace_uri) {
795     return $elpfx;
796     }
797     }
798     }
799     AT: for my $attr (@{$el->attributes}) {
800     my $attrpfx = $attr->prefix;
801     next AT if not defined $attrpfx or $attrpfx ne 'xmlns';
802     my $attrns = $attr->namespace_uri;
803     next AT if not defined $attrns or
804     $attrns ne 'http://www.w3.org/2000/xmlns/';
805     next AT unless $attr->value eq $namespace_uri;
806     my $attrln = $attr->local_name;
807     my $oeluri = $el->lookup_namespace_uri ($attrln);
808     next AT unless defined $oeluri;
809     if ($oeluri eq $namespace_uri) {
810     return $attrln;
811     }
812     }
813     $el = $el->manakai_parent_element;
814     redo EL if defined $el;
815     return undef;
816     } # EL
817     } else {
818     my $el = $nt == DOCUMENT_NODE
819     ? $_[0]->document_element
820     : $nt == ATTRIBUTE_NODE
821     ? $_[0]->owner_element
822     : $_[0]->manakai_parent_element;
823     if (defined $el) {
824     return $el->lookup_prefix ($_[1]);
825     } else {
826     return undef;
827     }
828     }
829     } # lookup_prefix
830    
831     sub normalize ($) {
832     my $self = shift;
833     my $ptext;
834     local $Error::Depth = $Error::Depth + 1;
835    
836     ## Children
837     my @remove;
838     for my $cn (@{$self->child_nodes}) {
839     if ($cn->node_type == TEXT_NODE) {
840     my $nv = $cn->node_value;
841     if (length $nv) {
842     if (defined $ptext) {
843     $ptext->manakai_append_text ($nv);
844     $ptext->is_element_content_whitespace (1)
845     if $cn->is_element_content_whitespace and
846     $ptext->is_element_content_whitespace;
847     push @remove, $cn;
848     } else {
849     $ptext = $cn;
850     }
851     } else {
852     push @remove, $cn;
853     }
854     } else {
855     $cn->normalize;
856     undef $ptext;
857     }
858     }
859     $self->remove_child ($_) for @remove;
860    
861     my $nt = $self->node_type;
862     if ($nt == ELEMENT_NODE) {
863     ## Attributes
864     $_->normalize for @{$self->attributes};
865     } elsif ($nt == DOCUMENT_TYPE_NODE) {
866     ## ISSUE: Document these explicitly in DOM XML Document Type Definitions spec
867     ## Element type definitions
868     $_->normalize for @{$self->element_types};
869     ## General entities
870     $_->normalize for @{$self->general_entities};
871     } elsif ($nt == ELEMENT_TYPE_DEFINITION_NODE) {
872     ## Attribute definitions
873     $_->normalize for @{$self->attribute_definitions};
874     }
875     ## TODO: normalize-characters
876    
877     ## TODO: In this implementation, if a modification raises a
878     ## |NO_MODIFICATION_ALLOWED_ERR|, then any modification before it
879     ## is not reverted.
880     } # normalize
881    
882 wakaba 1.1 ## NOTE: Only applied to Elements and Documents
883     sub remove_child ($$) {
884     my ($self, $old_child) = @_;
885     my $parent_list = $$self->{child_nodes};
886     for (0..$#$parent_list) {
887     if ($parent_list->[$_] eq $old_child) {
888     splice @$parent_list, $_, 1;
889 wakaba 1.7 last;
890 wakaba 1.1 }
891     }
892     delete $$old_child->{parent_node};
893     return $old_child;
894     } # remove_child
895    
896 wakaba 1.4 sub manakai_set_read_only ($;$$) {
897 wakaba 1.7 my $value = 1 if $_[1];
898     if ($_[2]) {
899     my @target = ($_[0]);
900     while (@target) {
901     my $target = shift @target;
902     if ($value) {
903     $$target->{manakai_read_only} = 1;
904     } else {
905     delete $$target->{manakai_read_only};
906     }
907     push @target, @{$target->child_nodes};
908    
909     my $nt = $target->node_type;
910     if ($nt == ELEMENT_NODE) {
911     push @target, @{$target->attributes};
912     } elsif ($nt == ELEMENT_TYPE_DEFINITION_NODE) {
913     push @target, @{$target->attribute_definitions};
914     } elsif ($nt == DOCUMENT_TYPE_NODE) {
915     push @target, @{$target->element_types};
916     push @target, @{$target->general_entities};
917     push @target, @{$target->notations};
918     }
919     }
920     } else { # not deep
921     if ($value) {
922     ${$_[0]}->{manakai_read_only} = 1;
923     } else {
924     delete ${$_[0]}->{manakai_read_only};
925     }
926     }
927 wakaba 1.4 } # manakai_set_read_only
928    
929     package Message::IF::Node;
930    
931     =head1 LICENSE
932 wakaba 1.1
933 wakaba 1.4 Copyright 2007 Wakaba <w@suika.fam.cx>
934 wakaba 1.1
935 wakaba 1.4 This program is free software; you can redistribute it and/or
936     modify it under the same terms as Perl itself.
937 wakaba 1.1
938 wakaba 1.4 =cut
939 wakaba 1.1
940     1;
941 wakaba 1.8 ## $Date: 2007/06/17 13:37:40 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24