/[suikacvs]/markup/html/whatpm/Whatpm/ContentChecker.pm
Suika

Contents of /markup/html/whatpm/Whatpm/ContentChecker.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.16 - (hide annotations) (download)
Sun May 20 07:12:11 2007 UTC (19 years, 2 months ago) by wakaba
Branch: MAIN
Changes since 1.15: +76 -17 lines
++ whatpm/t/ChangeLog	20 May 2007 07:12:09 -0000
	* content-model-1.dat: Required attributes are
	added to <link>s.

	* content-model-2.dat: Tests for global event handler
	content attributes, <link>s and <embed>s are added.

2007-05-20  Wakaba  <wakaba@suika.fam.cx>

++ whatpm/Whatpm/ChangeLog	20 May 2007 07:11:11 -0000
	* ContentChecker.pm ($HTMLEventHandlerAttrChecker): New placeholder.
	($HTMLAttrChecker): Event handler content attributes
	are added.
	(link, embed): Required attribute is now checked.
	(embed): Unknown local attributes are no longer warned.

2007-05-20  Wakaba  <wakaba@suika.fam.cx>

1 wakaba 1.1 package Whatpm::ContentChecker;
2     use strict;
3    
4 wakaba 1.13 ## ISSUE: How XML and XML Namespaces conformance can (or cannot)
5     ## be applied to an in-memory representation (i.e. DOM)?
6    
7 wakaba 1.9 my $XML_NS = q<http://www.w3.org/XML/1998/namespace>;
8     my $XMLNS_NS = q<http://www.w3.org/2000/xmlns/>;
9    
10     my $AttrChecker = {
11     $XML_NS => {
12 wakaba 1.13 space => sub {
13     my ($self, $attr) = @_;
14     my $value = $attr->value;
15     if ($value eq 'default' or $value eq 'preserve') {
16     #
17     } else {
18     ## NOTE: An XML "error"
19     $self->{onerror}->(node => $attr,
20     type => 'XML error:invalid xml:space value');
21     }
22     },
23     lang => sub {
24     ## NOTE: "The values of the attribute are language identifiers
25     ## as defined by [IETF RFC 3066], Tags for the Identification
26     ## of Languages, or its successor; in addition, the empty string
27     ## may be specified." ("may" in lower case)
28     ## TODO: xml:lang MUST NOT in HTML document
29     },
30     base => sub {
31     my ($self, $attr) = @_;
32     my $value = $attr->value;
33     if ($value =~ /[^\x{0000}-\x{10FFFF}]/) { ## ISSUE: Should we disallow noncharacters?
34     $self->{onerror}->(node => $attr,
35     type => 'syntax error');
36     }
37     ## NOTE: Conformance to URI standard is not checked.
38     },
39     id => sub {
40     my ($self, $attr) = @_;
41     my $value = $attr->value;
42     $value =~ s/[\x09\x0A\x0D\x20]+/ /g;
43     $value =~ s/^\x20//;
44     $value =~ s/\x20$//;
45     ## TODO: NCName in XML 1.0 or 1.1
46     ## TODO: declared type is ID?
47     if ($self->{id}->{$value}) {
48     $self->{onerror}->(node => $attr, type => 'xml:id error:duplicate ID');
49     } else {
50     $self->{id}->{$value} = 1;
51     }
52     },
53 wakaba 1.9 },
54     $XMLNS_NS => {
55 wakaba 1.13 '' => sub {
56     my ($self, $attr) = @_;
57     my $ln = $attr->manakai_local_name;
58     my $value = $attr->value;
59     if ($value eq $XML_NS and $ln ne 'xml') {
60     $self->{onerror}
61     ->(node => $attr,
62     type => 'NC:Reserved Prefixes and Namespace Names:=xml');
63     } elsif ($value eq $XMLNS_NS) {
64     $self->{onerror}
65     ->(node => $attr,
66     type => 'NC:Reserved Prefixes and Namespace Names:=xmlns');
67     }
68     if ($ln eq 'xml' and $value ne $XML_NS) {
69     $self->{onerror}
70     ->(node => $attr,
71     type => 'NC:Reserved Prefixes and Namespace Names:xmlns:xml=');
72     } elsif ($ln eq 'xmlns') {
73     $self->{onerror}
74     ->(node => $attr,
75     type => 'NC:Reserved Prefixes and Namespace Names:xmlns:xmlns=');
76     }
77     ## TODO: If XML 1.0 and empty
78     },
79     xmlns => sub {
80     my ($self, $attr) = @_;
81     ## TODO: In XML 1.0, URI reference [RFC 3986] or an empty string
82     ## TODO: In XML 1.1, IRI reference [RFC 3987] or an empty string
83     my $value = $attr->value;
84     if ($value eq $XML_NS) {
85     $self->{onerror}
86     ->(node => $attr,
87     type => 'NC:Reserved Prefixes and Namespace Names:=xml');
88     } elsif ($value eq $XMLNS_NS) {
89     $self->{onerror}
90     ->(node => $attr,
91     type => 'NC:Reserved Prefixes and Namespace Names:=xmlns');
92     }
93     },
94 wakaba 1.9 },
95     };
96    
97 wakaba 1.14 ## ISSUE: Should we really allow these attributes?
98 wakaba 1.13 $AttrChecker->{''}->{'xml:space'} = $AttrChecker->{$XML_NS}->{space};
99     $AttrChecker->{''}->{'xml:lang'} = $AttrChecker->{$XML_NS}->{lang};
100     $AttrChecker->{''}->{'xml:base'} = $AttrChecker->{$XML_NS}->{base};
101     $AttrChecker->{''}->{'xml:id'} = $AttrChecker->{$XML_NS}->{id};
102    
103 wakaba 1.3 ## ANY
104     my $AnyChecker = sub {
105 wakaba 1.4 my ($self, $todo) = @_;
106     my $el = $todo->{node};
107     my $new_todos = [];
108 wakaba 1.3 my @nodes = (@{$el->child_nodes});
109     while (@nodes) {
110     my $node = shift @nodes;
111     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
112    
113     my $nt = $node->node_type;
114     if ($nt == 1) {
115     my $node_ns = $node->namespace_uri;
116     $node_ns = '' unless defined $node_ns;
117     my $node_ln = $node->manakai_local_name;
118     if ($self->{minuses}->{$node_ns}->{$node_ln}) {
119     $self->{onerror}->(node => $node, type => 'element not allowed');
120     }
121 wakaba 1.4 push @$new_todos, {type => 'element', node => $node};
122 wakaba 1.3 } elsif ($nt == 5) {
123     unshift @nodes, @{$node->child_nodes};
124     }
125     }
126 wakaba 1.4 return ($new_todos);
127 wakaba 1.3 }; # $AnyChecker
128    
129 wakaba 1.1 my $ElementDefault = {
130     checker => sub {
131 wakaba 1.4 my ($self, $todo) = @_;
132     $self->{onerror}->(node => $todo->{node}, type => 'element not supported');
133     return $AnyChecker->($self, $todo);
134 wakaba 1.1 },
135 wakaba 1.9 attrs_checker => sub {
136     my ($self, $todo) = @_;
137     for my $attr (@{$todo->{node}->attributes}) {
138     my $attr_ns = $attr->namespace_uri;
139     $attr_ns = '' unless defined $attr_ns;
140     my $attr_ln = $attr->manakai_local_name;
141     my $checker = $AttrChecker->{$attr_ns}->{$attr_ln}
142     || $AttrChecker->{$attr_ns}->{''};
143     if ($checker) {
144     $checker->($self, $attr);
145     }
146     ## Don't check otherwise, since "element type not supported" warning
147     ## will be reported by the element checker.
148     }
149     },
150 wakaba 1.1 };
151    
152     my $Element = {};
153    
154     my $HTML_NS = q<http://www.w3.org/1999/xhtml>;
155    
156 wakaba 1.7 my $HTMLMetadataElements = {
157     $HTML_NS => {
158     qw/link 1 meta 1 style 1 script 1 event-source 1 command 1 base 1 title 1/,
159     },
160     };
161 wakaba 1.1
162 wakaba 1.2 my $HTMLSectioningElements = {
163     $HTML_NS => {qw/body 1 section 1 nav 1 article 1 blockquote 1 aside 1/},
164     };
165 wakaba 1.1
166 wakaba 1.7 my $HTMLBlockLevelElements = {
167     $HTML_NS => {
168     qw/
169     section 1 nav 1 article 1 blockquote 1 aside 1
170     h1 1 h2 1 h3 1 h4 1 h5 1 h6 1 header 1 footer 1
171     address 1 p 1 hr 1 dialog 1 pre 1 ol 1 ul 1 dl 1
172     ins 1 del 1 figure 1 map 1 table 1 script 1 noscript 1
173     event-source 1 details 1 datagrid 1 menu 1 div 1 font 1
174     /,
175     },
176     };
177    
178     my $HTMLStrictlyInlineLevelElements = {
179     $HTML_NS => {
180     qw/
181     br 1 a 1 q 1 cite 1 em 1 strong 1 small 1 m 1 dfn 1 abbr 1
182     time 1 meter 1 progress 1 code 1 var 1 samp 1 kbd 1
183     sub 1 sup 1 span 1 i 1 b 1 bdo 1 ins 1 del 1 img 1
184     iframe 1 embed 1 object 1 video 1 audio 1 canvas 1 area 1
185     script 1 noscript 1 event-source 1 command 1 font 1
186     /,
187     },
188     };
189    
190     my $HTMLStructuredInlineLevelElements = {
191     $HTML_NS => {qw/blockquote 1 pre 1 ol 1 ul 1 dl 1 table 1 menu 1/},
192     };
193 wakaba 1.1
194 wakaba 1.6 my $HTMLInteractiveElements = {
195     $HTML_NS => {a => 1, details => 1, datagrid => 1},
196     };
197     ## NOTE: |html:a| and |html:datagrid| are not allowed as a descendant
198     ## of interactive elements
199 wakaba 1.1
200 wakaba 1.7 my $HTMLTransparentElements = {
201     $HTML_NS => {qw/ins 1 font 1 noscript 1/},
202     ## NOTE: |html:noscript| is transparent if scripting is disabled.
203     };
204    
205     #my $HTMLSemiTransparentElements = {
206     # $HTML_NS => {qw/video 1 audio 1/},
207     #};
208    
209     my $HTMLEmbededElements = {
210     $HTML_NS => {qw/img 1 iframe 1 embed 1 object 1 video 1 audio 1 canvas 1/},
211     };
212 wakaba 1.1
213     ## Empty
214     my $HTMLEmptyChecker = sub {
215 wakaba 1.4 my ($self, $todo) = @_;
216     my $el = $todo->{node};
217     my $new_todos = [];
218 wakaba 1.1 my @nodes = (@{$el->child_nodes});
219    
220     while (@nodes) {
221     my $node = shift @nodes;
222 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
223    
224 wakaba 1.1 my $nt = $node->node_type;
225     if ($nt == 1) {
226 wakaba 1.2 ## NOTE: |minuses| list is not checked since redundant
227     $self->{onerror}->(node => $node, type => 'element not allowed');
228     my ($sib, $ch) = $self->_check_get_children ($node);
229     unshift @nodes, @$sib;
230 wakaba 1.4 push @$new_todos, @$ch;
231 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
232 wakaba 1.3 if ($node->data =~ /[^\x09-\x0D\x20]/) {
233     $self->{onerror}->(node => $node, type => 'character not allowed');
234     }
235 wakaba 1.1 } elsif ($nt == 5) {
236     unshift @nodes, @{$node->child_nodes};
237     }
238     }
239 wakaba 1.4 return ($new_todos);
240 wakaba 1.1 };
241    
242     ## Text
243     my $HTMLTextChecker = sub {
244 wakaba 1.4 my ($self, $todo) = @_;
245     my $el = $todo->{node};
246     my $new_todos = [];
247 wakaba 1.1 my @nodes = (@{$el->child_nodes});
248    
249     while (@nodes) {
250     my $node = shift @nodes;
251 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
252    
253 wakaba 1.1 my $nt = $node->node_type;
254     if ($nt == 1) {
255 wakaba 1.2 ## NOTE: |minuses| list is not checked since redundant
256     $self->{onerror}->(node => $node, type => 'element not allowed');
257     my ($sib, $ch) = $self->_check_get_children ($node);
258     unshift @nodes, @$sib;
259 wakaba 1.4 push @$new_todos, @$ch;
260 wakaba 1.1 } elsif ($nt == 5) {
261     unshift @nodes, @{$node->child_nodes};
262     }
263     }
264 wakaba 1.4 return ($new_todos);
265 wakaba 1.1 };
266    
267     ## Zero or more |html:style| elements,
268     ## followed by zero or more block-level elements
269     my $HTMLStylableBlockChecker = sub {
270 wakaba 1.4 my ($self, $todo) = @_;
271     my $el = $todo->{node};
272     my $new_todos = [];
273 wakaba 1.1 my @nodes = (@{$el->child_nodes});
274    
275     my $has_non_style;
276     while (@nodes) {
277     my $node = shift @nodes;
278 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
279    
280 wakaba 1.1 my $nt = $node->node_type;
281     if ($nt == 1) {
282 wakaba 1.2 my $node_ns = $node->namespace_uri;
283     $node_ns = '' unless defined $node_ns;
284     my $node_ln = $node->manakai_local_name;
285 wakaba 1.6 my $not_allowed = $self->{minuses}->{$node_ns}->{$node_ln};
286 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'style') {
287 wakaba 1.6 $not_allowed = 1 if $has_non_style;
288 wakaba 1.7 } elsif ($HTMLBlockLevelElements->{$node_ns}->{$node_ln}) {
289     $has_non_style = 1;
290 wakaba 1.1 } else {
291     $has_non_style = 1;
292 wakaba 1.7 $not_allowed = 1;
293 wakaba 1.1 }
294 wakaba 1.6 $self->{onerror}->(node => $node, type => 'element not allowed')
295     if $not_allowed;
296 wakaba 1.2 my ($sib, $ch) = $self->_check_get_children ($node);
297     unshift @nodes, @$sib;
298 wakaba 1.4 push @$new_todos, @$ch;
299 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
300     if ($node->data =~ /[^\x09-\x0D\x20]/) {
301 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
302 wakaba 1.1 }
303     } elsif ($nt == 5) {
304     unshift @nodes, @{$node->child_nodes};
305     }
306     }
307 wakaba 1.4 return ($new_todos);
308 wakaba 1.1 }; # $HTMLStylableBlockChecker
309    
310     ## Zero or more block-level elements
311     my $HTMLBlockChecker = sub {
312 wakaba 1.4 my ($self, $todo) = @_;
313     my $el = $todo->{node};
314     my $new_todos = [];
315 wakaba 1.1 my @nodes = (@{$el->child_nodes});
316    
317     while (@nodes) {
318     my $node = shift @nodes;
319 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
320    
321 wakaba 1.1 my $nt = $node->node_type;
322     if ($nt == 1) {
323 wakaba 1.2 my $node_ns = $node->namespace_uri;
324     $node_ns = '' unless defined $node_ns;
325     my $node_ln = $node->manakai_local_name;
326 wakaba 1.6 my $not_allowed = $self->{minuses}->{$node_ns}->{$node_ln};
327 wakaba 1.7 $not_allowed = 1
328     unless $HTMLBlockLevelElements->{$node_ns}->{$node_ln};
329 wakaba 1.6 $self->{onerror}->(node => $node, type => 'element not allowed')
330     if $not_allowed;
331 wakaba 1.2 my ($sib, $ch) = $self->_check_get_children ($node);
332     unshift @nodes, @$sib;
333 wakaba 1.4 push @$new_todos, @$ch;
334 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
335     if ($node->data =~ /[^\x09-\x0D\x20]/) {
336 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
337 wakaba 1.1 }
338     } elsif ($nt == 5) {
339     unshift @nodes, @{$node->child_nodes};
340     }
341     }
342 wakaba 1.4 return ($new_todos);
343 wakaba 1.1 }; # $HTMLBlockChecker
344    
345     ## Inline-level content
346     my $HTMLInlineChecker = sub {
347 wakaba 1.4 my ($self, $todo) = @_;
348     my $el = $todo->{node};
349     my $new_todos = [];
350 wakaba 1.1 my @nodes = (@{$el->child_nodes});
351    
352     while (@nodes) {
353     my $node = shift @nodes;
354 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
355    
356 wakaba 1.1 my $nt = $node->node_type;
357     if ($nt == 1) {
358 wakaba 1.2 my $node_ns = $node->namespace_uri;
359     $node_ns = '' unless defined $node_ns;
360     my $node_ln = $node->manakai_local_name;
361 wakaba 1.6 my $not_allowed = $self->{minuses}->{$node_ns}->{$node_ln};
362 wakaba 1.7 $not_allowed = 1
363     unless $HTMLStrictlyInlineLevelElements->{$node_ns}->{$node_ln} or
364     $HTMLStructuredInlineLevelElements->{$node_ns}->{$node_ln};
365 wakaba 1.6 $self->{onerror}->(node => $node, type => 'element not allowed')
366     if $not_allowed;
367 wakaba 1.2 my ($sib, $ch) = $self->_check_get_children ($node);
368     unshift @nodes, @$sib;
369 wakaba 1.4 push @$new_todos, @$ch;
370 wakaba 1.1 } elsif ($nt == 5) {
371     unshift @nodes, @{$node->child_nodes};
372     }
373     }
374 wakaba 1.4
375     for (@$new_todos) {
376     $_->{inline} = 1;
377     }
378     return ($new_todos);
379     }; # $HTMLInlineChecker
380 wakaba 1.1
381     my $HTMLSignificantInlineChecker = $HTMLInlineChecker;
382     ## TODO: check significant content
383    
384     ## Strictly inline-level content
385     my $HTMLStrictlyInlineChecker = sub {
386 wakaba 1.4 my ($self, $todo) = @_;
387     my $el = $todo->{node};
388     my $new_todos = [];
389 wakaba 1.1 my @nodes = (@{$el->child_nodes});
390    
391     while (@nodes) {
392     my $node = shift @nodes;
393 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
394    
395 wakaba 1.1 my $nt = $node->node_type;
396     if ($nt == 1) {
397 wakaba 1.2 my $node_ns = $node->namespace_uri;
398     $node_ns = '' unless defined $node_ns;
399     my $node_ln = $node->manakai_local_name;
400 wakaba 1.6 my $not_allowed = $self->{minuses}->{$node_ns}->{$node_ln};
401 wakaba 1.7 $not_allowed = 1
402     unless $HTMLStrictlyInlineLevelElements->{$node_ns}->{$node_ln};
403 wakaba 1.6 $self->{onerror}->(node => $node, type => 'element not allowed')
404     if $not_allowed;
405 wakaba 1.2 my ($sib, $ch) = $self->_check_get_children ($node);
406     unshift @nodes, @$sib;
407 wakaba 1.4 push @$new_todos, @$ch;
408 wakaba 1.1 } elsif ($nt == 5) {
409     unshift @nodes, @{$node->child_nodes};
410     }
411     }
412 wakaba 1.4
413     for (@$new_todos) {
414     $_->{inline} = 1;
415     $_->{strictly_inline} = 1;
416     }
417     return ($new_todos);
418 wakaba 1.1 }; # $HTMLStrictlyInlineChecker
419    
420     my $HTMLSignificantStrictlyInlineChecker = $HTMLStrictlyInlineChecker;
421     ## TODO: check significant content
422    
423 wakaba 1.4 ## Inline-level or strictly inline-kevek content
424     my $HTMLInlineOrStrictlyInlineChecker = sub {
425     my ($self, $todo) = @_;
426     my $el = $todo->{node};
427     my $new_todos = [];
428     my @nodes = (@{$el->child_nodes});
429    
430     while (@nodes) {
431     my $node = shift @nodes;
432     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
433    
434     my $nt = $node->node_type;
435     if ($nt == 1) {
436     my $node_ns = $node->namespace_uri;
437     $node_ns = '' unless defined $node_ns;
438     my $node_ln = $node->manakai_local_name;
439 wakaba 1.6 my $not_allowed = $self->{minuses}->{$node_ns}->{$node_ln};
440 wakaba 1.7 if ($todo->{strictly_inline}) {
441     $not_allowed = 1
442     unless $HTMLStrictlyInlineLevelElements->{$node_ns}->{$node_ln};
443     } else {
444     $not_allowed = 1
445     unless $HTMLStrictlyInlineLevelElements->{$node_ns}->{$node_ln} or
446     $HTMLStructuredInlineLevelElements->{$node_ns}->{$node_ln};
447     }
448 wakaba 1.6 $self->{onerror}->(node => $node, type => 'element not allowed')
449     if $not_allowed;
450 wakaba 1.4 my ($sib, $ch) = $self->_check_get_children ($node);
451     unshift @nodes, @$sib;
452     push @$new_todos, @$ch;
453     } elsif ($nt == 5) {
454     unshift @nodes, @{$node->child_nodes};
455     }
456     }
457    
458     for (@$new_todos) {
459     $_->{inline} = 1;
460     $_->{strictly_inline} = 1;
461     }
462     return ($new_todos);
463     }; # $HTMLInlineOrStrictlyInlineChecker
464    
465 wakaba 1.6 my $HTMLSignificantInlineOrStrictlyInlineChecker
466     = $HTMLInlineOrStrictlyInlineChecker;
467     ## TODO: check significant content
468    
469 wakaba 1.1 my $HTMLBlockOrInlineChecker = sub {
470 wakaba 1.4 my ($self, $todo) = @_;
471     my $el = $todo->{node};
472     my $new_todos = [];
473 wakaba 1.1 my @nodes = (@{$el->child_nodes});
474    
475     my $content = 'block-or-inline'; # or 'block' or 'inline'
476     my @block_not_inline;
477     while (@nodes) {
478     my $node = shift @nodes;
479 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
480    
481 wakaba 1.1 my $nt = $node->node_type;
482     if ($nt == 1) {
483 wakaba 1.2 my $node_ns = $node->namespace_uri;
484     $node_ns = '' unless defined $node_ns;
485     my $node_ln = $node->manakai_local_name;
486 wakaba 1.6 my $not_allowed = $self->{minuses}->{$node_ns}->{$node_ln};
487 wakaba 1.1 if ($content eq 'block') {
488 wakaba 1.7 $not_allowed = 1
489     unless $HTMLBlockLevelElements->{$node_ns}->{$node_ln};
490 wakaba 1.1 } elsif ($content eq 'inline') {
491 wakaba 1.7 $not_allowed = 1
492     unless $HTMLStrictlyInlineLevelElements->{$node_ns}->{$node_ln} or
493     $HTMLStructuredInlineLevelElements->{$node_ns}->{$node_ln};
494 wakaba 1.1 } else {
495 wakaba 1.7 my $is_block = $HTMLBlockLevelElements->{$node_ns}->{$node_ln};
496     my $is_inline
497     = $HTMLStrictlyInlineLevelElements->{$node_ns}->{$node_ln} ||
498     $HTMLStructuredInlineLevelElements->{$node_ns}->{$node_ln};
499 wakaba 1.1
500 wakaba 1.6 push @block_not_inline, $node
501     if $is_block and not $is_inline and not $not_allowed;
502 wakaba 1.1 unless ($is_block) {
503     $content = 'inline';
504     for (@block_not_inline) {
505 wakaba 1.2 $self->{onerror}->(node => $_, type => 'element not allowed');
506 wakaba 1.1 }
507 wakaba 1.6 $not_allowed = 1 unless $is_inline;
508 wakaba 1.1 }
509     }
510 wakaba 1.6 $self->{onerror}->(node => $node, type => 'element not allowed')
511     if $not_allowed;
512 wakaba 1.2 my ($sib, $ch) = $self->_check_get_children ($node);
513     unshift @nodes, @$sib;
514 wakaba 1.4 push @$new_todos, @$ch;
515 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
516     if ($node->data =~ /[^\x09-\x0D\x20]/) {
517     if ($content eq 'block') {
518 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
519 wakaba 1.1 } else {
520     $content = 'inline';
521     for (@block_not_inline) {
522 wakaba 1.2 $self->{onerror}->(node => $_, type => 'element not allowed');
523 wakaba 1.1 }
524     }
525     }
526     } elsif ($nt == 5) {
527     unshift @nodes, @{$node->child_nodes};
528     }
529     }
530 wakaba 1.4
531     if ($content eq 'inline') {
532     for (@$new_todos) {
533     $_->{inline} = 1;
534     }
535     }
536     return ($new_todos);
537 wakaba 1.1 };
538    
539 wakaba 1.2 ## Zero or more XXX element, then either block-level or inline-level
540     my $GetHTMLZeroOrMoreThenBlockOrInlineChecker = sub ($$) {
541     my ($elnsuri, $ellname) = @_;
542     return sub {
543 wakaba 1.4 my ($self, $todo) = @_;
544     my $el = $todo->{node};
545     my $new_todos = [];
546 wakaba 1.2 my @nodes = (@{$el->child_nodes});
547    
548     my $has_non_style;
549     my $content = 'block-or-inline'; # or 'block' or 'inline'
550     my @block_not_inline;
551     while (@nodes) {
552     my $node = shift @nodes;
553     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
554    
555     my $nt = $node->node_type;
556     if ($nt == 1) {
557     my $node_ns = $node->namespace_uri;
558     $node_ns = '' unless defined $node_ns;
559     my $node_ln = $node->manakai_local_name;
560 wakaba 1.6 my $not_allowed = $self->{minuses}->{$node_ns}->{$node_ln};
561 wakaba 1.8 if ($node_ns eq $elnsuri and $node_ln eq $ellname) {
562 wakaba 1.6 $not_allowed = 1 if $has_non_style;
563 wakaba 1.2 } elsif ($content eq 'block') {
564     $has_non_style = 1;
565 wakaba 1.7 $not_allowed = 1
566     unless $HTMLBlockLevelElements->{$node_ns}->{$node_ln};
567 wakaba 1.2 } elsif ($content eq 'inline') {
568     $has_non_style = 1;
569 wakaba 1.7 $not_allowed = 1
570     unless $HTMLStrictlyInlineLevelElements->{$node_ns}->{$node_ln} or
571     $HTMLStructuredInlineLevelElements->{$node_ns}->{$node_ln};
572 wakaba 1.2 } else {
573     $has_non_style = 1;
574 wakaba 1.7 my $is_block = $HTMLBlockLevelElements->{$node_ns}->{$node_ln};
575     my $is_inline
576     = $HTMLStrictlyInlineLevelElements->{$node_ns}->{$node_ln} ||
577     $HTMLStructuredInlineLevelElements->{$node_ns}->{$node_ln};
578 wakaba 1.2
579 wakaba 1.6 push @block_not_inline, $node
580     if $is_block and not $is_inline and not $not_allowed;
581 wakaba 1.2 unless ($is_block) {
582     $content = 'inline';
583     for (@block_not_inline) {
584     $self->{onerror}->(node => $_, type => 'element not allowed');
585     }
586 wakaba 1.6 $not_allowed = 1 unless $is_inline;
587 wakaba 1.1 }
588     }
589 wakaba 1.6 $self->{onerror}->(node => $node, type => 'element not allowed')
590     if $not_allowed;
591 wakaba 1.2 my ($sib, $ch) = $self->_check_get_children ($node);
592     unshift @nodes, @$sib;
593 wakaba 1.4 push @$new_todos, @$ch;
594 wakaba 1.2 } elsif ($nt == 3 or $nt == 4) {
595     if ($node->data =~ /[^\x09-\x0D\x20]/) {
596     $has_non_style = 1;
597     if ($content eq 'block') {
598     $self->{onerror}->(node => $node, type => 'character not allowed');
599     } else {
600     $content = 'inline';
601     for (@block_not_inline) {
602     $self->{onerror}->(node => $_, type => 'element not allowed');
603     }
604 wakaba 1.1 }
605     }
606 wakaba 1.2 } elsif ($nt == 5) {
607     unshift @nodes, @{$node->child_nodes};
608 wakaba 1.1 }
609     }
610 wakaba 1.4
611     if ($content eq 'inline') {
612     for (@$new_todos) {
613     $_->{inline} = 1;
614     }
615     }
616     return ($new_todos);
617 wakaba 1.2 };
618     }; # $GetHTMLZeroOrMoreThenBlockOrInlineChecker
619 wakaba 1.1
620     my $HTMLTransparentChecker = $HTMLBlockOrInlineChecker;
621    
622 wakaba 1.10 my $GetHTMLEnumeratedAttrChecker = sub {
623     my $states = shift; # {value => conforming ? 1 : -1}
624     return sub {
625     my ($self, $attr) = @_;
626     my $value = lc $attr->value; ## TODO: ASCII case insensitibility?
627     if ($states->{$value} > 0) {
628     #
629     } elsif ($states->{$value}) {
630     $self->{onerror}->(node => $attr,
631     type => 'non-conforming enumerated attribute value');
632     } else {
633     $self->{onerror}->(node => $attr,
634     type => 'invalid enumerated attribute value');
635     }
636     };
637     }; # $GetHTMLEnumeratedAttrChecker
638 wakaba 1.9
639 wakaba 1.10 my $GetHTMLBooleanAttrChecker = sub {
640     my $local_name = shift;
641     return sub {
642     my ($self, $attr) = @_;
643     my $value = $attr->value;
644     unless ($value eq $local_name or $value eq '') {
645     $self->{onerror}->(node => $attr,
646     type => 'invalid boolean attribute value');
647     }
648     };
649     }; # $GetHTMLBooleanAttrChecker
650 wakaba 1.9
651 wakaba 1.10 my $HTMLUnorderedSetOfSpaceSeparatedTokensAttrChecker = sub {
652     my ($self, $attr) = @_;
653     my %word;
654     for my $word (grep {length $_} split /[\x09-\x0D\x20]/, $attr->value) {
655     unless ($word{$word}) {
656     $word{$word} = 1;
657 wakaba 1.9 } else {
658 wakaba 1.10 $self->{onerror}->(node => $attr, type => 'duplicate token:'.$word);
659 wakaba 1.9 }
660     }
661 wakaba 1.10 }; # $HTMLUnorderedSetOfSpaceSeparatedTokensAttrChecker
662    
663 wakaba 1.11 my $HTMLURIAttrChecker = sub {
664     my ($self, $attr) = @_;
665     ## TODO: URI or IRI check
666 wakaba 1.15 ## ISSUE: Relative references are allowed? (RFC 3987 "IRI" is an absolute reference with optional fragment identifier.)
667 wakaba 1.11 }; # $HTMLURIAttrChecker
668    
669 wakaba 1.15 ## A space separated list of one or more URIs (or IRIs)
670     my $HTMLSpaceURIsAttrChecker = sub {
671     my ($self, $attr) = @_;
672     ## TODO: URI or IRI check
673     ## ISSUE: Relative references?
674     ## ISSUE: Leading or trailing white spaces are conformant?
675     ## ISSUE: A sequence of white space characters are conformant?
676     ## ISSUE: A zero-length string is conformant? (It does contain a relative reference, i.e. same as base URI.)
677     ## NOTE: Duplication seems not an error.
678     }; # $HTMLSpaceURIsAttrChecker
679    
680 wakaba 1.11 my $HTMLIntegerAttrChecker = sub {
681     my ($self, $attr) = @_;
682     my $value = $attr->value;
683     unless ($value =~ /\A-?[0-9]+\z/) {
684 wakaba 1.15 $self->{onerror}->(node => $attr, type => 'integer syntax error');
685 wakaba 1.11 }
686     }; # $HTMLIntegerAttrChecker
687    
688 wakaba 1.12 my $GetHTMLNonNegativeIntegerAttrChecker = sub {
689     my $range_check = shift;
690     return sub {
691     my ($self, $attr) = @_;
692     my $value = $attr->value;
693     if ($value =~ /\A[0-9]+\z/) {
694     unless ($range_check->($value + 0)) {
695     $self->{onerror}->(node => $attr, type => 'out of range');
696     }
697     } else {
698 wakaba 1.15 $self->{onerror}->(node => $attr,
699     type => 'non-negative integer syntax error');
700 wakaba 1.12 }
701     };
702     }; # $GetHTMLNonNegativeIntegerAttrChecker
703    
704 wakaba 1.11 my $GetHTMLFloatingPointNumberAttrChecker = sub {
705     my $range_check = shift;
706     return sub {
707     my ($self, $attr) = @_;
708     my $value = $attr->value;
709     if ($value =~ /\A-?[0-9.]+\z/ and $value =~ /[0-9]/) {
710     unless ($range_check->($value + 0)) {
711     $self->{onerror}->(node => $attr, type => 'out of range');
712     }
713     } else {
714 wakaba 1.15 $self->{onerror}->(node => $attr,
715     type => 'floating point number syntax error');
716 wakaba 1.11 }
717     };
718     }; # $GetHTMLFloatingPointNumberAttrChecker
719    
720 wakaba 1.15 ## "A valid MIME type, optionally with parameters. [RFC 2046]"
721     ## ISSUE: RFC 2046 does not define syntax of media types.
722     ## ISSUE: The definition of "a valid MIME type" is unknown.
723     ## Syntactical correctness?
724     my $HTMLIMTAttrChecker = sub {
725     my ($self, $attr) = @_;
726     my $value = $attr->value;
727     ## ISSUE: RFC 2045 Content-Type header field allows insertion
728     ## of LWS/comments between tokens. Is it allowed in HTML? Maybe no.
729     ## ISSUE: RFC 2231 extension? Maybe no.
730     my $lws0 = qr/(?>(?>\x0D\x0A)?[\x09\x20])*/;
731     my $token = qr/[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+/;
732     my $qs = qr/"(?>[\x00-\x0C\x0E-\x21\x23-\x5B\x5D-\x7E]|\x0D\x0A[\x09\x20]|\x5C[\x00-\x7F])*"/;
733     unless ($value =~ m#\A$lws0$token$lws0/$lws0$token$lws0(?>;$lws0$token$lws0=$lws0(?>$token|$qs)$lws0)*\z#) {
734     $self->{onerror}->(node => $attr, type => 'IMT syntax error');
735     }
736     ## TODO: Warn unless registered
737     }; # $HTMLIMTAttrChecker
738    
739 wakaba 1.16 my $HTMLEventHandlerAttrChecker = sub {
740     ## TODO: MUST contain valid ECMAScript code matching the
741     ## ECMAScript |FunctionBody| production. [ECMA262]
742     ## ISSUE: MUST be ES3? E4X? ES4? JS1.x?
743     ## ISSUE: Automatic semicolon insertion does not apply?
744     ## ISSUE: Other script languages?
745     }; # $HTMLEventHandlerAttrChecker
746    
747 wakaba 1.10 my $HTMLAttrChecker = {
748     id => sub {
749     my ($self, $attr) = @_;
750     my $value = $attr->value;
751     unless (length $value > 0) {
752     ## NOTE: MUST contain at least one character
753     $self->{onerror}->(node => $attr, type => 'attribute value is empty');
754     } else {
755     if ($self->{id}->{$value}) {
756     $self->{onerror}->(node => $attr, type => 'duplicate ID');
757     } else {
758     $self->{id}->{$value} = 1;
759     }
760     }
761     },
762     title => sub {}, ## NOTE: No conformance creteria
763     lang => sub {
764     ## TODO: RFC 3066 test
765     ## ISSUE: RFC 4646 (3066bis)?
766     ## TODO: HTML vs XHTML
767     },
768     dir => $GetHTMLEnumeratedAttrChecker->({ltr => 1, rtl => 1}),
769     class => $HTMLUnorderedSetOfSpaceSeparatedTokensAttrChecker,
770     irrelevant => $GetHTMLBooleanAttrChecker->('irrelevant'),
771     ## TODO: tabindex
772     };
773    
774 wakaba 1.16 for (qw/
775     onabort onbeforeunload onblur onchange onclick oncontextmenu
776     ondblclick ondrag ondragend ondragenter ondragleave ondragover
777     ondragstart ondrop onerror onfocus onkeydown onkeypress
778     onkeyup onload onmessage onmousedown onmousemove onmouseout
779     onmouseover onmouseup onmousewheel onresize onscroll onselect
780     onsubmit onunload
781     /) {
782     $HTMLAttrChecker->{$_} = $HTMLEventHandlerAttrChecker;
783     }
784    
785 wakaba 1.10 my $GetHTMLAttrsChecker = sub {
786     my $element_specific_checker = shift;
787     return sub {
788     my ($self, $todo) = @_;
789     for my $attr (@{$todo->{node}->attributes}) {
790     my $attr_ns = $attr->namespace_uri;
791     $attr_ns = '' unless defined $attr_ns;
792     my $attr_ln = $attr->manakai_local_name;
793     my $checker;
794     if ($attr_ns eq '') {
795     $checker = $element_specific_checker->{$attr_ln}
796     || $HTMLAttrChecker->{$attr_ln};
797     }
798     $checker ||= $AttrChecker->{$attr_ns}->{$attr_ln}
799     || $AttrChecker->{$attr_ns}->{''};
800     if ($checker) {
801     $checker->($self, $attr);
802     } else {
803     $self->{onerror}->(node => $attr, type => 'attribute not supported');
804     ## ISSUE: No comformance createria for unknown attributes in the spec
805     }
806     }
807     };
808     }; # $GetHTMLAttrsChecker
809 wakaba 1.9
810     $Element->{$HTML_NS}->{''} = {
811 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
812 wakaba 1.9 checker => $ElementDefault->{checker},
813     };
814    
815 wakaba 1.1 $Element->{$HTML_NS}->{html} = {
816 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({
817     xmlns => sub {
818     my ($self, $attr) = @_;
819     my $value = $attr->value;
820     unless ($value eq $HTML_NS) {
821     $self->{onerror}->(node => $attr, type => 'syntax error');
822     ## TODO: only in HTML documents
823     }
824     },
825     }),
826 wakaba 1.1 checker => sub {
827 wakaba 1.4 my ($self, $todo) = @_;
828     my $el = $todo->{node};
829     my $new_todos = [];
830 wakaba 1.1 my @nodes = (@{$el->child_nodes});
831    
832     my $phase = 'before head';
833     while (@nodes) {
834     my $node = shift @nodes;
835 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
836    
837 wakaba 1.1 my $nt = $node->node_type;
838     if ($nt == 1) {
839 wakaba 1.2 my $node_ns = $node->namespace_uri;
840     $node_ns = '' unless defined $node_ns;
841     my $node_ln = $node->manakai_local_name;
842 wakaba 1.6 my $not_allowed = $self->{minuses}->{$node_ns}->{$node_ln};
843 wakaba 1.1 if ($phase eq 'before head') {
844 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'head') {
845 wakaba 1.1 $phase = 'after head';
846 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'body') {
847     $self->{onerror}->(node => $node, type => 'ps element missing:head');
848 wakaba 1.1 $phase = 'after body';
849     } else {
850 wakaba 1.6 $not_allowed = 1;
851 wakaba 1.1 # before head
852     }
853     } elsif ($phase eq 'after head') {
854 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'body') {
855 wakaba 1.1 $phase = 'after body';
856     } else {
857 wakaba 1.6 $not_allowed = 1;
858 wakaba 1.1 # after head
859     }
860     } else { #elsif ($phase eq 'after body') {
861 wakaba 1.6 $not_allowed = 1;
862 wakaba 1.1 # after body
863     }
864 wakaba 1.6 $self->{onerror}->(node => $node, type => 'element not allowed')
865     if $not_allowed;
866 wakaba 1.2 my ($sib, $ch) = $self->_check_get_children ($node);
867     unshift @nodes, @$sib;
868 wakaba 1.4 push @$new_todos, @$ch;
869 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
870     if ($node->data =~ /[^\x09-\x0D\x20]/) {
871 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
872 wakaba 1.1 }
873     } elsif ($nt == 5) {
874     unshift @nodes, @{$node->child_nodes};
875     }
876     }
877 wakaba 1.3
878     if ($phase eq 'before head') {
879     $self->{onerror}->(node => $el, type => 'child element missing:head');
880     $self->{onerror}->(node => $el, type => 'child element missing:body');
881     } elsif ($phase eq 'after head') {
882     $self->{onerror}->(node => $el, type => 'child element missing:body');
883     }
884    
885 wakaba 1.4 return ($new_todos);
886 wakaba 1.1 },
887     };
888    
889     $Element->{$HTML_NS}->{head} = {
890 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
891 wakaba 1.1 checker => sub {
892 wakaba 1.4 my ($self, $todo) = @_;
893     my $el = $todo->{node};
894     my $new_todos = [];
895 wakaba 1.1 my @nodes = (@{$el->child_nodes});
896    
897     my $has_title;
898 wakaba 1.3 my $phase = 'initial'; # 'after charset', 'after base'
899 wakaba 1.1 while (@nodes) {
900     my $node = shift @nodes;
901 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
902    
903 wakaba 1.1 my $nt = $node->node_type;
904     if ($nt == 1) {
905 wakaba 1.2 my $node_ns = $node->namespace_uri;
906     $node_ns = '' unless defined $node_ns;
907     my $node_ln = $node->manakai_local_name;
908 wakaba 1.6 my $not_allowed = $self->{minuses}->{$node_ns}->{$node_ln};
909 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'title') {
910 wakaba 1.3 $phase = 'after base';
911 wakaba 1.1 unless ($has_title) {
912     $has_title = 1;
913     } else {
914 wakaba 1.6 $not_allowed = 1;
915 wakaba 1.1 }
916 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'meta') {
917 wakaba 1.1 if ($node->has_attribute_ns (undef, 'charset')) {
918 wakaba 1.3 if ($phase eq 'initial') {
919     $phase = 'after charset';
920 wakaba 1.1 } else {
921 wakaba 1.6 $not_allowed = 1;
922 wakaba 1.3 ## NOTE: See also |base|'s "contexts" field in the spec
923 wakaba 1.1 }
924     } else {
925 wakaba 1.3 $phase = 'after base';
926 wakaba 1.1 }
927 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'base') {
928 wakaba 1.3 if ($phase eq 'initial' or $phase eq 'after charset') {
929     $phase = 'after base';
930 wakaba 1.1 } else {
931 wakaba 1.6 $not_allowed = 1;
932 wakaba 1.1 }
933 wakaba 1.7 } elsif ($HTMLMetadataElements->{$node_ns}->{$node_ln}) {
934     $phase = 'after base';
935 wakaba 1.1 } else {
936 wakaba 1.7 $not_allowed = 1;
937 wakaba 1.1 }
938 wakaba 1.6 $self->{onerror}->(node => $node, type => 'element not allowed')
939     if $not_allowed;
940 wakaba 1.2 my ($sib, $ch) = $self->_check_get_children ($node);
941     unshift @nodes, @$sib;
942 wakaba 1.4 push @$new_todos, @$ch;
943 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
944     if ($node->data =~ /[^\x09-\x0D\x20]/) {
945 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
946 wakaba 1.1 }
947     } elsif ($nt == 5) {
948     unshift @nodes, @{$node->child_nodes};
949     }
950     }
951     unless ($has_title) {
952 wakaba 1.3 $self->{onerror}->(node => $el, type => 'child element missing:title');
953 wakaba 1.1 }
954 wakaba 1.4 return ($new_todos);
955 wakaba 1.1 },
956     };
957    
958     $Element->{$HTML_NS}->{title} = {
959 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
960 wakaba 1.1 checker => $HTMLTextChecker,
961     };
962    
963     $Element->{$HTML_NS}->{base} = {
964 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({
965 wakaba 1.11 href => $HTMLURIAttrChecker,
966 wakaba 1.10 ## TODO: target
967     }),
968 wakaba 1.1 checker => $HTMLEmptyChecker,
969     };
970    
971     $Element->{$HTML_NS}->{link} = {
972 wakaba 1.16 attrs_checker => sub {
973     my ($self, $todo) = @_;
974     $GetHTMLAttrsChecker->({
975     href => $HTMLURIAttrChecker,
976     rel => $HTMLUnorderedSetOfSpaceSeparatedTokensAttrChecker, ## TODO: registered? check
977     ## TODO: media
978     ## TODO: hreflang
979     type => $HTMLIMTAttrChecker,
980     ## NOTE: Though |title| has special semantics,
981     ## syntactically same as the |title| as global attribute.
982     })->($self, $todo);
983     unless ($todo->{node}->has_attribute_ns (undef, 'href')) {
984     $self->{onerror}->(node => $todo->{node},
985     type => 'attribute missing:href');
986     }
987     unless ($todo->{node}->has_attribute_ns (undef, 'rel')) {
988     $self->{onerror}->(node => $todo->{node},
989     type => 'attribute missing:rel');
990     }
991     },
992 wakaba 1.1 checker => $HTMLEmptyChecker,
993     };
994    
995     $Element->{$HTML_NS}->{meta} = {
996 wakaba 1.10 attrs_checker => sub {
997     my ($self, $todo) = @_;
998     my $name_attr;
999     my $http_equiv_attr;
1000     my $charset_attr;
1001     my $content_attr;
1002     for my $attr (@{$todo->{node}->attributes}) {
1003     my $attr_ns = $attr->namespace_uri;
1004     $attr_ns = '' unless defined $attr_ns;
1005     my $attr_ln = $attr->manakai_local_name;
1006     my $checker;
1007     if ($attr_ns eq '') {
1008     if ($attr_ln eq 'content') {
1009     $content_attr = $attr;
1010     $checker = 1;
1011     } elsif ($attr_ln eq 'name') {
1012     $name_attr = $attr;
1013     $checker = 1;
1014     } elsif ($attr_ln eq 'http-equiv') {
1015     $http_equiv_attr = $attr;
1016     $checker = 1;
1017     } elsif ($attr_ln eq 'charset') {
1018     $charset_attr = $attr;
1019     $checker = 1;
1020     } else {
1021     $checker = $HTMLAttrChecker->{$attr_ln}
1022     || $AttrChecker->{$attr_ns}->{$attr_ln}
1023     || $AttrChecker->{$attr_ns}->{''};
1024     }
1025     } else {
1026     $checker ||= $AttrChecker->{$attr_ns}->{$attr_ln}
1027     || $AttrChecker->{$attr_ns}->{''};
1028     }
1029     if ($checker) {
1030     $checker->($self, $attr) if ref $checker;
1031     } else {
1032     $self->{onerror}->(node => $attr, type => 'attribute not supported');
1033     ## ISSUE: No comformance createria for unknown attributes in the spec
1034     }
1035     }
1036    
1037     if (defined $name_attr) {
1038     if (defined $http_equiv_attr) {
1039     $self->{onerror}->(node => $http_equiv_attr,
1040     type => 'attribute not allowed');
1041     } elsif (defined $charset_attr) {
1042     $self->{onerror}->(node => $charset_attr,
1043     type => 'attribute not allowed');
1044     }
1045     my $metadata_name = $name_attr->value;
1046     my $metadata_value;
1047     if (defined $content_attr) {
1048     $metadata_value = $content_attr->value;
1049     } else {
1050     $self->{onerror}->(node => $todo->{node},
1051     type => 'attribute missing:content');
1052     $metadata_value = '';
1053     }
1054     } elsif (defined $http_equiv_attr) {
1055     if (defined $charset_attr) {
1056     $self->{onerror}->(node => $charset_attr,
1057     type => 'attribute not allowed');
1058     }
1059     unless (defined $content_attr) {
1060     $self->{onerror}->(node => $todo->{node},
1061     type => 'attribute missing:content');
1062     }
1063     } elsif (defined $charset_attr) {
1064     if (defined $content_attr) {
1065     $self->{onerror}->(node => $content_attr,
1066     type => 'attribute not allowed');
1067     }
1068     ## TODO: Allowed only in HTML documents
1069     } else {
1070     if (defined $content_attr) {
1071     $self->{onerror}->(node => $content_attr,
1072     type => 'attribute not allowed');
1073     $self->{onerror}->(node => $todo->{node},
1074     type => 'attribute missing:name|http-equiv');
1075     } else {
1076     $self->{onerror}->(node => $todo->{node},
1077     type => 'attribute missing:name|http-equiv|charset');
1078     }
1079     }
1080    
1081     ## TODO: metadata conformance
1082    
1083     ## TODO: pragma conformance
1084     if (defined $http_equiv_attr) { ## An enumerated attribute
1085     my $keyword = lc $http_equiv_attr->value; ## TODO: ascii case?
1086     if ({
1087     'refresh' => 1,
1088     'default-style' => 1,
1089     }->{$keyword}) {
1090     #
1091     } else {
1092     $self->{onerror}->(node => $http_equiv_attr,
1093     type => 'invalid enumerated attribute value');
1094     }
1095     }
1096    
1097     ## TODO: charset
1098     },
1099 wakaba 1.1 checker => $HTMLEmptyChecker,
1100     };
1101    
1102     ## NOTE: |html:style| has no conformance creteria on content model
1103 wakaba 1.3 $Element->{$HTML_NS}->{style} = {
1104 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({
1105 wakaba 1.15 type => $HTMLIMTAttrChecker, ## TODO: MUST be a styling language
1106 wakaba 1.10 ## TODO: media
1107     scoped => $GetHTMLBooleanAttrChecker->('scoped'),
1108     ## NOTE: |title| has special semantics for |style|s, but is syntactically
1109     ## not different
1110     }),
1111 wakaba 1.3 checker => $AnyChecker,
1112     };
1113 wakaba 1.1
1114     $Element->{$HTML_NS}->{body} = {
1115 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1116 wakaba 1.1 checker => $HTMLBlockChecker,
1117     };
1118    
1119     $Element->{$HTML_NS}->{section} = {
1120 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1121 wakaba 1.1 checker => $HTMLStylableBlockChecker,
1122     };
1123    
1124     $Element->{$HTML_NS}->{nav} = {
1125 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1126 wakaba 1.1 checker => $HTMLBlockOrInlineChecker,
1127     };
1128    
1129     $Element->{$HTML_NS}->{article} = {
1130 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1131 wakaba 1.1 checker => $HTMLStylableBlockChecker,
1132     };
1133    
1134     $Element->{$HTML_NS}->{blockquote} = {
1135 wakaba 1.11 attrs_checker => $GetHTMLAttrsChecker->({
1136     cite => $HTMLURIAttrChecker,
1137     }),
1138 wakaba 1.1 checker => $HTMLBlockChecker,
1139     };
1140    
1141     $Element->{$HTML_NS}->{aside} = {
1142 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1143 wakaba 1.2 checker => $GetHTMLZeroOrMoreThenBlockOrInlineChecker->($HTML_NS, 'style'),
1144 wakaba 1.1 };
1145    
1146     $Element->{$HTML_NS}->{h1} = {
1147 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1148 wakaba 1.1 checker => $HTMLSignificantStrictlyInlineChecker,
1149     };
1150    
1151     $Element->{$HTML_NS}->{h2} = {
1152 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1153 wakaba 1.1 checker => $HTMLSignificantStrictlyInlineChecker,
1154     };
1155    
1156     $Element->{$HTML_NS}->{h3} = {
1157 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1158 wakaba 1.1 checker => $HTMLSignificantStrictlyInlineChecker,
1159     };
1160    
1161     $Element->{$HTML_NS}->{h4} = {
1162 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1163 wakaba 1.1 checker => $HTMLSignificantStrictlyInlineChecker,
1164     };
1165    
1166     $Element->{$HTML_NS}->{h5} = {
1167 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1168 wakaba 1.1 checker => $HTMLSignificantStrictlyInlineChecker,
1169     };
1170    
1171     $Element->{$HTML_NS}->{h6} = {
1172 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1173 wakaba 1.1 checker => $HTMLSignificantStrictlyInlineChecker,
1174     };
1175    
1176     ## TODO: header
1177    
1178 wakaba 1.2 $Element->{$HTML_NS}->{footer} = {
1179 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1180 wakaba 1.2 checker => sub { ## block -hn -header -footer -sectioning or inline
1181 wakaba 1.4 my ($self, $todo) = @_;
1182     my $el = $todo->{node};
1183     my $new_todos = [];
1184 wakaba 1.2 my @nodes = (@{$el->child_nodes});
1185    
1186     my $content = 'block-or-inline'; # or 'block' or 'inline'
1187     my @block_not_inline;
1188     while (@nodes) {
1189     my $node = shift @nodes;
1190     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
1191    
1192     my $nt = $node->node_type;
1193     if ($nt == 1) {
1194     my $node_ns = $node->namespace_uri;
1195     $node_ns = '' unless defined $node_ns;
1196     my $node_ln = $node->manakai_local_name;
1197 wakaba 1.6 my $not_allowed;
1198 wakaba 1.2 if ($self->{minuses}->{$node_ns}->{$node_ln}) {
1199 wakaba 1.6 $not_allowed = 1;
1200 wakaba 1.2 } elsif ($node_ns eq $HTML_NS and
1201     {
1202     qw/h1 1 h2 1 h3 1 h4 1 h5 1 h6 1 header 1 footer 1/
1203     }->{$node_ln}) {
1204 wakaba 1.6 $not_allowed = 1;
1205 wakaba 1.2 } elsif ($HTMLSectioningElements->{$node_ns}->{$node_ln}) {
1206 wakaba 1.6 $not_allowed = 1;
1207 wakaba 1.2 }
1208     if ($content eq 'block') {
1209 wakaba 1.7 $not_allowed = 1
1210     unless $HTMLBlockLevelElements->{$node_ns}->{$node_ln};
1211 wakaba 1.2 } elsif ($content eq 'inline') {
1212 wakaba 1.7 $not_allowed = 1
1213     unless $HTMLStrictlyInlineLevelElements->{$node_ns}->{$node_ln} or
1214     $HTMLStructuredInlineLevelElements->{$node_ns}->{$node_ln};
1215 wakaba 1.2 } else {
1216 wakaba 1.7 my $is_block = $HTMLBlockLevelElements->{$node_ns}->{$node_ln};
1217     my $is_inline
1218     = $HTMLStrictlyInlineLevelElements->{$node_ns}->{$node_ln} ||
1219     $HTMLStructuredInlineLevelElements->{$node_ns}->{$node_ln};
1220 wakaba 1.2
1221 wakaba 1.6 push @block_not_inline, $node
1222     if $is_block and not $is_inline and not $not_allowed;
1223 wakaba 1.2 unless ($is_block) {
1224     $content = 'inline';
1225     for (@block_not_inline) {
1226     $self->{onerror}->(node => $_, type => 'element not allowed');
1227     }
1228 wakaba 1.6 $not_allowed = 1 unless $is_inline;
1229 wakaba 1.2 }
1230     }
1231 wakaba 1.6 $self->{onerror}->(node => $node, type => 'element not allowed')
1232     if $not_allowed;
1233 wakaba 1.2 my ($sib, $ch) = $self->_check_get_children ($node);
1234     unshift @nodes, @$sib;
1235 wakaba 1.4 push @$new_todos, @$ch;
1236 wakaba 1.2 } elsif ($nt == 3 or $nt == 4) {
1237     if ($node->data =~ /[^\x09-\x0D\x20]/) {
1238     if ($content eq 'block') {
1239     $self->{onerror}->(node => $node, type => 'character not allowed');
1240     } else {
1241     $content = 'inline';
1242     for (@block_not_inline) {
1243     $self->{onerror}->(node => $_, type => 'element not allowed');
1244     }
1245     }
1246     }
1247     } elsif ($nt == 5) {
1248     unshift @nodes, @{$node->child_nodes};
1249     }
1250     }
1251    
1252     my $end = $self->_add_minuses
1253     ({$HTML_NS => {qw/h1 1 h2 1 h3 1 h4 1 h5 1 h6 1/}},
1254     $HTMLSectioningElements);
1255 wakaba 1.4 push @$new_todos, $end;
1256 wakaba 1.2
1257 wakaba 1.4 if ($content eq 'inline') {
1258     for (@$new_todos) {
1259     $_->{inline} = 1;
1260     }
1261     }
1262    
1263     return ($new_todos);
1264 wakaba 1.2 },
1265     };
1266 wakaba 1.1
1267     $Element->{$HTML_NS}->{address} = {
1268 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1269 wakaba 1.1 checker => $HTMLInlineChecker,
1270     };
1271    
1272     $Element->{$HTML_NS}->{p} = {
1273 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1274 wakaba 1.1 checker => $HTMLSignificantInlineChecker,
1275     };
1276    
1277     $Element->{$HTML_NS}->{hr} = {
1278 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1279 wakaba 1.1 checker => $HTMLEmptyChecker,
1280     };
1281    
1282     $Element->{$HTML_NS}->{br} = {
1283 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1284 wakaba 1.1 checker => $HTMLEmptyChecker,
1285     };
1286    
1287     $Element->{$HTML_NS}->{dialog} = {
1288 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1289 wakaba 1.1 checker => sub {
1290 wakaba 1.4 my ($self, $todo) = @_;
1291     my $el = $todo->{node};
1292     my $new_todos = [];
1293 wakaba 1.1 my @nodes = (@{$el->child_nodes});
1294    
1295     my $phase = 'before dt';
1296     while (@nodes) {
1297     my $node = shift @nodes;
1298 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
1299    
1300 wakaba 1.1 my $nt = $node->node_type;
1301     if ($nt == 1) {
1302 wakaba 1.8 my $node_ns = $node->namespace_uri;
1303     $node_ns = '' unless defined $node_ns;
1304     my $node_ln = $node->manakai_local_name;
1305 wakaba 1.2 ## NOTE: |minuses| list is not checked since redundant
1306 wakaba 1.1 if ($phase eq 'before dt') {
1307 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'dt') {
1308 wakaba 1.1 $phase = 'before dd';
1309 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'dd') {
1310 wakaba 1.2 $self->{onerror}
1311 wakaba 1.3 ->(node => $node, type => 'ps element missing:dt');
1312 wakaba 1.1 $phase = 'before dt';
1313     } else {
1314 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
1315 wakaba 1.1 }
1316     } else { # before dd
1317 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'dd') {
1318 wakaba 1.1 $phase = 'before dt';
1319 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'dt') {
1320 wakaba 1.2 $self->{onerror}
1321 wakaba 1.3 ->(node => $node, type => 'ps element missing:dd');
1322 wakaba 1.1 $phase = 'before dd';
1323     } else {
1324 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
1325 wakaba 1.1 }
1326     }
1327 wakaba 1.2 my ($sib, $ch) = $self->_check_get_children ($node);
1328     unshift @nodes, @$sib;
1329 wakaba 1.4 push @$new_todos, @$ch;
1330 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
1331     if ($node->data =~ /[^\x09-\x0D\x20]/) {
1332 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
1333 wakaba 1.1 }
1334     } elsif ($nt == 5) {
1335     unshift @nodes, @{$node->child_nodes};
1336     }
1337     }
1338     if ($phase eq 'before dd') {
1339 wakaba 1.3 $self->{onerror}->(node => $el, type => 'ps element missing:dd');
1340 wakaba 1.1 }
1341 wakaba 1.4 return ($new_todos);
1342 wakaba 1.1 },
1343     };
1344    
1345     $Element->{$HTML_NS}->{pre} = {
1346 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1347 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1348     };
1349    
1350     $Element->{$HTML_NS}->{ol} = {
1351 wakaba 1.11 attrs_checker => $GetHTMLAttrsChecker->({
1352     start => $HTMLIntegerAttrChecker,
1353     }),
1354 wakaba 1.1 checker => sub {
1355 wakaba 1.4 my ($self, $todo) = @_;
1356     my $el = $todo->{node};
1357     my $new_todos = [];
1358 wakaba 1.1 my @nodes = (@{$el->child_nodes});
1359    
1360     while (@nodes) {
1361     my $node = shift @nodes;
1362 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
1363    
1364 wakaba 1.1 my $nt = $node->node_type;
1365     if ($nt == 1) {
1366 wakaba 1.8 my $node_ns = $node->namespace_uri;
1367     $node_ns = '' unless defined $node_ns;
1368     my $node_ln = $node->manakai_local_name;
1369 wakaba 1.2 ## NOTE: |minuses| list is not checked since redundant
1370 wakaba 1.8 unless ($node_ns eq $HTML_NS and $node_ln eq 'li') {
1371 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
1372 wakaba 1.1 }
1373 wakaba 1.2 my ($sib, $ch) = $self->_check_get_children ($node);
1374     unshift @nodes, @$sib;
1375 wakaba 1.4 push @$new_todos, @$ch;
1376 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
1377     if ($node->data =~ /[^\x09-\x0D\x20]/) {
1378 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
1379 wakaba 1.1 }
1380     } elsif ($nt == 5) {
1381     unshift @nodes, @{$node->child_nodes};
1382     }
1383     }
1384 wakaba 1.4
1385     if ($todo->{inline}) {
1386     for (@$new_todos) {
1387     $_->{inline} = 1;
1388     }
1389     }
1390     return ($new_todos);
1391 wakaba 1.1 },
1392     };
1393    
1394     $Element->{$HTML_NS}->{ul} = {
1395 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1396 wakaba 1.1 checker => $Element->{$HTML_NS}->{ol}->{checker},
1397     };
1398    
1399 wakaba 1.5
1400     $Element->{$HTML_NS}->{li} = {
1401 wakaba 1.11 attrs_checker => $GetHTMLAttrsChecker->({
1402     start => sub {
1403     my ($self, $attr) = @_;
1404     my $parent = $attr->owner_element->manakai_parent_element;
1405     if (defined $parent) {
1406     my $parent_ns = $parent->namespace_uri;
1407     $parent_ns = '' unless defined $parent_ns;
1408     my $parent_ln = $parent->manakai_local_name;
1409     unless ($parent_ns eq $HTML_NS and $parent_ln eq 'ol') {
1410     $self->{onerror}->(node => $attr, type => 'attribute not supported');
1411     }
1412     }
1413     $HTMLIntegerAttrChecker->($self, $attr);
1414     },
1415     }),
1416 wakaba 1.5 checker => sub {
1417     my ($self, $todo) = @_;
1418     if ($todo->{inline}) {
1419     return $HTMLInlineChecker->($self, $todo);
1420     } else {
1421     return $HTMLBlockOrInlineChecker->($self, $todo);
1422     }
1423     },
1424     };
1425 wakaba 1.1
1426     $Element->{$HTML_NS}->{dl} = {
1427 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1428 wakaba 1.1 checker => sub {
1429 wakaba 1.4 my ($self, $todo) = @_;
1430     my $el = $todo->{node};
1431     my $new_todos = [];
1432 wakaba 1.1 my @nodes = (@{$el->child_nodes});
1433    
1434     my $phase = 'before dt';
1435     while (@nodes) {
1436     my $node = shift @nodes;
1437 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
1438    
1439 wakaba 1.1 my $nt = $node->node_type;
1440     if ($nt == 1) {
1441 wakaba 1.8 my $node_ns = $node->namespace_uri;
1442     $node_ns = '' unless defined $node_ns;
1443     my $node_ln = $node->manakai_local_name;
1444 wakaba 1.2 ## NOTE: |minuses| list is not checked since redundant
1445 wakaba 1.1 if ($phase eq 'in dds') {
1446 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'dd') {
1447 wakaba 1.1 #$phase = 'in dds';
1448 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'dt') {
1449 wakaba 1.1 $phase = 'in dts';
1450     } else {
1451 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
1452 wakaba 1.1 }
1453     } elsif ($phase eq 'in dts') {
1454 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'dt') {
1455 wakaba 1.1 #$phase = 'in dts';
1456 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'dd') {
1457 wakaba 1.1 $phase = 'in dds';
1458     } else {
1459 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
1460 wakaba 1.1 }
1461     } else { # before dt
1462 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'dt') {
1463 wakaba 1.1 $phase = 'in dts';
1464 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'dd') {
1465 wakaba 1.2 $self->{onerror}
1466 wakaba 1.3 ->(node => $node, type => 'ps element missing:dt');
1467 wakaba 1.1 $phase = 'in dds';
1468     } else {
1469 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
1470 wakaba 1.1 }
1471     }
1472 wakaba 1.2 my ($sib, $ch) = $self->_check_get_children ($node);
1473     unshift @nodes, @$sib;
1474 wakaba 1.4 push @$new_todos, @$ch;
1475 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
1476     if ($node->data =~ /[^\x09-\x0D\x20]/) {
1477 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
1478 wakaba 1.1 }
1479     } elsif ($nt == 5) {
1480     unshift @nodes, @{$node->child_nodes};
1481     }
1482     }
1483     if ($phase eq 'in dts') {
1484 wakaba 1.3 $self->{onerror}->(node => $el, type => 'ps element missing:dd');
1485 wakaba 1.1 }
1486 wakaba 1.4
1487     if ($todo->{inline}) {
1488     for (@$new_todos) {
1489     $_->{inline} = 1;
1490     }
1491     }
1492     return ($new_todos);
1493 wakaba 1.1 },
1494     };
1495    
1496     $Element->{$HTML_NS}->{dt} = {
1497 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1498 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1499     };
1500    
1501 wakaba 1.4 $Element->{$HTML_NS}->{dd} = {
1502 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1503 wakaba 1.5 checker => $Element->{$HTML_NS}->{li}->{checker},
1504 wakaba 1.4 };
1505 wakaba 1.1
1506 wakaba 1.6 $Element->{$HTML_NS}->{a} = {
1507 wakaba 1.15 attrs_checker => do {
1508     my %attr;
1509     my $all_checker = $GetHTMLAttrsChecker->({
1510     href => sub { $HTMLURIAttrChecker->(@_); $attr{href} = $_[1] },
1511     ## TODO: target
1512     ping => sub { $HTMLSpaceURIsAttrChecker->(@_); $attr{ping} = $_[1] },
1513     rel => sub {
1514     $HTMLUnorderedSetOfSpaceSeparatedTokensAttrChecker->(@_); ## TODO: registered? check
1515     $attr{rel} = $_[1];
1516     },
1517     ## TODO: media
1518     ## TODO: hreflang
1519     type => sub { $HTMLIMTAttrChecker->(@_); $attr{type} = $_[1] },
1520     });
1521     sub {
1522     $all_checker->(@_);
1523     unless (defined $attr{href}) {
1524     for (qw/target ping rel media hreflang type/) {
1525     if (defined $attr{$_}) {
1526     $_[0]->{onerror}->(node => $attr{$_},
1527     type => 'attribute not allowed');
1528     }
1529     }
1530     }
1531     %attr = ();
1532     };
1533     },
1534 wakaba 1.6 checker => sub {
1535     my ($self, $todo) = @_;
1536    
1537     my $end = $self->_add_minuses ($HTMLInteractiveElements);
1538     my ($sib, $ch)
1539     = $HTMLSignificantInlineOrStrictlyInlineChecker->($self, $todo);
1540     push @$sib, $end;
1541     return ($sib, $ch);
1542     },
1543     };
1544 wakaba 1.1
1545 wakaba 1.4 $Element->{$HTML_NS}->{q} = {
1546 wakaba 1.11 attrs_checker => $GetHTMLAttrsChecker->({
1547     cite => $HTMLURIAttrChecker,
1548     }),
1549 wakaba 1.4 checker => $HTMLInlineOrStrictlyInlineChecker,
1550     };
1551 wakaba 1.1
1552     $Element->{$HTML_NS}->{cite} = {
1553 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1554 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1555     };
1556    
1557 wakaba 1.4 $Element->{$HTML_NS}->{em} = {
1558 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1559 wakaba 1.4 checker => $HTMLInlineOrStrictlyInlineChecker,
1560     };
1561    
1562     $Element->{$HTML_NS}->{strong} = {
1563 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1564 wakaba 1.4 checker => $HTMLInlineOrStrictlyInlineChecker,
1565     };
1566 wakaba 1.1
1567 wakaba 1.4 $Element->{$HTML_NS}->{small} = {
1568 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1569 wakaba 1.4 checker => $HTMLInlineOrStrictlyInlineChecker,
1570     };
1571    
1572     $Element->{$HTML_NS}->{m} = {
1573 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1574 wakaba 1.4 checker => $HTMLInlineOrStrictlyInlineChecker,
1575     };
1576    
1577 wakaba 1.11 $Element->{$HTML_NS}->{dfn} = { ## TODO: term duplication
1578 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1579 wakaba 1.4 checker => sub {
1580     my ($self, $todo) = @_;
1581    
1582     my $end = $self->_add_minuses ({$HTML_NS => {dfn => 1}});
1583     my ($sib, $ch) = $HTMLStrictlyInlineChecker->($self, $todo);
1584     push @$sib, $end;
1585     return ($sib, $ch);
1586     },
1587     };
1588 wakaba 1.1
1589     $Element->{$HTML_NS}->{abbr} = {
1590 wakaba 1.11 attrs_checker => $GetHTMLAttrsChecker->({
1591     ## NOTE: |title| has special semantics for |abbr|s, but is syntactically
1592     ## not different. The spec says that the |title| MAY be omitted
1593     ## if there is a |dfn| whose defining term is the abbreviation,
1594     ## but it does not prohibit |abbr| w/o |title| in other cases.
1595     }),
1596 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1597     };
1598    
1599 wakaba 1.11 $Element->{$HTML_NS}->{time} = { ## TODO: validate content
1600     attrs_checker => $GetHTMLAttrsChecker->({}), ## TODO: datetime
1601 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1602     };
1603    
1604 wakaba 1.11 $Element->{$HTML_NS}->{meter} = { ## TODO: "The recommended way of giving the value is to include it as contents of the element"
1605     attrs_checker => $GetHTMLAttrsChecker->({
1606     value => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
1607     min => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
1608     low => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
1609     high => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
1610     max => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
1611     optimum => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
1612     }),
1613 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1614     };
1615    
1616 wakaba 1.11 $Element->{$HTML_NS}->{progress} = { ## TODO: recommended to use content
1617     attrs_checker => $GetHTMLAttrsChecker->({
1618     value => $GetHTMLFloatingPointNumberAttrChecker->(sub { shift >= 0 }),
1619     max => $GetHTMLFloatingPointNumberAttrChecker->(sub { shift > 0 }),
1620     }),
1621 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1622     };
1623    
1624 wakaba 1.4 $Element->{$HTML_NS}->{code} = {
1625 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1626 wakaba 1.12 ## NOTE: Though |title| has special semantics,
1627     ## syntatically same as the |title| as global attribute.
1628 wakaba 1.4 checker => $HTMLInlineOrStrictlyInlineChecker,
1629     };
1630 wakaba 1.1
1631     $Element->{$HTML_NS}->{var} = {
1632 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1633 wakaba 1.12 ## NOTE: Though |title| has special semantics,
1634     ## syntatically same as the |title| as global attribute.
1635 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1636     };
1637    
1638 wakaba 1.4 $Element->{$HTML_NS}->{samp} = {
1639 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1640 wakaba 1.12 ## NOTE: Though |title| has special semantics,
1641     ## syntatically same as the |title| as global attribute.
1642 wakaba 1.4 checker => $HTMLInlineOrStrictlyInlineChecker,
1643     };
1644 wakaba 1.1
1645     $Element->{$HTML_NS}->{kbd} = {
1646 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1647 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1648     };
1649    
1650     $Element->{$HTML_NS}->{sub} = {
1651 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1652 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1653     };
1654    
1655     $Element->{$HTML_NS}->{sup} = {
1656 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1657 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1658     };
1659    
1660 wakaba 1.4 $Element->{$HTML_NS}->{span} = {
1661 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1662 wakaba 1.12 ## NOTE: Though |title| has special semantics,
1663     ## syntatically same as the |title| as global attribute.
1664 wakaba 1.4 checker => $HTMLInlineOrStrictlyInlineChecker,
1665     };
1666 wakaba 1.1
1667     $Element->{$HTML_NS}->{i} = {
1668 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1669 wakaba 1.12 ## NOTE: Though |title| has special semantics,
1670     ## syntatically same as the |title| as global attribute.
1671 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1672     };
1673    
1674     $Element->{$HTML_NS}->{b} = {
1675 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1676 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1677     };
1678    
1679     $Element->{$HTML_NS}->{bdo} = {
1680 wakaba 1.12 attrs_checker => sub {
1681     my ($self, $todo) = @_;
1682     $GetHTMLAttrsChecker->({})->($self, $todo);
1683     unless ($todo->{node}->has_attribute_ns (undef, 'dir')) {
1684     $self->{onerror}->(node => $todo->{node}, type => 'attribute missing:dir');
1685     }
1686     },
1687     ## ISSUE: The spec does not directly say that |dir| is a enumerated attr.
1688 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1689     };
1690    
1691     $Element->{$HTML_NS}->{ins} = {
1692 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
1693     cite => $HTMLURIAttrChecker,
1694     ## TODO: datetime
1695     }),
1696 wakaba 1.1 checker => $HTMLTransparentChecker,
1697     };
1698    
1699     $Element->{$HTML_NS}->{del} = {
1700 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
1701     cite => $HTMLURIAttrChecker,
1702     ## TODO: datetime
1703     }),
1704 wakaba 1.1 checker => sub {
1705 wakaba 1.4 my ($self, $todo) = @_;
1706 wakaba 1.1
1707 wakaba 1.4 my $parent = $todo->{node}->manakai_parent_element;
1708 wakaba 1.1 if (defined $parent) {
1709     my $nsuri = $parent->namespace_uri;
1710     $nsuri = '' unless defined $nsuri;
1711     my $ln = $parent->manakai_local_name;
1712     my $eldef = $Element->{$nsuri}->{$ln} ||
1713     $Element->{$nsuri}->{''} ||
1714     $ElementDefault;
1715 wakaba 1.4 return $eldef->{checker}->($self, $todo);
1716 wakaba 1.1 } else {
1717 wakaba 1.4 return $HTMLBlockOrInlineChecker->($self, $todo);
1718 wakaba 1.1 }
1719     },
1720     };
1721    
1722     ## TODO: figure
1723    
1724     $Element->{$HTML_NS}->{img} = {
1725 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({}), ## TODO
1726 wakaba 1.1 checker => $HTMLEmptyChecker,
1727     };
1728    
1729     $Element->{$HTML_NS}->{iframe} = {
1730 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
1731     src => $HTMLURIAttrChecker,
1732     }),
1733 wakaba 1.1 checker => $HTMLTextChecker,
1734     };
1735    
1736     $Element->{$HTML_NS}->{embed} = {
1737 wakaba 1.16 attrs_checker => sub {
1738     my ($self, $todo) = @_;
1739     my $has_src;
1740     for my $attr (@{$todo->{node}->attributes}) {
1741     my $attr_ns = $attr->namespace_uri;
1742     $attr_ns = '' unless defined $attr_ns;
1743     my $attr_ln = $attr->manakai_local_name;
1744     my $checker;
1745     if ($attr_ns eq '') {
1746     if ($attr_ln eq 'src') {
1747     $checker = $HTMLURIAttrChecker;
1748     $has_src = 1;
1749     } elsif ($attr_ln eq 'type') {
1750     $checker = $HTMLIMTAttrChecker;
1751     } else {
1752     ## TODO: height
1753     ## TODO: width
1754     $checker = $HTMLAttrChecker->{$attr_ln}
1755     || sub { }; ## NOTE: Any local attribute is ok.
1756     }
1757     }
1758     $checker ||= $AttrChecker->{$attr_ns}->{$attr_ln}
1759     || $AttrChecker->{$attr_ns}->{''};
1760     if ($checker) {
1761     $checker->($self, $attr);
1762     } else {
1763     $self->{onerror}->(node => $attr, type => 'attribute not supported');
1764     ## ISSUE: No comformance createria for global attributes in the spec
1765     }
1766     }
1767    
1768     unless ($has_src) {
1769     $self->{onerror}->(node => $todo->{node},
1770     type => 'attribute missing:src');
1771     }
1772     },
1773 wakaba 1.1 checker => $HTMLEmptyChecker,
1774     };
1775    
1776 wakaba 1.15 $Element->{$HTML_NS}->{object} = {
1777     attrs_checker => $GetHTMLAttrsChecker->({
1778     data => $HTMLURIAttrChecker,
1779     type => $HTMLIMTAttrChecker, ## TODO: one of |data| and |type| is required
1780     ## TODO: usemap
1781     ## TODO: width
1782     ## TODO: height
1783     }),
1784     checker => $ElementDefault->{checker}, ## TODO
1785     };
1786    
1787 wakaba 1.1 $Element->{$HTML_NS}->{param} = {
1788 wakaba 1.12 attrs_checker => sub {
1789     my ($self, $todo) = @_;
1790     $GetHTMLAttrsChecker->({
1791     name => sub { },
1792     value => sub { },
1793     })->($self, $todo);
1794     unless ($todo->{node}->has_attribute_ns (undef, 'name')) {
1795     $self->{onerror}->(node => $todo->{node},
1796     type => 'attribute missing:name');
1797     }
1798     unless ($todo->{node}->has_attribute_ns (undef, 'value')) {
1799     $self->{onerror}->(node => $todo->{node},
1800     type => 'attribute missing:value');
1801     }
1802     },
1803 wakaba 1.1 checker => $HTMLEmptyChecker,
1804     };
1805    
1806 wakaba 1.2 $Element->{$HTML_NS}->{video} = {
1807 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
1808     src => $HTMLURIAttrChecker,
1809     ## TODO: start, loopstart, loopend, end
1810     ## ISSUE: they MUST be "value time offset"s. Value?
1811     ## ISSUE: loopcount has no conformance creteria
1812     autoplay => $GetHTMLBooleanAttrChecker->('autoplay'),
1813     controls => $GetHTMLBooleanAttrChecker->('controls'),
1814     }),
1815 wakaba 1.2 checker => sub {
1816 wakaba 1.4 my ($self, $todo) = @_;
1817 wakaba 1.2
1818 wakaba 1.4 if ($todo->{node}->has_attribute_ns (undef, 'src')) {
1819     return $HTMLBlockOrInlineChecker->($self, $todo);
1820 wakaba 1.2 } else {
1821     return $GetHTMLZeroOrMoreThenBlockOrInlineChecker->($HTML_NS, 'source')
1822 wakaba 1.4 ->($self, $todo);
1823 wakaba 1.2 }
1824     },
1825     };
1826    
1827     $Element->{$HTML_NS}->{audio} = {
1828 wakaba 1.12 attrs_checker => $Element->{$HTML_NS}->{video}->{attrs_checker},
1829     checker => $Element->{$HTML_NS}->{video}->{checker},
1830 wakaba 1.2 };
1831 wakaba 1.1
1832     $Element->{$HTML_NS}->{source} = {
1833 wakaba 1.15 attrs_checker => $GetHTMLAttrsChecker->({
1834     src => $HTMLURIAttrChecker, # TODO: REQUIRED
1835     type => $HTMLIMTAttrChecker,
1836     ## TODO: media
1837     }),
1838 wakaba 1.1 checker => $HTMLEmptyChecker,
1839     };
1840    
1841     $Element->{$HTML_NS}->{canvas} = {
1842 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
1843     height => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }),
1844     width => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }),
1845     }),
1846 wakaba 1.1 checker => $HTMLInlineChecker,
1847     };
1848    
1849     $Element->{$HTML_NS}->{map} = {
1850 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1851 wakaba 1.1 checker => $HTMLBlockChecker,
1852     };
1853    
1854     $Element->{$HTML_NS}->{area} = {
1855 wakaba 1.15 attrs_checker => sub {
1856     my ($self, $todo) = @_;
1857     my %attr;
1858     my $coords;
1859     for my $attr (@{$todo->{node}->attributes}) {
1860     my $attr_ns = $attr->namespace_uri;
1861     $attr_ns = '' unless defined $attr_ns;
1862     my $attr_ln = $attr->manakai_local_name;
1863     my $checker;
1864     if ($attr_ns eq '') {
1865     $checker = {
1866     alt => sub { },
1867     ## NOTE: |alt| value has no conformance creteria.
1868     shape => $GetHTMLEnumeratedAttrChecker->({
1869     circ => -1, circle => 1,
1870     default => 1,
1871     poly => 1, polygon => -1,
1872     rect => 1, rectangle => -1,
1873     }),
1874     coords => sub {
1875     my ($self, $attr) = @_;
1876     my $value = $attr->value;
1877     if ($value =~ /\A-?[0-9]+(?>,-?[0-9]+)*\z/) {
1878     $coords = [split /,/, $value];
1879     } else {
1880     $self->{onerror}->(node => $attr,
1881     type => 'syntax error');
1882     }
1883     },
1884     ## TODO: coords
1885     target => sub { $self->{onerror}->(node => $attr, type => 'attribute not supported') }, ## TODO
1886     href => $HTMLURIAttrChecker,
1887     ping => $HTMLSpaceURIsAttrChecker,
1888     rel => $HTMLUnorderedSetOfSpaceSeparatedTokensAttrChecker, ## TODO: registered? check
1889     media => sub { $self->{onerror}->(node => $attr, type => 'attribute not supported') }, ## TODO
1890     hreflang => sub { $self->{onerror}->(node => $attr, type => 'attribute not supported') }, ## TODO
1891     type => $HTMLIMTAttrChecker,
1892     }->{$attr_ln};
1893     if ($checker) {
1894     $attr{$attr_ln} = $attr;
1895     } else {
1896     $checker = $HTMLAttrChecker->{$attr_ln};
1897     }
1898     }
1899     $checker ||= $AttrChecker->{$attr_ns}->{$attr_ln}
1900     || $AttrChecker->{$attr_ns}->{''};
1901     if ($checker) {
1902     $checker->($self, $attr) if ref $checker;
1903     } else {
1904     $self->{onerror}->(node => $attr, type => 'attribute not supported');
1905     ## ISSUE: No comformance createria for unknown attributes in the spec
1906     }
1907     }
1908    
1909     if (defined $attr{href}) {
1910     unless (defined $attr{alt}) {
1911     $self->{onerror}->(node => $todo->{node},
1912     type => 'attribute missing:alt');
1913     }
1914     } else {
1915     for (qw/target ping rel media hreflang type alt/) {
1916     if (defined $attr{$_}) {
1917     $self->{onerror}->(node => $attr{$_},
1918     type => 'attribute not allowed');
1919     }
1920     }
1921     }
1922    
1923     my $shape = 'rectangle';
1924     if (defined $attr{shape}) {
1925     $shape = {
1926     circ => 'circle', circle => 'circle',
1927     default => 'default',
1928     poly => 'polygon', polygon => 'polygon',
1929     rect => 'rectangle', rectangle => 'rectangle',
1930     }->{lc $attr{shape}->value} || 'rectangle';
1931     ## TODO: ASCII lowercase?
1932     }
1933    
1934     if ($shape eq 'circle') {
1935     if (defined $attr{coords}) {
1936     if (defined $coords) {
1937     if (@$coords == 3) {
1938     if ($coords->[2] < 0) {
1939     $self->{onerror}->(node => $attr{coords},
1940     type => 'out of range:2');
1941     }
1942     } else {
1943     $self->{onerror}->(node => $attr{coords},
1944     type => 'list item number:3:'.@$coords);
1945     }
1946     } else {
1947     ## NOTE: A syntax error has been reported.
1948     }
1949     } else {
1950     $self->{onerror}->(node => $todo->{node},
1951     type => 'attribute missing:coords');
1952     }
1953     } elsif ($shape eq 'default') {
1954     if (defined $attr{coords}) {
1955     $self->{onerror}->(node => $attr{coords},
1956     type => 'attribute not allowed');
1957     }
1958     } elsif ($shape eq 'polygon') {
1959     if (defined $attr{coords}) {
1960     if (defined $coords) {
1961     if (@$coords >= 6) {
1962     unless (@$coords % 2 == 0) {
1963     $self->{onerror}->(node => $attr{coords},
1964     type => 'list item number:even:'.@$coords);
1965     }
1966     } else {
1967     $self->{onerror}->(node => $attr{coords},
1968     type => 'list item number:>=6:'.@$coords);
1969     }
1970     } else {
1971     ## NOTE: A syntax error has been reported.
1972     }
1973     } else {
1974     $self->{onerror}->(node => $todo->{node},
1975     type => 'attribute missing:coords');
1976     }
1977     } elsif ($shape eq 'rectangle') {
1978     if (defined $attr{coords}) {
1979     if (defined $coords) {
1980     if (@$coords == 4) {
1981     unless ($coords->[0] < $coords->[2]) {
1982     $self->{onerror}->(node => $attr{coords},
1983     type => 'out of range:0');
1984     }
1985     unless ($coords->[1] < $coords->[3]) {
1986     $self->{onerror}->(node => $attr{coords},
1987     type => 'out of range:1');
1988     }
1989     } else {
1990     $self->{onerror}->(node => $attr{coords},
1991     type => 'list item number:4:'.@$coords);
1992     }
1993     } else {
1994     ## NOTE: A syntax error has been reported.
1995     }
1996     } else {
1997     $self->{onerror}->(node => $todo->{node},
1998     type => 'attribute missing:coords');
1999     }
2000     }
2001     },
2002 wakaba 1.1 checker => $HTMLEmptyChecker,
2003     };
2004     ## TODO: only in map
2005    
2006     $Element->{$HTML_NS}->{table} = {
2007 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2008 wakaba 1.1 checker => sub {
2009 wakaba 1.4 my ($self, $todo) = @_;
2010     my $el = $todo->{node};
2011     my $new_todos = [];
2012 wakaba 1.1 my @nodes = (@{$el->child_nodes});
2013    
2014     my $phase = 'before caption';
2015     my $has_tfoot;
2016     while (@nodes) {
2017     my $node = shift @nodes;
2018 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
2019    
2020 wakaba 1.1 my $nt = $node->node_type;
2021     if ($nt == 1) {
2022 wakaba 1.8 my $node_ns = $node->namespace_uri;
2023     $node_ns = '' unless defined $node_ns;
2024     my $node_ln = $node->manakai_local_name;
2025 wakaba 1.2 ## NOTE: |minuses| list is not checked since redundant
2026 wakaba 1.1 if ($phase eq 'in tbodys') {
2027 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'tbody') {
2028 wakaba 1.1 #$phase = 'in tbodys';
2029     } elsif (not $has_tfoot and
2030 wakaba 1.8 $node_ns eq $HTML_NS and $node_ln eq 'tfoot') {
2031 wakaba 1.1 $phase = 'after tfoot';
2032     $has_tfoot = 1;
2033     } else {
2034 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2035 wakaba 1.1 }
2036     } elsif ($phase eq 'in trs') {
2037 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'tr') {
2038 wakaba 1.1 #$phase = 'in trs';
2039     } elsif (not $has_tfoot and
2040 wakaba 1.8 $node_ns eq $HTML_NS and $node_ln eq 'tfoot') {
2041 wakaba 1.1 $phase = 'after tfoot';
2042     $has_tfoot = 1;
2043     } else {
2044 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2045 wakaba 1.1 }
2046     } elsif ($phase eq 'after thead') {
2047 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'tbody') {
2048 wakaba 1.1 $phase = 'in tbodys';
2049 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'tr') {
2050 wakaba 1.1 $phase = 'in trs';
2051 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'tfoot') {
2052 wakaba 1.1 $phase = 'in tbodys';
2053     $has_tfoot = 1;
2054     } else {
2055 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2056 wakaba 1.1 }
2057     } elsif ($phase eq 'in colgroup') {
2058 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'colgroup') {
2059 wakaba 1.1 $phase = 'in colgroup';
2060 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'thead') {
2061 wakaba 1.1 $phase = 'after thead';
2062 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'tbody') {
2063 wakaba 1.1 $phase = 'in tbodys';
2064 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'tr') {
2065 wakaba 1.1 $phase = 'in trs';
2066 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'tfoot') {
2067 wakaba 1.1 $phase = 'in tbodys';
2068     $has_tfoot = 1;
2069     } else {
2070 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2071 wakaba 1.1 }
2072     } elsif ($phase eq 'before caption') {
2073 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'caption') {
2074 wakaba 1.1 $phase = 'in colgroup';
2075 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'colgroup') {
2076 wakaba 1.1 $phase = 'in colgroup';
2077 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'thead') {
2078 wakaba 1.1 $phase = 'after thead';
2079 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'tbody') {
2080 wakaba 1.1 $phase = 'in tbodys';
2081 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'tr') {
2082 wakaba 1.1 $phase = 'in trs';
2083 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'tfoot') {
2084 wakaba 1.1 $phase = 'in tbodys';
2085     $has_tfoot = 1;
2086     } else {
2087 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2088 wakaba 1.1 }
2089     } else { # after tfoot
2090 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2091 wakaba 1.1 }
2092 wakaba 1.2 my ($sib, $ch) = $self->_check_get_children ($node);
2093     unshift @nodes, @$sib;
2094 wakaba 1.4 push @$new_todos, @$ch;
2095 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
2096     if ($node->data =~ /[^\x09-\x0D\x20]/) {
2097 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
2098 wakaba 1.1 }
2099     } elsif ($nt == 5) {
2100     unshift @nodes, @{$node->child_nodes};
2101     }
2102     }
2103 wakaba 1.4 return ($new_todos);
2104 wakaba 1.1 },
2105     };
2106    
2107     $Element->{$HTML_NS}->{caption} = {
2108 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2109 wakaba 1.1 checker => $HTMLSignificantStrictlyInlineChecker,
2110     };
2111    
2112     $Element->{$HTML_NS}->{colgroup} = {
2113 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({}), ## TODO
2114 wakaba 1.1 checker => sub {
2115 wakaba 1.4 my ($self, $todo) = @_;
2116     my $el = $todo->{node};
2117     my $new_todos = [];
2118 wakaba 1.1 my @nodes = (@{$el->child_nodes});
2119    
2120     while (@nodes) {
2121     my $node = shift @nodes;
2122 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
2123    
2124 wakaba 1.1 my $nt = $node->node_type;
2125     if ($nt == 1) {
2126 wakaba 1.8 my $node_ns = $node->namespace_uri;
2127     $node_ns = '' unless defined $node_ns;
2128     my $node_ln = $node->manakai_local_name;
2129 wakaba 1.2 ## NOTE: |minuses| list is not checked since redundant
2130 wakaba 1.8 unless ($node_ns eq $HTML_NS and $node_ln eq 'col') {
2131 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2132 wakaba 1.1 }
2133 wakaba 1.2 my ($sib, $ch) = $self->_check_get_children ($node);
2134     unshift @nodes, @$sib;
2135 wakaba 1.4 push @$new_todos, @$ch;
2136 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
2137     if ($node->data =~ /[^\x09-\x0D\x20]/) {
2138 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
2139 wakaba 1.1 }
2140     } elsif ($nt == 5) {
2141     unshift @nodes, @{$node->child_nodes};
2142     }
2143     }
2144 wakaba 1.4 return ($new_todos);
2145 wakaba 1.1 },
2146     };
2147    
2148     $Element->{$HTML_NS}->{col} = {
2149 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2150     span => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
2151     }),
2152 wakaba 1.1 checker => $HTMLEmptyChecker,
2153     };
2154    
2155     $Element->{$HTML_NS}->{tbody} = {
2156 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2157 wakaba 1.1 checker => sub {
2158 wakaba 1.4 my ($self, $todo) = @_;
2159     my $el = $todo->{node};
2160     my $new_todos = [];
2161 wakaba 1.1 my @nodes = (@{$el->child_nodes});
2162    
2163     my $has_tr;
2164     while (@nodes) {
2165     my $node = shift @nodes;
2166 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
2167    
2168 wakaba 1.1 my $nt = $node->node_type;
2169     if ($nt == 1) {
2170 wakaba 1.8 my $node_ns = $node->namespace_uri;
2171     $node_ns = '' unless defined $node_ns;
2172     my $node_ln = $node->manakai_local_name;
2173 wakaba 1.2 ## NOTE: |minuses| list is not checked since redundant
2174 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'tr') {
2175 wakaba 1.1 $has_tr = 1;
2176     } else {
2177 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2178 wakaba 1.1 }
2179 wakaba 1.2 my ($sib, $ch) = $self->_check_get_children ($node);
2180     unshift @nodes, @$sib;
2181 wakaba 1.4 push @$new_todos, @$ch;
2182 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
2183     if ($node->data =~ /[^\x09-\x0D\x20]/) {
2184 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
2185 wakaba 1.1 }
2186     } elsif ($nt == 5) {
2187     unshift @nodes, @{$node->child_nodes};
2188     }
2189     }
2190     unless ($has_tr) {
2191 wakaba 1.3 $self->{onerror}->(node => $el, type => 'child element missing:tr');
2192 wakaba 1.1 }
2193 wakaba 1.4 return ($new_todos);
2194 wakaba 1.1 },
2195     };
2196    
2197     $Element->{$HTML_NS}->{thead} = {
2198 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2199 wakaba 1.1 checker => $Element->{$HTML_NS}->{tbody},
2200     };
2201    
2202     $Element->{$HTML_NS}->{tfoot} = {
2203 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2204 wakaba 1.1 checker => $Element->{$HTML_NS}->{tbody},
2205     };
2206    
2207     $Element->{$HTML_NS}->{tr} = {
2208 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2209 wakaba 1.1 checker => sub {
2210 wakaba 1.4 my ($self, $todo) = @_;
2211     my $el = $todo->{node};
2212     my $new_todos = [];
2213 wakaba 1.1 my @nodes = (@{$el->child_nodes});
2214    
2215     my $has_td;
2216     while (@nodes) {
2217     my $node = shift @nodes;
2218 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
2219    
2220 wakaba 1.1 my $nt = $node->node_type;
2221     if ($nt == 1) {
2222 wakaba 1.8 my $node_ns = $node->namespace_uri;
2223     $node_ns = '' unless defined $node_ns;
2224     my $node_ln = $node->manakai_local_name;
2225 wakaba 1.2 ## NOTE: |minuses| list is not checked since redundant
2226 wakaba 1.8 if ($node_ns eq $HTML_NS and ($node_ln eq 'td' or $node_ln eq 'th')) {
2227 wakaba 1.1 $has_td = 1;
2228     } else {
2229 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2230 wakaba 1.1 }
2231 wakaba 1.2 my ($sib, $ch) = $self->_check_get_children ($node);
2232     unshift @nodes, @$sib;
2233 wakaba 1.4 push @$new_todos, @$ch;
2234 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
2235     if ($node->data =~ /[^\x09-\x0D\x20]/) {
2236 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
2237 wakaba 1.1 }
2238     } elsif ($nt == 5) {
2239     unshift @nodes, @{$node->child_nodes};
2240     }
2241     }
2242     unless ($has_td) {
2243 wakaba 1.3 $self->{onerror}->(node => $el, type => 'child element missing:td|th');
2244 wakaba 1.1 }
2245 wakaba 1.4 return ($new_todos);
2246 wakaba 1.1 },
2247     };
2248    
2249     $Element->{$HTML_NS}->{td} = {
2250 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2251     colspan => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
2252     rowspan => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
2253     }),
2254 wakaba 1.1 checker => $HTMLBlockOrInlineChecker,
2255     };
2256    
2257     $Element->{$HTML_NS}->{th} = {
2258 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2259     colspan => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
2260     rowspan => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
2261     scope => $GetHTMLEnumeratedAttrChecker
2262     ->({row => 1, col => 1, rowgroup => 1, colgroup => 1}),
2263     }),
2264 wakaba 1.1 checker => $HTMLBlockOrInlineChecker,
2265     };
2266    
2267 wakaba 1.12 ## TODO: table model error checking
2268    
2269 wakaba 1.1 ## TODO: forms
2270    
2271 wakaba 1.2 $Element->{$HTML_NS}->{script} = {
2272 wakaba 1.15 attrs_checker => $GetHTMLAttrsChecker->({
2273     src => $HTMLURIAttrChecker,
2274     defer => $GetHTMLBooleanAttrChecker->('defer'), ## TODO: if src ## ISSUE: no MUST NOT
2275     async => $GetHTMLBooleanAttrChecker->('async'), ## TODO: if src ## ISSUE: no MUST NOT
2276     type => $HTMLIMTAttrChecker,
2277     }),
2278 wakaba 1.2 checker => sub {
2279 wakaba 1.4 my ($self, $todo) = @_;
2280 wakaba 1.2
2281 wakaba 1.4 if ($todo->{node}->has_attribute_ns (undef, 'src')) {
2282     return $HTMLEmptyChecker->($self, $todo);
2283 wakaba 1.2 } else {
2284     ## NOTE: No content model conformance in HTML5 spec.
2285 wakaba 1.4 return $AnyChecker->($self, $todo);
2286 wakaba 1.2 }
2287     },
2288     };
2289    
2290     ## NOTE: When script is disabled.
2291     $Element->{$HTML_NS}->{noscript} = {
2292 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2293 wakaba 1.2 checker => sub {
2294 wakaba 1.4 my ($self, $todo) = @_;
2295 wakaba 1.1
2296 wakaba 1.2 my $end = $self->_add_minuses ({$HTML_NS => {noscript => 1}});
2297 wakaba 1.4 my ($sib, $ch) = $HTMLBlockOrInlineChecker->($self, $todo);
2298 wakaba 1.2 push @$sib, $end;
2299     return ($sib, $ch);
2300     },
2301     };
2302 wakaba 1.1
2303     $Element->{$HTML_NS}->{'event-source'} = {
2304 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2305     src => $HTMLURIAttrChecker,
2306     }),
2307 wakaba 1.1 checker => $HTMLEmptyChecker,
2308     };
2309    
2310     $Element->{$HTML_NS}->{details} = {
2311 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2312     open => $GetHTMLBooleanAttrChecker->('open'),
2313     }),
2314 wakaba 1.6 checker => sub {
2315     my ($self, $todo) = @_;
2316    
2317     my $end = $self->_add_minuses ({$HTML_NS => {a => 1, datagrid => 1}});
2318     my ($sib, $ch)
2319     = $GetHTMLZeroOrMoreThenBlockOrInlineChecker->($HTML_NS, 'legend')
2320     ->($self, $todo);
2321     push @$sib, $end;
2322     return ($sib, $ch);
2323     },
2324 wakaba 1.1 };
2325    
2326     $Element->{$HTML_NS}->{datagrid} = {
2327 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2328     disabled => $GetHTMLBooleanAttrChecker->('disabled'),
2329     multiple => $GetHTMLBooleanAttrChecker->('multiple'),
2330     }),
2331 wakaba 1.6 checker => sub {
2332     my ($self, $todo) = @_;
2333    
2334     my $end = $self->_add_minuses ({$HTML_NS => {a => 1, datagrid => 1}});
2335     my ($sib, $ch) = $HTMLBlockChecker->($self, $todo);
2336     push @$sib, $end;
2337     return ($sib, $ch);
2338     },
2339 wakaba 1.1 };
2340    
2341     $Element->{$HTML_NS}->{command} = {
2342 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({}), ## TODO
2343 wakaba 1.1 checker => $HTMLEmptyChecker,
2344     };
2345    
2346     $Element->{$HTML_NS}->{menu} = {
2347 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({}), ## TODO
2348 wakaba 1.1 checker => sub {
2349 wakaba 1.4 my ($self, $todo) = @_;
2350     my $el = $todo->{node};
2351     my $new_todos = [];
2352 wakaba 1.1 my @nodes = (@{$el->child_nodes});
2353    
2354     my $content = 'li or inline';
2355     while (@nodes) {
2356     my $node = shift @nodes;
2357 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
2358    
2359 wakaba 1.1 my $nt = $node->node_type;
2360     if ($nt == 1) {
2361 wakaba 1.2 my $node_ns = $node->namespace_uri;
2362     $node_ns = '' unless defined $node_ns;
2363     my $node_ln = $node->manakai_local_name;
2364 wakaba 1.6 my $not_allowed = $self->{minuses}->{$node_ns}->{$node_ln};
2365 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'li') {
2366 wakaba 1.1 if ($content eq 'inline') {
2367 wakaba 1.6 $not_allowed = 1;
2368 wakaba 1.1 } elsif ($content eq 'li or inline') {
2369     $content = 'li';
2370     }
2371     } else {
2372 wakaba 1.7 if ($HTMLStrictlyInlineLevelElements->{$node_ns}->{$node_ln} or
2373     $HTMLStructuredInlineLevelElements->{$node_ns}->{$node_ln}) {
2374     $content = 'inline';
2375     } else {
2376 wakaba 1.6 $not_allowed = 1;
2377 wakaba 1.7 }
2378 wakaba 1.1 }
2379 wakaba 1.6 $self->{onerror}->(node => $node, type => 'element not allowed')
2380     if $not_allowed;
2381 wakaba 1.2 my ($sib, $ch) = $self->_check_get_children ($node);
2382     unshift @nodes, @$sib;
2383 wakaba 1.4 push @$new_todos, @$ch;
2384 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
2385     if ($node->data =~ /[^\x09-\x0D\x20]/) {
2386     if ($content eq 'li') {
2387 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
2388 wakaba 1.1 } elsif ($content eq 'li or inline') {
2389     $content = 'inline';
2390     }
2391     }
2392     } elsif ($nt == 5) {
2393     unshift @nodes, @{$node->child_nodes};
2394     }
2395     }
2396 wakaba 1.4
2397     for (@$new_todos) {
2398     $_->{inline} = 1;
2399     }
2400     return ($new_todos);
2401 wakaba 1.1 },
2402     };
2403    
2404 wakaba 1.6 $Element->{$HTML_NS}->{legend} = {
2405 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2406 wakaba 1.6 checker => sub {
2407     my ($self, $todo) = @_;
2408    
2409     my $parent = $todo->{node}->manakai_parent_element;
2410     if (defined $parent) {
2411     my $nsuri = $parent->namespace_uri;
2412     $nsuri = '' unless defined $nsuri;
2413     my $ln = $parent->manakai_local_name;
2414     if ($nsuri eq $HTML_NS and $ln eq 'figure') {
2415     return $HTMLInlineChecker->($self, $todo);
2416     } else {
2417     return $HTMLSignificantStrictlyInlineChecker->($self, $todo);
2418     }
2419     } else {
2420     return $HTMLInlineChecker->($self, $todo);
2421     }
2422    
2423     ## ISSUE: Content model is defined only for fieldset/legend,
2424     ## details/legend, and figure/legend.
2425     },
2426     };
2427 wakaba 1.1
2428     $Element->{$HTML_NS}->{div} = {
2429 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2430 wakaba 1.2 checker => $GetHTMLZeroOrMoreThenBlockOrInlineChecker->($HTML_NS, 'style'),
2431 wakaba 1.1 };
2432    
2433     $Element->{$HTML_NS}->{font} = {
2434 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({}), ## TODO
2435 wakaba 1.1 checker => $HTMLTransparentChecker,
2436     };
2437    
2438 wakaba 1.2 sub new ($) {
2439     return bless {}, shift;
2440     } # new
2441    
2442 wakaba 1.1 sub check_element ($$$) {
2443     my ($self, $el, $onerror) = @_;
2444    
2445 wakaba 1.2 $self->{minuses} = {};
2446     $self->{onerror} = $onerror;
2447 wakaba 1.10 $self->{id} = {};
2448 wakaba 1.2
2449 wakaba 1.4 my @todo = ({type => 'element', node => $el});
2450     while (@todo) {
2451     my $todo = shift @todo;
2452     if ($todo->{type} eq 'element') {
2453 wakaba 1.13 my $prefix = $todo->{node}->prefix;
2454     if (defined $prefix and $prefix eq 'xmlns') {
2455     $self->{onerror}
2456     ->(node => $todo->{node},
2457     type => 'NC:Reserved Prefixes and Namespace Names:<xmlns:>');
2458     }
2459 wakaba 1.4 my $nsuri = $todo->{node}->namespace_uri;
2460     $nsuri = '' unless defined $nsuri;
2461     my $ln = $todo->{node}->manakai_local_name;
2462     my $eldef = $Element->{$nsuri}->{$ln} ||
2463     $Element->{$nsuri}->{''} ||
2464     $ElementDefault;
2465 wakaba 1.9 $eldef->{attrs_checker}->($self, $todo);
2466 wakaba 1.4 my ($new_todos) = $eldef->{checker}->($self, $todo);
2467 wakaba 1.14 unshift @todo, @$new_todos;
2468 wakaba 1.9 } elsif ($todo->{type} eq 'element-attributes') {
2469 wakaba 1.13 my $prefix = $todo->{node}->prefix;
2470     if (defined $prefix and $prefix eq 'xmlns') {
2471     $self->{onerror}
2472     ->(node => $todo->{node},
2473     type => 'NC:Reserved Prefixes and Namespace Names:<xmlns:>');
2474     }
2475 wakaba 1.9 my $nsuri = $todo->{node}->namespace_uri;
2476     $nsuri = '' unless defined $nsuri;
2477     my $ln = $todo->{node}->manakai_local_name;
2478     my $eldef = $Element->{$nsuri}->{$ln} ||
2479     $Element->{$nsuri}->{''} ||
2480     $ElementDefault;
2481     $eldef->{attrs_checker}->($self, $todo);
2482 wakaba 1.4 } elsif ($todo->{type} eq 'plus') {
2483     $self->_remove_minuses ($todo);
2484     }
2485 wakaba 1.1 }
2486     } # check_element
2487    
2488 wakaba 1.2 sub _add_minuses ($@) {
2489     my $self = shift;
2490     my $r = {};
2491     for my $list (@_) {
2492     for my $ns (keys %$list) {
2493     for my $ln (keys %{$list->{$ns}}) {
2494     unless ($self->{minuses}->{$ns}->{$ln}) {
2495     $self->{minuses}->{$ns}->{$ln} = 1;
2496     $r->{$ns}->{$ln} = 1;
2497     }
2498     }
2499     }
2500     }
2501 wakaba 1.4 return {type => 'plus', list => $r};
2502 wakaba 1.2 } # _add_minuses
2503    
2504     sub _remove_minuses ($$) {
2505 wakaba 1.4 my ($self, $todo) = @_;
2506     for my $ns (keys %{$todo->{list}}) {
2507     for my $ln (keys %{$todo->{list}->{$ns}}) {
2508     delete $self->{minuses}->{$ns}->{$ln} if $todo->{list}->{$ns}->{$ln};
2509 wakaba 1.2 }
2510     }
2511     1;
2512     } # _remove_minuses
2513    
2514     sub _check_get_children ($$) {
2515     my ($self, $node) = @_;
2516 wakaba 1.4 my $new_todos = [];
2517 wakaba 1.2 my $sib = [];
2518     TP: {
2519     my $node_ns = $node->namespace_uri;
2520     $node_ns = '' unless defined $node_ns;
2521     my $node_ln = $node->manakai_local_name;
2522     if ($node_ns eq $HTML_NS) {
2523     if ($node_ln eq 'noscript') {
2524     my $end = $self->_add_minuses ({$HTML_NS, {noscript => 1}});
2525     push @$sib, $end;
2526     }
2527     }
2528 wakaba 1.7 if ($HTMLTransparentElements->{$node_ns}->{$node_ln}) {
2529     unshift @$sib, @{$node->child_nodes};
2530 wakaba 1.9 push @$new_todos, {type => 'element-attributes', node => $node};
2531 wakaba 1.7 last TP;
2532 wakaba 1.2 }
2533 wakaba 1.8 if ($node_ns eq $HTML_NS and ($node_ln eq 'video' or $node_ln eq 'audio')) {
2534 wakaba 1.2 if ($node->has_attribute_ns (undef, 'src')) {
2535     unshift @$sib, @{$node->child_nodes};
2536 wakaba 1.9 push @$new_todos, {type => 'element-attributes', node => $node};
2537 wakaba 1.2 last TP;
2538     } else {
2539     my @cn = @{$node->child_nodes};
2540     CN: while (@cn) {
2541     my $cn = shift @cn;
2542     my $cnt = $cn->node_type;
2543     if ($cnt == 1) {
2544 wakaba 1.8 my $cn_nsuri = $cn->namespace_uri;
2545     $cn_nsuri = '' unless defined $cn_nsuri;
2546     if ($cn_nsuri eq $HTML_NS and $cn->manakai_local_name eq 'source') {
2547 wakaba 1.2 #
2548     } else {
2549     last CN;
2550     }
2551     } elsif ($cnt == 3 or $cnt == 4) {
2552     if ($cn->data =~ /[^\x09-\x0D\x20]/) {
2553     last CN;
2554     }
2555     }
2556     } # CN
2557     unshift @$sib, @cn;
2558     }
2559     }
2560 wakaba 1.4 push @$new_todos, {type => 'element', node => $node};
2561 wakaba 1.2 } # TP
2562 wakaba 1.4 return ($sib, $new_todos);
2563 wakaba 1.2 } # _check_get_children
2564    
2565 wakaba 1.1 1;
2566 wakaba 1.16 # $Date: 2007/05/20 05:07:12 $

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24