/[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.34 - (show annotations) (download)
Sun Jul 1 04:46:48 2007 UTC (19 years, 2 months ago) by wakaba
Branch: MAIN
Changes since 1.33: +4 -3 lines
++ whatpm/t/ChangeLog	1 Jul 2007 04:46:30 -0000
2007-07-01  Wakaba  <wakaba@suika.fam.cx>

	* table-1.dat: New test data.

	* ContentChecker.t: |table-1.dat| is added.

++ whatpm/Whatpm/ChangeLog	1 Jul 2007 04:45:38 -0000
2007-07-01  Wakaba  <wakaba@suika.fam.cx>

	* HTMLTable.pm: An error description was incorrect.

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

	* ContentChecker.pm: Return |{term}| list.

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24