/[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.33 - (hide annotations) (download)
Sat Jun 30 13:12:32 2007 UTC (19 years, 1 month ago) by wakaba
Branch: MAIN
Changes since 1.32: +83 -72 lines
++ whatpm/t/ChangeLog	30 Jun 2007 12:28:52 -0000
2007-06-30  Wakaba  <wakaba@suika.fam.cx>

	* URIChecker.t: Error level names in test results has
	been changed.

	* tokenizer-test-1.test: A test for bogus SYSTEM identifier
	is added.

	* content-model-1.dat, content-model-2.dat, content-model-3.dat,
	content-model-4.dat: Error messages has been changed.

	* ContentChecker.t: Appends error level to the error
	message if any.

++ whatpm/Whatpm/ChangeLog	30 Jun 2007 13:03:50 -0000
2007-06-30  Wakaba  <wakaba@suika.fam.cx>

	* IMTChecker.pm: Report warning for unregistered
	and private types/subtypes.

	* ContentChecker.pm, HTML.pm.src, IMTChecker.pm,
	URIChecker.pm, HTMLTable.pm: Error messages are now
	consistent; they are all listed in
	<http://suika.fam.cx/gate/2005/sw/Whatpm%20Error%20Types>.

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24