/[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.15 - (hide annotations) (download)
Sun May 20 05:07:12 2007 UTC (19 years, 2 months ago) by wakaba
Branch: MAIN
Changes since 1.14: +250 -13 lines
++ whatpm/t/ChangeLog	20 May 2007 05:06:29 -0000
	* content-model-2.dat: Tests for additionally-implemented
	attributes are added.

2007-05-20  Wakaba  <wakaba@suika.fam.cx>

++ whatpm/Whatpm/ChangeLog	20 May 2007 05:06:03 -0000
	* ContentChecker.pm ($HTMLSpaceURIsAttrChecker): New placeholder.
	($HTMLIMTAttrChecker): New checker.
	(link@rel, link@href, link@type, style@type,
	a@href, a@ping, a@ping, a@type, embed@src, embed@type,
	object@data, object@type, source@src, source@type, area@alt,
	area@shape, area@coords, area@href,
	area@ping, area@rel, area@type, script@src,
	script@defer, script@async, script@type): Checkers added.

2007-05-20  Wakaba  <wakaba@suika.fam.cx>

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24