/[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.39 - (hide annotations) (download)
Sat Jul 21 04:55:20 2007 UTC (19 years ago) by wakaba
Branch: MAIN
Changes since 1.38: +3 -1 lines
++ whatpm/Whatpm/ChangeLog	21 Jul 2007 04:51:33 -0000
2007-07-21  Wakaba  <wakaba@suika.fam.cx>

	* HTML.pm.src: Add the name of the attribute
	to the "duplicate attribute" error.

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 wakaba 1.39 ## ISSUE: The HTML5 algorithm does not work with |ruby| unless |dfn|
1912     ## has |title|.
1913 wakaba 1.30
1914 wakaba 1.4 return ($sib, $ch);
1915     },
1916     };
1917 wakaba 1.1
1918     $Element->{$HTML_NS}->{abbr} = {
1919 wakaba 1.11 attrs_checker => $GetHTMLAttrsChecker->({
1920     ## NOTE: |title| has special semantics for |abbr|s, but is syntactically
1921     ## not different. The spec says that the |title| MAY be omitted
1922     ## if there is a |dfn| whose defining term is the abbreviation,
1923     ## but it does not prohibit |abbr| w/o |title| in other cases.
1924     }),
1925 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1926     };
1927    
1928 wakaba 1.11 $Element->{$HTML_NS}->{time} = { ## TODO: validate content
1929     attrs_checker => $GetHTMLAttrsChecker->({}), ## TODO: datetime
1930 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1931     };
1932    
1933 wakaba 1.11 $Element->{$HTML_NS}->{meter} = { ## TODO: "The recommended way of giving the value is to include it as contents of the element"
1934     attrs_checker => $GetHTMLAttrsChecker->({
1935     value => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
1936     min => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
1937     low => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
1938     high => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
1939     max => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
1940     optimum => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
1941     }),
1942 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1943     };
1944    
1945 wakaba 1.11 $Element->{$HTML_NS}->{progress} = { ## TODO: recommended to use content
1946     attrs_checker => $GetHTMLAttrsChecker->({
1947     value => $GetHTMLFloatingPointNumberAttrChecker->(sub { shift >= 0 }),
1948     max => $GetHTMLFloatingPointNumberAttrChecker->(sub { shift > 0 }),
1949     }),
1950 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1951     };
1952    
1953 wakaba 1.4 $Element->{$HTML_NS}->{code} = {
1954 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1955 wakaba 1.12 ## NOTE: Though |title| has special semantics,
1956     ## syntatically same as the |title| as global attribute.
1957 wakaba 1.4 checker => $HTMLInlineOrStrictlyInlineChecker,
1958     };
1959 wakaba 1.1
1960     $Element->{$HTML_NS}->{var} = {
1961 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1962 wakaba 1.12 ## NOTE: Though |title| has special semantics,
1963     ## syntatically same as the |title| as global attribute.
1964 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1965     };
1966    
1967 wakaba 1.4 $Element->{$HTML_NS}->{samp} = {
1968 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1969 wakaba 1.12 ## NOTE: Though |title| has special semantics,
1970     ## syntatically same as the |title| as global attribute.
1971 wakaba 1.4 checker => $HTMLInlineOrStrictlyInlineChecker,
1972     };
1973 wakaba 1.1
1974     $Element->{$HTML_NS}->{kbd} = {
1975 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1976 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1977     };
1978    
1979     $Element->{$HTML_NS}->{sub} = {
1980 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1981 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1982     };
1983    
1984     $Element->{$HTML_NS}->{sup} = {
1985 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1986 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
1987     };
1988    
1989 wakaba 1.4 $Element->{$HTML_NS}->{span} = {
1990 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1991 wakaba 1.12 ## NOTE: Though |title| has special semantics,
1992     ## syntatically same as the |title| as global attribute.
1993 wakaba 1.4 checker => $HTMLInlineOrStrictlyInlineChecker,
1994     };
1995 wakaba 1.1
1996     $Element->{$HTML_NS}->{i} = {
1997 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
1998 wakaba 1.12 ## NOTE: Though |title| has special semantics,
1999     ## syntatically same as the |title| as global attribute.
2000 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
2001     };
2002    
2003     $Element->{$HTML_NS}->{b} = {
2004 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2005 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
2006     };
2007    
2008     $Element->{$HTML_NS}->{bdo} = {
2009 wakaba 1.12 attrs_checker => sub {
2010     my ($self, $todo) = @_;
2011     $GetHTMLAttrsChecker->({})->($self, $todo);
2012     unless ($todo->{node}->has_attribute_ns (undef, 'dir')) {
2013     $self->{onerror}->(node => $todo->{node}, type => 'attribute missing:dir');
2014     }
2015     },
2016     ## ISSUE: The spec does not directly say that |dir| is a enumerated attr.
2017 wakaba 1.1 checker => $HTMLStrictlyInlineChecker,
2018     };
2019    
2020     $Element->{$HTML_NS}->{ins} = {
2021 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2022     cite => $HTMLURIAttrChecker,
2023 wakaba 1.30 datetime => $HTMLDatetimeAttrChecker,
2024 wakaba 1.12 }),
2025 wakaba 1.1 checker => $HTMLTransparentChecker,
2026     };
2027    
2028     $Element->{$HTML_NS}->{del} = {
2029 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2030     cite => $HTMLURIAttrChecker,
2031 wakaba 1.30 datetime => $HTMLDatetimeAttrChecker,
2032 wakaba 1.12 }),
2033 wakaba 1.1 checker => sub {
2034 wakaba 1.4 my ($self, $todo) = @_;
2035 wakaba 1.1
2036 wakaba 1.4 my $parent = $todo->{node}->manakai_parent_element;
2037 wakaba 1.1 if (defined $parent) {
2038     my $nsuri = $parent->namespace_uri;
2039     $nsuri = '' unless defined $nsuri;
2040     my $ln = $parent->manakai_local_name;
2041     my $eldef = $Element->{$nsuri}->{$ln} ||
2042     $Element->{$nsuri}->{''} ||
2043     $ElementDefault;
2044 wakaba 1.4 return $eldef->{checker}->($self, $todo);
2045 wakaba 1.1 } else {
2046 wakaba 1.4 return $HTMLBlockOrInlineChecker->($self, $todo);
2047 wakaba 1.1 }
2048     },
2049     };
2050    
2051     ## TODO: figure
2052    
2053     $Element->{$HTML_NS}->{img} = {
2054 wakaba 1.17 attrs_checker => sub {
2055     my ($self, $todo) = @_;
2056     $GetHTMLAttrsChecker->({
2057     alt => sub { }, ## NOTE: No syntactical requirement
2058     src => $HTMLURIAttrChecker,
2059     usemap => $HTMLUsemapAttrChecker,
2060 wakaba 1.32 ismap => sub {
2061     my ($self, $attr, $parent_todo) = @_;
2062     if (not $todo->{flag}->{has_a}) {
2063     $self->{onerror}->(node => $attr, type => 'attribute not allowed');
2064     }
2065     $GetHTMLBooleanAttrChecker->('ismap')->($self, $attr, $parent_todo);
2066     },
2067 wakaba 1.17 ## TODO: height
2068     ## TODO: width
2069     })->($self, $todo);
2070     unless ($todo->{node}->has_attribute_ns (undef, 'alt')) {
2071     $self->{onerror}->(node => $todo->{node}, type => 'attribute missing:alt');
2072     }
2073     unless ($todo->{node}->has_attribute_ns (undef, 'src')) {
2074     $self->{onerror}->(node => $todo->{node}, type => 'attribute missing:src');
2075     }
2076     },
2077 wakaba 1.1 checker => $HTMLEmptyChecker,
2078     };
2079    
2080     $Element->{$HTML_NS}->{iframe} = {
2081 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2082     src => $HTMLURIAttrChecker,
2083     }),
2084 wakaba 1.1 checker => $HTMLTextChecker,
2085     };
2086    
2087     $Element->{$HTML_NS}->{embed} = {
2088 wakaba 1.16 attrs_checker => sub {
2089     my ($self, $todo) = @_;
2090     my $has_src;
2091     for my $attr (@{$todo->{node}->attributes}) {
2092     my $attr_ns = $attr->namespace_uri;
2093     $attr_ns = '' unless defined $attr_ns;
2094     my $attr_ln = $attr->manakai_local_name;
2095     my $checker;
2096     if ($attr_ns eq '') {
2097     if ($attr_ln eq 'src') {
2098     $checker = $HTMLURIAttrChecker;
2099     $has_src = 1;
2100     } elsif ($attr_ln eq 'type') {
2101     $checker = $HTMLIMTAttrChecker;
2102     } else {
2103     ## TODO: height
2104     ## TODO: width
2105     $checker = $HTMLAttrChecker->{$attr_ln}
2106     || sub { }; ## NOTE: Any local attribute is ok.
2107     }
2108     }
2109     $checker ||= $AttrChecker->{$attr_ns}->{$attr_ln}
2110     || $AttrChecker->{$attr_ns}->{''};
2111     if ($checker) {
2112     $checker->($self, $attr);
2113     } else {
2114 wakaba 1.33 $self->{onerror}->(node => $attr, level => 'unsupported',
2115     type => 'attribute');
2116 wakaba 1.16 ## ISSUE: No comformance createria for global attributes in the spec
2117     }
2118     }
2119    
2120     unless ($has_src) {
2121     $self->{onerror}->(node => $todo->{node},
2122     type => 'attribute missing:src');
2123     }
2124     },
2125 wakaba 1.1 checker => $HTMLEmptyChecker,
2126     };
2127    
2128 wakaba 1.15 $Element->{$HTML_NS}->{object} = {
2129 wakaba 1.17 attrs_checker => sub {
2130     my ($self, $todo) = @_;
2131     $GetHTMLAttrsChecker->({
2132     data => $HTMLURIAttrChecker,
2133     type => $HTMLIMTAttrChecker,
2134     usemap => $HTMLUsemapAttrChecker,
2135     ## TODO: width
2136     ## TODO: height
2137     })->($self, $todo);
2138     unless ($todo->{node}->has_attribute_ns (undef, 'data')) {
2139     unless ($todo->{node}->has_attribute_ns (undef, 'type')) {
2140     $self->{onerror}->(node => $todo->{node},
2141     type => 'attribute missing:data|type');
2142     }
2143     }
2144     },
2145 wakaba 1.15 checker => $ElementDefault->{checker}, ## TODO
2146     };
2147    
2148 wakaba 1.1 $Element->{$HTML_NS}->{param} = {
2149 wakaba 1.12 attrs_checker => sub {
2150     my ($self, $todo) = @_;
2151     $GetHTMLAttrsChecker->({
2152     name => sub { },
2153     value => sub { },
2154     })->($self, $todo);
2155     unless ($todo->{node}->has_attribute_ns (undef, 'name')) {
2156     $self->{onerror}->(node => $todo->{node},
2157     type => 'attribute missing:name');
2158     }
2159     unless ($todo->{node}->has_attribute_ns (undef, 'value')) {
2160     $self->{onerror}->(node => $todo->{node},
2161     type => 'attribute missing:value');
2162     }
2163     },
2164 wakaba 1.1 checker => $HTMLEmptyChecker,
2165     };
2166    
2167 wakaba 1.2 $Element->{$HTML_NS}->{video} = {
2168 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2169     src => $HTMLURIAttrChecker,
2170     ## TODO: start, loopstart, loopend, end
2171     ## ISSUE: they MUST be "value time offset"s. Value?
2172     ## ISSUE: loopcount has no conformance creteria
2173     autoplay => $GetHTMLBooleanAttrChecker->('autoplay'),
2174     controls => $GetHTMLBooleanAttrChecker->('controls'),
2175     }),
2176 wakaba 1.2 checker => sub {
2177 wakaba 1.4 my ($self, $todo) = @_;
2178 wakaba 1.2
2179 wakaba 1.4 if ($todo->{node}->has_attribute_ns (undef, 'src')) {
2180     return $HTMLBlockOrInlineChecker->($self, $todo);
2181 wakaba 1.2 } else {
2182     return $GetHTMLZeroOrMoreThenBlockOrInlineChecker->($HTML_NS, 'source')
2183 wakaba 1.4 ->($self, $todo);
2184 wakaba 1.2 }
2185     },
2186     };
2187    
2188     $Element->{$HTML_NS}->{audio} = {
2189 wakaba 1.12 attrs_checker => $Element->{$HTML_NS}->{video}->{attrs_checker},
2190     checker => $Element->{$HTML_NS}->{video}->{checker},
2191 wakaba 1.2 };
2192 wakaba 1.1
2193     $Element->{$HTML_NS}->{source} = {
2194 wakaba 1.17 attrs_checker => sub {
2195     my ($self, $todo) = @_;
2196     $GetHTMLAttrsChecker->({
2197     src => $HTMLURIAttrChecker,
2198     type => $HTMLIMTAttrChecker,
2199     media => $HTMLMQAttrChecker,
2200     })->($self, $todo);
2201     unless ($todo->{node}->has_attribute_ns (undef, 'src')) {
2202     $self->{onerror}->(node => $todo->{node},
2203     type => 'attribute missing:src');
2204     }
2205     },
2206 wakaba 1.1 checker => $HTMLEmptyChecker,
2207     };
2208    
2209     $Element->{$HTML_NS}->{canvas} = {
2210 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2211     height => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }),
2212     width => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }),
2213     }),
2214 wakaba 1.1 checker => $HTMLInlineChecker,
2215     };
2216    
2217     $Element->{$HTML_NS}->{map} = {
2218 wakaba 1.17 attrs_checker => $GetHTMLAttrsChecker->({
2219     id => sub {
2220     ## NOTE: same as global |id=""|, with |$self->{map}| registeration
2221     my ($self, $attr) = @_;
2222     my $value = $attr->value;
2223     if (length $value > 0) {
2224     if ($self->{id}->{$value}) {
2225     $self->{onerror}->(node => $attr, type => 'duplicate ID');
2226 wakaba 1.37 push @{$self->{id}->{$value}}, $attr;
2227 wakaba 1.17 } else {
2228 wakaba 1.37 $self->{id}->{$value} = [$attr];
2229 wakaba 1.17 }
2230     } else {
2231     ## NOTE: MUST contain at least one character
2232 wakaba 1.33 $self->{onerror}->(node => $attr, type => 'empty attribute value');
2233 wakaba 1.17 }
2234 wakaba 1.27 if ($value =~ /[\x09-\x0D\x20]/) {
2235     $self->{onerror}->(node => $attr, type => 'space in ID');
2236     }
2237 wakaba 1.17 $self->{map}->{$value} ||= $attr;
2238     },
2239     }),
2240 wakaba 1.1 checker => $HTMLBlockChecker,
2241     };
2242    
2243     $Element->{$HTML_NS}->{area} = {
2244 wakaba 1.15 attrs_checker => sub {
2245     my ($self, $todo) = @_;
2246     my %attr;
2247     my $coords;
2248     for my $attr (@{$todo->{node}->attributes}) {
2249     my $attr_ns = $attr->namespace_uri;
2250     $attr_ns = '' unless defined $attr_ns;
2251     my $attr_ln = $attr->manakai_local_name;
2252     my $checker;
2253     if ($attr_ns eq '') {
2254     $checker = {
2255     alt => sub { },
2256     ## NOTE: |alt| value has no conformance creteria.
2257     shape => $GetHTMLEnumeratedAttrChecker->({
2258     circ => -1, circle => 1,
2259     default => 1,
2260     poly => 1, polygon => -1,
2261     rect => 1, rectangle => -1,
2262     }),
2263     coords => sub {
2264     my ($self, $attr) = @_;
2265     my $value = $attr->value;
2266     if ($value =~ /\A-?[0-9]+(?>,-?[0-9]+)*\z/) {
2267     $coords = [split /,/, $value];
2268     } else {
2269     $self->{onerror}->(node => $attr,
2270 wakaba 1.33 type => 'coords:syntax error');
2271 wakaba 1.15 }
2272     },
2273 wakaba 1.17 target => $HTMLTargetAttrChecker,
2274 wakaba 1.15 href => $HTMLURIAttrChecker,
2275     ping => $HTMLSpaceURIsAttrChecker,
2276 wakaba 1.20 rel => sub { $HTMLLinkTypesAttrChecker->(1, @_) },
2277 wakaba 1.17 media => $HTMLMQAttrChecker,
2278     hreflang => $HTMLLanguageTagAttrChecker,
2279 wakaba 1.15 type => $HTMLIMTAttrChecker,
2280     }->{$attr_ln};
2281     if ($checker) {
2282     $attr{$attr_ln} = $attr;
2283     } else {
2284     $checker = $HTMLAttrChecker->{$attr_ln};
2285     }
2286     }
2287     $checker ||= $AttrChecker->{$attr_ns}->{$attr_ln}
2288     || $AttrChecker->{$attr_ns}->{''};
2289     if ($checker) {
2290     $checker->($self, $attr) if ref $checker;
2291     } else {
2292 wakaba 1.33 $self->{onerror}->(node => $attr, level => 'unsupported',
2293     type => 'attribute');
2294 wakaba 1.15 ## ISSUE: No comformance createria for unknown attributes in the spec
2295     }
2296     }
2297    
2298     if (defined $attr{href}) {
2299     unless (defined $attr{alt}) {
2300     $self->{onerror}->(node => $todo->{node},
2301     type => 'attribute missing:alt');
2302     }
2303     } else {
2304     for (qw/target ping rel media hreflang type alt/) {
2305     if (defined $attr{$_}) {
2306     $self->{onerror}->(node => $attr{$_},
2307     type => 'attribute not allowed');
2308     }
2309     }
2310     }
2311    
2312     my $shape = 'rectangle';
2313     if (defined $attr{shape}) {
2314     $shape = {
2315     circ => 'circle', circle => 'circle',
2316     default => 'default',
2317     poly => 'polygon', polygon => 'polygon',
2318     rect => 'rectangle', rectangle => 'rectangle',
2319     }->{lc $attr{shape}->value} || 'rectangle';
2320     ## TODO: ASCII lowercase?
2321     }
2322    
2323     if ($shape eq 'circle') {
2324     if (defined $attr{coords}) {
2325     if (defined $coords) {
2326     if (@$coords == 3) {
2327     if ($coords->[2] < 0) {
2328     $self->{onerror}->(node => $attr{coords},
2329 wakaba 1.33 type => 'coords:out of range:2');
2330 wakaba 1.15 }
2331     } else {
2332     $self->{onerror}->(node => $attr{coords},
2333 wakaba 1.33 type => 'coords:number:3:'.@$coords);
2334 wakaba 1.15 }
2335     } else {
2336     ## NOTE: A syntax error has been reported.
2337     }
2338     } else {
2339     $self->{onerror}->(node => $todo->{node},
2340     type => 'attribute missing:coords');
2341     }
2342     } elsif ($shape eq 'default') {
2343     if (defined $attr{coords}) {
2344     $self->{onerror}->(node => $attr{coords},
2345     type => 'attribute not allowed');
2346     }
2347     } elsif ($shape eq 'polygon') {
2348     if (defined $attr{coords}) {
2349     if (defined $coords) {
2350     if (@$coords >= 6) {
2351     unless (@$coords % 2 == 0) {
2352     $self->{onerror}->(node => $attr{coords},
2353 wakaba 1.33 type => 'coords:number:even:'.@$coords);
2354 wakaba 1.15 }
2355     } else {
2356     $self->{onerror}->(node => $attr{coords},
2357 wakaba 1.33 type => 'coords:number:>=6:'.@$coords);
2358 wakaba 1.15 }
2359     } else {
2360     ## NOTE: A syntax error has been reported.
2361     }
2362     } else {
2363     $self->{onerror}->(node => $todo->{node},
2364     type => 'attribute missing:coords');
2365     }
2366     } elsif ($shape eq 'rectangle') {
2367     if (defined $attr{coords}) {
2368     if (defined $coords) {
2369     if (@$coords == 4) {
2370     unless ($coords->[0] < $coords->[2]) {
2371     $self->{onerror}->(node => $attr{coords},
2372 wakaba 1.33 type => 'coords:out of range:0');
2373 wakaba 1.15 }
2374     unless ($coords->[1] < $coords->[3]) {
2375     $self->{onerror}->(node => $attr{coords},
2376 wakaba 1.33 type => 'coords:out of range:1');
2377 wakaba 1.15 }
2378     } else {
2379     $self->{onerror}->(node => $attr{coords},
2380 wakaba 1.33 type => 'coords:number:4:'.@$coords);
2381 wakaba 1.15 }
2382     } else {
2383     ## NOTE: A syntax error has been reported.
2384     }
2385     } else {
2386     $self->{onerror}->(node => $todo->{node},
2387     type => 'attribute missing:coords');
2388     }
2389     }
2390     },
2391 wakaba 1.1 checker => $HTMLEmptyChecker,
2392     };
2393     ## TODO: only in map
2394    
2395     $Element->{$HTML_NS}->{table} = {
2396 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2397 wakaba 1.1 checker => sub {
2398 wakaba 1.4 my ($self, $todo) = @_;
2399     my $el = $todo->{node};
2400     my $new_todos = [];
2401 wakaba 1.1 my @nodes = (@{$el->child_nodes});
2402    
2403     my $phase = 'before caption';
2404     my $has_tfoot;
2405     while (@nodes) {
2406     my $node = shift @nodes;
2407 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
2408    
2409 wakaba 1.1 my $nt = $node->node_type;
2410     if ($nt == 1) {
2411 wakaba 1.8 my $node_ns = $node->namespace_uri;
2412     $node_ns = '' unless defined $node_ns;
2413     my $node_ln = $node->manakai_local_name;
2414 wakaba 1.2 ## NOTE: |minuses| list is not checked since redundant
2415 wakaba 1.1 if ($phase eq 'in tbodys') {
2416 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'tbody') {
2417 wakaba 1.1 #$phase = 'in tbodys';
2418     } elsif (not $has_tfoot and
2419 wakaba 1.8 $node_ns eq $HTML_NS and $node_ln eq 'tfoot') {
2420 wakaba 1.1 $phase = 'after tfoot';
2421     $has_tfoot = 1;
2422     } else {
2423 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2424 wakaba 1.1 }
2425     } elsif ($phase eq 'in trs') {
2426 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'tr') {
2427 wakaba 1.1 #$phase = 'in trs';
2428     } elsif (not $has_tfoot and
2429 wakaba 1.8 $node_ns eq $HTML_NS and $node_ln eq 'tfoot') {
2430 wakaba 1.1 $phase = 'after tfoot';
2431     $has_tfoot = 1;
2432     } else {
2433 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2434 wakaba 1.1 }
2435     } elsif ($phase eq 'after thead') {
2436 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'tbody') {
2437 wakaba 1.1 $phase = 'in tbodys';
2438 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'tr') {
2439 wakaba 1.1 $phase = 'in trs';
2440 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'tfoot') {
2441 wakaba 1.1 $phase = 'in tbodys';
2442     $has_tfoot = 1;
2443     } else {
2444 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2445 wakaba 1.1 }
2446     } elsif ($phase eq 'in colgroup') {
2447 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'colgroup') {
2448 wakaba 1.1 $phase = 'in colgroup';
2449 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'thead') {
2450 wakaba 1.1 $phase = 'after thead';
2451 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'tbody') {
2452 wakaba 1.1 $phase = 'in tbodys';
2453 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'tr') {
2454 wakaba 1.1 $phase = 'in trs';
2455 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'tfoot') {
2456 wakaba 1.1 $phase = 'in tbodys';
2457     $has_tfoot = 1;
2458     } else {
2459 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2460 wakaba 1.1 }
2461     } elsif ($phase eq 'before caption') {
2462 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'caption') {
2463 wakaba 1.1 $phase = 'in colgroup';
2464 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'colgroup') {
2465 wakaba 1.1 $phase = 'in colgroup';
2466 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'thead') {
2467 wakaba 1.1 $phase = 'after thead';
2468 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'tbody') {
2469 wakaba 1.1 $phase = 'in tbodys';
2470 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'tr') {
2471 wakaba 1.1 $phase = 'in trs';
2472 wakaba 1.8 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'tfoot') {
2473 wakaba 1.1 $phase = 'in tbodys';
2474     $has_tfoot = 1;
2475     } else {
2476 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2477 wakaba 1.1 }
2478     } else { # after tfoot
2479 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2480 wakaba 1.1 }
2481 wakaba 1.30 my ($sib, $ch) = $self->_check_get_children ($node, $todo);
2482 wakaba 1.2 unshift @nodes, @$sib;
2483 wakaba 1.4 push @$new_todos, @$ch;
2484 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
2485     if ($node->data =~ /[^\x09-\x0D\x20]/) {
2486 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
2487 wakaba 1.1 }
2488     } elsif ($nt == 5) {
2489     unshift @nodes, @{$node->child_nodes};
2490     }
2491     }
2492 wakaba 1.21
2493     ## Table model errors
2494     require Whatpm::HTMLTable;
2495     Whatpm::HTMLTable->form_table ($todo->{node}, sub {
2496     my %opt = @_;
2497     $self->{onerror}->(type => 'table:'.$opt{type}, node => $opt{node});
2498     });
2499 wakaba 1.33 push @{$self->{return}->{table}}, $todo->{node};
2500 wakaba 1.21
2501 wakaba 1.4 return ($new_todos);
2502 wakaba 1.1 },
2503     };
2504    
2505     $Element->{$HTML_NS}->{caption} = {
2506 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2507 wakaba 1.1 checker => $HTMLSignificantStrictlyInlineChecker,
2508     };
2509    
2510     $Element->{$HTML_NS}->{colgroup} = {
2511 wakaba 1.17 attrs_checker => $GetHTMLAttrsChecker->({
2512     span => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
2513     ## NOTE: Defined only if "the |colgroup| element contains no |col| elements"
2514     ## TODO: "attribute not supported" if |col|.
2515     ## ISSUE: MUST NOT if any |col|?
2516     ## ISSUE: MUST NOT for |<colgroup span="1"><any><col/></any></colgroup>| (though non-conforming)?
2517     }),
2518 wakaba 1.1 checker => sub {
2519 wakaba 1.4 my ($self, $todo) = @_;
2520     my $el = $todo->{node};
2521     my $new_todos = [];
2522 wakaba 1.1 my @nodes = (@{$el->child_nodes});
2523    
2524     while (@nodes) {
2525     my $node = shift @nodes;
2526 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
2527    
2528 wakaba 1.1 my $nt = $node->node_type;
2529     if ($nt == 1) {
2530 wakaba 1.8 my $node_ns = $node->namespace_uri;
2531     $node_ns = '' unless defined $node_ns;
2532     my $node_ln = $node->manakai_local_name;
2533 wakaba 1.2 ## NOTE: |minuses| list is not checked since redundant
2534 wakaba 1.8 unless ($node_ns eq $HTML_NS and $node_ln eq 'col') {
2535 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2536 wakaba 1.1 }
2537 wakaba 1.30 my ($sib, $ch) = $self->_check_get_children ($node, $todo);
2538 wakaba 1.2 unshift @nodes, @$sib;
2539 wakaba 1.4 push @$new_todos, @$ch;
2540 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
2541     if ($node->data =~ /[^\x09-\x0D\x20]/) {
2542 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
2543 wakaba 1.1 }
2544     } elsif ($nt == 5) {
2545     unshift @nodes, @{$node->child_nodes};
2546     }
2547     }
2548 wakaba 1.4 return ($new_todos);
2549 wakaba 1.1 },
2550     };
2551    
2552     $Element->{$HTML_NS}->{col} = {
2553 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2554     span => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
2555     }),
2556 wakaba 1.1 checker => $HTMLEmptyChecker,
2557     };
2558    
2559     $Element->{$HTML_NS}->{tbody} = {
2560 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2561 wakaba 1.1 checker => sub {
2562 wakaba 1.4 my ($self, $todo) = @_;
2563     my $el = $todo->{node};
2564     my $new_todos = [];
2565 wakaba 1.1 my @nodes = (@{$el->child_nodes});
2566    
2567     my $has_tr;
2568     while (@nodes) {
2569     my $node = shift @nodes;
2570 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
2571    
2572 wakaba 1.1 my $nt = $node->node_type;
2573     if ($nt == 1) {
2574 wakaba 1.8 my $node_ns = $node->namespace_uri;
2575     $node_ns = '' unless defined $node_ns;
2576     my $node_ln = $node->manakai_local_name;
2577 wakaba 1.2 ## NOTE: |minuses| list is not checked since redundant
2578 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'tr') {
2579 wakaba 1.1 $has_tr = 1;
2580     } else {
2581 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2582 wakaba 1.1 }
2583 wakaba 1.30 my ($sib, $ch) = $self->_check_get_children ($node, $todo);
2584 wakaba 1.2 unshift @nodes, @$sib;
2585 wakaba 1.4 push @$new_todos, @$ch;
2586 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
2587     if ($node->data =~ /[^\x09-\x0D\x20]/) {
2588 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
2589 wakaba 1.1 }
2590     } elsif ($nt == 5) {
2591     unshift @nodes, @{$node->child_nodes};
2592     }
2593     }
2594     unless ($has_tr) {
2595 wakaba 1.3 $self->{onerror}->(node => $el, type => 'child element missing:tr');
2596 wakaba 1.1 }
2597 wakaba 1.4 return ($new_todos);
2598 wakaba 1.1 },
2599     };
2600    
2601     $Element->{$HTML_NS}->{thead} = {
2602 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2603 wakaba 1.23 checker => $Element->{$HTML_NS}->{tbody}->{checker},
2604 wakaba 1.1 };
2605    
2606     $Element->{$HTML_NS}->{tfoot} = {
2607 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2608 wakaba 1.23 checker => $Element->{$HTML_NS}->{tbody}->{checker},
2609 wakaba 1.1 };
2610    
2611     $Element->{$HTML_NS}->{tr} = {
2612 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2613 wakaba 1.1 checker => sub {
2614 wakaba 1.4 my ($self, $todo) = @_;
2615     my $el = $todo->{node};
2616     my $new_todos = [];
2617 wakaba 1.1 my @nodes = (@{$el->child_nodes});
2618    
2619     my $has_td;
2620     while (@nodes) {
2621     my $node = shift @nodes;
2622 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
2623    
2624 wakaba 1.1 my $nt = $node->node_type;
2625     if ($nt == 1) {
2626 wakaba 1.8 my $node_ns = $node->namespace_uri;
2627     $node_ns = '' unless defined $node_ns;
2628     my $node_ln = $node->manakai_local_name;
2629 wakaba 1.2 ## NOTE: |minuses| list is not checked since redundant
2630 wakaba 1.8 if ($node_ns eq $HTML_NS and ($node_ln eq 'td' or $node_ln eq 'th')) {
2631 wakaba 1.1 $has_td = 1;
2632     } else {
2633 wakaba 1.2 $self->{onerror}->(node => $node, type => 'element not allowed');
2634 wakaba 1.1 }
2635 wakaba 1.30 my ($sib, $ch) = $self->_check_get_children ($node, $todo);
2636 wakaba 1.2 unshift @nodes, @$sib;
2637 wakaba 1.4 push @$new_todos, @$ch;
2638 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
2639     if ($node->data =~ /[^\x09-\x0D\x20]/) {
2640 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
2641 wakaba 1.1 }
2642     } elsif ($nt == 5) {
2643     unshift @nodes, @{$node->child_nodes};
2644     }
2645     }
2646     unless ($has_td) {
2647 wakaba 1.3 $self->{onerror}->(node => $el, type => 'child element missing:td|th');
2648 wakaba 1.1 }
2649 wakaba 1.4 return ($new_todos);
2650 wakaba 1.1 },
2651     };
2652    
2653     $Element->{$HTML_NS}->{td} = {
2654 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2655     colspan => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
2656     rowspan => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
2657     }),
2658 wakaba 1.1 checker => $HTMLBlockOrInlineChecker,
2659     };
2660    
2661     $Element->{$HTML_NS}->{th} = {
2662 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2663     colspan => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
2664     rowspan => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
2665     scope => $GetHTMLEnumeratedAttrChecker
2666     ->({row => 1, col => 1, rowgroup => 1, colgroup => 1}),
2667     }),
2668 wakaba 1.1 checker => $HTMLBlockOrInlineChecker,
2669     };
2670    
2671     ## TODO: forms
2672    
2673 wakaba 1.2 $Element->{$HTML_NS}->{script} = {
2674 wakaba 1.25 attrs_checker => sub {
2675     my ($self, $todo) = @_;
2676     $GetHTMLAttrsChecker->({
2677     src => $HTMLURIAttrChecker,
2678     defer => $GetHTMLBooleanAttrChecker->('defer'),
2679     async => $GetHTMLBooleanAttrChecker->('async'),
2680     type => $HTMLIMTAttrChecker,
2681     })->($self, $todo);
2682     if ($todo->{node}->has_attribute_ns (undef, 'defer')) {
2683     my $async_attr = $todo->{node}->get_attribute_node_ns (undef, 'async');
2684     if ($async_attr) {
2685     $self->{onerror}->(node => $async_attr,
2686     type => 'attribute not allowed'); # MUST NOT
2687     }
2688     }
2689     },
2690 wakaba 1.2 checker => sub {
2691 wakaba 1.4 my ($self, $todo) = @_;
2692 wakaba 1.2
2693 wakaba 1.4 if ($todo->{node}->has_attribute_ns (undef, 'src')) {
2694     return $HTMLEmptyChecker->($self, $todo);
2695 wakaba 1.2 } else {
2696     ## NOTE: No content model conformance in HTML5 spec.
2697 wakaba 1.36 my $type = $todo->{node}->get_attribute_ns (undef, 'type');
2698     my $language = $todo->{node}->get_attribute_ns (undef, 'language');
2699     if ((defined $type and $type eq '') or
2700     (defined $language and $language eq '')) {
2701     $type = 'text/javascript';
2702     } elsif (defined $type) {
2703     #
2704     } elsif (defined $language) {
2705     $type = 'text/' . $language;
2706     } else {
2707     $type = 'text/javascript';
2708     }
2709     $self->{onerror}->(node => $todo->{node}, level => 'unsupported',
2710     type => 'script:'.$type); ## TODO: $type normalization
2711 wakaba 1.4 return $AnyChecker->($self, $todo);
2712 wakaba 1.2 }
2713     },
2714     };
2715    
2716     ## NOTE: When script is disabled.
2717     $Element->{$HTML_NS}->{noscript} = {
2718 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2719 wakaba 1.2 checker => sub {
2720 wakaba 1.4 my ($self, $todo) = @_;
2721 wakaba 1.1
2722 wakaba 1.2 my $end = $self->_add_minuses ({$HTML_NS => {noscript => 1}});
2723 wakaba 1.4 my ($sib, $ch) = $HTMLBlockOrInlineChecker->($self, $todo);
2724 wakaba 1.2 push @$sib, $end;
2725     return ($sib, $ch);
2726     },
2727     };
2728 wakaba 1.29 ## TODO: noscript in head
2729 wakaba 1.35 ## TODO: noscript in XHTML
2730 wakaba 1.1
2731     $Element->{$HTML_NS}->{'event-source'} = {
2732 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2733     src => $HTMLURIAttrChecker,
2734     }),
2735 wakaba 1.1 checker => $HTMLEmptyChecker,
2736     };
2737    
2738     $Element->{$HTML_NS}->{details} = {
2739 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2740     open => $GetHTMLBooleanAttrChecker->('open'),
2741     }),
2742 wakaba 1.6 checker => sub {
2743     my ($self, $todo) = @_;
2744    
2745     my $end = $self->_add_minuses ({$HTML_NS => {a => 1, datagrid => 1}});
2746     my ($sib, $ch)
2747     = $GetHTMLZeroOrMoreThenBlockOrInlineChecker->($HTML_NS, 'legend')
2748     ->($self, $todo);
2749     push @$sib, $end;
2750     return ($sib, $ch);
2751     },
2752 wakaba 1.1 };
2753    
2754     $Element->{$HTML_NS}->{datagrid} = {
2755 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({
2756     disabled => $GetHTMLBooleanAttrChecker->('disabled'),
2757     multiple => $GetHTMLBooleanAttrChecker->('multiple'),
2758     }),
2759 wakaba 1.6 checker => sub {
2760     my ($self, $todo) = @_;
2761 wakaba 1.32 my $el = $todo->{node};
2762     my $new_todos = [];
2763     my @nodes = (@{$el->child_nodes});
2764 wakaba 1.6
2765     my $end = $self->_add_minuses ({$HTML_NS => {a => 1, datagrid => 1}});
2766 wakaba 1.32
2767     ## Block-table Block* | table | select | datalist | Empty
2768     my $mode = 'any';
2769     while (@nodes) {
2770     my $node = shift @nodes;
2771     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
2772    
2773     my $nt = $node->node_type;
2774     if ($nt == 1) {
2775     my $node_ns = $node->namespace_uri;
2776     $node_ns = '' unless defined $node_ns;
2777     my $node_ln = $node->manakai_local_name;
2778     my $not_allowed = $self->{minuses}->{$node_ns}->{$node_ln};
2779     if ($mode eq 'block') {
2780     $not_allowed = 1
2781     unless $HTMLBlockLevelElements->{$node_ns}->{$node_ln};
2782     } elsif ($mode eq 'any') {
2783     if ($node_ns eq $HTML_NS and
2784     {table => 1, select => 1, datalist => 1}->{$node_ln}) {
2785     $mode = 'none';
2786     } elsif ($HTMLBlockLevelElements->{$node_ns}->{$node_ln}) {
2787     $mode = 'block';
2788     } else {
2789     $not_allowed = 1;
2790     }
2791     } else {
2792     $not_allowed = 1;
2793     }
2794     $self->{onerror}->(node => $node, type => 'element not allowed')
2795     if $not_allowed;
2796     my ($sib, $ch) = $self->_check_get_children ($node, $todo);
2797     unshift @nodes, @$sib;
2798     push @$new_todos, @$ch;
2799     } elsif ($nt == 3 or $nt == 4) {
2800     if ($node->data =~ /[^\x09-\x0D\x20]/) {
2801     $self->{onerror}->(node => $node, type => 'character not allowed');
2802     }
2803     } elsif ($nt == 5) {
2804     unshift @nodes, @{$node->child_nodes};
2805     }
2806     }
2807    
2808     push @$new_todos, $end;
2809     return ($new_todos);
2810 wakaba 1.6 },
2811 wakaba 1.1 };
2812    
2813     $Element->{$HTML_NS}->{command} = {
2814 wakaba 1.32 attrs_checker => $GetHTMLAttrsChecker->({
2815     checked => $GetHTMLBooleanAttrChecker->('checked'),
2816     default => $GetHTMLBooleanAttrChecker->('default'),
2817     disabled => $GetHTMLBooleanAttrChecker->('disabled'),
2818     hidden => $GetHTMLBooleanAttrChecker->('hidden'),
2819     icon => $HTMLURIAttrChecker,
2820     label => sub { }, ## NOTE: No conformance creteria
2821     radiogroup => sub { }, ## NOTE: No conformance creteria
2822     ## NOTE: |title| has special semantics, but no syntactical difference
2823     type => sub {
2824     my ($self, $attr) = @_;
2825     my $value = $attr->value;
2826     unless ({command => 1, checkbox => 1, radio => 1}->{$value}) {
2827     $self->{onerror}->(node => $attr, type => 'attribute value not allowed');
2828     }
2829     },
2830     }),
2831 wakaba 1.1 checker => $HTMLEmptyChecker,
2832     };
2833    
2834     $Element->{$HTML_NS}->{menu} = {
2835 wakaba 1.32 attrs_checker => $GetHTMLAttrsChecker->({
2836     autosubmit => $GetHTMLBooleanAttrChecker->('autosubmit'),
2837     id => sub {
2838     ## NOTE: same as global |id=""|, with |$self->{menu}| registeration
2839     my ($self, $attr) = @_;
2840     my $value = $attr->value;
2841     if (length $value > 0) {
2842     if ($self->{id}->{$value}) {
2843     $self->{onerror}->(node => $attr, type => 'duplicate ID');
2844 wakaba 1.37 push @{$self->{id}->{$value}}, $attr;
2845 wakaba 1.32 } else {
2846 wakaba 1.37 $self->{id}->{$value} = [$attr];
2847 wakaba 1.32 }
2848     } else {
2849     ## NOTE: MUST contain at least one character
2850 wakaba 1.33 $self->{onerror}->(node => $attr, type => 'empty attribute value');
2851 wakaba 1.32 }
2852     if ($value =~ /[\x09-\x0D\x20]/) {
2853     $self->{onerror}->(node => $attr, type => 'space in ID');
2854     }
2855     $self->{menu}->{$value} ||= $attr;
2856     ## ISSUE: <menu id=""><p contextmenu=""> match?
2857     },
2858     label => sub { }, ## NOTE: No conformance creteria
2859     type => $GetHTMLEnumeratedAttrChecker->({context => 1, toolbar => 1}),
2860     }),
2861 wakaba 1.1 checker => sub {
2862 wakaba 1.4 my ($self, $todo) = @_;
2863     my $el = $todo->{node};
2864     my $new_todos = [];
2865 wakaba 1.1 my @nodes = (@{$el->child_nodes});
2866    
2867     my $content = 'li or inline';
2868     while (@nodes) {
2869     my $node = shift @nodes;
2870 wakaba 1.2 $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
2871    
2872 wakaba 1.1 my $nt = $node->node_type;
2873     if ($nt == 1) {
2874 wakaba 1.2 my $node_ns = $node->namespace_uri;
2875     $node_ns = '' unless defined $node_ns;
2876     my $node_ln = $node->manakai_local_name;
2877 wakaba 1.6 my $not_allowed = $self->{minuses}->{$node_ns}->{$node_ln};
2878 wakaba 1.8 if ($node_ns eq $HTML_NS and $node_ln eq 'li') {
2879 wakaba 1.1 if ($content eq 'inline') {
2880 wakaba 1.6 $not_allowed = 1;
2881 wakaba 1.1 } elsif ($content eq 'li or inline') {
2882     $content = 'li';
2883     }
2884     } else {
2885 wakaba 1.7 if ($HTMLStrictlyInlineLevelElements->{$node_ns}->{$node_ln} or
2886     $HTMLStructuredInlineLevelElements->{$node_ns}->{$node_ln}) {
2887     $content = 'inline';
2888     } else {
2889 wakaba 1.6 $not_allowed = 1;
2890 wakaba 1.7 }
2891 wakaba 1.1 }
2892 wakaba 1.6 $self->{onerror}->(node => $node, type => 'element not allowed')
2893     if $not_allowed;
2894 wakaba 1.30 my ($sib, $ch) = $self->_check_get_children ($node, $todo);
2895 wakaba 1.2 unshift @nodes, @$sib;
2896 wakaba 1.4 push @$new_todos, @$ch;
2897 wakaba 1.1 } elsif ($nt == 3 or $nt == 4) {
2898     if ($node->data =~ /[^\x09-\x0D\x20]/) {
2899     if ($content eq 'li') {
2900 wakaba 1.2 $self->{onerror}->(node => $node, type => 'character not allowed');
2901 wakaba 1.1 } elsif ($content eq 'li or inline') {
2902     $content = 'inline';
2903     }
2904     }
2905     } elsif ($nt == 5) {
2906     unshift @nodes, @{$node->child_nodes};
2907     }
2908     }
2909 wakaba 1.4
2910     for (@$new_todos) {
2911     $_->{inline} = 1;
2912     }
2913     return ($new_todos);
2914 wakaba 1.1 },
2915     };
2916    
2917 wakaba 1.6 $Element->{$HTML_NS}->{legend} = {
2918 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2919 wakaba 1.6 checker => sub {
2920     my ($self, $todo) = @_;
2921    
2922     my $parent = $todo->{node}->manakai_parent_element;
2923     if (defined $parent) {
2924     my $nsuri = $parent->namespace_uri;
2925     $nsuri = '' unless defined $nsuri;
2926     my $ln = $parent->manakai_local_name;
2927     if ($nsuri eq $HTML_NS and $ln eq 'figure') {
2928     return $HTMLInlineChecker->($self, $todo);
2929     } else {
2930     return $HTMLSignificantStrictlyInlineChecker->($self, $todo);
2931     }
2932     } else {
2933     return $HTMLInlineChecker->($self, $todo);
2934     }
2935    
2936     ## ISSUE: Content model is defined only for fieldset/legend,
2937     ## details/legend, and figure/legend.
2938     },
2939     };
2940 wakaba 1.1
2941     $Element->{$HTML_NS}->{div} = {
2942 wakaba 1.10 attrs_checker => $GetHTMLAttrsChecker->({}),
2943 wakaba 1.2 checker => $GetHTMLZeroOrMoreThenBlockOrInlineChecker->($HTML_NS, 'style'),
2944 wakaba 1.1 };
2945    
2946     $Element->{$HTML_NS}->{font} = {
2947 wakaba 1.12 attrs_checker => $GetHTMLAttrsChecker->({}), ## TODO
2948 wakaba 1.1 checker => $HTMLTransparentChecker,
2949     };
2950    
2951 wakaba 1.24 sub check_document ($$$) {
2952     my ($self, $doc, $onerror) = @_;
2953     $self = bless {}, $self unless ref $self;
2954     $self->{onerror} = $onerror;
2955    
2956     my $docel = $doc->document_element;
2957 wakaba 1.26 unless (defined $docel) {
2958     ## ISSUE: Should we check content of Document node?
2959     $onerror->(node => $doc, type => 'no document element');
2960     ## ISSUE: Is this non-conforming (to what spec)? Or just a warning?
2961     return;
2962     }
2963    
2964     ## ISSUE: Unexpanded entity references and HTML5 conformance
2965    
2966 wakaba 1.24 my $docel_nsuri = $docel->namespace_uri;
2967     $docel_nsuri = '' unless defined $docel_nsuri;
2968     my $docel_def = $Element->{$docel_nsuri}->{$docel->manakai_local_name} ||
2969     $Element->{$docel_nsuri}->{''} ||
2970     $ElementDefault;
2971     if ($docel_def->{is_root}) {
2972     #
2973     } else {
2974     $onerror->(node => $docel, type => 'element not allowed');
2975     }
2976    
2977     ## TODO: Check for other items other than document element
2978     ## (second (errorous) element, text nodes, PI nodes, doctype nodes)
2979    
2980 wakaba 1.33 return $self->check_element ($docel, $onerror);
2981 wakaba 1.24 } # check_document
2982 wakaba 1.2
2983 wakaba 1.1 sub check_element ($$$) {
2984     my ($self, $el, $onerror) = @_;
2985 wakaba 1.24 $self = bless {}, $self unless ref $self;
2986     $self->{onerror} = $onerror;
2987 wakaba 1.1
2988 wakaba 1.2 $self->{minuses} = {};
2989 wakaba 1.10 $self->{id} = {};
2990 wakaba 1.30 $self->{term} = {};
2991 wakaba 1.17 $self->{usemap} = [];
2992 wakaba 1.32 $self->{contextmenu} = [];
2993 wakaba 1.17 $self->{map} = {};
2994 wakaba 1.32 $self->{menu} = {};
2995 wakaba 1.20 $self->{has_link_type} = {};
2996 wakaba 1.33 $self->{return} = {
2997 wakaba 1.38 class => {},
2998 wakaba 1.37 id => $self->{id}, table => [], term => $self->{term},
2999 wakaba 1.33 };
3000 wakaba 1.2
3001 wakaba 1.4 my @todo = ({type => 'element', node => $el});
3002     while (@todo) {
3003     my $todo = shift @todo;
3004     if ($todo->{type} eq 'element') {
3005 wakaba 1.13 my $prefix = $todo->{node}->prefix;
3006     if (defined $prefix and $prefix eq 'xmlns') {
3007     $self->{onerror}
3008 wakaba 1.33 ->(node => $todo->{node}, level => 'NC',
3009     type => 'Reserved Prefixes and Namespace Names:<xmlns:>');
3010 wakaba 1.13 }
3011 wakaba 1.4 my $nsuri = $todo->{node}->namespace_uri;
3012     $nsuri = '' unless defined $nsuri;
3013     my $ln = $todo->{node}->manakai_local_name;
3014     my $eldef = $Element->{$nsuri}->{$ln} ||
3015     $Element->{$nsuri}->{''} ||
3016     $ElementDefault;
3017 wakaba 1.9 $eldef->{attrs_checker}->($self, $todo);
3018 wakaba 1.4 my ($new_todos) = $eldef->{checker}->($self, $todo);
3019 wakaba 1.14 unshift @todo, @$new_todos;
3020 wakaba 1.9 } elsif ($todo->{type} eq 'element-attributes') {
3021 wakaba 1.13 my $prefix = $todo->{node}->prefix;
3022     if (defined $prefix and $prefix eq 'xmlns') {
3023     $self->{onerror}
3024 wakaba 1.33 ->(node => $todo->{node}, level => 'NC',
3025     type => 'Reserved Prefixes and Namespace Names:<xmlns:>');
3026 wakaba 1.13 }
3027 wakaba 1.9 my $nsuri = $todo->{node}->namespace_uri;
3028     $nsuri = '' unless defined $nsuri;
3029     my $ln = $todo->{node}->manakai_local_name;
3030     my $eldef = $Element->{$nsuri}->{$ln} ||
3031     $Element->{$nsuri}->{''} ||
3032     $ElementDefault;
3033     $eldef->{attrs_checker}->($self, $todo);
3034 wakaba 1.4 } elsif ($todo->{type} eq 'plus') {
3035     $self->_remove_minuses ($todo);
3036 wakaba 1.30 } elsif ($todo->{type} eq 'code') {
3037     $todo->{code}->();
3038     } else {
3039     die "$0: Internal error: Unsupported checking action type |$todo->{type}|";
3040 wakaba 1.4 }
3041 wakaba 1.1 }
3042 wakaba 1.17
3043     for (@{$self->{usemap}}) {
3044     unless ($self->{map}->{$_->[0]}) {
3045     $self->{onerror}->(node => $_->[1], type => 'no referenced map');
3046     }
3047     }
3048    
3049 wakaba 1.32 for (@{$self->{contextmenu}}) {
3050     unless ($self->{menu}->{$_->[0]}) {
3051     $self->{onerror}->(node => $_->[1], type => 'no referenced menu');
3052     }
3053     }
3054    
3055 wakaba 1.17 delete $self->{minuses};
3056     delete $self->{onerror};
3057     delete $self->{id};
3058     delete $self->{usemap};
3059     delete $self->{map};
3060 wakaba 1.33 return $self->{return};
3061 wakaba 1.1 } # check_element
3062    
3063 wakaba 1.2 sub _add_minuses ($@) {
3064     my $self = shift;
3065     my $r = {};
3066     for my $list (@_) {
3067     for my $ns (keys %$list) {
3068     for my $ln (keys %{$list->{$ns}}) {
3069     unless ($self->{minuses}->{$ns}->{$ln}) {
3070     $self->{minuses}->{$ns}->{$ln} = 1;
3071     $r->{$ns}->{$ln} = 1;
3072     }
3073     }
3074     }
3075     }
3076 wakaba 1.4 return {type => 'plus', list => $r};
3077 wakaba 1.2 } # _add_minuses
3078    
3079     sub _remove_minuses ($$) {
3080 wakaba 1.4 my ($self, $todo) = @_;
3081     for my $ns (keys %{$todo->{list}}) {
3082     for my $ln (keys %{$todo->{list}->{$ns}}) {
3083     delete $self->{minuses}->{$ns}->{$ln} if $todo->{list}->{$ns}->{$ln};
3084 wakaba 1.2 }
3085     }
3086     1;
3087     } # _remove_minuses
3088    
3089 wakaba 1.30 sub _check_get_children ($$$) {
3090     my ($self, $node, $parent_todo) = @_;
3091 wakaba 1.4 my $new_todos = [];
3092 wakaba 1.2 my $sib = [];
3093     TP: {
3094     my $node_ns = $node->namespace_uri;
3095     $node_ns = '' unless defined $node_ns;
3096     my $node_ln = $node->manakai_local_name;
3097     if ($node_ns eq $HTML_NS) {
3098     if ($node_ln eq 'noscript') {
3099     my $end = $self->_add_minuses ({$HTML_NS, {noscript => 1}});
3100     push @$sib, $end;
3101     }
3102     }
3103 wakaba 1.31 ## TODO: |noscript| is not a transparent element in |head|.
3104 wakaba 1.7 if ($HTMLTransparentElements->{$node_ns}->{$node_ln}) {
3105     unshift @$sib, @{$node->child_nodes};
3106 wakaba 1.9 push @$new_todos, {type => 'element-attributes', node => $node};
3107 wakaba 1.7 last TP;
3108 wakaba 1.2 }
3109 wakaba 1.8 if ($node_ns eq $HTML_NS and ($node_ln eq 'video' or $node_ln eq 'audio')) {
3110 wakaba 1.2 if ($node->has_attribute_ns (undef, 'src')) {
3111     unshift @$sib, @{$node->child_nodes};
3112 wakaba 1.9 push @$new_todos, {type => 'element-attributes', node => $node};
3113 wakaba 1.2 last TP;
3114     } else {
3115     my @cn = @{$node->child_nodes};
3116     CN: while (@cn) {
3117     my $cn = shift @cn;
3118     my $cnt = $cn->node_type;
3119     if ($cnt == 1) {
3120 wakaba 1.8 my $cn_nsuri = $cn->namespace_uri;
3121     $cn_nsuri = '' unless defined $cn_nsuri;
3122     if ($cn_nsuri eq $HTML_NS and $cn->manakai_local_name eq 'source') {
3123 wakaba 1.2 #
3124     } else {
3125     last CN;
3126     }
3127     } elsif ($cnt == 3 or $cnt == 4) {
3128     if ($cn->data =~ /[^\x09-\x0D\x20]/) {
3129     last CN;
3130     }
3131     }
3132     } # CN
3133     unshift @$sib, @cn;
3134     }
3135     }
3136 wakaba 1.4 push @$new_todos, {type => 'element', node => $node};
3137 wakaba 1.2 } # TP
3138 wakaba 1.30
3139     for my $new_todo (@$new_todos) {
3140     $new_todo->{flag} = {%{$parent_todo->{flag} or {}}};
3141     }
3142    
3143 wakaba 1.4 return ($sib, $new_todos);
3144 wakaba 1.2 } # _check_get_children
3145    
3146 wakaba 1.1 1;
3147 wakaba 1.39 # $Date: 2007/07/17 14:26:48 $

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24