/[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 - (hide 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 wakaba 1.1 package Message::DOM::Node;
2     use strict;
3 wakaba 1.11 our $VERSION=do{my @r=(q$Revision: 1.10 $=~/\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 wakaba 1.11 '==' => 'is_equal_node',
28 wakaba 1.4 '!=' => sub {
29     return not ($_[0] == $_[1]);
30     },
31 wakaba 1.11 #eq => sub { $_[0] eq $_[1] }, ## is_same_node
32     #ne => sub { $_[0] ne $_[1] }, ## not is_same_node
33 wakaba 1.4 fallback => 1;
34    
35 wakaba 1.2 ## 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 wakaba 1.1 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 wakaba 1.6 sub ___report_error ($$) {
76     $_[1]->throw;
77     } # ___report_error
78    
79 wakaba 1.1 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 wakaba 1.4 manakai_read_only => 1,
89 wakaba 1.1 }->{$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 wakaba 1.2 sub $method_name (\$;\$) {
103 wakaba 1.1 if (\@_ > 1) {
104 wakaba 1.2 \${\$_[0]}->{$method_name} = ''.\$_[1];
105 wakaba 1.1 }
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 wakaba 1.6 ## |Node| attributes
117 wakaba 1.1
118 wakaba 1.6 ## NOTE: Overridden by |Element|.
119     sub attributes () { undef }
120 wakaba 1.2
121 wakaba 1.7 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 wakaba 1.3
129     sub child_nodes ($) {
130 wakaba 1.6 ## NOTE: Overridden by |CharacterData|, |ElementTypeDefinition|,
131     ## |Notation|, and |ProcessingInstruction|.
132 wakaba 1.4 require Message::DOM::NodeList;
133     return bless \\($_[0]), 'Message::DOM::NodeList::ChildNodeList';
134 wakaba 1.3 } # child_nodes
135    
136 wakaba 1.8 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 wakaba 1.3 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 wakaba 1.6 sub local_name { undef }
164 wakaba 1.3
165 wakaba 1.6 sub manakai_local_name { undef }
166    
167     sub namespace_uri { undef }
168 wakaba 1.3
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 wakaba 1.2
184 wakaba 1.6 ## NOTE: Overridden by subclasses.
185     sub node_name () { undef }
186 wakaba 1.2
187 wakaba 1.6 ## NOTE: Overridden by subclasses.
188     sub node_type () { }
189 wakaba 1.2
190 wakaba 1.6 ## NOTE: Overridden by |Attr|, |AttributeDefinition|,
191     ## |CharacterData|, and |ProcessingInstruction|.
192     sub node_value () { undef }
193 wakaba 1.3
194     sub owner_document ($);
195    
196 wakaba 1.8 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 wakaba 1.3 sub parent_node ($);
210    
211 wakaba 1.6 ## NOTE: Overridden by |Element| and |Attr|.
212 wakaba 1.3 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 wakaba 1.4 sub manakai_read_only ($);
230    
231 wakaba 1.3 sub text_content ($;$) {
232 wakaba 1.6 ## 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 wakaba 1.7 if (${$$self->{owner_document} or $self}->{strict_error_checking} and
243     $$self->{manakai_read_only}) {
244 wakaba 1.6 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 wakaba 1.3 } # text_content
279    
280 wakaba 1.7 ## |Node| methods
281    
282 wakaba 1.8 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 wakaba 1.10 my $er_copy_asis
297     = $cfg->get_parameter
298     (q<http://suika.fam.cx/www/2006/dom-config/clone-entity-reference-subtree>);
299 wakaba 1.8
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 wakaba 1.10 $clone->is_element_content_whitespace (1)
332     if $node->is_element_content_whitespace;
333 wakaba 1.8 } 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 wakaba 1.11 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 wakaba 1.8 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 wakaba 1.11 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 wakaba 1.4
617 wakaba 1.11 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 wakaba 1.1 } # is_equal_node
650    
651 wakaba 1.11 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 wakaba 1.1 ## NOTE: Only applied to Elements and Documents
661     sub append_child ($$) {
662     my ($self, $new_child) = @_;
663     if (defined $$new_child->{parent_node}) {
664 wakaba 1.5 my $parent_list = ${$$new_child->{parent_node}}->{child_nodes};
665 wakaba 1.1 for (0..$#$parent_list) {
666     if ($parent_list->[$_] eq $new_child) {
667     splice @$parent_list, $_, 1;
668 wakaba 1.7 last;
669 wakaba 1.1 }
670     }
671     }
672     push @{$$self->{child_nodes}}, $new_child;
673     $$new_child->{parent_node} = $self;
674     Scalar::Util::weaken ($$new_child->{parent_node});
675 wakaba 1.7 ## TODO:
676     $$new_child->{owner_document} = $self if $self->node_type == DOCUMENT_NODE;
677 wakaba 1.1 return $new_child;
678     } # append_child
679    
680 wakaba 1.7 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 wakaba 1.1 ## 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 wakaba 1.5 my $parent_list = ${$$new_child->{parent_node}}->{child_nodes};
704 wakaba 1.1 for (0..$#$parent_list) {
705     if ($parent_list->[$_] eq $new_child) {
706     splice @$parent_list, $_, 1;
707 wakaba 1.7 last;
708 wakaba 1.1 }
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 wakaba 1.8 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 wakaba 1.1 ## 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 wakaba 1.7 last;
944 wakaba 1.1 }
945     }
946     delete $$old_child->{parent_node};
947     return $old_child;
948     } # remove_child
949    
950 wakaba 1.4 sub manakai_set_read_only ($;$$) {
951 wakaba 1.7 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 wakaba 1.4 } # manakai_set_read_only
982    
983 wakaba 1.11 # {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 wakaba 1.9 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 wakaba 1.11 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 wakaba 1.9 }
1012     }
1013     };
1014     }
1015     } else {
1016     delete $v->{$key};
1017     }
1018     return $r;
1019     } # set_user_data
1020    
1021 wakaba 1.4 package Message::IF::Node;
1022    
1023     =head1 LICENSE
1024 wakaba 1.1
1025 wakaba 1.4 Copyright 2007 Wakaba <w@suika.fam.cx>
1026 wakaba 1.1
1027 wakaba 1.4 This program is free software; you can redistribute it and/or
1028     modify it under the same terms as Perl itself.
1029 wakaba 1.1
1030 wakaba 1.4 =cut
1031 wakaba 1.1
1032     1;
1033 wakaba 1.11 ## $Date: 2007/07/07 11:11:34 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24