/[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.36 - (hide annotations) (download)
Mon Jul 16 14:28:35 2007 UTC (19 years ago) by wakaba
Branch: MAIN
Changes since 1.35: +42 -11 lines
++ whatpm/t/ChangeLog	16 Jul 2007 14:28:33 -0000
	* content-model-1.dat, content-model-2.dat: Some test data
	have been updated due to new warnings.

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

++ whatpm/Whatpm/ChangeLog	16 Jul 2007 14:01:47 -0000
	* ContentChecker.pm: Drop wrong |level => 'error'| specification
	from "in HTML:xml:lang" error.  Character position
	is now the last part of the error type in the URI error
	description.  Report "unsupported" status for language
	tags, media queries, script codes, and style sheets.

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

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24