/[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.28 - (hide annotations) (download)
Sat Jun 23 16:42:43 2007 UTC (19 years, 1 month ago) by wakaba
Branch: MAIN
Changes since 1.27: +12 -2 lines
++ whatpm/t/ChangeLog	23 Jun 2007 16:36:55 -0000
	* tree-test-1.dat: Some test results are changed
	since <base>, <link>, and <meta> in body
	are no longer appended to the head element pointer (HTML5
	revision 935).

	* content-model-2.dat: Tests for |scoped|
	attribute are added (HTML5 revision 938).

2007-06-24  Wakaba  <wakaba@suika.fam.cx>

++ whatpm/Whatpm/ChangeLog	23 Jun 2007 16:37:20 -0000
	* HTML.pm.src: HTML5 revision 935 (<base>, <link>, <meta>
	in body).

	* ContentChecker.pm: HTML5 revision 938 (scoped="").

2007-06-24  Wakaba  <wakaba@suika.fam.cx>

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24