/[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.38 - (hide annotations) (download)
Tue Jul 17 14:26:48 2007 UTC (19 years ago) by wakaba
Branch: MAIN
Changes since 1.37: +14 -14 lines
++ whatpm/Whatpm/ChangeLog	17 Jul 2007 14:26:39 -0000
	* ContentChecker.pm: Return the |class| node list.

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

1 wakaba 1.1 package Whatpm::ContentChecker;
2     use strict;
3    
4 wakaba 1.18 require Whatpm::URIChecker;
5    
6 wakaba 1.13 ## ISSUE: How XML and XML Namespaces conformance can (or cannot)
7     ## be applied to an in-memory representation (i.e. DOM)?
8    
9 wakaba 1.9 my $XML_NS = q<http://www.w3.org/XML/1998/namespace>;
10     my $XMLNS_NS = q<http://www.w3.org/2000/xmlns/>;
11    
12     my $AttrChecker = {
13     $XML_NS => {
14 wakaba 1.13 space => sub {
15     my ($self, $attr) = @_;
16     my $value = $attr->value;
17     if ($value eq 'default' or $value eq 'preserve') {
18     #
19     } else {
20     ## NOTE: An XML "error"
21 wakaba 1.33 $self->{onerror}->(node => $attr, level => 'error',
22     type => 'invalid attribute value');
23 wakaba 1.13 }
24     },
25     lang => sub {
26 wakaba 1.35 my ($self, $attr) = @_;
27 wakaba 1.13 ## NOTE: "The values of the attribute are language identifiers
28     ## as defined by [IETF RFC 3066], Tags for the Identification
29     ## of Languages, or its successor; in addition, the empty string
30     ## may be specified." ("may" in lower case)
31 wakaba 1.36 $self->{onerror}->(node => $attr, level => 'unsupported',
32     type => 'language tag');
33 wakaba 1.35 if ($attr->owner_document->manakai_is_html) { # MUST NOT
34 wakaba 1.36 $self->{onerror}->(node => $attr, type => 'in HTML:xml:lang');
35 wakaba 1.35 ## TODO: Test data...
36     }
37 wakaba 1.13 },
38     base => sub {
39     my ($self, $attr) = @_;
40     my $value = $attr->value;
41     if ($value =~ /[^\x{0000}-\x{10FFFF}]/) { ## ISSUE: Should we disallow noncharacters?
42     $self->{onerror}->(node => $attr,
43 wakaba 1.33 type => 'invalid attribute value');
44 wakaba 1.13 }
45 wakaba 1.18 ## NOTE: Conformance to URI standard is not checked since there is
46     ## no author requirement on conformance in the XML Base specification.
47 wakaba 1.13 },
48     id => sub {
49     my ($self, $attr) = @_;
50     my $value = $attr->value;
51     $value =~ s/[\x09\x0A\x0D\x20]+/ /g;
52     $value =~ s/^\x20//;
53     $value =~ s/\x20$//;
54     ## TODO: NCName in XML 1.0 or 1.1
55     ## TODO: declared type is ID?
56 wakaba 1.33 if ($self->{id}->{$value}) { ## NOTE: An xml:id error
57     $self->{onerror}->(node => $attr, level => 'error',
58     type => 'duplicate ID');
59 wakaba 1.37 push @{$self->{id}->{$value}}, $attr;
60 wakaba 1.13 } else {
61 wakaba 1.37 $self->{id}->{$value} = [$attr];
62 wakaba 1.13 }
63     },
64 wakaba 1.9 },
65     $XMLNS_NS => {
66 wakaba 1.13 '' => sub {
67     my ($self, $attr) = @_;
68     my $ln = $attr->manakai_local_name;
69     my $value = $attr->value;
70     if ($value eq $XML_NS and $ln ne 'xml') {
71     $self->{onerror}
72 wakaba 1.33 ->(node => $attr, level => 'NC',
73     type => 'Reserved Prefixes and Namespace Names:=xml');
74 wakaba 1.13 } elsif ($value eq $XMLNS_NS) {
75     $self->{onerror}
76 wakaba 1.33 ->(node => $attr, level => 'NC',
77     type => 'Reserved Prefixes and Namespace Names:=xmlns');
78 wakaba 1.13 }
79     if ($ln eq 'xml' and $value ne $XML_NS) {
80     $self->{onerror}
81 wakaba 1.33 ->(node => $attr, level => 'NC',
82     type => 'Reserved Prefixes and Namespace Names:xmlns:xml=');
83 wakaba 1.13 } elsif ($ln eq 'xmlns') {
84     $self->{onerror}
85 wakaba 1.33 ->(node => $attr, level => 'NC',
86     type => 'Reserved Prefixes and Namespace Names:xmlns:xmlns=');
87 wakaba 1.13 }
88     ## TODO: If XML 1.0 and empty
89     },
90     xmlns => sub {
91     my ($self, $attr) = @_;
92     ## TODO: In XML 1.0, URI reference [RFC 3986] or an empty string
93     ## TODO: In XML 1.1, IRI reference [RFC 3987] or an empty string
94 wakaba 1.18 ## TODO: relative references are deprecated
95 wakaba 1.13 my $value = $attr->value;
96     if ($value eq $XML_NS) {
97     $self->{onerror}
98 wakaba 1.33 ->(node => $attr, level => 'NC',
99     type => 'Reserved Prefixes and Namespace Names:=xml');
100 wakaba 1.13 } elsif ($value eq $XMLNS_NS) {
101     $self->{onerror}
102 wakaba 1.33 ->(node => $attr, level => 'NC',
103     type => 'Reserved Prefixes and Namespace Names:=xmlns');
104 wakaba 1.13 }
105     },
106 wakaba 1.9 },
107     };
108    
109 wakaba 1.14 ## ISSUE: Should we really allow these attributes?
110 wakaba 1.13 $AttrChecker->{''}->{'xml:space'} = $AttrChecker->{$XML_NS}->{space};
111     $AttrChecker->{''}->{'xml:lang'} = $AttrChecker->{$XML_NS}->{lang};
112     $AttrChecker->{''}->{'xml:base'} = $AttrChecker->{$XML_NS}->{base};
113     $AttrChecker->{''}->{'xml:id'} = $AttrChecker->{$XML_NS}->{id};
114    
115 wakaba 1.3 ## ANY
116     my $AnyChecker = sub {
117 wakaba 1.4 my ($self, $todo) = @_;
118     my $el = $todo->{node};
119     my $new_todos = [];
120 wakaba 1.3 my @nodes = (@{$el->child_nodes});
121     while (@nodes) {
122     my $node = shift @nodes;
123     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
124    
125     my $nt = $node->node_type;
126     if ($nt == 1) {
127     my $node_ns = $node->namespace_uri;
128     $node_ns = '' unless defined $node_ns;
129     my $node_ln = $node->manakai_local_name;
130     if ($self->{minuses}->{$node_ns}->{$node_ln}) {
131     $self->{onerror}->(node => $node, type => 'element not allowed');
132     }
133 wakaba 1.4 push @$new_todos, {type => 'element', node => $node};
134 wakaba 1.3 } elsif ($nt == 5) {
135     unshift @nodes, @{$node->child_nodes};
136     }
137     }
138 wakaba 1.4 return ($new_todos);
139 wakaba 1.3 }; # $AnyChecker
140    
141 wakaba 1.1 my $ElementDefault = {
142     checker => sub {
143 wakaba 1.4 my ($self, $todo) = @_;
144 wakaba 1.33 $self->{onerror}->(node => $todo->{node}, level => 'unsupported',
145     type => 'element');
146 wakaba 1.4 return $AnyChecker->($self, $todo);
147 wakaba 1.1 },
148 wakaba 1.9 attrs_checker => sub {
149     my ($self, $todo) = @_;
150     for my $attr (@{$todo->{node}->attributes}) {
151     my $attr_ns = $attr->namespace_uri;
152     $attr_ns = '' unless defined $attr_ns;
153     my $attr_ln = $attr->manakai_local_name;
154     my $checker = $AttrChecker->{$attr_ns}->{$attr_ln}
155     || $AttrChecker->{$attr_ns}->{''};
156     if ($checker) {
157     $checker->($self, $attr);
158 wakaba 1.17 } else {
159 wakaba 1.33 $self->{onerror}->(node => $attr, level => 'unsupported',
160     type => 'attribute');
161 wakaba 1.9 }
162     }
163     },
164 wakaba 1.1 };
165    
166     my $Element = {};
167    
168     my $HTML_NS = q<http://www.w3.org/1999/xhtml>;
169    
170 wakaba 1.7 my $HTMLMetadataElements = {
171     $HTML_NS => {
172 wakaba 1.29 qw/link 1 meta 1 style 1 script 1 event-source 1 command 1 base 1 title 1
173     noscript 1
174     /,
175 wakaba 1.7 },
176     };
177 wakaba 1.1
178 wakaba 1.2 my $HTMLSectioningElements = {
179     $HTML_NS => {qw/body 1 section 1 nav 1 article 1 blockquote 1 aside 1/},
180     };
181 wakaba 1.1
182 wakaba 1.7 my $HTMLBlockLevelElements = {
183     $HTML_NS => {
184     qw/
185     section 1 nav 1 article 1 blockquote 1 aside 1
186     h1 1 h2 1 h3 1 h4 1 h5 1 h6 1 header 1 footer 1
187     address 1 p 1 hr 1 dialog 1 pre 1 ol 1 ul 1 dl 1
188     ins 1 del 1 figure 1 map 1 table 1 script 1 noscript 1
189     event-source 1 details 1 datagrid 1 menu 1 div 1 font 1
190     /,
191     },
192     };
193    
194     my $HTMLStrictlyInlineLevelElements = {
195     $HTML_NS => {
196     qw/
197     br 1 a 1 q 1 cite 1 em 1 strong 1 small 1 m 1 dfn 1 abbr 1
198     time 1 meter 1 progress 1 code 1 var 1 samp 1 kbd 1
199     sub 1 sup 1 span 1 i 1 b 1 bdo 1 ins 1 del 1 img 1
200     iframe 1 embed 1 object 1 video 1 audio 1 canvas 1 area 1
201     script 1 noscript 1 event-source 1 command 1 font 1
202     /,
203     },
204     };
205    
206     my $HTMLStructuredInlineLevelElements = {
207     $HTML_NS => {qw/blockquote 1 pre 1 ol 1 ul 1 dl 1 table 1 menu 1/},
208     };
209 wakaba 1.1
210 wakaba 1.6 my $HTMLInteractiveElements = {
211     $HTML_NS => {a => 1, details => 1, datagrid => 1},
212     };
213     ## NOTE: |html:a| and |html:datagrid| are not allowed as a descendant
214     ## of interactive elements
215 wakaba 1.1
216 wakaba 1.7 my $HTMLTransparentElements = {
217     $HTML_NS => {qw/ins 1 font 1 noscript 1/},
218 wakaba 1.29 ## NOTE: |html:noscript| is transparent if scripting is disabled
219     ## and not in |head|.
220 wakaba 1.7 };
221    
222     #my $HTMLSemiTransparentElements = {
223     # $HTML_NS => {qw/video 1 audio 1/},
224     #};
225    
226     my $HTMLEmbededElements = {
227     $HTML_NS => {qw/img 1 iframe 1 embed 1 object 1 video 1 audio 1 canvas 1/},
228     };
229 wakaba 1.1
230     ## Empty
231     my $HTMLEmptyChecker = sub {
232 wakaba 1.4 my ($self, $todo) = @_;
233     my $el = $todo->{node};
234     my $new_todos = [];
235 wakaba 1.1 my @nodes = (@{$el->child_nodes});
236    
237     while (@nodes) {
238     my $node = shift @nodes;
239 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
240    
241 wakaba 1.1 my $nt = $node->node_type;
242     if ($nt == 1) {
243 wakaba 1.2 ## NOTE: |minuses| list is not checked since redundant
244     $self->{onerror}->(node => $node, type => 'element not allowed');
245 wakaba 1.30 my ($sib, $ch) = $self->_check_get_children ($node, $todo);
246 wakaba 1.2 unshift @nodes, @$sib;
247 wakaba 1.4 push @$new_todos, @$ch;
248 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
249 wakaba 1.3 if ($node->data =~ /[^\x09-\x0D\x20]/) {
250     $self->{onerror}->(node => $node, type => 'character not allowed');
251     }
252 wakaba 1.1 } elsif ($nt == 5) {
253     unshift @nodes, @{$node->child_nodes};
254     }
255     }
256 wakaba 1.4 return ($new_todos);
257 wakaba 1.1 };
258    
259     ## Text
260     my $HTMLTextChecker = sub {
261 wakaba 1.4 my ($self, $todo) = @_;
262     my $el = $todo->{node};
263     my $new_todos = [];
264 wakaba 1.1 my @nodes = (@{$el->child_nodes});
265    
266     while (@nodes) {
267     my $node = shift @nodes;
268 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
269    
270 wakaba 1.1 my $nt = $node->node_type;
271     if ($nt == 1) {
272 wakaba 1.2 ## NOTE: |minuses| list is not checked since redundant
273     $self->{onerror}->(node => $node, type => 'element not allowed');
274 wakaba 1.30 my ($sib, $ch) = $self->_check_get_children ($node, $todo);
275 wakaba 1.2 unshift @nodes, @$sib;
276 wakaba 1.4 push @$new_todos, @$ch;
277 wakaba 1.1 } elsif ($nt == 5) {
278     unshift @nodes, @{$node->child_nodes};
279     }
280     }
281 wakaba 1.4 return ($new_todos);
282 wakaba 1.1 };
283    
284     ## Zero or more |html:style| elements,
285     ## followed by zero or more block-level elements
286     my $HTMLStylableBlockChecker = sub {
287 wakaba 1.4 my ($self, $todo) = @_;
288     my $el = $todo->{node};
289     my $new_todos = [];
290 wakaba 1.1 my @nodes = (@{$el->child_nodes});
291    
292     my $has_non_style;
293     while (@nodes) {
294     my $node = shift @nodes;
295 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
296    
297 wakaba 1.1 my $nt = $node->node_type;
298     if ($nt == 1) {
299 wakaba 1.2 my $node_ns = $node->namespace_uri;
300     $node_ns = '' unless defined $node_ns;
301     my $node_ln = $node->manakai_local_name;
302 wakaba 1.6 my $not_allowed = $self->{minuses}->{$node_ns}->{$node_ln};
303 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'style') {
304 wakaba 1.28 $not_allowed = 1 if $has_non_style or
305     not $node->has_attribute_ns (undef, 'scoped');
306 wakaba 1.7 } elsif ($HTMLBlockLevelElements->{$node_ns}->{$node_ln}) {
307     $has_non_style = 1;
308 wakaba 1.1 } else {
309     $has_non_style = 1;
310 wakaba 1.7 $not_allowed = 1;
311 wakaba 1.1 }
312 wakaba 1.6 $self->{onerror}->(node => $node, type => 'element not allowed')
313     if $not_allowed;
314 wakaba 1.30 my ($sib, $ch) = $self->_check_get_children ($node, $todo);
315 wakaba 1.2 unshift @nodes, @$sib;
316 wakaba 1.4 push @$new_todos, @$ch;
317 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
318     if ($node->data =~ /[^\x09-\x0D\x20]/) {
319 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
320 wakaba 1.1 }
321     } elsif ($nt == 5) {
322     unshift @nodes, @{$node->child_nodes};
323     }
324     }
325 wakaba 1.4 return ($new_todos);
326 wakaba 1.1 }; # $HTMLStylableBlockChecker
327    
328     ## Zero or more block-level elements
329     my $HTMLBlockChecker = sub {
330 wakaba 1.4 my ($self, $todo) = @_;
331     my $el = $todo->{node};
332     my $new_todos = [];
333 wakaba 1.1 my @nodes = (@{$el->child_nodes});
334    
335     while (@nodes) {
336     my $node = shift @nodes;
337 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
338    
339 wakaba 1.1 my $nt = $node->node_type;
340     if ($nt == 1) {
341 wakaba 1.2 my $node_ns = $node->namespace_uri;
342     $node_ns = '' unless defined $node_ns;
343     my $node_ln = $node->manakai_local_name;
344 wakaba 1.6 my $not_allowed = $self->{minuses}->{$node_ns}->{$node_ln};
345 wakaba 1.7 $not_allowed = 1
346     unless $HTMLBlockLevelElements->{$node_ns}->{$node_ln};
347 wakaba 1.6 $self->{onerror}->(node => $node, type => 'element not allowed')
348     if $not_allowed;
349 wakaba 1.30 my ($sib, $ch) = $self->_check_get_children ($node, $todo);
350 wakaba 1.2 unshift @nodes, @$sib;
351 wakaba 1.4 push @$new_todos, @$ch;
352 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
353     if ($node->data =~ /[^\x09-\x0D\x20]/) {
354 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
355 wakaba 1.1 }
356     } elsif ($nt == 5) {
357     unshift @nodes, @{$node->child_nodes};
358     }
359     }
360 wakaba 1.4 return ($new_todos);
361 wakaba 1.1 }; # $HTMLBlockChecker
362    
363     ## Inline-level content
364     my $HTMLInlineChecker = sub {
365 wakaba 1.4 my ($self, $todo) = @_;
366     my $el = $todo->{node};
367     my $new_todos = [];
368 wakaba 1.1 my @nodes = (@{$el->child_nodes});
369    
370     while (@nodes) {
371     my $node = shift @nodes;
372 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
373    
374 wakaba 1.1 my $nt = $node->node_type;
375     if ($nt == 1) {
376 wakaba 1.2 my $node_ns = $node->namespace_uri;
377     $node_ns = '' unless defined $node_ns;
378     my $node_ln = $node->manakai_local_name;
379 wakaba 1.6 my $not_allowed = $self->{minuses}->{$node_ns}->{$node_ln};
380 wakaba 1.7 $not_allowed = 1
381     unless $HTMLStrictlyInlineLevelElements->{$node_ns}->{$node_ln} or
382     $HTMLStructuredInlineLevelElements->{$node_ns}->{$node_ln};
383 wakaba 1.6 $self->{onerror}->(node => $node, type => 'element not allowed')
384     if $not_allowed;
385 wakaba 1.30 my ($sib, $ch) = $self->_check_get_children ($node, $todo);
386 wakaba 1.2 unshift @nodes, @$sib;
387 wakaba 1.4 push @$new_todos, @$ch;
388 wakaba 1.1 } elsif ($nt == 5) {
389     unshift @nodes, @{$node->child_nodes};
390     }
391     }
392 wakaba 1.4
393     for (@$new_todos) {
394     $_->{inline} = 1;
395     }
396     return ($new_todos);
397     }; # $HTMLInlineChecker
398 wakaba 1.1
399     my $HTMLSignificantInlineChecker = $HTMLInlineChecker;
400     ## TODO: check significant content
401    
402     ## Strictly inline-level content
403     my $HTMLStrictlyInlineChecker = sub {
404 wakaba 1.4 my ($self, $todo) = @_;
405     my $el = $todo->{node};
406     my $new_todos = [];
407 wakaba 1.1 my @nodes = (@{$el->child_nodes});
408    
409     while (@nodes) {
410     my $node = shift @nodes;
411 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
412    
413 wakaba 1.1 my $nt = $node->node_type;
414     if ($nt == 1) {
415 wakaba 1.2 my $node_ns = $node->namespace_uri;
416     $node_ns = '' unless defined $node_ns;
417     my $node_ln = $node->manakai_local_name;
418 wakaba 1.6 my $not_allowed = $self->{minuses}->{$node_ns}->{$node_ln};
419 wakaba 1.7 $not_allowed = 1
420     unless $HTMLStrictlyInlineLevelElements->{$node_ns}->{$node_ln};
421 wakaba 1.6 $self->{onerror}->(node => $node, type => 'element not allowed')
422     if $not_allowed;
423 wakaba 1.30 my ($sib, $ch) = $self->_check_get_children ($node, $todo);
424 wakaba 1.2 unshift @nodes, @$sib;
425 wakaba 1.4 push @$new_todos, @$ch;
426 wakaba 1.1 } elsif ($nt == 5) {
427     unshift @nodes, @{$node->child_nodes};
428     }
429     }
430 wakaba 1.4
431     for (@$new_todos) {
432     $_->{inline} = 1;
433     $_->{strictly_inline} = 1;
434     }
435     return ($new_todos);
436 wakaba 1.1 }; # $HTMLStrictlyInlineChecker
437    
438     my $HTMLSignificantStrictlyInlineChecker = $HTMLStrictlyInlineChecker;
439     ## TODO: check significant content
440    
441 wakaba 1.4 ## Inline-level or strictly inline-kevek content
442     my $HTMLInlineOrStrictlyInlineChecker = sub {
443     my ($self, $todo) = @_;
444     my $el = $todo->{node};
445     my $new_todos = [];
446     my @nodes = (@{$el->child_nodes});
447    
448     while (@nodes) {
449     my $node = shift @nodes;
450     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
451    
452     my $nt = $node->node_type;
453     if ($nt == 1) {
454     my $node_ns = $node->namespace_uri;
455     $node_ns = '' unless defined $node_ns;
456     my $node_ln = $node->manakai_local_name;
457 wakaba 1.6 my $not_allowed = $self->{minuses}->{$node_ns}->{$node_ln};
458 wakaba 1.7 if ($todo->{strictly_inline}) {
459     $not_allowed = 1
460     unless $HTMLStrictlyInlineLevelElements->{$node_ns}->{$node_ln};
461     } else {
462     $not_allowed = 1
463     unless $HTMLStrictlyInlineLevelElements->{$node_ns}->{$node_ln} or
464     $HTMLStructuredInlineLevelElements->{$node_ns}->{$node_ln};
465     }
466 wakaba 1.6 $self->{onerror}->(node => $node, type => 'element not allowed')
467     if $not_allowed;
468 wakaba 1.30 my ($sib, $ch) = $self->_check_get_children ($node, $todo);
469 wakaba 1.4 unshift @nodes, @$sib;
470     push @$new_todos, @$ch;
471     } elsif ($nt == 5) {
472     unshift @nodes, @{$node->child_nodes};
473     }
474     }
475    
476     for (@$new_todos) {
477     $_->{inline} = 1;
478     $_->{strictly_inline} = 1;
479     }
480     return ($new_todos);
481     }; # $HTMLInlineOrStrictlyInlineChecker
482    
483 wakaba 1.6 my $HTMLSignificantInlineOrStrictlyInlineChecker
484     = $HTMLInlineOrStrictlyInlineChecker;
485     ## TODO: check significant content
486    
487 wakaba 1.1 my $HTMLBlockOrInlineChecker = sub {
488 wakaba 1.4 my ($self, $todo) = @_;
489     my $el = $todo->{node};
490     my $new_todos = [];
491 wakaba 1.1 my @nodes = (@{$el->child_nodes});
492    
493     my $content = 'block-or-inline'; # or 'block' or 'inline'
494     my @block_not_inline;
495     while (@nodes) {
496     my $node = shift @nodes;
497 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
498    
499 wakaba 1.1 my $nt = $node->node_type;
500     if ($nt == 1) {
501 wakaba 1.2 my $node_ns = $node->namespace_uri;
502     $node_ns = '' unless defined $node_ns;
503     my $node_ln = $node->manakai_local_name;
504 wakaba 1.6 my $not_allowed = $self->{minuses}->{$node_ns}->{$node_ln};
505 wakaba 1.1 if ($content eq 'block') {
506 wakaba 1.7 $not_allowed = 1
507     unless $HTMLBlockLevelElements->{$node_ns}->{$node_ln};
508 wakaba 1.1 } elsif ($content eq 'inline') {
509 wakaba 1.7 $not_allowed = 1
510     unless $HTMLStrictlyInlineLevelElements->{$node_ns}->{$node_ln} or
511     $HTMLStructuredInlineLevelElements->{$node_ns}->{$node_ln};
512 wakaba 1.1 } else {
513 wakaba 1.7 my $is_block = $HTMLBlockLevelElements->{$node_ns}->{$node_ln};
514     my $is_inline
515     = $HTMLStrictlyInlineLevelElements->{$node_ns}->{$node_ln} ||
516     $HTMLStructuredInlineLevelElements->{$node_ns}->{$node_ln};
517 wakaba 1.1
518 wakaba 1.6 push @block_not_inline, $node
519     if $is_block and not $is_inline and not $not_allowed;
520 wakaba 1.1 unless ($is_block) {
521     $content = 'inline';
522     for (@block_not_inline) {
523 wakaba 1.2 $self->{onerror}->(node => $_, type => 'element not allowed');
524 wakaba 1.1 }
525 wakaba 1.6 $not_allowed = 1 unless $is_inline;
526 wakaba 1.1 }
527     }
528 wakaba 1.6 $self->{onerror}->(node => $node, type => 'element not allowed')
529     if $not_allowed;
530 wakaba 1.30 my ($sib, $ch) = $self->_check_get_children ($node, $todo);
531 wakaba 1.2 unshift @nodes, @$sib;
532 wakaba 1.4 push @$new_todos, @$ch;
533 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
534     if ($node->data =~ /[^\x09-\x0D\x20]/) {
535     if ($content eq 'block') {
536 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
537 wakaba 1.1 } else {
538     $content = 'inline';
539     for (@block_not_inline) {
540 wakaba 1.2 $self->{onerror}->(node => $_, type => 'element not allowed');
541 wakaba 1.1 }
542     }
543     }
544     } elsif ($nt == 5) {
545     unshift @nodes, @{$node->child_nodes};
546     }
547     }
548 wakaba 1.4
549     if ($content eq 'inline') {
550     for (@$new_todos) {
551     $_->{inline} = 1;
552     }
553     }
554     return ($new_todos);
555 wakaba 1.1 };
556    
557 wakaba 1.2 ## Zero or more XXX element, then either block-level or inline-level
558     my $GetHTMLZeroOrMoreThenBlockOrInlineChecker = sub ($$) {
559     my ($elnsuri, $ellname) = @_;
560     return sub {
561 wakaba 1.4 my ($self, $todo) = @_;
562     my $el = $todo->{node};
563     my $new_todos = [];
564 wakaba 1.2 my @nodes = (@{$el->child_nodes});
565    
566     my $has_non_style;
567     my $content = 'block-or-inline'; # or 'block' or 'inline'
568     my @block_not_inline;
569     while (@nodes) {
570     my $node = shift @nodes;
571     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
572    
573     my $nt = $node->node_type;
574     if ($nt == 1) {
575     my $node_ns = $node->namespace_uri;
576     $node_ns = '' unless defined $node_ns;
577     my $node_ln = $node->manakai_local_name;
578 wakaba 1.6 my $not_allowed = $self->{minuses}->{$node_ns}->{$node_ln};
579 wakaba 1.8 if ($node_ns eq $elnsuri and $node_ln eq $ellname) {
580 wakaba 1.6 $not_allowed = 1 if $has_non_style;
581 wakaba 1.28 if ($ellname eq 'style' and
582     not $node->has_attribute_ns (undef, 'scoped')) {
583     $not_allowed = 1;
584     }
585 wakaba 1.2 } elsif ($content eq 'block') {
586     $has_non_style = 1;
587 wakaba 1.7 $not_allowed = 1
588     unless $HTMLBlockLevelElements->{$node_ns}->{$node_ln};
589 wakaba 1.2 } elsif ($content eq 'inline') {
590     $has_non_style = 1;
591 wakaba 1.7 $not_allowed = 1
592     unless $HTMLStrictlyInlineLevelElements->{$node_ns}->{$node_ln} or
593     $HTMLStructuredInlineLevelElements->{$node_ns}->{$node_ln};
594 wakaba 1.2 } else {
595     $has_non_style = 1;
596 wakaba 1.7 my $is_block = $HTMLBlockLevelElements->{$node_ns}->{$node_ln};
597     my $is_inline
598     = $HTMLStrictlyInlineLevelElements->{$node_ns}->{$node_ln} ||
599     $HTMLStructuredInlineLevelElements->{$node_ns}->{$node_ln};
600 wakaba 1.2
601 wakaba 1.6 push @block_not_inline, $node
602     if $is_block and not $is_inline and not $not_allowed;
603 wakaba 1.2 unless ($is_block) {
604     $content = 'inline';
605     for (@block_not_inline) {
606     $self->{onerror}->(node => $_, type => 'element not allowed');
607     }
608 wakaba 1.6 $not_allowed = 1 unless $is_inline;
609 wakaba 1.1 }
610     }
611 wakaba 1.6 $self->{onerror}->(node => $node, type => 'element not allowed')
612     if $not_allowed;
613 wakaba 1.30 my ($sib, $ch) = $self->_check_get_children ($node, $todo);
614 wakaba 1.2 unshift @nodes, @$sib;
615 wakaba 1.4 push @$new_todos, @$ch;
616 wakaba 1.2 } elsif ($nt == 3 or $nt == 4) {
617     if ($node->data =~ /[^\x09-\x0D\x20]/) {
618     $has_non_style = 1;
619     if ($content eq 'block') {
620     $self->{onerror}->(node => $node, type => 'character not allowed');
621     } else {
622     $content = 'inline';
623     for (@block_not_inline) {
624     $self->{onerror}->(node => $_, type => 'element not allowed');
625     }
626 wakaba 1.1 }
627     }
628 wakaba 1.2 } elsif ($nt == 5) {
629     unshift @nodes, @{$node->child_nodes};
630 wakaba 1.1 }
631     }
632 wakaba 1.4
633     if ($content eq 'inline') {
634     for (@$new_todos) {
635     $_->{inline} = 1;
636     }
637     }
638     return ($new_todos);
639 wakaba 1.2 };
640     }; # $GetHTMLZeroOrMoreThenBlockOrInlineChecker
641 wakaba 1.1
642     my $HTMLTransparentChecker = $HTMLBlockOrInlineChecker;
643    
644 wakaba 1.10 my $GetHTMLEnumeratedAttrChecker = sub {
645     my $states = shift; # {value => conforming ? 1 : -1}
646     return sub {
647     my ($self, $attr) = @_;
648     my $value = lc $attr->value; ## TODO: ASCII case insensitibility?
649     if ($states->{$value} > 0) {
650     #
651     } elsif ($states->{$value}) {
652 wakaba 1.33 $self->{onerror}->(node => $attr, type => 'enumerated:non-conforming');
653 wakaba 1.10 } else {
654 wakaba 1.33 $self->{onerror}->(node => $attr, type => 'enumerated:invalid');
655 wakaba 1.10 }
656     };
657     }; # $GetHTMLEnumeratedAttrChecker
658 wakaba 1.9
659 wakaba 1.10 my $GetHTMLBooleanAttrChecker = sub {
660     my $local_name = shift;
661     return sub {
662     my ($self, $attr) = @_;
663     my $value = $attr->value;
664     unless ($value eq $local_name or $value eq '') {
665 wakaba 1.33 $self->{onerror}->(node => $attr, type => 'boolean:invalid');
666 wakaba 1.10 }
667     };
668     }; # $GetHTMLBooleanAttrChecker
669 wakaba 1.9
670 wakaba 1.20 ## |rel| attribute (unordered set of space separated tokens,
671     ## whose allowed values are defined by the section on link types)
672     my $HTMLLinkTypesAttrChecker = sub {
673     my ($a_or_area, $self, $attr) = @_;
674     my %word;
675     for my $word (grep {length $_} split /[\x09-\x0D\x20]/, $attr->value) {
676     unless ($word{$word}) {
677     $word{$word} = 1;
678     } else {
679     $self->{onerror}->(node => $attr, type => 'duplicate token:'.$word);
680     }
681     }
682     ## NOTE: Case sensitive match (since HTML5 spec does not say link
683     ## types are case-insensitive and it says "The value should not
684     ## be confusingly similar to any other defined value (e.g.
685     ## differing only in case).").
686     ## NOTE: Though there is no explicit "MUST NOT" for undefined values,
687     ## "MAY"s and "only ... MAY" restrict non-standard non-registered
688     ## values to be used conformingly.
689     require Whatpm::_LinkTypeList;
690     our $LinkType;
691     for my $word (keys %word) {
692     my $def = $LinkType->{$word};
693     if (defined $def) {
694     if ($def->{status} eq 'accepted') {
695     if (defined $def->{effect}->[$a_or_area]) {
696     #
697     } else {
698     $self->{onerror}->(node => $attr,
699 wakaba 1.33 type => 'link type:bad context:'.$word);
700 wakaba 1.20 }
701     } elsif ($def->{status} eq 'proposal') {
702 wakaba 1.33 $self->{onerror}->(node => $attr, level => 's',
703     type => 'link type:proposed:'.$word);
704 wakaba 1.20 } else { # rejected or synonym
705     $self->{onerror}->(node => $attr,
706 wakaba 1.33 type => 'link type:non-conforming:'.$word);
707 wakaba 1.20 }
708     if ($def->{unique}) {
709     unless ($self->{has_link_type}->{$word}) {
710     $self->{has_link_type}->{$word} = 1;
711     } else {
712     $self->{onerror}->(node => $attr,
713 wakaba 1.33 type => 'link type:duplicate:'.$word);
714 wakaba 1.20 }
715     }
716     } else {
717 wakaba 1.33 $self->{onerror}->(node => $attr, level => 'unsupported',
718     type => 'link type:'.$word);
719 wakaba 1.20 }
720     }
721     ## TODO: The Pingback 1.0 specification, which is referenced by HTML5,
722     ## says that using both X-Pingback: header field and HTML
723     ## <link rel=pingback> is deprecated and if both appears they
724     ## SHOULD contain exactly the same value.
725     ## ISSUE: Pingback 1.0 specification defines the exact representation
726     ## of its link element, which cannot be tested by the current arch.
727     ## ISSUE: Pingback 1.0 specification says that the document MUST NOT
728     ## include any string that matches to the pattern for the rel=pingback link,
729     ## which again inpossible to test.
730     ## ISSUE: rel=pingback href MUST NOT include entities other than predefined 4.
731     }; # $HTMLLinkTypesAttrChecker
732    
733 wakaba 1.18 ## URI (or IRI)
734 wakaba 1.11 my $HTMLURIAttrChecker = sub {
735     my ($self, $attr) = @_;
736 wakaba 1.15 ## ISSUE: Relative references are allowed? (RFC 3987 "IRI" is an absolute reference with optional fragment identifier.)
737 wakaba 1.18 my $value = $attr->value;
738     Whatpm::URIChecker->check_iri_reference ($value, sub {
739     my %opt = @_;
740 wakaba 1.33 $self->{onerror}->(node => $attr, level => $opt{level},
741 wakaba 1.36 type => 'URI::'.$opt{type}.
742     (defined $opt{position} ? ':'.$opt{position} : ''));
743 wakaba 1.18 });
744 wakaba 1.11 }; # $HTMLURIAttrChecker
745    
746 wakaba 1.15 ## A space separated list of one or more URIs (or IRIs)
747     my $HTMLSpaceURIsAttrChecker = sub {
748     my ($self, $attr) = @_;
749 wakaba 1.30 my $i = 0;
750     for my $value (split /[\x09-\x0D\x20]+/, $attr->value) {
751     Whatpm::URIChecker->check_iri_reference ($value, sub {
752     my %opt = @_;
753 wakaba 1.33 $self->{onerror}->(node => $attr, level => $opt{level},
754 wakaba 1.36 type => 'URIs:'.$i.'::'.
755     $opt{type}.
756     (defined $opt{position} ? ':'.$opt{position} : ''));
757 wakaba 1.30 });
758     $i++;
759     }
760 wakaba 1.15 ## ISSUE: Relative references?
761     ## ISSUE: Leading or trailing white spaces are conformant?
762     ## ISSUE: A sequence of white space characters are conformant?
763     ## ISSUE: A zero-length string is conformant? (It does contain a relative reference, i.e. same as base URI.)
764     ## NOTE: Duplication seems not an error.
765     }; # $HTMLSpaceURIsAttrChecker
766    
767 wakaba 1.30 my $HTMLDatetimeAttrChecker = sub {
768     my ($self, $attr) = @_;
769     my $value = $attr->value;
770     ## ISSUE: "space", not "space character" (in parsing algorihtm, "space character")
771     if ($value =~ /\A([0-9]{4})-([0-9]{2})-([0-9]{2})(?>[\x09-\x0D\x20]+(?>T[\x09-\x0D\x20]*)?|T[\x09-\x0D\x20]*)([0-9]{2}):([0-9]{2})(?>:([0-9]{2}))?(?>\.([0-9]+))?[\x09-\x0D\x20]*(?>Z|[+-]([0-9]{2}):([0-9]{2}))\z/) {
772     my ($y, $M, $d, $h, $m, $s, $f, $zh, $zm)
773     = ($1, $2, $3, $4, $5, $6, $7, $8, $9);
774     if (0 < $M and $M < 13) { ## ISSUE: This is not explicitly specified (though in parsing algorithm)
775     $self->{onerror}->(node => $attr, type => 'datetime:bad day')
776     if $d < 1 or
777     $d > [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]->[$M];
778     $self->{onerror}->(node => $attr, type => 'datetime:bad day')
779     if $M == 2 and $d == 29 and
780     not ($y % 400 == 0 or ($y % 4 == 0 and $y % 100 != 0));
781     } else {
782     $self->{onerror}->(node => $attr, type => 'datetime:bad month');
783     }
784     $self->{onerror}->(node => $attr, type => 'datetime:bad hour') if $h > 23;
785     $self->{onerror}->(node => $attr, type => 'datetime:bad minute') if $m > 59;
786     $self->{onerror}->(node => $attr, type => 'datetime:bad second')
787     if defined $s and $s > 59;
788     $self->{onerror}->(node => $attr, type => 'datetime:bad timezone hour')
789     if $zh > 23;
790     $self->{onerror}->(node => $attr, type => 'datetime:bad timezone minute')
791     if $zm > 59;
792     ## ISSUE: Maybe timezone -00:00 should have same semantics as in RFC 3339.
793     } else {
794 wakaba 1.33 $self->{onerror}->(node => $attr, type => 'datetime:syntax error');
795 wakaba 1.30 }
796     }; # $HTMLDatetimeAttrChecker
797    
798 wakaba 1.11 my $HTMLIntegerAttrChecker = sub {
799     my ($self, $attr) = @_;
800     my $value = $attr->value;
801     unless ($value =~ /\A-?[0-9]+\z/) {
802 wakaba 1.33 $self->{onerror}->(node => $attr, type => 'integer:syntax error');
803 wakaba 1.11 }
804     }; # $HTMLIntegerAttrChecker
805    
806 wakaba 1.12 my $GetHTMLNonNegativeIntegerAttrChecker = sub {
807     my $range_check = shift;
808     return sub {
809     my ($self, $attr) = @_;
810     my $value = $attr->value;
811     if ($value =~ /\A[0-9]+\z/) {
812     unless ($range_check->($value + 0)) {
813 wakaba 1.33 $self->{onerror}->(node => $attr, type => 'nninteger:out of range');
814 wakaba 1.12 }
815     } else {
816 wakaba 1.15 $self->{onerror}->(node => $attr,
817 wakaba 1.33 type => 'nninteger:syntax error');
818 wakaba 1.12 }
819     };
820     }; # $GetHTMLNonNegativeIntegerAttrChecker
821    
822 wakaba 1.11 my $GetHTMLFloatingPointNumberAttrChecker = sub {
823     my $range_check = shift;
824     return sub {
825     my ($self, $attr) = @_;
826     my $value = $attr->value;
827     if ($value =~ /\A-?[0-9.]+\z/ and $value =~ /[0-9]/) {
828     unless ($range_check->($value + 0)) {
829 wakaba 1.33 $self->{onerror}->(node => $attr, type => 'float:out of range');
830 wakaba 1.11 }
831     } else {
832 wakaba 1.15 $self->{onerror}->(node => $attr,
833 wakaba 1.33 type => 'float:syntax error');
834 wakaba 1.11 }
835     };
836     }; # $GetHTMLFloatingPointNumberAttrChecker
837    
838 wakaba 1.15 ## "A valid MIME type, optionally with parameters. [RFC 2046]"
839     ## ISSUE: RFC 2046 does not define syntax of media types.
840     ## ISSUE: The definition of "a valid MIME type" is unknown.
841     ## Syntactical correctness?
842     my $HTMLIMTAttrChecker = sub {
843     my ($self, $attr) = @_;
844     my $value = $attr->value;
845     ## ISSUE: RFC 2045 Content-Type header field allows insertion
846     ## of LWS/comments between tokens. Is it allowed in HTML? Maybe no.
847     ## ISSUE: RFC 2231 extension? Maybe no.
848     my $lws0 = qr/(?>(?>\x0D\x0A)?[\x09\x20])*/;
849     my $token = qr/[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+/;
850     my $qs = qr/"(?>[\x00-\x0C\x0E-\x21\x23-\x5B\x5D-\x7E]|\x0D\x0A[\x09\x20]|\x5C[\x00-\x7F])*"/;
851 wakaba 1.19 if ($value =~ m#\A$lws0($token)$lws0/$lws0($token)$lws0((?>;$lws0$token$lws0=$lws0(?>$token|$qs)$lws0)*)\z#) {
852     my @type = ($1, $2);
853     my $param = $3;
854     while ($param =~ s/^;$lws0($token)$lws0=$lws0(?>($token)|($qs))$lws0//) {
855     if (defined $2) {
856     push @type, $1 => $2;
857     } else {
858     my $n = $1;
859     my $v = $2;
860     $v =~ s/\\(.)/$1/gs;
861     push @type, $n => $v;
862     }
863     }
864     require Whatpm::IMTChecker;
865     Whatpm::IMTChecker->check_imt (sub {
866     my %opt = @_;
867 wakaba 1.33 $self->{onerror}->(node => $attr, level => $opt{level},
868     type => 'IMT:'.$opt{type});
869 wakaba 1.19 }, @type);
870     } else {
871 wakaba 1.33 $self->{onerror}->(node => $attr, type => 'IMT:syntax error');
872 wakaba 1.15 }
873     }; # $HTMLIMTAttrChecker
874    
875 wakaba 1.17 my $HTMLLanguageTagAttrChecker = sub {
876     my ($self, $attr) = @_;
877 wakaba 1.36 $self->{onerror}->(node => $attr, level => 'unsupported',
878     type => 'language tag');
879 wakaba 1.17 if ($attr->value eq '') {
880 wakaba 1.33 $self->{onerror}->(node => $attr, type => 'language tag:syntax error');
881 wakaba 1.17 }
882     ## TODO: RFC 3066 test
883     ## ISSUE: RFC 4646 (3066bis)?
884     }; # $HTMLLanguageTagAttrChecker
885    
886     ## "A valid media query [MQ]"
887     my $HTMLMQAttrChecker = sub {
888 wakaba 1.36 my ($self, $attr) = @_;
889     $self->{onerror}->(node => $attr, level => 'unsupported',
890     type => 'media query');
891 wakaba 1.17 ## ISSUE: What is "a valid media query"?
892     }; # $HTMLMQAttrChecker
893    
894 wakaba 1.16 my $HTMLEventHandlerAttrChecker = sub {
895 wakaba 1.36 my ($self, $attr) = @_;
896     $self->{onerror}->(node => $attr, level => 'unsupported',
897     type => 'event handler');
898 wakaba 1.16 ## TODO: MUST contain valid ECMAScript code matching the
899     ## ECMAScript |FunctionBody| production. [ECMA262]
900     ## ISSUE: MUST be ES3? E4X? ES4? JS1.x?
901     ## ISSUE: Automatic semicolon insertion does not apply?
902     ## ISSUE: Other script languages?
903     }; # $HTMLEventHandlerAttrChecker
904    
905 wakaba 1.17 my $HTMLUsemapAttrChecker = sub {
906     my ($self, $attr) = @_;
907     ## MUST be a valid hashed ID reference to a |map| element
908     my $value = $attr->value;
909     if ($value =~ s/^#//) {
910     ## ISSUE: Is |usemap="#"| conformant? (c.f. |id=""| is non-conformant.)
911     push @{$self->{usemap}}, [$value => $attr];
912     } else {
913 wakaba 1.33 $self->{onerror}->(node => $attr, type => '#idref:syntax error');
914 wakaba 1.17 }
915 wakaba 1.27 ## NOTE: Space characters in hashed ID references are conforming.
916 wakaba 1.20 ## ISSUE: UA algorithm for matching is case-insensitive; IDs only different in cases should be reported
917 wakaba 1.17 }; # $HTMLUsemapAttrChecker
918    
919     my $HTMLTargetAttrChecker = sub {
920     my ($self, $attr) = @_;
921     my $value = $attr->value;
922     if ($value =~ /^_/) {
923     $value = lc $value; ## ISSUE: ASCII case-insentitive?
924     unless ({
925     _self => 1, _parent => 1, _top => 1,
926     }->{$value}) {
927     $self->{onerror}->(node => $attr,
928     type => 'reserved browsing context name');
929     }
930     } else {
931     #$ ISSUE: An empty string is conforming?
932     }
933     }; # $HTMLTargetAttrChecker
934    
935 wakaba 1.10 my $HTMLAttrChecker = {
936     id => sub {
937 wakaba 1.17 ## NOTE: |map| has its own variant of |id=""| checker
938 wakaba 1.10 my ($self, $attr) = @_;
939     my $value = $attr->value;
940 wakaba 1.17 if (length $value > 0) {
941 wakaba 1.10 if ($self->{id}->{$value}) {
942     $self->{onerror}->(node => $attr, type => 'duplicate ID');
943 wakaba 1.37 push @{$self->{id}->{$value}}, $attr;
944 wakaba 1.10 } else {
945 wakaba 1.37 $self->{id}->{$value} = [$attr];
946 wakaba 1.10 }
947 wakaba 1.27 if ($value =~ /[\x09-\x0D\x20]/) {
948     $self->{onerror}->(node => $attr, type => 'space in ID');
949     }
950 wakaba 1.17 } else {
951     ## NOTE: MUST contain at least one character
952 wakaba 1.33 $self->{onerror}->(node => $attr, type => 'empty attribute value');
953 wakaba 1.10 }
954     },
955     title => sub {}, ## NOTE: No conformance creteria
956     lang => sub {
957 wakaba 1.35 my ($self, $attr) = @_;
958 wakaba 1.36 $self->{onerror}->(node => $attr, level => 'unsupported',
959     type => 'language tag');
960 wakaba 1.17 ## TODO: RFC 3066 or empty test
961 wakaba 1.10 ## ISSUE: RFC 4646 (3066bis)?
962 wakaba 1.35 unless ($attr->owner_document->manakai_is_html) {
963     $self->{onerror}->(node => $attr, type => 'in XML:lang');
964     }
965 wakaba 1.10 },
966     dir => $GetHTMLEnumeratedAttrChecker->({ltr => 1, rtl => 1}),
967 wakaba 1.38 class => sub {
968     my ($self, $attr) = @_;
969     my %word;
970     for my $word (grep {length $_} split /[\x09-\x0D\x20]/, $attr->value) {
971     unless ($word{$word}) {
972     $word{$word} = 1;
973     push @{$self->{return}->{class}->{$word}||=[]}, $attr;
974     } else {
975     $self->{onerror}->(node => $attr, type => 'duplicate token:'.$word);
976     }
977     }
978     },
979 wakaba 1.32 contextmenu => sub {
980     my ($self, $attr) = @_;
981     my $value = $attr->value;
982     push @{$self->{contextmenu}}, [$value => $attr];
983     ## ISSUE: "The value must be the ID of a menu element in the DOM."
984     ## What is "in the DOM"? A menu Element node that is not part
985     ## of the Document tree is in the DOM? A menu Element node that
986     ## belong to another Document tree is in the DOM?
987     },
988 wakaba 1.10 irrelevant => $GetHTMLBooleanAttrChecker->('irrelevant'),
989 wakaba 1.30 tabindex => $HTMLIntegerAttrChecker,
990 wakaba 1.10 };
991    
992 wakaba 1.16 for (qw/
993     onabort onbeforeunload onblur onchange onclick oncontextmenu
994     ondblclick ondrag ondragend ondragenter ondragleave ondragover
995     ondragstart ondrop onerror onfocus onkeydown onkeypress
996     onkeyup onload onmessage onmousedown onmousemove onmouseout
997     onmouseover onmouseup onmousewheel onresize onscroll onselect
998     onsubmit onunload
999     /) {
1000     $HTMLAttrChecker->{$_} = $HTMLEventHandlerAttrChecker;
1001     }
1002    
1003 wakaba 1.10 my $GetHTMLAttrsChecker = sub {
1004     my $element_specific_checker = shift;
1005     return sub {
1006     my ($self, $todo) = @_;
1007     for my $attr (@{$todo->{node}->attributes}) {
1008     my $attr_ns = $attr->namespace_uri;
1009     $attr_ns = '' unless defined $attr_ns;
1010     my $attr_ln = $attr->manakai_local_name;
1011     my $checker;
1012     if ($attr_ns eq '') {
1013     $checker = $element_specific_checker->{$attr_ln}
1014     || $HTMLAttrChecker->{$attr_ln};
1015     }
1016     $checker ||= $AttrChecker->{$attr_ns}->{$attr_ln}
1017     || $AttrChecker->{$attr_ns}->{''};
1018     if ($checker) {
1019 wakaba 1.32 $checker->($self, $attr, $todo);
1020 wakaba 1.10 } else {
1021 wakaba 1.33 $self->{onerror}->(node => $attr, level => 'unsupported',
1022     type => 'attribute');
1023 wakaba 1.10 ## ISSUE: No comformance createria for unknown attributes in the spec
1024     }
1025     }
1026     };
1027     }; # $GetHTMLAttrsChecker
1028 wakaba 1.9
1029     $Element->{$HTML_NS}->{''} = {
1030 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1031 wakaba 1.9 checker => $ElementDefault->{checker},
1032     };
1033    
1034 wakaba 1.1 $Element->{$HTML_NS}->{html} = {
1035 wakaba 1.24 is_root => 1,
1036 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({
1037     xmlns => sub {
1038     my ($self, $attr) = @_;
1039     my $value = $attr->value;
1040     unless ($value eq $HTML_NS) {
1041 wakaba 1.33 $self->{onerror}->(node => $attr, type => 'invalid attribute value');
1042 wakaba 1.35 }
1043     unless ($attr->owner_document->manakai_is_html) {
1044     $self->{onerror}->(node => $attr, type => 'in XML:xmlns');
1045     ## TODO: Test
1046 wakaba 1.10 }
1047     },
1048     }),
1049 wakaba 1.1 checker => sub {
1050 wakaba 1.4 my ($self, $todo) = @_;
1051     my $el = $todo->{node};
1052     my $new_todos = [];
1053 wakaba 1.1 my @nodes = (@{$el->child_nodes});
1054    
1055     my $phase = 'before head';
1056     while (@nodes) {
1057     my $node = shift @nodes;
1058 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
1059    
1060 wakaba 1.1 my $nt = $node->node_type;
1061     if ($nt == 1) {
1062 wakaba 1.2 my $node_ns = $node->namespace_uri;
1063     $node_ns = '' unless defined $node_ns;
1064     my $node_ln = $node->manakai_local_name;
1065 wakaba 1.6 my $not_allowed = $self->{minuses}->{$node_ns}->{$node_ln};
1066 wakaba 1.1 if ($phase eq 'before head') {
1067 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'head') {
1068 wakaba 1.1 $phase = 'after head';
1069 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'body') {
1070     $self->{onerror}->(node => $node, type => 'ps element missing:head');
1071 wakaba 1.1 $phase = 'after body';
1072     } else {
1073 wakaba 1.6 $not_allowed = 1;
1074 wakaba 1.1 # before head
1075     }
1076     } elsif ($phase eq 'after head') {
1077 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'body') {
1078 wakaba 1.1 $phase = 'after body';
1079     } else {
1080 wakaba 1.6 $not_allowed = 1;
1081 wakaba 1.1 # after head
1082     }
1083     } else { #elsif ($phase eq 'after body') {
1084 wakaba 1.6 $not_allowed = 1;
1085 wakaba 1.1 # after body
1086     }
1087 wakaba 1.6 $self->{onerror}->(node => $node, type => 'element not allowed')
1088     if $not_allowed;
1089 wakaba 1.30 my ($sib, $ch) = $self->_check_get_children ($node, $todo);
1090 wakaba 1.2 unshift @nodes, @$sib;
1091 wakaba 1.4 push @$new_todos, @$ch;
1092 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
1093     if ($node->data =~ /[^\x09-\x0D\x20]/) {
1094 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
1095 wakaba 1.1 }
1096     } elsif ($nt == 5) {
1097     unshift @nodes, @{$node->child_nodes};
1098     }
1099     }
1100 wakaba 1.3
1101     if ($phase eq 'before head') {
1102     $self->{onerror}->(node => $el, type => 'child element missing:head');
1103     $self->{onerror}->(node => $el, type => 'child element missing:body');
1104     } elsif ($phase eq 'after head') {
1105     $self->{onerror}->(node => $el, type => 'child element missing:body');
1106     }
1107    
1108 wakaba 1.4 return ($new_todos);
1109 wakaba 1.1 },
1110     };
1111    
1112     $Element->{$HTML_NS}->{head} = {
1113 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1114 wakaba 1.1 checker => sub {
1115 wakaba 1.4 my ($self, $todo) = @_;
1116     my $el = $todo->{node};
1117     my $new_todos = [];
1118 wakaba 1.1 my @nodes = (@{$el->child_nodes});
1119    
1120     my $has_title;
1121 wakaba 1.3 my $phase = 'initial'; # 'after charset', 'after base'
1122 wakaba 1.1 while (@nodes) {
1123     my $node = shift @nodes;
1124 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
1125    
1126 wakaba 1.1 my $nt = $node->node_type;
1127     if ($nt == 1) {
1128 wakaba 1.2 my $node_ns = $node->namespace_uri;
1129     $node_ns = '' unless defined $node_ns;
1130     my $node_ln = $node->manakai_local_name;
1131 wakaba 1.6 my $not_allowed = $self->{minuses}->{$node_ns}->{$node_ln};
1132 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'title') {
1133 wakaba 1.3 $phase = 'after base';
1134 wakaba 1.1 unless ($has_title) {
1135     $has_title = 1;
1136     } else {
1137 wakaba 1.6 $not_allowed = 1;
1138 wakaba 1.1 }
1139 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'meta') {
1140 wakaba 1.1 if ($node->has_attribute_ns (undef, 'charset')) {
1141 wakaba 1.3 if ($phase eq 'initial') {
1142     $phase = 'after charset';
1143 wakaba 1.1 } else {
1144 wakaba 1.6 $not_allowed = 1;
1145 wakaba 1.3 ## NOTE: See also |base|'s "contexts" field in the spec
1146 wakaba 1.1 }
1147     } else {
1148 wakaba 1.3 $phase = 'after base';
1149 wakaba 1.1 }
1150 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'base') {
1151 wakaba 1.3 if ($phase eq 'initial' or $phase eq 'after charset') {
1152     $phase = 'after base';
1153 wakaba 1.1 } else {
1154 wakaba 1.6 $not_allowed = 1;
1155 wakaba 1.1 }
1156 wakaba 1.28 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'style') {
1157     $phase = 'after base';
1158     if ($node->has_attribute_ns (undef, 'scoped')) {
1159     $not_allowed = 1;
1160     }
1161 wakaba 1.7 } elsif ($HTMLMetadataElements->{$node_ns}->{$node_ln}) {
1162     $phase = 'after base';
1163 wakaba 1.1 } else {
1164 wakaba 1.7 $not_allowed = 1;
1165 wakaba 1.1 }
1166 wakaba 1.6 $self->{onerror}->(node => $node, type => 'element not allowed')
1167     if $not_allowed;
1168 wakaba 1.30 my ($sib, $ch) = $self->_check_get_children ($node, $todo);
1169 wakaba 1.2 unshift @nodes, @$sib;
1170 wakaba 1.4 push @$new_todos, @$ch;
1171 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
1172     if ($node->data =~ /[^\x09-\x0D\x20]/) {
1173 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
1174 wakaba 1.1 }
1175     } elsif ($nt == 5) {
1176     unshift @nodes, @{$node->child_nodes};
1177     }
1178     }
1179     unless ($has_title) {
1180 wakaba 1.3 $self->{onerror}->(node => $el, type => 'child element missing:title');
1181 wakaba 1.1 }
1182 wakaba 1.4 return ($new_todos);
1183 wakaba 1.1 },
1184     };
1185    
1186     $Element->{$HTML_NS}->{title} = {
1187 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1188 wakaba 1.1 checker => $HTMLTextChecker,
1189     };
1190    
1191     $Element->{$HTML_NS}->{base} = {
1192 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({
1193 wakaba 1.11 href => $HTMLURIAttrChecker,
1194 wakaba 1.17 target => $HTMLTargetAttrChecker,
1195 wakaba 1.10 }),
1196 wakaba 1.1 checker => $HTMLEmptyChecker,
1197     };
1198    
1199     $Element->{$HTML_NS}->{link} = {
1200 wakaba 1.16 attrs_checker => sub {
1201     my ($self, $todo) = @_;
1202     $GetHTMLAttrsChecker->({
1203     href => $HTMLURIAttrChecker,
1204 wakaba 1.20 rel => sub { $HTMLLinkTypesAttrChecker->(0, @_) },
1205 wakaba 1.17 media => $HTMLMQAttrChecker,
1206     hreflang => $HTMLLanguageTagAttrChecker,
1207 wakaba 1.16 type => $HTMLIMTAttrChecker,
1208     ## NOTE: Though |title| has special semantics,
1209     ## syntactically same as the |title| as global attribute.
1210     })->($self, $todo);
1211     unless ($todo->{node}->has_attribute_ns (undef, 'href')) {
1212     $self->{onerror}->(node => $todo->{node},
1213     type => 'attribute missing:href');
1214     }
1215     unless ($todo->{node}->has_attribute_ns (undef, 'rel')) {
1216     $self->{onerror}->(node => $todo->{node},
1217     type => 'attribute missing:rel');
1218     }
1219     },
1220 wakaba 1.1 checker => $HTMLEmptyChecker,
1221     };
1222    
1223     $Element->{$HTML_NS}->{meta} = {
1224 wakaba 1.10 attrs_checker => sub {
1225     my ($self, $todo) = @_;
1226     my $name_attr;
1227     my $http_equiv_attr;
1228     my $charset_attr;
1229     my $content_attr;
1230     for my $attr (@{$todo->{node}->attributes}) {
1231     my $attr_ns = $attr->namespace_uri;
1232     $attr_ns = '' unless defined $attr_ns;
1233     my $attr_ln = $attr->manakai_local_name;
1234     my $checker;
1235     if ($attr_ns eq '') {
1236     if ($attr_ln eq 'content') {
1237     $content_attr = $attr;
1238     $checker = 1;
1239     } elsif ($attr_ln eq 'name') {
1240     $name_attr = $attr;
1241     $checker = 1;
1242     } elsif ($attr_ln eq 'http-equiv') {
1243     $http_equiv_attr = $attr;
1244     $checker = 1;
1245     } elsif ($attr_ln eq 'charset') {
1246     $charset_attr = $attr;
1247     $checker = 1;
1248     } else {
1249     $checker = $HTMLAttrChecker->{$attr_ln}
1250     || $AttrChecker->{$attr_ns}->{$attr_ln}
1251     || $AttrChecker->{$attr_ns}->{''};
1252     }
1253     } else {
1254     $checker ||= $AttrChecker->{$attr_ns}->{$attr_ln}
1255     || $AttrChecker->{$attr_ns}->{''};
1256     }
1257     if ($checker) {
1258     $checker->($self, $attr) if ref $checker;
1259     } else {
1260 wakaba 1.33 $self->{onerror}->(node => $attr, level => 'unsupported',
1261     type => 'attribute');
1262 wakaba 1.10 ## ISSUE: No comformance createria for unknown attributes in the spec
1263     }
1264     }
1265    
1266     if (defined $name_attr) {
1267     if (defined $http_equiv_attr) {
1268     $self->{onerror}->(node => $http_equiv_attr,
1269     type => 'attribute not allowed');
1270     } elsif (defined $charset_attr) {
1271     $self->{onerror}->(node => $charset_attr,
1272     type => 'attribute not allowed');
1273     }
1274     my $metadata_name = $name_attr->value;
1275     my $metadata_value;
1276     if (defined $content_attr) {
1277     $metadata_value = $content_attr->value;
1278     } else {
1279     $self->{onerror}->(node => $todo->{node},
1280     type => 'attribute missing:content');
1281     $metadata_value = '';
1282     }
1283     } elsif (defined $http_equiv_attr) {
1284     if (defined $charset_attr) {
1285     $self->{onerror}->(node => $charset_attr,
1286     type => 'attribute not allowed');
1287     }
1288     unless (defined $content_attr) {
1289     $self->{onerror}->(node => $todo->{node},
1290     type => 'attribute missing:content');
1291     }
1292     } elsif (defined $charset_attr) {
1293     if (defined $content_attr) {
1294     $self->{onerror}->(node => $content_attr,
1295     type => 'attribute not allowed');
1296     }
1297     } else {
1298     if (defined $content_attr) {
1299     $self->{onerror}->(node => $content_attr,
1300     type => 'attribute not allowed');
1301     $self->{onerror}->(node => $todo->{node},
1302     type => 'attribute missing:name|http-equiv');
1303     } else {
1304     $self->{onerror}->(node => $todo->{node},
1305     type => 'attribute missing:name|http-equiv|charset');
1306     }
1307     }
1308    
1309     ## TODO: metadata conformance
1310    
1311     ## TODO: pragma conformance
1312     if (defined $http_equiv_attr) { ## An enumerated attribute
1313     my $keyword = lc $http_equiv_attr->value; ## TODO: ascii case?
1314     if ({
1315     'refresh' => 1,
1316     'default-style' => 1,
1317     }->{$keyword}) {
1318     #
1319     } else {
1320     $self->{onerror}->(node => $http_equiv_attr,
1321 wakaba 1.33 type => 'enumerated:invalid');
1322 wakaba 1.10 }
1323     }
1324    
1325 wakaba 1.35 if (defined $charset_attr) {
1326     unless ($todo->{node}->owner_document->manakai_is_html) {
1327     $self->{onerror}->(node => $charset_attr,
1328     type => 'in XML:charset');
1329     }
1330     ## TODO: charset
1331     }
1332 wakaba 1.10 },
1333 wakaba 1.1 checker => $HTMLEmptyChecker,
1334     };
1335    
1336 wakaba 1.3 $Element->{$HTML_NS}->{style} = {
1337 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({
1338 wakaba 1.15 type => $HTMLIMTAttrChecker, ## TODO: MUST be a styling language
1339 wakaba 1.17 media => $HTMLMQAttrChecker,
1340 wakaba 1.10 scoped => $GetHTMLBooleanAttrChecker->('scoped'),
1341     ## NOTE: |title| has special semantics for |style|s, but is syntactically
1342     ## not different
1343     }),
1344 wakaba 1.36 checker => sub {
1345     ## NOTE: |html:style| has no conformance creteria on content model
1346     my ($self, $todo) = @_;
1347     my $type = $todo->{node}->get_attribute_ns (undef, 'type');
1348     $type = 'text/css' unless defined $type;
1349     $self->{onerror}->(node => $todo->{node}, level => 'unsupported',
1350     type => 'style:'.$type); ## TODO: $type normalization
1351     return $AnyChecker->($self, $todo);
1352     },
1353 wakaba 1.3 };
1354 wakaba 1.1
1355     $Element->{$HTML_NS}->{body} = {
1356 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1357 wakaba 1.1 checker => $HTMLBlockChecker,
1358     };
1359    
1360     $Element->{$HTML_NS}->{section} = {
1361 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1362 wakaba 1.1 checker => $HTMLStylableBlockChecker,
1363     };
1364    
1365     $Element->{$HTML_NS}->{nav} = {
1366 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1367 wakaba 1.1 checker => $HTMLBlockOrInlineChecker,
1368     };
1369    
1370     $Element->{$HTML_NS}->{article} = {
1371 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1372 wakaba 1.1 checker => $HTMLStylableBlockChecker,
1373     };
1374    
1375     $Element->{$HTML_NS}->{blockquote} = {
1376 wakaba 1.11 attrs_checker => $GetHTMLAttrsChecker->({
1377     cite => $HTMLURIAttrChecker,
1378     }),
1379 wakaba 1.1 checker => $HTMLBlockChecker,
1380     };
1381    
1382     $Element->{$HTML_NS}->{aside} = {
1383 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1384 wakaba 1.2 checker => $GetHTMLZeroOrMoreThenBlockOrInlineChecker->($HTML_NS, 'style'),
1385 wakaba 1.1 };
1386    
1387     $Element->{$HTML_NS}->{h1} = {
1388 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1389 wakaba 1.30 checker => sub {
1390     my ($self, $todo) = @_;
1391     $todo->{flag}->{has_heading}->[0] = 1;
1392     return $HTMLSignificantStrictlyInlineChecker->($self, $todo);
1393     },
1394 wakaba 1.1 };
1395    
1396     $Element->{$HTML_NS}->{h2} = {
1397 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1398 wakaba 1.30 checker => $Element->{$HTML_NS}->{h1}->{checker},
1399 wakaba 1.1 };
1400    
1401     $Element->{$HTML_NS}->{h3} = {
1402 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1403 wakaba 1.30 checker => $Element->{$HTML_NS}->{h1}->{checker},
1404 wakaba 1.1 };
1405    
1406     $Element->{$HTML_NS}->{h4} = {
1407 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1408 wakaba 1.30 checker => $Element->{$HTML_NS}->{h1}->{checker},
1409 wakaba 1.1 };
1410    
1411     $Element->{$HTML_NS}->{h5} = {
1412 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1413 wakaba 1.30 checker => $Element->{$HTML_NS}->{h1}->{checker},
1414 wakaba 1.1 };
1415    
1416     $Element->{$HTML_NS}->{h6} = {
1417 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1418 wakaba 1.30 checker => $Element->{$HTML_NS}->{h1}->{checker},
1419 wakaba 1.1 };
1420    
1421 wakaba 1.30 $Element->{$HTML_NS}->{header} = {
1422     attrs_checker => $GetHTMLAttrsChecker->({}),
1423     checker => sub {
1424     my ($self, $todo) = @_;
1425     my $old_flag = $todo->{flag}->{has_heading} || [];
1426     my $new_flag = [];
1427     local $todo->{flag}->{has_heading} = $new_flag;
1428     my $node = $todo->{node};
1429    
1430     my $end = $self->_add_minuses
1431     ({$HTML_NS => {qw/header 1 footer 1/}},
1432     $HTMLSectioningElements);
1433     my ($new_todos, $ch) = $HTMLBlockChecker->($self, $todo);
1434     push @$new_todos, $end,
1435     {type => 'code', code => sub {
1436     if ($new_flag->[0]) {
1437     $old_flag->[0] = 1;
1438     } else {
1439     $self->{onerror}->(node => $node, type => 'element missing:hn');
1440     }
1441     }};
1442     return ($new_todos, $ch);
1443     },
1444     };
1445 wakaba 1.1
1446 wakaba 1.2 $Element->{$HTML_NS}->{footer} = {
1447 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1448 wakaba 1.2 checker => sub { ## block -hn -header -footer -sectioning or inline
1449 wakaba 1.4 my ($self, $todo) = @_;
1450     my $el = $todo->{node};
1451     my $new_todos = [];
1452 wakaba 1.2 my @nodes = (@{$el->child_nodes});
1453    
1454     my $content = 'block-or-inline'; # or 'block' or 'inline'
1455     my @block_not_inline;
1456     while (@nodes) {
1457     my $node = shift @nodes;
1458     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
1459    
1460     my $nt = $node->node_type;
1461     if ($nt == 1) {
1462     my $node_ns = $node->namespace_uri;
1463     $node_ns = '' unless defined $node_ns;
1464     my $node_ln = $node->manakai_local_name;
1465 wakaba 1.6 my $not_allowed;
1466 wakaba 1.2 if ($self->{minuses}->{$node_ns}->{$node_ln}) {
1467 wakaba 1.6 $not_allowed = 1;
1468 wakaba 1.2 } elsif ($node_ns eq $HTML_NS and
1469     {
1470     qw/h1 1 h2 1 h3 1 h4 1 h5 1 h6 1 header 1 footer 1/
1471     }->{$node_ln}) {
1472 wakaba 1.6 $not_allowed = 1;
1473 wakaba 1.2 } elsif ($HTMLSectioningElements->{$node_ns}->{$node_ln}) {
1474 wakaba 1.6 $not_allowed = 1;
1475 wakaba 1.2 }
1476     if ($content eq 'block') {
1477 wakaba 1.7 $not_allowed = 1
1478     unless $HTMLBlockLevelElements->{$node_ns}->{$node_ln};
1479 wakaba 1.2 } elsif ($content eq 'inline') {
1480 wakaba 1.7 $not_allowed = 1
1481     unless $HTMLStrictlyInlineLevelElements->{$node_ns}->{$node_ln} or
1482     $HTMLStructuredInlineLevelElements->{$node_ns}->{$node_ln};
1483 wakaba 1.2 } else {
1484 wakaba 1.7 my $is_block = $HTMLBlockLevelElements->{$node_ns}->{$node_ln};
1485     my $is_inline
1486     = $HTMLStrictlyInlineLevelElements->{$node_ns}->{$node_ln} ||
1487     $HTMLStructuredInlineLevelElements->{$node_ns}->{$node_ln};
1488 wakaba 1.2
1489 wakaba 1.6 push @block_not_inline, $node
1490     if $is_block and not $is_inline and not $not_allowed;
1491 wakaba 1.2 unless ($is_block) {
1492     $content = 'inline';
1493     for (@block_not_inline) {
1494     $self->{onerror}->(node => $_, type => 'element not allowed');
1495     }
1496 wakaba 1.6 $not_allowed = 1 unless $is_inline;
1497 wakaba 1.2 }
1498     }
1499 wakaba 1.6 $self->{onerror}->(node => $node, type => 'element not allowed')
1500     if $not_allowed;
1501 wakaba 1.30 my ($sib, $ch) = $self->_check_get_children ($node, $todo);
1502 wakaba 1.2 unshift @nodes, @$sib;
1503 wakaba 1.4 push @$new_todos, @$ch;
1504 wakaba 1.2 } elsif ($nt == 3 or $nt == 4) {
1505     if ($node->data =~ /[^\x09-\x0D\x20]/) {
1506     if ($content eq 'block') {
1507     $self->{onerror}->(node => $node, type => 'character not allowed');
1508     } else {
1509     $content = 'inline';
1510     for (@block_not_inline) {
1511     $self->{onerror}->(node => $_, type => 'element not allowed');
1512     }
1513     }
1514     }
1515     } elsif ($nt == 5) {
1516     unshift @nodes, @{$node->child_nodes};
1517     }
1518     }
1519    
1520     my $end = $self->_add_minuses
1521     ({$HTML_NS => {qw/h1 1 h2 1 h3 1 h4 1 h5 1 h6 1/}},
1522     $HTMLSectioningElements);
1523 wakaba 1.4 push @$new_todos, $end;
1524 wakaba 1.2
1525 wakaba 1.4 if ($content eq 'inline') {
1526     for (@$new_todos) {
1527     $_->{inline} = 1;
1528     }
1529     }
1530    
1531     return ($new_todos);
1532 wakaba 1.2 },
1533     };
1534 wakaba 1.1
1535     $Element->{$HTML_NS}->{address} = {
1536 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1537 wakaba 1.1 checker => $HTMLInlineChecker,
1538     };
1539    
1540     $Element->{$HTML_NS}->{p} = {
1541 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1542 wakaba 1.1 checker => $HTMLSignificantInlineChecker,
1543     };
1544    
1545     $Element->{$HTML_NS}->{hr} = {
1546 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1547 wakaba 1.1 checker => $HTMLEmptyChecker,
1548     };
1549    
1550     $Element->{$HTML_NS}->{br} = {
1551 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1552 wakaba 1.1 checker => $HTMLEmptyChecker,
1553     };
1554    
1555     $Element->{$HTML_NS}->{dialog} = {
1556 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1557 wakaba 1.1 checker => sub {
1558 wakaba 1.4 my ($self, $todo) = @_;
1559     my $el = $todo->{node};
1560     my $new_todos = [];
1561 wakaba 1.1 my @nodes = (@{$el->child_nodes});
1562    
1563     my $phase = 'before dt';
1564     while (@nodes) {
1565     my $node = shift @nodes;
1566 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
1567    
1568 wakaba 1.1 my $nt = $node->node_type;
1569     if ($nt == 1) {
1570 wakaba 1.8 my $node_ns = $node->namespace_uri;
1571     $node_ns = '' unless defined $node_ns;
1572     my $node_ln = $node->manakai_local_name;
1573 wakaba 1.2 ## NOTE: |minuses| list is not checked since redundant
1574 wakaba 1.1 if ($phase eq 'before dt') {
1575 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'dt') {
1576 wakaba 1.1 $phase = 'before dd';
1577 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'dd') {
1578 wakaba 1.2 $self->{onerror}
1579 wakaba 1.3 ->(node => $node, type => 'ps element missing:dt');
1580 wakaba 1.1 $phase = 'before dt';
1581     } else {
1582 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
1583 wakaba 1.1 }
1584     } else { # before dd
1585 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'dd') {
1586 wakaba 1.1 $phase = 'before dt';
1587 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'dt') {
1588 wakaba 1.2 $self->{onerror}
1589 wakaba 1.3 ->(node => $node, type => 'ps element missing:dd');
1590 wakaba 1.1 $phase = 'before dd';
1591     } else {
1592 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
1593 wakaba 1.1 }
1594     }
1595 wakaba 1.30 my ($sib, $ch) = $self->_check_get_children ($node, $todo);
1596 wakaba 1.2 unshift @nodes, @$sib;
1597 wakaba 1.4 push @$new_todos, @$ch;
1598 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
1599     if ($node->data =~ /[^\x09-\x0D\x20]/) {
1600 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
1601 wakaba 1.1 }
1602     } elsif ($nt == 5) {
1603     unshift @nodes, @{$node->child_nodes};
1604     }
1605     }
1606     if ($phase eq 'before dd') {
1607 wakaba 1.3 $self->{onerror}->(node => $el, type => 'ps element missing:dd');
1608 wakaba 1.1 }
1609 wakaba 1.4 return ($new_todos);
1610 wakaba 1.1 },
1611     };
1612    
1613     $Element->{$HTML_NS}->{pre} = {
1614 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1615 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1616     };
1617    
1618     $Element->{$HTML_NS}->{ol} = {
1619 wakaba 1.11 attrs_checker => $GetHTMLAttrsChecker->({
1620     start => $HTMLIntegerAttrChecker,
1621     }),
1622 wakaba 1.1 checker => sub {
1623 wakaba 1.4 my ($self, $todo) = @_;
1624     my $el = $todo->{node};
1625     my $new_todos = [];
1626 wakaba 1.1 my @nodes = (@{$el->child_nodes});
1627    
1628     while (@nodes) {
1629     my $node = shift @nodes;
1630 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
1631    
1632 wakaba 1.1 my $nt = $node->node_type;
1633     if ($nt == 1) {
1634 wakaba 1.8 my $node_ns = $node->namespace_uri;
1635     $node_ns = '' unless defined $node_ns;
1636     my $node_ln = $node->manakai_local_name;
1637 wakaba 1.2 ## NOTE: |minuses| list is not checked since redundant
1638 wakaba 1.8 unless ($node_ns eq $HTML_NS and $node_ln eq 'li') {
1639 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
1640 wakaba 1.1 }
1641 wakaba 1.30 my ($sib, $ch) = $self->_check_get_children ($node, $todo);
1642 wakaba 1.2 unshift @nodes, @$sib;
1643 wakaba 1.4 push @$new_todos, @$ch;
1644 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
1645     if ($node->data =~ /[^\x09-\x0D\x20]/) {
1646 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
1647 wakaba 1.1 }
1648     } elsif ($nt == 5) {
1649     unshift @nodes, @{$node->child_nodes};
1650     }
1651     }
1652 wakaba 1.4
1653     if ($todo->{inline}) {
1654     for (@$new_todos) {
1655     $_->{inline} = 1;
1656     }
1657     }
1658     return ($new_todos);
1659 wakaba 1.1 },
1660     };
1661    
1662     $Element->{$HTML_NS}->{ul} = {
1663 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1664 wakaba 1.1 checker => $Element->{$HTML_NS}->{ol}->{checker},
1665     };
1666    
1667 wakaba 1.5
1668     $Element->{$HTML_NS}->{li} = {
1669 wakaba 1.11 attrs_checker => $GetHTMLAttrsChecker->({
1670     start => sub {
1671     my ($self, $attr) = @_;
1672     my $parent = $attr->owner_element->manakai_parent_element;
1673     if (defined $parent) {
1674     my $parent_ns = $parent->namespace_uri;
1675     $parent_ns = '' unless defined $parent_ns;
1676     my $parent_ln = $parent->manakai_local_name;
1677     unless ($parent_ns eq $HTML_NS and $parent_ln eq 'ol') {
1678 wakaba 1.33 $self->{onerror}->(node => $attr, level => 'unsupported',
1679     type => 'attribute');
1680 wakaba 1.11 }
1681     }
1682     $HTMLIntegerAttrChecker->($self, $attr);
1683     },
1684     }),
1685 wakaba 1.5 checker => sub {
1686     my ($self, $todo) = @_;
1687     if ($todo->{inline}) {
1688     return $HTMLInlineChecker->($self, $todo);
1689     } else {
1690     return $HTMLBlockOrInlineChecker->($self, $todo);
1691     }
1692     },
1693     };
1694 wakaba 1.1
1695     $Element->{$HTML_NS}->{dl} = {
1696 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1697 wakaba 1.1 checker => sub {
1698 wakaba 1.4 my ($self, $todo) = @_;
1699     my $el = $todo->{node};
1700     my $new_todos = [];
1701 wakaba 1.1 my @nodes = (@{$el->child_nodes});
1702    
1703     my $phase = 'before dt';
1704     while (@nodes) {
1705     my $node = shift @nodes;
1706 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
1707    
1708 wakaba 1.1 my $nt = $node->node_type;
1709     if ($nt == 1) {
1710 wakaba 1.8 my $node_ns = $node->namespace_uri;
1711     $node_ns = '' unless defined $node_ns;
1712     my $node_ln = $node->manakai_local_name;
1713 wakaba 1.2 ## NOTE: |minuses| list is not checked since redundant
1714 wakaba 1.1 if ($phase eq 'in dds') {
1715 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'dd') {
1716 wakaba 1.1 #$phase = 'in dds';
1717 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'dt') {
1718 wakaba 1.1 $phase = 'in dts';
1719     } else {
1720 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
1721 wakaba 1.1 }
1722     } elsif ($phase eq 'in dts') {
1723 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'dt') {
1724 wakaba 1.1 #$phase = 'in dts';
1725 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'dd') {
1726 wakaba 1.1 $phase = 'in dds';
1727     } else {
1728 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
1729 wakaba 1.1 }
1730     } else { # before dt
1731 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'dt') {
1732 wakaba 1.1 $phase = 'in dts';
1733 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'dd') {
1734 wakaba 1.2 $self->{onerror}
1735 wakaba 1.3 ->(node => $node, type => 'ps element missing:dt');
1736 wakaba 1.1 $phase = 'in dds';
1737     } else {
1738 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
1739 wakaba 1.1 }
1740     }
1741 wakaba 1.30 my ($sib, $ch) = $self->_check_get_children ($node, $todo);
1742 wakaba 1.2 unshift @nodes, @$sib;
1743 wakaba 1.4 push @$new_todos, @$ch;
1744 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
1745     if ($node->data =~ /[^\x09-\x0D\x20]/) {
1746 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
1747 wakaba 1.1 }
1748     } elsif ($nt == 5) {
1749     unshift @nodes, @{$node->child_nodes};
1750     }
1751     }
1752     if ($phase eq 'in dts') {
1753 wakaba 1.3 $self->{onerror}->(node => $el, type => 'ps element missing:dd');
1754 wakaba 1.1 }
1755 wakaba 1.4
1756     if ($todo->{inline}) {
1757     for (@$new_todos) {
1758     $_->{inline} = 1;
1759     }
1760     }
1761     return ($new_todos);
1762 wakaba 1.1 },
1763     };
1764    
1765     $Element->{$HTML_NS}->{dt} = {
1766 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1767 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1768     };
1769    
1770 wakaba 1.4 $Element->{$HTML_NS}->{dd} = {
1771 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1772 wakaba 1.5 checker => $Element->{$HTML_NS}->{li}->{checker},
1773 wakaba 1.4 };
1774 wakaba 1.1
1775 wakaba 1.6 $Element->{$HTML_NS}->{a} = {
1776 wakaba 1.17 attrs_checker => sub {
1777     my ($self, $todo) = @_;
1778 wakaba 1.15 my %attr;
1779 wakaba 1.17 for my $attr (@{$todo->{node}->attributes}) {
1780     my $attr_ns = $attr->namespace_uri;
1781     $attr_ns = '' unless defined $attr_ns;
1782     my $attr_ln = $attr->manakai_local_name;
1783     my $checker;
1784     if ($attr_ns eq '') {
1785     $checker = {
1786     target => $HTMLTargetAttrChecker,
1787     href => $HTMLURIAttrChecker,
1788     ping => $HTMLSpaceURIsAttrChecker,
1789 wakaba 1.20 rel => sub { $HTMLLinkTypesAttrChecker->(1, @_) },
1790 wakaba 1.17 media => $HTMLMQAttrChecker,
1791     hreflang => $HTMLLanguageTagAttrChecker,
1792     type => $HTMLIMTAttrChecker,
1793     }->{$attr_ln};
1794     if ($checker) {
1795     $attr{$attr_ln} = $attr;
1796     } else {
1797     $checker = $HTMLAttrChecker->{$attr_ln};
1798     }
1799     }
1800     $checker ||= $AttrChecker->{$attr_ns}->{$attr_ln}
1801     || $AttrChecker->{$attr_ns}->{''};
1802     if ($checker) {
1803     $checker->($self, $attr) if ref $checker;
1804     } else {
1805 wakaba 1.33 $self->{onerror}->(node => $attr, level => 'unsupported',
1806     type => 'attribute');
1807 wakaba 1.17 ## ISSUE: No comformance createria for unknown attributes in the spec
1808     }
1809     }
1810    
1811     unless (defined $attr{href}) {
1812     for (qw/target ping rel media hreflang type/) {
1813     if (defined $attr{$_}) {
1814     $self->{onerror}->(node => $attr{$_},
1815     type => 'attribute not allowed');
1816 wakaba 1.15 }
1817     }
1818 wakaba 1.17 }
1819 wakaba 1.15 },
1820 wakaba 1.6 checker => sub {
1821     my ($self, $todo) = @_;
1822    
1823     my $end = $self->_add_minuses ($HTMLInteractiveElements);
1824 wakaba 1.32 my ($new_todos, $ch)
1825 wakaba 1.6 = $HTMLSignificantInlineOrStrictlyInlineChecker->($self, $todo);
1826 wakaba 1.32 push @$new_todos, $end;
1827    
1828     $_->{flag}->{has_a} = 1 for @$new_todos;
1829    
1830     return ($new_todos, $ch);
1831 wakaba 1.6 },
1832     };
1833 wakaba 1.1
1834 wakaba 1.4 $Element->{$HTML_NS}->{q} = {
1835 wakaba 1.11 attrs_checker => $GetHTMLAttrsChecker->({
1836     cite => $HTMLURIAttrChecker,
1837     }),
1838 wakaba 1.4 checker => $HTMLInlineOrStrictlyInlineChecker,
1839     };
1840 wakaba 1.1
1841     $Element->{$HTML_NS}->{cite} = {
1842 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1843 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1844     };
1845    
1846 wakaba 1.4 $Element->{$HTML_NS}->{em} = {
1847 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1848 wakaba 1.4 checker => $HTMLInlineOrStrictlyInlineChecker,
1849     };
1850    
1851     $Element->{$HTML_NS}->{strong} = {
1852 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1853 wakaba 1.4 checker => $HTMLInlineOrStrictlyInlineChecker,
1854     };
1855 wakaba 1.1
1856 wakaba 1.4 $Element->{$HTML_NS}->{small} = {
1857 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1858 wakaba 1.4 checker => $HTMLInlineOrStrictlyInlineChecker,
1859     };
1860    
1861     $Element->{$HTML_NS}->{m} = {
1862 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1863 wakaba 1.4 checker => $HTMLInlineOrStrictlyInlineChecker,
1864     };
1865    
1866 wakaba 1.30 $Element->{$HTML_NS}->{dfn} = {
1867 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1868 wakaba 1.4 checker => sub {
1869     my ($self, $todo) = @_;
1870    
1871     my $end = $self->_add_minuses ({$HTML_NS => {dfn => 1}});
1872     my ($sib, $ch) = $HTMLStrictlyInlineChecker->($self, $todo);
1873     push @$sib, $end;
1874 wakaba 1.30
1875     my $node = $todo->{node};
1876     my $term = $node->get_attribute_ns (undef, 'title');
1877     unless (defined $term) {
1878     for my $child (@{$node->child_nodes}) {
1879     if ($child->node_type == 1) { # ELEMENT_NODE
1880     if (defined $term) {
1881     undef $term;
1882     last;
1883     } elsif ($child->manakai_local_name eq 'abbr') {
1884     my $nsuri = $child->namespace_uri;
1885     if (defined $nsuri and $nsuri eq $HTML_NS) {
1886     my $attr = $child->get_attribute_node_ns (undef, 'title');
1887     if ($attr) {
1888     $term = $attr->value;
1889     }
1890     }
1891     }
1892     } elsif ($child->node_type == 3 or $child->node_type == 4) {
1893     ## TEXT_NODE or CDATA_SECTION_NODE
1894     if ($child->data =~ /\A[\x09-\x0D\x20]+\z/) { # Inter-element whitespace
1895     next;
1896     }
1897     undef $term;
1898     last;
1899     }
1900     }
1901     unless (defined $term) {
1902     $term = $node->text_content;
1903     }
1904     }
1905     if ($self->{term}->{$term}) {
1906     $self->{onerror}->(node => $node, type => 'duplicate term');
1907 wakaba 1.34 push @{$self->{term}->{$term}}, $node;
1908 wakaba 1.30 } else {
1909 wakaba 1.34 $self->{term}->{$term} = [$node];
1910 wakaba 1.30 }
1911    
1912 wakaba 1.4 return ($sib, $ch);
1913     },
1914     };
1915 wakaba 1.1
1916     $Element->{$HTML_NS}->{abbr} = {
1917 wakaba 1.11 attrs_checker => $GetHTMLAttrsChecker->({
1918     ## NOTE: |title| has special semantics for |abbr|s, but is syntactically
1919     ## not different. The spec says that the |title| MAY be omitted
1920     ## if there is a |dfn| whose defining term is the abbreviation,
1921     ## but it does not prohibit |abbr| w/o |title| in other cases.
1922     }),
1923 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1924     };
1925    
1926 wakaba 1.11 $Element->{$HTML_NS}->{time} = { ## TODO: validate content
1927     attrs_checker => $GetHTMLAttrsChecker->({}), ## TODO: datetime
1928 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1929     };
1930    
1931 wakaba 1.11 $Element->{$HTML_NS}->{meter} = { ## TODO: "The recommended way of giving the value is to include it as contents of the element"
1932     attrs_checker => $GetHTMLAttrsChecker->({
1933     value => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
1934     min => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
1935     low => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
1936     high => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
1937     max => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
1938     optimum => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
1939     }),
1940 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1941     };
1942    
1943 wakaba 1.11 $Element->{$HTML_NS}->{progress} = { ## TODO: recommended to use content
1944     attrs_checker => $GetHTMLAttrsChecker->({
1945     value => $GetHTMLFloatingPointNumberAttrChecker->(sub { shift >= 0 }),
1946     max => $GetHTMLFloatingPointNumberAttrChecker->(sub { shift > 0 }),
1947     }),
1948 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1949     };
1950    
1951 wakaba 1.4 $Element->{$HTML_NS}->{code} = {
1952 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1953 wakaba 1.12 ## NOTE: Though |title| has special semantics,
1954     ## syntatically same as the |title| as global attribute.
1955 wakaba 1.4 checker => $HTMLInlineOrStrictlyInlineChecker,
1956     };
1957 wakaba 1.1
1958     $Element->{$HTML_NS}->{var} = {
1959 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1960 wakaba 1.12 ## NOTE: Though |title| has special semantics,
1961     ## syntatically same as the |title| as global attribute.
1962 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1963     };
1964    
1965 wakaba 1.4 $Element->{$HTML_NS}->{samp} = {
1966 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1967 wakaba 1.12 ## NOTE: Though |title| has special semantics,
1968     ## syntatically same as the |title| as global attribute.
1969 wakaba 1.4 checker => $HTMLInlineOrStrictlyInlineChecker,
1970     };
1971 wakaba 1.1
1972     $Element->{$HTML_NS}->{kbd} = {
1973 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1974 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1975     };
1976    
1977     $Element->{$HTML_NS}->{sub} = {
1978 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1979 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1980     };
1981    
1982     $Element->{$HTML_NS}->{sup} = {
1983 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1984 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1985     };
1986    
1987 wakaba 1.4 $Element->{$HTML_NS}->{span} = {
1988 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1989 wakaba 1.12 ## NOTE: Though |title| has special semantics,
1990     ## syntatically same as the |title| as global attribute.
1991 wakaba 1.4 checker => $HTMLInlineOrStrictlyInlineChecker,
1992     };
1993 wakaba 1.1
1994     $Element->{$HTML_NS}->{i} = {
1995 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1996 wakaba 1.12 ## NOTE: Though |title| has special semantics,
1997     ## syntatically same as the |title| as global attribute.
1998 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1999     };
2000    
2001     $Element->{$HTML_NS}->{b} = {
2002 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2003 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
2004     };
2005    
2006     $Element->{$HTML_NS}->{bdo} = {
2007 wakaba 1.12 attrs_checker => sub {
2008     my ($self, $todo) = @_;
2009     $GetHTMLAttrsChecker->({})->($self, $todo);
2010     unless ($todo->{node}->has_attribute_ns (undef, 'dir')) {
2011     $self->{onerror}->(node => $todo->{node}, type => 'attribute missing:dir');
2012     }
2013     },
2014     ## ISSUE: The spec does not directly say that |dir| is a enumerated attr.
2015 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
2016     };
2017    
2018     $Element->{$HTML_NS}->{ins} = {
2019 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2020     cite => $HTMLURIAttrChecker,
2021 wakaba 1.30 datetime => $HTMLDatetimeAttrChecker,
2022 wakaba 1.12 }),
2023 wakaba 1.1 checker => $HTMLTransparentChecker,
2024     };
2025    
2026     $Element->{$HTML_NS}->{del} = {
2027 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2028     cite => $HTMLURIAttrChecker,
2029 wakaba 1.30 datetime => $HTMLDatetimeAttrChecker,
2030 wakaba 1.12 }),
2031 wakaba 1.1 checker => sub {
2032 wakaba 1.4 my ($self, $todo) = @_;
2033 wakaba 1.1
2034 wakaba 1.4 my $parent = $todo->{node}->manakai_parent_element;
2035 wakaba 1.1 if (defined $parent) {
2036     my $nsuri = $parent->namespace_uri;
2037     $nsuri = '' unless defined $nsuri;
2038     my $ln = $parent->manakai_local_name;
2039     my $eldef = $Element->{$nsuri}->{$ln} ||
2040     $Element->{$nsuri}->{''} ||
2041     $ElementDefault;
2042 wakaba 1.4 return $eldef->{checker}->($self, $todo);
2043 wakaba 1.1 } else {
2044 wakaba 1.4 return $HTMLBlockOrInlineChecker->($self, $todo);
2045 wakaba 1.1 }
2046     },
2047     };
2048    
2049     ## TODO: figure
2050    
2051     $Element->{$HTML_NS}->{img} = {
2052 wakaba 1.17 attrs_checker => sub {
2053     my ($self, $todo) = @_;
2054     $GetHTMLAttrsChecker->({
2055     alt => sub { }, ## NOTE: No syntactical requirement
2056     src => $HTMLURIAttrChecker,
2057     usemap => $HTMLUsemapAttrChecker,
2058 wakaba 1.32 ismap => sub {
2059     my ($self, $attr, $parent_todo) = @_;
2060     if (not $todo->{flag}->{has_a}) {
2061     $self->{onerror}->(node => $attr, type => 'attribute not allowed');
2062     }
2063     $GetHTMLBooleanAttrChecker->('ismap')->($self, $attr, $parent_todo);
2064     },
2065 wakaba 1.17 ## TODO: height
2066     ## TODO: width
2067     })->($self, $todo);
2068     unless ($todo->{node}->has_attribute_ns (undef, 'alt')) {
2069     $self->{onerror}->(node => $todo->{node}, type => 'attribute missing:alt');
2070     }
2071     unless ($todo->{node}->has_attribute_ns (undef, 'src')) {
2072     $self->{onerror}->(node => $todo->{node}, type => 'attribute missing:src');
2073     }
2074     },
2075 wakaba 1.1 checker => $HTMLEmptyChecker,
2076     };
2077    
2078     $Element->{$HTML_NS}->{iframe} = {
2079 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2080     src => $HTMLURIAttrChecker,
2081     }),
2082 wakaba 1.1 checker => $HTMLTextChecker,
2083     };
2084    
2085     $Element->{$HTML_NS}->{embed} = {
2086 wakaba 1.16 attrs_checker => sub {
2087     my ($self, $todo) = @_;
2088     my $has_src;
2089     for my $attr (@{$todo->{node}->attributes}) {
2090     my $attr_ns = $attr->namespace_uri;
2091     $attr_ns = '' unless defined $attr_ns;
2092     my $attr_ln = $attr->manakai_local_name;
2093     my $checker;
2094     if ($attr_ns eq '') {
2095     if ($attr_ln eq 'src') {
2096     $checker = $HTMLURIAttrChecker;
2097     $has_src = 1;
2098     } elsif ($attr_ln eq 'type') {
2099     $checker = $HTMLIMTAttrChecker;
2100     } else {
2101     ## TODO: height
2102     ## TODO: width
2103     $checker = $HTMLAttrChecker->{$attr_ln}
2104     || sub { }; ## NOTE: Any local attribute is ok.
2105     }
2106     }
2107     $checker ||= $AttrChecker->{$attr_ns}->{$attr_ln}
2108     || $AttrChecker->{$attr_ns}->{''};
2109     if ($checker) {
2110     $checker->($self, $attr);
2111     } else {
2112 wakaba 1.33 $self->{onerror}->(node => $attr, level => 'unsupported',
2113     type => 'attribute');
2114 wakaba 1.16 ## ISSUE: No comformance createria for global attributes in the spec
2115     }
2116     }
2117    
2118     unless ($has_src) {
2119     $self->{onerror}->(node => $todo->{node},
2120     type => 'attribute missing:src');
2121     }
2122     },
2123 wakaba 1.1 checker => $HTMLEmptyChecker,
2124     };
2125    
2126 wakaba 1.15 $Element->{$HTML_NS}->{object} = {
2127 wakaba 1.17 attrs_checker => sub {
2128     my ($self, $todo) = @_;
2129     $GetHTMLAttrsChecker->({
2130     data => $HTMLURIAttrChecker,
2131     type => $HTMLIMTAttrChecker,
2132     usemap => $HTMLUsemapAttrChecker,
2133     ## TODO: width
2134     ## TODO: height
2135     })->($self, $todo);
2136     unless ($todo->{node}->has_attribute_ns (undef, 'data')) {
2137     unless ($todo->{node}->has_attribute_ns (undef, 'type')) {
2138     $self->{onerror}->(node => $todo->{node},
2139     type => 'attribute missing:data|type');
2140     }
2141     }
2142     },
2143 wakaba 1.15 checker => $ElementDefault->{checker}, ## TODO
2144     };
2145    
2146 wakaba 1.1 $Element->{$HTML_NS}->{param} = {
2147 wakaba 1.12 attrs_checker => sub {
2148     my ($self, $todo) = @_;
2149     $GetHTMLAttrsChecker->({
2150     name => sub { },
2151     value => sub { },
2152     })->($self, $todo);
2153     unless ($todo->{node}->has_attribute_ns (undef, 'name')) {
2154     $self->{onerror}->(node => $todo->{node},
2155     type => 'attribute missing:name');
2156     }
2157     unless ($todo->{node}->has_attribute_ns (undef, 'value')) {
2158     $self->{onerror}->(node => $todo->{node},
2159     type => 'attribute missing:value');
2160     }
2161     },
2162 wakaba 1.1 checker => $HTMLEmptyChecker,
2163     };
2164    
2165 wakaba 1.2 $Element->{$HTML_NS}->{video} = {
2166 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2167     src => $HTMLURIAttrChecker,
2168     ## TODO: start, loopstart, loopend, end
2169     ## ISSUE: they MUST be "value time offset"s. Value?
2170     ## ISSUE: loopcount has no conformance creteria
2171     autoplay => $GetHTMLBooleanAttrChecker->('autoplay'),
2172     controls => $GetHTMLBooleanAttrChecker->('controls'),
2173     }),
2174 wakaba 1.2 checker => sub {
2175 wakaba 1.4 my ($self, $todo) = @_;
2176 wakaba 1.2
2177 wakaba 1.4 if ($todo->{node}->has_attribute_ns (undef, 'src')) {
2178     return $HTMLBlockOrInlineChecker->($self, $todo);
2179 wakaba 1.2 } else {
2180     return $GetHTMLZeroOrMoreThenBlockOrInlineChecker->($HTML_NS, 'source')
2181 wakaba 1.4 ->($self, $todo);
2182 wakaba 1.2 }
2183     },
2184     };
2185    
2186     $Element->{$HTML_NS}->{audio} = {
2187 wakaba 1.12 attrs_checker => $Element->{$HTML_NS}->{video}->{attrs_checker},
2188     checker => $Element->{$HTML_NS}->{video}->{checker},
2189 wakaba 1.2 };
2190 wakaba 1.1
2191     $Element->{$HTML_NS}->{source} = {
2192 wakaba 1.17 attrs_checker => sub {
2193     my ($self, $todo) = @_;
2194     $GetHTMLAttrsChecker->({
2195     src => $HTMLURIAttrChecker,
2196     type => $HTMLIMTAttrChecker,
2197     media => $HTMLMQAttrChecker,
2198     })->($self, $todo);
2199     unless ($todo->{node}->has_attribute_ns (undef, 'src')) {
2200     $self->{onerror}->(node => $todo->{node},
2201     type => 'attribute missing:src');
2202     }
2203     },
2204 wakaba 1.1 checker => $HTMLEmptyChecker,
2205     };
2206    
2207     $Element->{$HTML_NS}->{canvas} = {
2208 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2209     height => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }),
2210     width => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }),
2211     }),
2212 wakaba 1.1 checker => $HTMLInlineChecker,
2213     };
2214    
2215     $Element->{$HTML_NS}->{map} = {
2216 wakaba 1.17 attrs_checker => $GetHTMLAttrsChecker->({
2217     id => sub {
2218     ## NOTE: same as global |id=""|, with |$self->{map}| registeration
2219     my ($self, $attr) = @_;
2220     my $value = $attr->value;
2221     if (length $value > 0) {
2222     if ($self->{id}->{$value}) {
2223     $self->{onerror}->(node => $attr, type => 'duplicate ID');
2224 wakaba 1.37 push @{$self->{id}->{$value}}, $attr;
2225 wakaba 1.17 } else {
2226 wakaba 1.37 $self->{id}->{$value} = [$attr];
2227 wakaba 1.17 }
2228     } else {
2229     ## NOTE: MUST contain at least one character
2230 wakaba 1.33 $self->{onerror}->(node => $attr, type => 'empty attribute value');
2231 wakaba 1.17 }
2232 wakaba 1.27 if ($value =~ /[\x09-\x0D\x20]/) {
2233     $self->{onerror}->(node => $attr, type => 'space in ID');
2234     }
2235 wakaba 1.17 $self->{map}->{$value} ||= $attr;
2236     },
2237     }),
2238 wakaba 1.1 checker => $HTMLBlockChecker,
2239     };
2240    
2241     $Element->{$HTML_NS}->{area} = {
2242 wakaba 1.15 attrs_checker => sub {
2243     my ($self, $todo) = @_;
2244     my %attr;
2245     my $coords;
2246     for my $attr (@{$todo->{node}->attributes}) {
2247     my $attr_ns = $attr->namespace_uri;
2248     $attr_ns = '' unless defined $attr_ns;
2249     my $attr_ln = $attr->manakai_local_name;
2250     my $checker;
2251     if ($attr_ns eq '') {
2252     $checker = {
2253     alt => sub { },
2254     ## NOTE: |alt| value has no conformance creteria.
2255     shape => $GetHTMLEnumeratedAttrChecker->({
2256     circ => -1, circle => 1,
2257     default => 1,
2258     poly => 1, polygon => -1,
2259     rect => 1, rectangle => -1,
2260     }),
2261     coords => sub {
2262     my ($self, $attr) = @_;
2263     my $value = $attr->value;
2264     if ($value =~ /\A-?[0-9]+(?>,-?[0-9]+)*\z/) {
2265     $coords = [split /,/, $value];
2266     } else {
2267     $self->{onerror}->(node => $attr,
2268 wakaba 1.33 type => 'coords:syntax error');
2269 wakaba 1.15 }
2270     },
2271 wakaba 1.17 target => $HTMLTargetAttrChecker,
2272 wakaba 1.15 href => $HTMLURIAttrChecker,
2273     ping => $HTMLSpaceURIsAttrChecker,
2274 wakaba 1.20 rel => sub { $HTMLLinkTypesAttrChecker->(1, @_) },
2275 wakaba 1.17 media => $HTMLMQAttrChecker,
2276     hreflang => $HTMLLanguageTagAttrChecker,
2277 wakaba 1.15 type => $HTMLIMTAttrChecker,
2278     }->{$attr_ln};
2279     if ($checker) {
2280     $attr{$attr_ln} = $attr;
2281     } else {
2282     $checker = $HTMLAttrChecker->{$attr_ln};
2283     }
2284     }
2285     $checker ||= $AttrChecker->{$attr_ns}->{$attr_ln}
2286     || $AttrChecker->{$attr_ns}->{''};
2287     if ($checker) {
2288     $checker->($self, $attr) if ref $checker;
2289     } else {
2290 wakaba 1.33 $self->{onerror}->(node => $attr, level => 'unsupported',
2291     type => 'attribute');
2292 wakaba 1.15 ## ISSUE: No comformance createria for unknown attributes in the spec
2293     }
2294     }
2295    
2296     if (defined $attr{href}) {
2297     unless (defined $attr{alt}) {
2298     $self->{onerror}->(node => $todo->{node},
2299     type => 'attribute missing:alt');
2300     }
2301     } else {
2302     for (qw/target ping rel media hreflang type alt/) {
2303     if (defined $attr{$_}) {
2304     $self->{onerror}->(node => $attr{$_},
2305     type => 'attribute not allowed');
2306     }
2307     }
2308     }
2309    
2310     my $shape = 'rectangle';
2311     if (defined $attr{shape}) {
2312     $shape = {
2313     circ => 'circle', circle => 'circle',
2314     default => 'default',
2315     poly => 'polygon', polygon => 'polygon',
2316     rect => 'rectangle', rectangle => 'rectangle',
2317     }->{lc $attr{shape}->value} || 'rectangle';
2318     ## TODO: ASCII lowercase?
2319     }
2320    
2321     if ($shape eq 'circle') {
2322     if (defined $attr{coords}) {
2323     if (defined $coords) {
2324     if (@$coords == 3) {
2325     if ($coords->[2] < 0) {
2326     $self->{onerror}->(node => $attr{coords},
2327 wakaba 1.33 type => 'coords:out of range:2');
2328 wakaba 1.15 }
2329     } else {
2330     $self->{onerror}->(node => $attr{coords},
2331 wakaba 1.33 type => 'coords:number:3:'.@$coords);
2332 wakaba 1.15 }
2333     } else {
2334     ## NOTE: A syntax error has been reported.
2335     }
2336     } else {
2337     $self->{onerror}->(node => $todo->{node},
2338     type => 'attribute missing:coords');
2339     }
2340     } elsif ($shape eq 'default') {
2341     if (defined $attr{coords}) {
2342     $self->{onerror}->(node => $attr{coords},
2343     type => 'attribute not allowed');
2344     }
2345     } elsif ($shape eq 'polygon') {
2346     if (defined $attr{coords}) {
2347     if (defined $coords) {
2348     if (@$coords >= 6) {
2349     unless (@$coords % 2 == 0) {
2350     $self->{onerror}->(node => $attr{coords},
2351 wakaba 1.33 type => 'coords:number:even:'.@$coords);
2352 wakaba 1.15 }
2353     } else {
2354     $self->{onerror}->(node => $attr{coords},
2355 wakaba 1.33 type => 'coords:number:>=6:'.@$coords);
2356 wakaba 1.15 }
2357     } else {
2358     ## NOTE: A syntax error has been reported.
2359     }
2360     } else {
2361     $self->{onerror}->(node => $todo->{node},
2362     type => 'attribute missing:coords');
2363     }
2364     } elsif ($shape eq 'rectangle') {
2365     if (defined $attr{coords}) {
2366     if (defined $coords) {
2367     if (@$coords == 4) {
2368     unless ($coords->[0] < $coords->[2]) {
2369     $self->{onerror}->(node => $attr{coords},
2370 wakaba 1.33 type => 'coords:out of range:0');
2371 wakaba 1.15 }
2372     unless ($coords->[1] < $coords->[3]) {
2373     $self->{onerror}->(node => $attr{coords},
2374 wakaba 1.33 type => 'coords:out of range:1');
2375 wakaba 1.15 }
2376     } else {
2377     $self->{onerror}->(node => $attr{coords},
2378 wakaba 1.33 type => 'coords:number:4:'.@$coords);
2379 wakaba 1.15 }
2380     } else {
2381     ## NOTE: A syntax error has been reported.
2382     }
2383     } else {
2384     $self->{onerror}->(node => $todo->{node},
2385     type => 'attribute missing:coords');
2386     }
2387     }
2388     },
2389 wakaba 1.1 checker => $HTMLEmptyChecker,
2390     };
2391     ## TODO: only in map
2392    
2393     $Element->{$HTML_NS}->{table} = {
2394 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2395 wakaba 1.1 checker => sub {
2396 wakaba 1.4 my ($self, $todo) = @_;
2397     my $el = $todo->{node};
2398     my $new_todos = [];
2399 wakaba 1.1 my @nodes = (@{$el->child_nodes});
2400    
2401     my $phase = 'before caption';
2402     my $has_tfoot;
2403     while (@nodes) {
2404     my $node = shift @nodes;
2405 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
2406    
2407 wakaba 1.1 my $nt = $node->node_type;
2408     if ($nt == 1) {
2409 wakaba 1.8 my $node_ns = $node->namespace_uri;
2410     $node_ns = '' unless defined $node_ns;
2411     my $node_ln = $node->manakai_local_name;
2412 wakaba 1.2 ## NOTE: |minuses| list is not checked since redundant
2413 wakaba 1.1 if ($phase eq 'in tbodys') {
2414 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'tbody') {
2415 wakaba 1.1 #$phase = 'in tbodys';
2416     } elsif (not $has_tfoot and
2417 wakaba 1.8 $node_ns eq $HTML_NS and $node_ln eq 'tfoot') {
2418 wakaba 1.1 $phase = 'after tfoot';
2419     $has_tfoot = 1;
2420     } else {
2421 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2422 wakaba 1.1 }
2423     } elsif ($phase eq 'in trs') {
2424 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'tr') {
2425 wakaba 1.1 #$phase = 'in trs';
2426     } elsif (not $has_tfoot and
2427 wakaba 1.8 $node_ns eq $HTML_NS and $node_ln eq 'tfoot') {
2428 wakaba 1.1 $phase = 'after tfoot';
2429     $has_tfoot = 1;
2430     } else {
2431 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2432 wakaba 1.1 }
2433     } elsif ($phase eq 'after thead') {
2434 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'tbody') {
2435 wakaba 1.1 $phase = 'in tbodys';
2436 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'tr') {
2437 wakaba 1.1 $phase = 'in trs';
2438 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'tfoot') {
2439 wakaba 1.1 $phase = 'in tbodys';
2440     $has_tfoot = 1;
2441     } else {
2442 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2443 wakaba 1.1 }
2444     } elsif ($phase eq 'in colgroup') {
2445 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'colgroup') {
2446 wakaba 1.1 $phase = 'in colgroup';
2447 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'thead') {
2448 wakaba 1.1 $phase = 'after thead';
2449 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'tbody') {
2450 wakaba 1.1 $phase = 'in tbodys';
2451 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'tr') {
2452 wakaba 1.1 $phase = 'in trs';
2453 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'tfoot') {
2454 wakaba 1.1 $phase = 'in tbodys';
2455     $has_tfoot = 1;
2456     } else {
2457 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2458 wakaba 1.1 }
2459     } elsif ($phase eq 'before caption') {
2460 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'caption') {
2461 wakaba 1.1 $phase = 'in colgroup';
2462 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'colgroup') {
2463 wakaba 1.1 $phase = 'in colgroup';
2464 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'thead') {
2465 wakaba 1.1 $phase = 'after thead';
2466 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'tbody') {
2467 wakaba 1.1 $phase = 'in tbodys';
2468 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'tr') {
2469 wakaba 1.1 $phase = 'in trs';
2470 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'tfoot') {
2471 wakaba 1.1 $phase = 'in tbodys';
2472     $has_tfoot = 1;
2473     } else {
2474 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2475 wakaba 1.1 }
2476     } else { # after tfoot
2477 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2478 wakaba 1.1 }
2479 wakaba 1.30 my ($sib, $ch) = $self->_check_get_children ($node, $todo);
2480 wakaba 1.2 unshift @nodes, @$sib;
2481 wakaba 1.4 push @$new_todos, @$ch;
2482 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
2483     if ($node->data =~ /[^\x09-\x0D\x20]/) {
2484 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
2485 wakaba 1.1 }
2486     } elsif ($nt == 5) {
2487     unshift @nodes, @{$node->child_nodes};
2488     }
2489     }
2490 wakaba 1.21
2491     ## Table model errors
2492     require Whatpm::HTMLTable;
2493     Whatpm::HTMLTable->form_table ($todo->{node}, sub {
2494     my %opt = @_;
2495     $self->{onerror}->(type => 'table:'.$opt{type}, node => $opt{node});
2496     });
2497 wakaba 1.33 push @{$self->{return}->{table}}, $todo->{node};
2498 wakaba 1.21
2499 wakaba 1.4 return ($new_todos);
2500 wakaba 1.1 },
2501     };
2502    
2503     $Element->{$HTML_NS}->{caption} = {
2504 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2505 wakaba 1.1 checker => $HTMLSignificantStrictlyInlineChecker,
2506     };
2507    
2508     $Element->{$HTML_NS}->{colgroup} = {
2509 wakaba 1.17 attrs_checker => $GetHTMLAttrsChecker->({
2510     span => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
2511     ## NOTE: Defined only if "the |colgroup| element contains no |col| elements"
2512     ## TODO: "attribute not supported" if |col|.
2513     ## ISSUE: MUST NOT if any |col|?
2514     ## ISSUE: MUST NOT for |<colgroup span="1"><any><col/></any></colgroup>| (though non-conforming)?
2515     }),
2516 wakaba 1.1 checker => sub {
2517 wakaba 1.4 my ($self, $todo) = @_;
2518     my $el = $todo->{node};
2519     my $new_todos = [];
2520 wakaba 1.1 my @nodes = (@{$el->child_nodes});
2521    
2522     while (@nodes) {
2523     my $node = shift @nodes;
2524 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
2525    
2526 wakaba 1.1 my $nt = $node->node_type;
2527     if ($nt == 1) {
2528 wakaba 1.8 my $node_ns = $node->namespace_uri;
2529     $node_ns = '' unless defined $node_ns;
2530     my $node_ln = $node->manakai_local_name;
2531 wakaba 1.2 ## NOTE: |minuses| list is not checked since redundant
2532 wakaba 1.8 unless ($node_ns eq $HTML_NS and $node_ln eq 'col') {
2533 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2534 wakaba 1.1 }
2535 wakaba 1.30 my ($sib, $ch) = $self->_check_get_children ($node, $todo);
2536 wakaba 1.2 unshift @nodes, @$sib;
2537 wakaba 1.4 push @$new_todos, @$ch;
2538 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
2539     if ($node->data =~ /[^\x09-\x0D\x20]/) {
2540 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
2541 wakaba 1.1 }
2542     } elsif ($nt == 5) {
2543     unshift @nodes, @{$node->child_nodes};
2544     }
2545     }
2546 wakaba 1.4 return ($new_todos);
2547 wakaba 1.1 },
2548     };
2549    
2550     $Element->{$HTML_NS}->{col} = {
2551 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2552     span => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
2553     }),
2554 wakaba 1.1 checker => $HTMLEmptyChecker,
2555     };
2556    
2557     $Element->{$HTML_NS}->{tbody} = {
2558 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2559 wakaba 1.1 checker => sub {
2560 wakaba 1.4 my ($self, $todo) = @_;
2561     my $el = $todo->{node};
2562     my $new_todos = [];
2563 wakaba 1.1 my @nodes = (@{$el->child_nodes});
2564    
2565     my $has_tr;
2566     while (@nodes) {
2567     my $node = shift @nodes;
2568 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
2569    
2570 wakaba 1.1 my $nt = $node->node_type;
2571     if ($nt == 1) {
2572 wakaba 1.8 my $node_ns = $node->namespace_uri;
2573     $node_ns = '' unless defined $node_ns;
2574     my $node_ln = $node->manakai_local_name;
2575 wakaba 1.2 ## NOTE: |minuses| list is not checked since redundant
2576 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'tr') {
2577 wakaba 1.1 $has_tr = 1;
2578     } else {
2579 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2580 wakaba 1.1 }
2581 wakaba 1.30 my ($sib, $ch) = $self->_check_get_children ($node, $todo);
2582 wakaba 1.2 unshift @nodes, @$sib;
2583 wakaba 1.4 push @$new_todos, @$ch;
2584 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
2585     if ($node->data =~ /[^\x09-\x0D\x20]/) {
2586 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
2587 wakaba 1.1 }
2588     } elsif ($nt == 5) {
2589     unshift @nodes, @{$node->child_nodes};
2590     }
2591     }
2592     unless ($has_tr) {
2593 wakaba 1.3 $self->{onerror}->(node => $el, type => 'child element missing:tr');
2594 wakaba 1.1 }
2595 wakaba 1.4 return ($new_todos);
2596 wakaba 1.1 },
2597     };
2598    
2599     $Element->{$HTML_NS}->{thead} = {
2600 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2601 wakaba 1.23 checker => $Element->{$HTML_NS}->{tbody}->{checker},
2602 wakaba 1.1 };
2603    
2604     $Element->{$HTML_NS}->{tfoot} = {
2605 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2606 wakaba 1.23 checker => $Element->{$HTML_NS}->{tbody}->{checker},
2607 wakaba 1.1 };
2608    
2609     $Element->{$HTML_NS}->{tr} = {
2610 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2611 wakaba 1.1 checker => sub {
2612 wakaba 1.4 my ($self, $todo) = @_;
2613     my $el = $todo->{node};
2614     my $new_todos = [];
2615 wakaba 1.1 my @nodes = (@{$el->child_nodes});
2616    
2617     my $has_td;
2618     while (@nodes) {
2619     my $node = shift @nodes;
2620 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
2621    
2622 wakaba 1.1 my $nt = $node->node_type;
2623     if ($nt == 1) {
2624 wakaba 1.8 my $node_ns = $node->namespace_uri;
2625     $node_ns = '' unless defined $node_ns;
2626     my $node_ln = $node->manakai_local_name;
2627 wakaba 1.2 ## NOTE: |minuses| list is not checked since redundant
2628 wakaba 1.8 if ($node_ns eq $HTML_NS and ($node_ln eq 'td' or $node_ln eq 'th')) {
2629 wakaba 1.1 $has_td = 1;
2630     } else {
2631 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2632 wakaba 1.1 }
2633 wakaba 1.30 my ($sib, $ch) = $self->_check_get_children ($node, $todo);
2634 wakaba 1.2 unshift @nodes, @$sib;
2635 wakaba 1.4 push @$new_todos, @$ch;
2636 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
2637     if ($node->data =~ /[^\x09-\x0D\x20]/) {
2638 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
2639 wakaba 1.1 }
2640     } elsif ($nt == 5) {
2641     unshift @nodes, @{$node->child_nodes};
2642     }
2643     }
2644     unless ($has_td) {
2645 wakaba 1.3 $self->{onerror}->(node => $el, type => 'child element missing:td|th');
2646 wakaba 1.1 }
2647 wakaba 1.4 return ($new_todos);
2648 wakaba 1.1 },
2649     };
2650    
2651     $Element->{$HTML_NS}->{td} = {
2652 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2653     colspan => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
2654     rowspan => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
2655     }),
2656 wakaba 1.1 checker => $HTMLBlockOrInlineChecker,
2657     };
2658    
2659     $Element->{$HTML_NS}->{th} = {
2660 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2661     colspan => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
2662     rowspan => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
2663     scope => $GetHTMLEnumeratedAttrChecker
2664     ->({row => 1, col => 1, rowgroup => 1, colgroup => 1}),
2665     }),
2666 wakaba 1.1 checker => $HTMLBlockOrInlineChecker,
2667     };
2668    
2669     ## TODO: forms
2670    
2671 wakaba 1.2 $Element->{$HTML_NS}->{script} = {
2672 wakaba 1.25 attrs_checker => sub {
2673     my ($self, $todo) = @_;
2674     $GetHTMLAttrsChecker->({
2675     src => $HTMLURIAttrChecker,
2676     defer => $GetHTMLBooleanAttrChecker->('defer'),
2677     async => $GetHTMLBooleanAttrChecker->('async'),
2678     type => $HTMLIMTAttrChecker,
2679     })->($self, $todo);
2680     if ($todo->{node}->has_attribute_ns (undef, 'defer')) {
2681     my $async_attr = $todo->{node}->get_attribute_node_ns (undef, 'async');
2682     if ($async_attr) {
2683     $self->{onerror}->(node => $async_attr,
2684     type => 'attribute not allowed'); # MUST NOT
2685     }
2686     }
2687     },
2688 wakaba 1.2 checker => sub {
2689 wakaba 1.4 my ($self, $todo) = @_;
2690 wakaba 1.2
2691 wakaba 1.4 if ($todo->{node}->has_attribute_ns (undef, 'src')) {
2692     return $HTMLEmptyChecker->($self, $todo);
2693 wakaba 1.2 } else {
2694     ## NOTE: No content model conformance in HTML5 spec.
2695 wakaba 1.36 my $type = $todo->{node}->get_attribute_ns (undef, 'type');
2696     my $language = $todo->{node}->get_attribute_ns (undef, 'language');
2697     if ((defined $type and $type eq '') or
2698     (defined $language and $language eq '')) {
2699     $type = 'text/javascript';
2700     } elsif (defined $type) {
2701     #
2702     } elsif (defined $language) {
2703     $type = 'text/' . $language;
2704     } else {
2705     $type = 'text/javascript';
2706     }
2707     $self->{onerror}->(node => $todo->{node}, level => 'unsupported',
2708     type => 'script:'.$type); ## TODO: $type normalization
2709 wakaba 1.4 return $AnyChecker->($self, $todo);
2710 wakaba 1.2 }
2711     },
2712     };
2713    
2714     ## NOTE: When script is disabled.
2715     $Element->{$HTML_NS}->{noscript} = {
2716 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2717 wakaba 1.2 checker => sub {
2718 wakaba 1.4 my ($self, $todo) = @_;
2719 wakaba 1.1
2720 wakaba 1.2 my $end = $self->_add_minuses ({$HTML_NS => {noscript => 1}});
2721 wakaba 1.4 my ($sib, $ch) = $HTMLBlockOrInlineChecker->($self, $todo);
2722 wakaba 1.2 push @$sib, $end;
2723     return ($sib, $ch);
2724     },
2725     };
2726 wakaba 1.29 ## TODO: noscript in head
2727 wakaba 1.35 ## TODO: noscript in XHTML
2728 wakaba 1.1
2729     $Element->{$HTML_NS}->{'event-source'} = {
2730 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2731     src => $HTMLURIAttrChecker,
2732     }),
2733 wakaba 1.1 checker => $HTMLEmptyChecker,
2734     };
2735    
2736     $Element->{$HTML_NS}->{details} = {
2737 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2738     open => $GetHTMLBooleanAttrChecker->('open'),
2739     }),
2740 wakaba 1.6 checker => sub {
2741     my ($self, $todo) = @_;
2742    
2743     my $end = $self->_add_minuses ({$HTML_NS => {a => 1, datagrid => 1}});
2744     my ($sib, $ch)
2745     = $GetHTMLZeroOrMoreThenBlockOrInlineChecker->($HTML_NS, 'legend')
2746     ->($self, $todo);
2747     push @$sib, $end;
2748     return ($sib, $ch);
2749     },
2750 wakaba 1.1 };
2751    
2752     $Element->{$HTML_NS}->{datagrid} = {
2753 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2754     disabled => $GetHTMLBooleanAttrChecker->('disabled'),
2755     multiple => $GetHTMLBooleanAttrChecker->('multiple'),
2756     }),
2757 wakaba 1.6 checker => sub {
2758     my ($self, $todo) = @_;
2759 wakaba 1.32 my $el = $todo->{node};
2760     my $new_todos = [];
2761     my @nodes = (@{$el->child_nodes});
2762 wakaba 1.6
2763     my $end = $self->_add_minuses ({$HTML_NS => {a => 1, datagrid => 1}});
2764 wakaba 1.32
2765     ## Block-table Block* | table | select | datalist | Empty
2766     my $mode = 'any';
2767     while (@nodes) {
2768     my $node = shift @nodes;
2769     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
2770    
2771     my $nt = $node->node_type;
2772     if ($nt == 1) {
2773     my $node_ns = $node->namespace_uri;
2774     $node_ns = '' unless defined $node_ns;
2775     my $node_ln = $node->manakai_local_name;
2776     my $not_allowed = $self->{minuses}->{$node_ns}->{$node_ln};
2777     if ($mode eq 'block') {
2778     $not_allowed = 1
2779     unless $HTMLBlockLevelElements->{$node_ns}->{$node_ln};
2780     } elsif ($mode eq 'any') {
2781     if ($node_ns eq $HTML_NS and
2782     {table => 1, select => 1, datalist => 1}->{$node_ln}) {
2783     $mode = 'none';
2784     } elsif ($HTMLBlockLevelElements->{$node_ns}->{$node_ln}) {
2785     $mode = 'block';
2786     } else {
2787     $not_allowed = 1;
2788     }
2789     } else {
2790     $not_allowed = 1;
2791     }
2792     $self->{onerror}->(node => $node, type => 'element not allowed')
2793     if $not_allowed;
2794     my ($sib, $ch) = $self->_check_get_children ($node, $todo);
2795     unshift @nodes, @$sib;
2796     push @$new_todos, @$ch;
2797     } elsif ($nt == 3 or $nt == 4) {
2798     if ($node->data =~ /[^\x09-\x0D\x20]/) {
2799     $self->{onerror}->(node => $node, type => 'character not allowed');
2800     }
2801     } elsif ($nt == 5) {
2802     unshift @nodes, @{$node->child_nodes};
2803     }
2804     }
2805    
2806     push @$new_todos, $end;
2807     return ($new_todos);
2808 wakaba 1.6 },
2809 wakaba 1.1 };
2810    
2811     $Element->{$HTML_NS}->{command} = {
2812 wakaba 1.32 attrs_checker => $GetHTMLAttrsChecker->({
2813     checked => $GetHTMLBooleanAttrChecker->('checked'),
2814     default => $GetHTMLBooleanAttrChecker->('default'),
2815     disabled => $GetHTMLBooleanAttrChecker->('disabled'),
2816     hidden => $GetHTMLBooleanAttrChecker->('hidden'),
2817     icon => $HTMLURIAttrChecker,
2818     label => sub { }, ## NOTE: No conformance creteria
2819     radiogroup => sub { }, ## NOTE: No conformance creteria
2820     ## NOTE: |title| has special semantics, but no syntactical difference
2821     type => sub {
2822     my ($self, $attr) = @_;
2823     my $value = $attr->value;
2824     unless ({command => 1, checkbox => 1, radio => 1}->{$value}) {
2825     $self->{onerror}->(node => $attr, type => 'attribute value not allowed');
2826     }
2827     },
2828     }),
2829 wakaba 1.1 checker => $HTMLEmptyChecker,
2830     };
2831    
2832     $Element->{$HTML_NS}->{menu} = {
2833 wakaba 1.32 attrs_checker => $GetHTMLAttrsChecker->({
2834     autosubmit => $GetHTMLBooleanAttrChecker->('autosubmit'),
2835     id => sub {
2836     ## NOTE: same as global |id=""|, with |$self->{menu}| registeration
2837     my ($self, $attr) = @_;
2838     my $value = $attr->value;
2839     if (length $value > 0) {
2840     if ($self->{id}->{$value}) {
2841     $self->{onerror}->(node => $attr, type => 'duplicate ID');
2842 wakaba 1.37 push @{$self->{id}->{$value}}, $attr;
2843 wakaba 1.32 } else {
2844 wakaba 1.37 $self->{id}->{$value} = [$attr];
2845 wakaba 1.32 }
2846     } else {
2847     ## NOTE: MUST contain at least one character
2848 wakaba 1.33 $self->{onerror}->(node => $attr, type => 'empty attribute value');
2849 wakaba 1.32 }
2850     if ($value =~ /[\x09-\x0D\x20]/) {
2851     $self->{onerror}->(node => $attr, type => 'space in ID');
2852     }
2853     $self->{menu}->{$value} ||= $attr;
2854     ## ISSUE: <menu id=""><p contextmenu=""> match?
2855     },
2856     label => sub { }, ## NOTE: No conformance creteria
2857     type => $GetHTMLEnumeratedAttrChecker->({context => 1, toolbar => 1}),
2858     }),
2859 wakaba 1.1 checker => sub {
2860 wakaba 1.4 my ($self, $todo) = @_;
2861     my $el = $todo->{node};
2862     my $new_todos = [];
2863 wakaba 1.1 my @nodes = (@{$el->child_nodes});
2864    
2865     my $content = 'li or inline';
2866     while (@nodes) {
2867     my $node = shift @nodes;
2868 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
2869    
2870 wakaba 1.1 my $nt = $node->node_type;
2871     if ($nt == 1) {
2872 wakaba 1.2 my $node_ns = $node->namespace_uri;
2873     $node_ns = '' unless defined $node_ns;
2874     my $node_ln = $node->manakai_local_name;
2875 wakaba 1.6 my $not_allowed = $self->{minuses}->{$node_ns}->{$node_ln};
2876 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'li') {
2877 wakaba 1.1 if ($content eq 'inline') {
2878 wakaba 1.6 $not_allowed = 1;
2879 wakaba 1.1 } elsif ($content eq 'li or inline') {
2880     $content = 'li';
2881     }
2882     } else {
2883 wakaba 1.7 if ($HTMLStrictlyInlineLevelElements->{$node_ns}->{$node_ln} or
2884     $HTMLStructuredInlineLevelElements->{$node_ns}->{$node_ln}) {
2885     $content = 'inline';
2886     } else {
2887 wakaba 1.6 $not_allowed = 1;
2888 wakaba 1.7 }
2889 wakaba 1.1 }
2890 wakaba 1.6 $self->{onerror}->(node => $node, type => 'element not allowed')
2891     if $not_allowed;
2892 wakaba 1.30 my ($sib, $ch) = $self->_check_get_children ($node, $todo);
2893 wakaba 1.2 unshift @nodes, @$sib;
2894 wakaba 1.4 push @$new_todos, @$ch;
2895 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
2896     if ($node->data =~ /[^\x09-\x0D\x20]/) {
2897     if ($content eq 'li') {
2898 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
2899 wakaba 1.1 } elsif ($content eq 'li or inline') {
2900     $content = 'inline';
2901     }
2902     }
2903     } elsif ($nt == 5) {
2904     unshift @nodes, @{$node->child_nodes};
2905     }
2906     }
2907 wakaba 1.4
2908     for (@$new_todos) {
2909     $_->{inline} = 1;
2910     }
2911     return ($new_todos);
2912 wakaba 1.1 },
2913     };
2914    
2915 wakaba 1.6 $Element->{$HTML_NS}->{legend} = {
2916 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2917 wakaba 1.6 checker => sub {
2918     my ($self, $todo) = @_;
2919    
2920     my $parent = $todo->{node}->manakai_parent_element;
2921     if (defined $parent) {
2922     my $nsuri = $parent->namespace_uri;
2923     $nsuri = '' unless defined $nsuri;
2924     my $ln = $parent->manakai_local_name;
2925     if ($nsuri eq $HTML_NS and $ln eq 'figure') {
2926     return $HTMLInlineChecker->($self, $todo);
2927     } else {
2928     return $HTMLSignificantStrictlyInlineChecker->($self, $todo);
2929     }
2930     } else {
2931     return $HTMLInlineChecker->($self, $todo);
2932     }
2933    
2934     ## ISSUE: Content model is defined only for fieldset/legend,
2935     ## details/legend, and figure/legend.
2936     },
2937     };
2938 wakaba 1.1
2939     $Element->{$HTML_NS}->{div} = {
2940 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2941 wakaba 1.2 checker => $GetHTMLZeroOrMoreThenBlockOrInlineChecker->($HTML_NS, 'style'),
2942 wakaba 1.1 };
2943    
2944     $Element->{$HTML_NS}->{font} = {
2945 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({}), ## TODO
2946 wakaba 1.1 checker => $HTMLTransparentChecker,
2947     };
2948    
2949 wakaba 1.24 sub check_document ($$$) {
2950     my ($self, $doc, $onerror) = @_;
2951     $self = bless {}, $self unless ref $self;
2952     $self->{onerror} = $onerror;
2953    
2954     my $docel = $doc->document_element;
2955 wakaba 1.26 unless (defined $docel) {
2956     ## ISSUE: Should we check content of Document node?
2957     $onerror->(node => $doc, type => 'no document element');
2958     ## ISSUE: Is this non-conforming (to what spec)? Or just a warning?
2959     return;
2960     }
2961    
2962     ## ISSUE: Unexpanded entity references and HTML5 conformance
2963    
2964 wakaba 1.24 my $docel_nsuri = $docel->namespace_uri;
2965     $docel_nsuri = '' unless defined $docel_nsuri;
2966     my $docel_def = $Element->{$docel_nsuri}->{$docel->manakai_local_name} ||
2967     $Element->{$docel_nsuri}->{''} ||
2968     $ElementDefault;
2969     if ($docel_def->{is_root}) {
2970     #
2971     } else {
2972     $onerror->(node => $docel, type => 'element not allowed');
2973     }
2974    
2975     ## TODO: Check for other items other than document element
2976     ## (second (errorous) element, text nodes, PI nodes, doctype nodes)
2977    
2978 wakaba 1.33 return $self->check_element ($docel, $onerror);
2979 wakaba 1.24 } # check_document
2980 wakaba 1.2
2981 wakaba 1.1 sub check_element ($$$) {
2982     my ($self, $el, $onerror) = @_;
2983 wakaba 1.24 $self = bless {}, $self unless ref $self;
2984     $self->{onerror} = $onerror;
2985 wakaba 1.1
2986 wakaba 1.2 $self->{minuses} = {};
2987 wakaba 1.10 $self->{id} = {};
2988 wakaba 1.30 $self->{term} = {};
2989 wakaba 1.17 $self->{usemap} = [];
2990 wakaba 1.32 $self->{contextmenu} = [];
2991 wakaba 1.17 $self->{map} = {};
2992 wakaba 1.32 $self->{menu} = {};
2993 wakaba 1.20 $self->{has_link_type} = {};
2994 wakaba 1.33 $self->{return} = {
2995 wakaba 1.38 class => {},
2996 wakaba 1.37 id => $self->{id}, table => [], term => $self->{term},
2997 wakaba 1.33 };
2998 wakaba 1.2
2999 wakaba 1.4 my @todo = ({type => 'element', node => $el});
3000     while (@todo) {
3001     my $todo = shift @todo;
3002     if ($todo->{type} eq 'element') {
3003 wakaba 1.13 my $prefix = $todo->{node}->prefix;
3004     if (defined $prefix and $prefix eq 'xmlns') {
3005     $self->{onerror}
3006 wakaba 1.33 ->(node => $todo->{node}, level => 'NC',
3007     type => 'Reserved Prefixes and Namespace Names:<xmlns:>');
3008 wakaba 1.13 }
3009 wakaba 1.4 my $nsuri = $todo->{node}->namespace_uri;
3010     $nsuri = '' unless defined $nsuri;
3011     my $ln = $todo->{node}->manakai_local_name;
3012     my $eldef = $Element->{$nsuri}->{$ln} ||
3013     $Element->{$nsuri}->{''} ||
3014     $ElementDefault;
3015 wakaba 1.9 $eldef->{attrs_checker}->($self, $todo);
3016 wakaba 1.4 my ($new_todos) = $eldef->{checker}->($self, $todo);
3017 wakaba 1.14 unshift @todo, @$new_todos;
3018 wakaba 1.9 } elsif ($todo->{type} eq 'element-attributes') {
3019 wakaba 1.13 my $prefix = $todo->{node}->prefix;
3020     if (defined $prefix and $prefix eq 'xmlns') {
3021     $self->{onerror}
3022 wakaba 1.33 ->(node => $todo->{node}, level => 'NC',
3023     type => 'Reserved Prefixes and Namespace Names:<xmlns:>');
3024 wakaba 1.13 }
3025 wakaba 1.9 my $nsuri = $todo->{node}->namespace_uri;
3026     $nsuri = '' unless defined $nsuri;
3027     my $ln = $todo->{node}->manakai_local_name;
3028     my $eldef = $Element->{$nsuri}->{$ln} ||
3029     $Element->{$nsuri}->{''} ||
3030     $ElementDefault;
3031     $eldef->{attrs_checker}->($self, $todo);
3032 wakaba 1.4 } elsif ($todo->{type} eq 'plus') {
3033     $self->_remove_minuses ($todo);
3034 wakaba 1.30 } elsif ($todo->{type} eq 'code') {
3035     $todo->{code}->();
3036     } else {
3037     die "$0: Internal error: Unsupported checking action type |$todo->{type}|";
3038 wakaba 1.4 }
3039 wakaba 1.1 }
3040 wakaba 1.17
3041     for (@{$self->{usemap}}) {
3042     unless ($self->{map}->{$_->[0]}) {
3043     $self->{onerror}->(node => $_->[1], type => 'no referenced map');
3044     }
3045     }
3046    
3047 wakaba 1.32 for (@{$self->{contextmenu}}) {
3048     unless ($self->{menu}->{$_->[0]}) {
3049     $self->{onerror}->(node => $_->[1], type => 'no referenced menu');
3050     }
3051     }
3052    
3053 wakaba 1.17 delete $self->{minuses};
3054     delete $self->{onerror};
3055     delete $self->{id};
3056     delete $self->{usemap};
3057     delete $self->{map};
3058 wakaba 1.33 return $self->{return};
3059 wakaba 1.1 } # check_element
3060    
3061 wakaba 1.2 sub _add_minuses ($@) {
3062     my $self = shift;
3063     my $r = {};
3064     for my $list (@_) {
3065     for my $ns (keys %$list) {
3066     for my $ln (keys %{$list->{$ns}}) {
3067     unless ($self->{minuses}->{$ns}->{$ln}) {
3068     $self->{minuses}->{$ns}->{$ln} = 1;
3069     $r->{$ns}->{$ln} = 1;
3070     }
3071     }
3072     }
3073     }
3074 wakaba 1.4 return {type => 'plus', list => $r};
3075 wakaba 1.2 } # _add_minuses
3076    
3077     sub _remove_minuses ($$) {
3078 wakaba 1.4 my ($self, $todo) = @_;
3079     for my $ns (keys %{$todo->{list}}) {
3080     for my $ln (keys %{$todo->{list}->{$ns}}) {
3081     delete $self->{minuses}->{$ns}->{$ln} if $todo->{list}->{$ns}->{$ln};
3082 wakaba 1.2 }
3083     }
3084     1;
3085     } # _remove_minuses
3086    
3087 wakaba 1.30 sub _check_get_children ($$$) {
3088     my ($self, $node, $parent_todo) = @_;
3089 wakaba 1.4 my $new_todos = [];
3090 wakaba 1.2 my $sib = [];
3091     TP: {
3092     my $node_ns = $node->namespace_uri;
3093     $node_ns = '' unless defined $node_ns;
3094     my $node_ln = $node->manakai_local_name;
3095     if ($node_ns eq $HTML_NS) {
3096     if ($node_ln eq 'noscript') {
3097     my $end = $self->_add_minuses ({$HTML_NS, {noscript => 1}});
3098     push @$sib, $end;
3099     }
3100     }
3101 wakaba 1.31 ## TODO: |noscript| is not a transparent element in |head|.
3102 wakaba 1.7 if ($HTMLTransparentElements->{$node_ns}->{$node_ln}) {
3103     unshift @$sib, @{$node->child_nodes};
3104 wakaba 1.9 push @$new_todos, {type => 'element-attributes', node => $node};
3105 wakaba 1.7 last TP;
3106 wakaba 1.2 }
3107 wakaba 1.8 if ($node_ns eq $HTML_NS and ($node_ln eq 'video' or $node_ln eq 'audio')) {
3108 wakaba 1.2 if ($node->has_attribute_ns (undef, 'src')) {
3109     unshift @$sib, @{$node->child_nodes};
3110 wakaba 1.9 push @$new_todos, {type => 'element-attributes', node => $node};
3111 wakaba 1.2 last TP;
3112     } else {
3113     my @cn = @{$node->child_nodes};
3114     CN: while (@cn) {
3115     my $cn = shift @cn;
3116     my $cnt = $cn->node_type;
3117     if ($cnt == 1) {
3118 wakaba 1.8 my $cn_nsuri = $cn->namespace_uri;
3119     $cn_nsuri = '' unless defined $cn_nsuri;
3120     if ($cn_nsuri eq $HTML_NS and $cn->manakai_local_name eq 'source') {
3121 wakaba 1.2 #
3122     } else {
3123     last CN;
3124     }
3125     } elsif ($cnt == 3 or $cnt == 4) {
3126     if ($cn->data =~ /[^\x09-\x0D\x20]/) {
3127     last CN;
3128     }
3129     }
3130     } # CN
3131     unshift @$sib, @cn;
3132     }
3133     }
3134 wakaba 1.4 push @$new_todos, {type => 'element', node => $node};
3135 wakaba 1.2 } # TP
3136 wakaba 1.30
3137     for my $new_todo (@$new_todos) {
3138     $new_todo->{flag} = {%{$parent_todo->{flag} or {}}};
3139     }
3140    
3141 wakaba 1.4 return ($sib, $new_todos);
3142 wakaba 1.2 } # _check_get_children
3143    
3144 wakaba 1.1 1;
3145 wakaba 1.38 # $Date: 2007/07/17 13:54:57 $

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24