/[suikacvs]/markup/html/whatpm/Whatpm/ContentChecker/HTML.pm
Suika

Contents of /markup/html/whatpm/Whatpm/ContentChecker/HTML.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.150 - (show annotations) (download)
Tue Oct 7 08:58:11 2008 UTC (18 years ago) by wakaba
Branch: MAIN
Changes since 1.149: +49 -22 lines
++ whatpm/t/dom-conformance/ChangeLog	7 Oct 2008 08:58:06 -0000
2008-10-07  Wakaba  <wakaba@suika.fam.cx>

	* html-form-input-1.dat: Some of test results for <input
	type=email>, <input type=url>, <input type=password> were wrong.
	Tests for <input accept-charset>, <input accesskey>, <input
	replace>, and <input align> are added.

	* html-forms-1.dat: Tests on <input accept-charset> removed.

	* html-global-1.dat: Tests on <input accesskey> removed.

++ whatpm/Whatpm/ContentChecker/ChangeLog	7 Oct 2008 08:56:00 -0000
2008-10-07  Wakaba  <wakaba@suika.fam.cx>

	* HTML.pm: Mark <input inputmode> as dropped.  <input accesskey>
	support fixed.  <input align> support.  Tentative support for
	<input inputmode>.  Support for <input replace>.  Status of
	<button template> fixed.

1 package Whatpm::ContentChecker;
2 use strict;
3 require Whatpm::ContentChecker;
4
5 use Char::Class::XML qw/InXML_NCNameStartChar10 InXMLNCNameChar10/;
6
7 my $HTML_NS = q<http://www.w3.org/1999/xhtml>;
8
9 sub FEATURE_HTML5_ROLE () {
10 Whatpm::ContentChecker::FEATURE_STATUS_WD
11 ## TODO: svg:*/@role
12 }
13
14 sub FEATURE_HTML5_COMPLETE () {
15 Whatpm::ContentChecker::FEATURE_STATUS_REC |
16 Whatpm::ContentChecker::FEATURE_ALLOWED
17 }
18
19 sub FEATURE_HTML5_LC () {
20 Whatpm::ContentChecker::FEATURE_STATUS_LC |
21 Whatpm::ContentChecker::FEATURE_ALLOWED
22 }
23 sub FEATURE_HTML5_AT_RISK () {
24 Whatpm::ContentChecker::FEATURE_STATUS_WD |
25 Whatpm::ContentChecker::FEATURE_ALLOWED
26 }
27 sub FEATURE_HTML5_WD () {
28 Whatpm::ContentChecker::FEATURE_STATUS_WD |
29 Whatpm::ContentChecker::FEATURE_ALLOWED
30 }
31 sub FEATURE_HTML5_FD () {
32 Whatpm::ContentChecker::FEATURE_STATUS_WD |
33 Whatpm::ContentChecker::FEATURE_ALLOWED
34 }
35 sub FEATURE_HTML5_DEFAULT () {
36 Whatpm::ContentChecker::FEATURE_STATUS_WD |
37 Whatpm::ContentChecker::FEATURE_ALLOWED
38 }
39 sub FEATURE_HTML5_DROPPED () {
40 ## NOTE: Was part of HTML5, but was dropped.
41 Whatpm::ContentChecker::FEATURE_STATUS_WD
42 }
43 sub FEATURE_HTML5_LC_DROPPED () {
44 ## NOTE: Was part of HTML5, but was dropped.
45 Whatpm::ContentChecker::FEATURE_STATUS_LC
46 }
47 sub FEATURE_WF2X () {
48 ## NOTE: Defined in WF2 and then incorporated into the HTML5 spec.
49 Whatpm::ContentChecker::FEATURE_STATUS_LC
50 }
51 sub FEATURE_WF2 () {
52 ## NOTE: Part of WF2, still not added to the HTML5 spec.
53 Whatpm::ContentChecker::FEATURE_STATUS_LC
54 }
55 sub FEATURE_WF2_INFORMATIVE () {
56 ## NOTE: Part of WF2 Appendix A, still not added to the HTML5 spec.
57 Whatpm::ContentChecker::FEATURE_STATUS_LC
58 }
59 sub FEATURE_WF2X_DEPRECATED () {
60 Whatpm::ContentChecker::FEATURE_STATUS_LC
61 ## NOTE: MUST NOT be used according to WF2.
62 }
63
64 ## NOTE: Metainformation Attributes Module by W3C XHTML2 WG.
65 sub FEATURE_RDFA_PR () {
66 Whatpm::ContentChecker::FEATURE_STATUS_CR
67 ## NOTE: Diff from FEATURE_RDFA_CR
68 }
69 sub FEATURE_RDFA_CR () {
70 Whatpm::ContentChecker::FEATURE_STATUS_CR
71 }
72 sub FEATURE_RDFA_LC () {
73 Whatpm::ContentChecker::FEATURE_STATUS_LC
74 }
75
76 sub FEATURE_RDFA_ED () {
77 Whatpm::ContentChecker::FEATURE_STATUS_WD
78 }
79
80 ## NOTE: XHTML Role LCWD has almost no information on how the |role|
81 ## attribute can be used- the only requirements for that matter is:
82 ## "the attribute MUST be referenced using its namespace-qualified form" (and
83 ## this is a host language conformance!).
84 sub FEATURE_ROLE_LC () {
85 Whatpm::ContentChecker::FEATURE_STATUS_LC
86 }
87
88 sub FEATURE_XHTML2_ED () {
89 Whatpm::ContentChecker::FEATURE_STATUS_WD
90 }
91
92 sub FEATURE_XHTMLBASIC11_CR () {
93 ## NOTE: Only additions to M12N10_REC are marked.
94 Whatpm::ContentChecker::FEATURE_STATUS_CR
95 }
96 sub FEATURE_XHTMLBASIC11_CR_DEPRECATED () {
97 Whatpm::ContentChecker::FEATURE_STATUS_CR |
98 Whatpm::ContentChecker::FEATURE_DEPRECATED_INFO
99 }
100
101 sub FEATURE_M12N11_LC () {
102 Whatpm::ContentChecker::FEATURE_STATUS_LC
103 }
104
105 sub FEATURE_RUBY_REC () {
106 Whatpm::ContentChecker::FEATURE_STATUS_CR
107 }
108
109 ## NOTE: M12N10 status is based on its abstract module definition.
110 ## It contains a number of problems. (However, again, it's a REC!)
111 sub FEATURE_M12N10_REC () {
112 ## NOTE: Oh, XHTML m12n 1.0 passed the CR phase! W3C Process suck!
113 Whatpm::ContentChecker::FEATURE_STATUS_REC
114 }
115 sub FEATURE_M12N10_REC_DEPRECATED () {
116 Whatpm::ContentChecker::FEATURE_STATUS_REC |
117 Whatpm::ContentChecker::FEATURE_DEPRECATED_INFO
118 }
119 ## NOTE: XHTML M12N 1.1 is in the PR status at the time of writing and no
120 ## addition from 1.0.
121
122 ## NOTE: XHTML10 status is based on its transitional and frameset DTDs
123 ## (second edition). Only missing attributes from M12N10 abstract
124 ## definition are added.
125 sub FEATURE_XHTML10_REC () {
126 Whatpm::ContentChecker::FEATURE_STATUS_CR
127 }
128
129 ## NOTE: Diff from HTML4.
130 sub FEATURE_ISOHTML_PREPARATION () { ## Informative documentation
131 Whatpm::ContentChecker::FEATURE_STATUS_CR
132 }
133
134 ## NOTE: HTML4 status is based on its transitional and frameset DTDs (HTML
135 ## 4.01). Only missing attributes from XHTML10 are added.
136 sub FEATURE_HTML4_REC_RESERVED () {
137 Whatpm::ContentChecker::FEATURE_STATUS_WD
138 }
139
140 ## TODO: According to HTML4 definition, authors SHOULD use style sheets
141 ## rather than presentational attributes (deprecated or not deprecated).
142
143 ## NOTE: Diff from HTML4.
144 sub FEATURE_HTML32_REC_OBSOLETE () {
145 Whatpm::ContentChecker::FEATURE_STATUS_CR |
146 Whatpm::ContentChecker::FEATURE_DEPRECATED_SHOULD
147 ## NOTE: Lowercase normative "should".
148 }
149
150 sub FEATURE_RFC2659 () { ## Experimental RFC
151 Whatpm::ContentChecker::FEATURE_STATUS_CR
152 }
153
154 ## NOTE: HTML 2.x - diff from HTML 2.0 and not in newer versions.
155 sub FEATURE_HTML2X_RFC () { ## Proposed Standard, obsolete
156 Whatpm::ContentChecker::FEATURE_STATUS_CR
157 }
158
159 ## NOTE: Diff from HTML 2.0.
160 sub FEATURE_RFC1942 () { ## Experimental RFC, obsolete
161 Whatpm::ContentChecker::FEATURE_STATUS_CR
162 }
163
164 ## NOTE: Diff from HTML 3.2.
165 sub FEATURE_HTML20_RFC () { ## Proposed Standard, obsolete
166 Whatpm::ContentChecker::FEATURE_STATUS_CR
167 }
168
169 ## December 2007 HTML5 Classification
170
171 my $HTMLMetadataContent = {
172 $HTML_NS => {
173 title => 1, base => 1, link => 1, style => 1, script => 1, noscript => 1,
174 'event-source' => 1, eventsource => 1,
175 command => 1, datatemplate => 1,
176 ## NOTE: A |meta| with no |name| element is not allowed as
177 ## a metadata content other than |head| element.
178 meta => 1,
179 },
180 ## NOTE: RDF is mentioned in the HTML5 spec.
181 ## TODO: Other RDF elements?
182 q<http://www.w3.org/1999/02/22-rdf-syntax-ns#> => {RDF => 1},
183 };
184
185 my $HTMLFlowContent = {
186 $HTML_NS => {
187 section => 1, nav => 1, article => 1, blockquote => 1, aside => 1,
188 h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1, header => 1,
189 footer => 1, address => 1, p => 1, hr => 1, dialog => 1, pre => 1,
190 ol => 1, ul => 1, dl => 1, figure => 1, map => 1, table => 1,
191 form => 1, fieldset => 1,
192 details => 1, ## ISSUE: "Flow element" in spec.
193 datagrid => 1, ## ISSUE: "Flow element" in spec.
194 datatemplate => 1,
195 div => 1, ## ISSUE: No category in spec.
196 ## NOTE: |style| is only allowed if |scoped| attribute is specified.
197 ## Additionally, it must be before any other element or
198 ## non-inter-element-whitespace text node.
199 style => 1,
200
201 br => 1, q => 1, cite => 1, em => 1, strong => 1, small => 1, mark => 1,
202 dfn => 1, abbr => 1, time => 1, progress => 1, meter => 1, code => 1,
203 var => 1, samp => 1, kbd => 1, sub => 1, sup => 1, span => 1, i => 1,
204 b => 1, bdo => 1, ruby => 1,
205 script => 1, noscript => 1, 'event-source' => 1, eventsource => 1,
206 command => 1, bb => 1,
207 input => 1, button => 1, label => 1, select => 1, datalist => 1,
208 textarea => 1, output => 1,
209 datagrid => 1, ## ISSUE: "Interactive element" in the spec.
210 ## NOTE: |area| is allowed only as a descendant of |map|.
211 area => 1,
212
213 ## NOTE: Transparent.
214 a => 1, ins => 1, del => 1, font => 1,
215
216 ## NOTE: If there is a |menu| ancestor, phrasing. Otherwise, flow.
217 menu => 1,
218
219 img => 1, iframe => 1, embed => 1, object => 1, video => 1, audio => 1,
220 canvas => 1,
221 },
222
223 ## NOTE: Embedded
224 q<http://www.w3.org/1998/Math/MathML> => {math => 1},
225 q<http://www.w3.org/2000/svg> => {svg => 1},
226 };
227
228 my $HTMLSectioningContent = {
229 $HTML_NS => {
230 section => 1, nav => 1, article => 1, aside => 1,
231 ## NOTE: |body| is only allowed in |html| element.
232 body => 1,
233 },
234 };
235
236 my $HTMLSectioningRoot = {
237 $HTML_NS => {
238 blockquote => 1, datagrid => 1, figure => 1, td => 1,
239 },
240 };
241
242 my $HTMLHeadingContent = {
243 $HTML_NS => {
244 h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1, header => 1,
245 },
246 };
247
248 my $HTMLPhrasingContent = {
249 ## NOTE: All phrasing content is also flow content.
250 $HTML_NS => {
251 br => 1, q => 1, cite => 1, em => 1, strong => 1, small => 1, mark => 1,
252 dfn => 1, abbr => 1, time => 1, progress => 1, meter => 1, code => 1,
253 var => 1, samp => 1, kbd => 1, sub => 1, sup => 1, span => 1, i => 1,
254 b => 1, bdo => 1, ruby => 1,
255 script => 1, noscript => 1, 'event-source' => 1, eventsource => 1,
256 command => 1, bb => 1,
257 input => 1, button => 1, label => 1, select => 1, datalist => 1,
258 textarea => 1, output => 1,
259 datagrid => 1, ## ISSUE: "Interactive element" in the spec.
260 ## NOTE: |area| is allowed only as a descendant of |map|.
261 area => 1,
262
263 ## NOTE: Transparent.
264 a => 1, ins => 1, del => 1, font => 1,
265
266 ## NOTE: If there is a |menu| ancestor, phrasing. Otherwise, flow.
267 menu => 1,
268
269 img => 1, iframe => 1, embed => 1, object => 1, video => 1, audio => 1,
270 canvas => 1,
271 },
272
273 ## NOTE: Embedded
274 q<http://www.w3.org/1998/Math/MathML> => {math => 1},
275 q<http://www.w3.org/2000/svg> => {svg => 1},
276
277 ## NOTE: And non-inter-element-whitespace text nodes.
278 };
279
280 ## $HTMLEmbeddedContent: See Whatpm::ContentChecker.
281
282 my $HTMLInteractiveContent = {
283 $HTML_NS => {
284 a => 1,
285 label => 1, input => 1, button => 1, select => 1, textarea => 1,
286 details => 1, datagrid => 1, bb => 1,
287
288 ## NOTE: When "controls" attribute is specified.
289 video => 1, audio => 1,
290
291 ## NOTE: When "type=toolbar" attribute is specified.
292 menu => 1,
293 },
294 };
295
296 ## NOTE: Labelable form-associated element.
297 my $LabelableFAE = {
298 $HTML_NS => {
299 input => 1, button => 1, select => 1, textarea => 1,
300 },
301 };
302
303 our $IsInHTMLInteractiveContent; # See Whatpm::ContentChecker.
304
305 ## NOTE: $HTMLTransparentElements: See Whatpm::ContentChecker.
306 ## NOTE: Semi-transparent elements: See Whatpm::ContentChecker.
307
308 ## -- Common attribute syntacx checkers
309
310 our $AttrChecker;
311 our $AttrStatus;
312
313 my $GetHTMLEnumeratedAttrChecker = sub {
314 my $states = shift; # {value => conforming ? 1 : -1}
315 return sub {
316 my ($self, $attr) = @_;
317 my $value = lc $attr->value; ## TODO: ASCII case insensitibility?
318 if ($states->{$value} > 0) {
319 #
320 } elsif ($states->{$value}) {
321 $self->{onerror}->(node => $attr, type => 'enumerated:non-conforming',
322 level => $self->{level}->{must});
323 } else {
324 $self->{onerror}->(node => $attr, type => 'enumerated:invalid',
325 level => $self->{level}->{must});
326 }
327 };
328 }; # $GetHTMLEnumeratedAttrChecker
329
330 my $GetHTMLBooleanAttrChecker = sub {
331 my $local_name = shift;
332 return sub {
333 my ($self, $attr) = @_;
334 my $value = lc $attr->value; ## TODO: case
335 unless ($value eq $local_name or $value eq '') {
336 $self->{onerror}->(node => $attr, type => 'boolean:invalid',
337 level => $self->{level}->{must});
338 }
339 };
340 }; # $GetHTMLBooleanAttrChecker
341
342 ## Unordered set of space-separated tokens
343 my $GetHTMLUnorderedUniqueSetOfSpaceSeparatedTokensAttrChecker = sub {
344 my $allowed_words = shift;
345 return sub {
346 my ($self, $attr) = @_;
347 my %word;
348 for my $word (grep {length $_}
349 split /[\x09\x0A\x0C\x0D\x20]+/, $attr->value) {
350 unless ($word{$word}) {
351 $word{$word} = 1;
352 if (not defined $allowed_words or
353 $allowed_words->{$word}) {
354 #
355 } else {
356 $self->{onerror}->(node => $attr, type => 'word not allowed',
357 value => $word,
358 level => $self->{level}->{must});
359 }
360 } else {
361 $self->{onerror}->(node => $attr, type => 'duplicate token',
362 value => $word,
363 level => $self->{level}->{must});
364 }
365 }
366 };
367 }; # $GetHTMLUnorderedUniqueSetOfSpaceSeparatedTokensAttrChecker
368
369 ## |rel| attribute (set of space separated tokens,
370 ## whose allowed values are defined by the section on link types)
371 my $HTMLLinkTypesAttrChecker = sub {
372 my ($a_or_area, $todo, $self, $attr, $item, $element_state) = @_;
373 my %word;
374 for my $word (grep {length $_}
375 split /[\x09\x0A\x0C\x0D\x20]+/, $attr->value) {
376 unless ($word{$word}) {
377 $word{$word} = 1;
378 } elsif ($word eq 'up') {
379 #
380 } else {
381 $self->{onerror}->(node => $attr, type => 'duplicate token',
382 value => $word,
383 level => $self->{level}->{must});
384 }
385 }
386 ## NOTE: Case sensitive match (since HTML5 spec does not say link
387 ## types are case-insensitive and it says "The value should not
388 ## be confusingly similar to any other defined value (e.g.
389 ## differing only in case).").
390 ## NOTE: Though there is no explicit "MUST NOT" for undefined values,
391 ## "MAY"s and "only ... MAY" restrict non-standard non-registered
392 ## values to be used conformingly.
393
394 my $is_hyperlink;
395 my $is_resource;
396 require Whatpm::_LinkTypeList;
397 our $LinkType;
398 for my $word (keys %word) {
399 my $def = $LinkType->{$word};
400 if (defined $def) {
401 if ($def->{status} eq 'accepted') {
402 if (defined $def->{effect}->[$a_or_area]) {
403 #
404 } else {
405 $self->{onerror}->(node => $attr,
406 type => 'link type:bad context',
407 value => $word,
408 level => $self->{level}->{must});
409 }
410 } elsif ($def->{status} eq 'proposal') {
411 $self->{onerror}->(node => $attr,
412 type => 'link type:proposed',
413 value => $word,
414 level => $self->{level}->{should});
415 if (defined $def->{effect}->[$a_or_area]) {
416 #
417 } else {
418 $self->{onerror}->(node => $attr,
419 type => 'link type:bad context',
420 value => $word,
421 level => $self->{level}->{must});
422 }
423 } else { # rejected or synonym
424 $self->{onerror}->(node => $attr,
425 type => 'link type:non-conforming',
426 value => $word,
427 level => $self->{level}->{must});
428 }
429 if (defined $def->{effect}->[$a_or_area]) {
430 if ($word eq 'alternate') {
431 #
432 } elsif ($def->{effect}->[$a_or_area] eq 'hyperlink') {
433 $is_hyperlink = 1;
434 }
435 }
436 if ($def->{unique}) {
437 unless ($self->{has_link_type}->{$word}) {
438 $self->{has_link_type}->{$word} = 1;
439 } else {
440 $self->{onerror}->(node => $attr,
441 type => 'link type:duplicate',
442 value => $word,
443 level => $self->{level}->{must});
444 }
445 }
446
447 if (defined $def->{effect}->[$a_or_area] and $word ne 'alternate') {
448 $is_hyperlink = 1 if $def->{effect}->[$a_or_area] eq 'hyperlink';
449 $is_resource = 1 if $def->{effect}->[$a_or_area] eq 'external resource';
450 }
451 } else {
452 $self->{onerror}->(node => $attr,
453 type => 'unknown link type',
454 value => $word,
455 level => $self->{level}->{uncertain});
456 }
457 }
458 $is_hyperlink = 1 if $word{alternate} and not $word{stylesheet};
459 ## TODO: The Pingback 1.0 specification, which is referenced by HTML5,
460 ## says that using both X-Pingback: header field and HTML
461 ## <link rel=pingback> is deprecated and if both appears they
462 ## SHOULD contain exactly the same value.
463 ## ISSUE: Pingback 1.0 specification defines the exact representation
464 ## of its link element, which cannot be tested by the current arch.
465 ## ISSUE: Pingback 1.0 specification says that the document MUST NOT
466 ## include any string that matches to the pattern for the rel=pingback link,
467 ## which again inpossible to test.
468 ## ISSUE: rel=pingback href MUST NOT include entities other than predefined 4.
469
470 ## NOTE: <link rel="up index"><link rel="up up index"> is not an error.
471 ## NOTE: We can't check "If the page is part of multiple hierarchies,
472 ## then they SHOULD be described in different paragraphs.".
473
474 $todo->{has_hyperlink_link_type} = 1 if $is_hyperlink;
475 if ($is_hyperlink or $a_or_area) {
476 $element_state->{uri_info}->{href}->{type}->{hyperlink} = 1;
477 }
478 if ($is_resource and not $a_or_area) {
479 $element_state->{uri_info}->{href}->{type}->{resource} = 1;
480 }
481
482 $element_state->{link_rel} = \%word;
483 }; # $HTMLLinkTypesAttrChecker
484
485 ## TODO: "When an author uses a new type not defined by either this specification or the Wiki page, conformance checkers should offer to add the value to the Wiki, with the details described above, with the "proposal" status."
486
487 ## URI (or IRI)
488 my $HTMLURIAttrChecker = sub {
489 my ($self, $attr, $item, $element_state) = @_;
490 ## ISSUE: Relative references are allowed? (RFC 3987 "IRI" is an absolute reference with optional fragment identifier.)
491 my $value = $attr->value;
492 Whatpm::URIChecker->check_iri_reference ($value, sub {
493 $self->{onerror}->(@_, node => $attr);
494 }), $self->{level};
495 $self->{has_uri_attr} = 1; ## TODO: <html manifest>
496
497 my $attr_name = $attr->name;
498 $element_state->{uri_info}->{$attr_name}->{node} = $attr;
499 ## TODO: absolute
500 push @{$self->{return}->{uri}->{$value} ||= []},
501 $element_state->{uri_info}->{$attr_name};
502 }; # $HTMLURIAttrChecker
503
504 ## A space separated list of one or more URIs (or IRIs)
505 my $HTMLSpaceURIsAttrChecker = sub {
506 my ($self, $attr) = @_;
507
508 my $type = {ping => 'action',
509 profile => 'namespace',
510 archive => 'resource'}->{$attr->name};
511
512 my $i = 0;
513 for my $value (split /[\x09\x0A\x0C\x0D\x20]+/, $attr->value) {
514 Whatpm::URIChecker->check_iri_reference ($value, sub {
515 $self->{onerror}->(value => $value, @_, node => $attr, index => $i);
516 }, $self->{level});
517
518 ## TODO: absolute
519 push @{$self->{return}->{uri}->{$value} ||= []},
520 {node => $attr, type => {$type => 1}};
521
522 $i++;
523 }
524 ## ISSUE: Relative references? (especially, in profile="")
525 ## ISSUE: Leading or trailing white spaces are conformant?
526 ## ISSUE: A sequence of white space characters are conformant?
527 ## ISSUE: A zero-length string is conformant? (It does contain a relative reference, i.e. same as base URI.)
528 ## ISSUE: What is "space"?
529 ## NOTE: Duplication seems not an error.
530 $self->{has_uri_attr} = 1;
531 }; # $HTMLSpaceURIsAttrChecker
532
533 ## NOTE: "Valid datetime".
534 my $HTMLDatetimeAttrChecker = sub {
535 my ($self, $attr) = @_;
536 my $value = $attr->value;
537 ## ISSUE: "space", not "space character" (in parsing algorihtm, "space character")
538 if ($value =~ /\A([0-9]{4})-([0-9]{2})-([0-9]{2})
539 (?>[\x09\x0A\x0C\x0D\x20]+
540 (?>T[\x09\x0A\x0C\x0D\x20]*)?|T[\x09\x0A\x0C\x0D\x20]*)
541 ([0-9]{2}):([0-9]{2})(?>:([0-9]{2}))?(?>\.([0-9]+))?
542 [\x09\x0A\x0C\x0D\x20]*
543 (?>Z|[+-]([0-9]{2}):([0-9]{2}))\z/x) {
544 my ($y, $M, $d, $h, $m, $s, $f, $zh, $zm)
545 = ($1, $2, $3, $4, $5, $6, $7, $8, $9);
546 if (0 < $M and $M < 13) { ## ISSUE: This is not explicitly specified (though in parsing algorithm)
547 $self->{onerror}->(node => $attr, type => 'datetime:bad day',
548 level => $self->{level}->{must})
549 if $d < 1 or
550 $d > [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]->[$M];
551 $self->{onerror}->(node => $attr, type => 'datetime:bad day',
552 level => $self->{level}->{must})
553 if $M == 2 and $d == 29 and
554 not ($y % 400 == 0 or ($y % 4 == 0 and $y % 100 != 0));
555 } else {
556 $self->{onerror}->(node => $attr, type => 'datetime:bad month',
557 level => $self->{level}->{must});
558 }
559 $self->{onerror}->(node => $attr, type => 'datetime:bad hour',
560 level => $self->{level}->{must}) if $h > 23;
561 $self->{onerror}->(node => $attr, type => 'datetime:bad minute',
562 level => $self->{level}->{must}) if $m > 59;
563 $self->{onerror}->(node => $attr, type => 'datetime:bad second',
564 level => $self->{level}->{must})
565 if defined $s and $s > 59;
566 $self->{onerror}->(node => $attr, type => 'datetime:bad timezone hour',
567 level => $self->{level}->{must}) if $zh > 23;
568 $self->{onerror}->(node => $attr, type => 'datetime:bad timezone minute',
569 level => $self->{level}->{must}) if $zm > 59;
570 ## ISSUE: Maybe timezone -00:00 should have same semantics as in RFC 3339.
571 } else {
572 $self->{onerror}->(node => $attr, type => 'datetime:syntax error',
573 level => $self->{level}->{must});
574 }
575 }; # $HTMLDatetimeAttrChecker
576
577 my $HTMLIntegerAttrChecker = sub {
578 my ($self, $attr) = @_;
579 my $value = $attr->value;
580 unless ($value =~ /\A-?[0-9]+\z/) {
581 $self->{onerror}->(node => $attr, type => 'integer:syntax error',
582 level => $self->{level}->{must});
583 }
584 }; # $HTMLIntegerAttrChecker
585
586 my $GetHTMLNonNegativeIntegerAttrChecker = sub {
587 my $range_check = shift;
588 return sub {
589 my ($self, $attr) = @_;
590 my $value = $attr->value;
591 if ($value =~ /\A[0-9]+\z/) {
592 unless ($range_check->($value + 0)) {
593 $self->{onerror}->(node => $attr, type => 'nninteger:out of range',
594 level => $self->{level}->{must});
595 }
596 } else {
597 $self->{onerror}->(node => $attr,
598 type => 'nninteger:syntax error',
599 level => $self->{level}->{must});
600 }
601 };
602 }; # $GetHTMLNonNegativeIntegerAttrChecker
603
604 my $GetHTMLFloatingPointNumberAttrChecker = sub {
605 my $range_check = shift;
606 return sub {
607 my ($self, $attr) = @_;
608 my $value = $attr->value;
609 if ($value =~ /\A-?[0-9]+(?>\.[0-9]*)?\z/ or
610 $value =~ /\A-?\.[0-9]+\z/) {
611 unless ($range_check->($value + 0)) {
612 $self->{onerror}->(node => $attr, type => 'float:out of range',
613 level => $self->{level}->{must});
614 }
615 } else {
616 $self->{onerror}->(node => $attr,
617 type => 'float:syntax error',
618 level => $self->{level}->{must});
619 }
620 };
621
622 ## TODO: scientific notation
623 }; # $GetHTMLFloatingPointNumberAttrChecker
624
625 my $StepAttrChecker = sub {
626 ## NOTE: A valid floating point number (> 0), or ASCII
627 ## case-insensitive "any".
628
629 my ($self, $attr) = @_;
630 my $value = $attr->value;
631 if ($value =~ /\A-?[0-9]+(?>\.[0-9]*)?\z/ or
632 $value =~ /\A-?\.[0-9]+\z/) {
633 unless ($value > 0) {
634 $self->{onerror}->(node => $attr, type => 'float:out of range',
635 level => $self->{level}->{must});
636 }
637 } elsif ($value =~ /\A[Aa][Nn][Yy]\z/) {
638 #
639 } else {
640 $self->{onerror}->(node => $attr,
641 type => 'float:syntax error',
642 level => $self->{level}->{must});
643 }
644
645 ## TODO: scientific
646 }; # $StepAttrChecker
647
648 ## HTML4 %Length;
649 my $HTMLLengthAttrChecker = sub {
650 my ($self, $attr) = @_;
651 my $value = $attr->value;
652 unless ($value =~ /\A[0-9]+%?\z/) {
653 $self->{onerror}->(node => $attr, type => 'length:syntax error',
654 level => $self->{level}->{must});
655 }
656
657 ## NOTE: HTML4 definition is too vague - it does not define the syntax
658 ## of percentage value at all (!).
659 }; # $HTMLLengthAttrChecker
660
661 ## "A valid MIME type, optionally with parameters. [RFC 2046]"
662 ## ISSUE: RFC 2046 does not define syntax of media types.
663 ## ISSUE: The definition of "a valid MIME type" is unknown.
664 ## Syntactical correctness?
665 my $HTMLIMTAttrChecker = sub {
666 my ($self, $attr) = @_;
667 my $value = $attr->value;
668 ## ISSUE: RFC 2045 Content-Type header field allows insertion
669 ## of LWS/comments between tokens. Is it allowed in HTML? Maybe no.
670 ## ISSUE: RFC 2231 extension? Maybe no.
671 my $lws0 = qr/(?>(?>\x0D\x0A)?[\x09\x20])*/;
672 my $token = qr/[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+/;
673 my $qs = qr/"(?>[\x00-\x0C\x0E-\x21\x23-\x5B\x5D-\x7E]|\x0D\x0A[\x09\x20]|\x5C[\x00-\x7F])*"/;
674 if ($value =~ m#\A$lws0($token)$lws0/$lws0($token)$lws0((?>;$lws0$token$lws0=$lws0(?>$token|$qs)$lws0)*)\z#) {
675 my @type = ($1, $2);
676 my $param = $3;
677 while ($param =~ s/^;$lws0($token)$lws0=$lws0(?>($token)|($qs))$lws0//) {
678 if (defined $2) {
679 push @type, $1 => $2;
680 } else {
681 my $n = $1;
682 my $v = $2;
683 $v =~ s/\\(.)/$1/gs;
684 push @type, $n => $v;
685 }
686 }
687 require Whatpm::IMTChecker;
688 my $ic = Whatpm::IMTChecker->new;
689 $ic->{level} = $self->{level};
690 $ic->check_imt (sub {
691 $self->{onerror}->(@_, node => $attr);
692 }, @type);
693 } else {
694 $self->{onerror}->(node => $attr, type => 'IMT:syntax error',
695 level => $self->{level}->{must});
696 }
697 }; # $HTMLIMTAttrChecker
698
699 my $HTMLLanguageTagAttrChecker = sub {
700 ## NOTE: See also $AtomLanguageTagAttrChecker in Atom.pm.
701
702 my ($self, $attr) = @_;
703 my $value = $attr->value;
704 require Whatpm::LangTag;
705 Whatpm::LangTag->check_rfc3066_language_tag ($value, sub {
706 $self->{onerror}->(@_, node => $attr);
707 }, $self->{level});
708 ## ISSUE: RFC 4646 (3066bis)?
709
710 ## TODO: testdata
711 }; # $HTMLLanguageTagAttrChecker
712
713 ## "A valid media query [MQ]"
714 my $HTMLMQAttrChecker = sub {
715 my ($self, $attr) = @_;
716 $self->{onerror}->(node => $attr,
717 type => 'media query',
718 level => $self->{level}->{uncertain});
719 ## ISSUE: What is "a valid media query"?
720 }; # $HTMLMQAttrChecker
721
722 my $HTMLEventHandlerAttrChecker = sub {
723 my ($self, $attr) = @_;
724 $self->{onerror}->(node => $attr,
725 type => 'event handler',
726 level => $self->{level}->{uncertain});
727 ## TODO: MUST contain valid ECMAScript code matching the
728 ## ECMAScript |FunctionBody| production. [ECMA262]
729 ## ISSUE: MUST be ES3? E4X? ES4? JS1.x?
730 ## ISSUE: Automatic semicolon insertion does not apply?
731 ## ISSUE: Other script languages?
732 }; # $HTMLEventHandlerAttrChecker
733
734 my $HTMLFormAttrChecker = sub {
735 my ($self, $attr) = @_;
736
737 ## NOTE: MUST be the ID of a |form| element.
738
739 my $value = $attr->value;
740 push @{$self->{idref}}, ['form', $value => $attr];
741
742 ## ISSUE: <form id=""><input form=""> (empty ID)?
743 }; # $HTMLFormAttrChecker
744
745 my $HTMLUsemapAttrChecker = sub {
746 my ($self, $attr) = @_;
747 ## MUST be a valid hash-name reference to a |map| element.
748 my $value = $attr->value;
749 if ($value =~ s/^#//) {
750 ## NOTE: |usemap="#"| is conforming, though it identifies no |map| element
751 ## according to the "rules for parsing a hash-name reference" algorithm.
752 ## The document is non-conforming anyway, since |<map name="">| (empty
753 ## name) is non-conforming.
754 push @{$self->{usemap}}, [$value => $attr];
755 } else {
756 $self->{onerror}->(node => $attr, type => 'hashref:syntax error',
757 level => $self->{level}->{must});
758 }
759 ## NOTE: Space characters in hash-name references are conforming.
760 ## ISSUE: UA algorithm for matching is case-insensitive; IDs only different in cases should be reported
761 }; # $HTMLUsemapAttrChecker
762
763 ## Valid browsing context name
764 my $HTMLBrowsingContextNameAttrChecker = sub {
765 my ($self, $attr) = @_;
766 my $value = $attr->value;
767 if ($value =~ /^_/) {
768 $self->{onerror}->(node => $attr, type => 'window name:reserved',
769 level => $self->{level}->{must},
770 value => $value);
771 } elsif (length $value) {
772 #
773 } else {
774 $self->{onerror}->(node => $attr, type => 'window name:empty',
775 level => $self->{level}->{must});
776 }
777 }; # $HTMLBrowsingContextNameAttrChecker
778
779 ## Valid browsing context name or keyword
780 my $HTMLTargetAttrChecker = sub {
781 my ($self, $attr) = @_;
782 my $value = $attr->value;
783 if ($value =~ /^_/) {
784 $value = lc $value; ## ISSUE: ASCII case-insentitive?
785 unless ({
786 _blank => 1,_self => 1, _parent => 1, _top => 1,
787 }->{$value}) {
788 $self->{onerror}->(node => $attr,
789 type => 'window name:reserved',
790 level => $self->{level}->{must},
791 value => $value);
792 }
793 } elsif (length $value) {
794 #
795 } else {
796 $self->{onerror}->(node => $attr, type => 'window name:empty',
797 level => $self->{level}->{must});
798 }
799 }; # $HTMLTargetAttrChecker
800
801 my $HTMLSelectorsAttrChecker = sub {
802 my ($self, $attr) = @_;
803
804 ## ISSUE: Namespace resolution?
805
806 my $value = $attr->value;
807
808 require Whatpm::CSS::SelectorsParser;
809 my $p = Whatpm::CSS::SelectorsParser->new;
810 $p->{pseudo_class}->{$_} = 1 for qw/
811 active checked disabled empty enabled first-child first-of-type
812 focus hover indeterminate last-child last-of-type link only-child
813 only-of-type root target visited
814 lang nth-child nth-last-child nth-of-type nth-last-of-type not
815 -manakai-contains -manakai-current
816 /;
817
818 $p->{pseudo_element}->{$_} = 1 for qw/
819 after before first-letter first-line
820 /;
821
822 $p->{level} = $self->{level};
823 $p->{onerror} = sub {
824 $self->{onerror}->(@_, node => $attr);
825 };
826 $p->parse_string ($value);
827 }; # $HTMLSelectorsAttrChecker
828
829 my $HTMLAccesskeyAttrChecker = sub {
830 my ($self, $attr) = @_;
831
832 ## NOTE: "character" or |%Character;| in HTML4.
833
834 my $value = $attr->value;
835 if (length $value != 1) {
836 $self->{onerror}->(node => $attr, type => 'char:syntax error',
837 level => $self->{level}->{html4_fact});
838 }
839
840 ## NOTE: "Note. Authors should consider the input method of the expected
841 ## reader when specifying an accesskey." [HTML4] This is hard to implement,
842 ## since it depends on keyboard and so on.
843 ## NOTE: "We recommend that authors include the access key in label text
844 ## or wherever the access key is to apply." [HTML4] (informative)
845 }; # $HTMLAccesskeyAttrChecker
846
847 my $HTMLCharsetChecker = sub ($$$;$) {
848 my ($charset_value, $self, $attr, $ascii_compat) = @_;
849
850 ## NOTE: This code is used for |charset=""| attributes, |charset=|
851 ## portion of the |content=""| attributes, and |accept-charset=""|
852 ## attributes.
853
854 ## NOTE: Though the case-sensitivility of |charset| attribute value
855 ## is not explicitly spelled in the HTML5 spec, the Character Set
856 ## registry of IANA, which is referenced from HTML5 spec, says that
857 ## charset name is case-insensitive.
858 $charset_value =~ tr/A-Z/a-z/; ## NOTE: ASCII Case-insensitive.
859
860 require Message::Charset::Info;
861 my $charset = $Message::Charset::Info::IANACharset->{$charset_value};
862
863 ## ISSUE: What is "valid character encoding name"? Syntactically valid?
864 ## Syntactically valid and registered? What about x-charset names?
865 unless (Message::Charset::Info::is_syntactically_valid_iana_charset_name
866 ($charset_value)) {
867 $self->{onerror}->(node => $attr,
868 type => 'charset:syntax error',
869 value => $charset_value,
870 level => $self->{level}->{must});
871 }
872
873 if ($charset) {
874 ## ISSUE: What is "the preferred name for that encoding" (for a charset
875 ## with no "preferred MIME name" label)?
876 my $charset_status = $charset->{iana_names}->{$charset_value} || 0;
877 if (($charset_status &
878 Message::Charset::Info::PREFERRED_CHARSET_NAME ())
879 != Message::Charset::Info::PREFERRED_CHARSET_NAME ()) {
880 $self->{onerror}->(node => $attr,
881 type => 'charset:not preferred',
882 value => $charset_value,
883 level => $self->{level}->{must});
884 }
885
886 if (($charset_status &
887 Message::Charset::Info::REGISTERED_CHARSET_NAME ())
888 != Message::Charset::Info::REGISTERED_CHARSET_NAME ()) {
889 if ($charset_value =~ /^x-/) {
890 $self->{onerror}->(node => $attr,
891 type => 'charset:private',
892 value => $charset_value,
893 level => $self->{level}->{good});
894 } else {
895 $self->{onerror}->(node => $attr,
896 type => 'charset:not registered',
897 value => $charset_value,
898 level => $self->{level}->{good});
899 }
900 }
901
902 if ($ascii_compat) {
903 if ($charset->{category} &
904 Message::Charset::Info::CHARSET_CATEGORY_ASCII_COMPAT ()) {
905 #
906 } else {
907 $self->{onerror}->(node => $attr,
908 type => 'charset:not ascii compat',
909 value => $charset_value,
910 level => $self->{level}->{must});
911 }
912 }
913
914 ## TODO: non-preferred-name error for following cases:
915 } elsif ($charset_value =~ /^x-/) {
916 $self->{onerror}->(node => $attr,
917 type => 'charset:private',
918 value => $charset_value,
919 level => $self->{level}->{good});
920
921 ## NOTE: Whether this is an ASCII-compatible character encoding or
922 ## not is unknown.
923 } else {
924 $self->{onerror}->(node => $attr,
925 type => 'charset:not registered',
926 value => $charset_value,
927 level => $self->{level}->{good});
928
929 ## NOTE: Whether this is an ASCII-compatible character encoding or
930 ## not is unknown.
931 }
932
933 return ($charset, $charset_value);
934 }; # $HTMLCharsetChecker
935
936 ## NOTE: "An ordered set of space-separated tokens" where "each token
937 ## MUST be the preferred name of an ASCII-compatible character
938 ## encoding".
939 my $HTMLCharsetsAttrChecker = sub {
940 my ($self, $attr) = @_;
941
942 ## ISSUE: "ordered set of space-separated tokens" is not defined.
943
944 my @value = grep {length $_} split /[\x09\x0A\x0C\x0D\x20]+/, $attr->value;
945
946 ## ISSUE: Uniqueness is not enforced.
947
948 for my $charset (@value) {
949 $HTMLCharsetChecker->($charset, $self, $attr, 1);
950 }
951
952 ## ISSUE: Shift_JIS is ASCII-compatible? What about ISO-2022-JP?
953 }; # $HTMLCharsetsAttrChecker
954
955 my $HTMLColorAttrChecker = sub {
956 my ($self, $attr) = @_;
957
958 ## NOTE: HTML4 "color" or |%Color;|
959
960 my $value = $attr->value;
961
962 if ($value !~ /\A(?>#[0-9A-F]+|black|silver|gray|white|maroon|red|purple|fuchsia|green|lime|olive|yellow|navy|blue|teal|aqua)\z/i) {
963 $self->{onerror}->(node => $attr, type => 'color:syntax error',
964 level => $self->{level}->{html4_fact});
965 }
966
967 ## TODO: HTML4 has some guideline on usage of color.
968 }; # $HTMLColorAttrChecker
969
970 my $HTMLRefOrTemplateAttrChecker = sub {
971 my ($self, $attr) = @_;
972 $HTMLURIAttrChecker->(@_);
973
974 my $attr_name = $attr->name;
975
976 if ($attr_name eq 'ref') {
977 unless ($attr->owner_element->has_attribute_ns (undef, 'template')) {
978 $self->{onerror}->(node => $attr, type => 'attribute not allowed',
979 level => $self->{level}->{must});
980 }
981 }
982
983 my $doc = $attr->owner_document;
984 my $doc_uri = $doc->document_uri;
985 my $uri = $doc->implementation
986 ->create_uri_reference ($attr->value)
987 ->get_absolute_reference ($doc_uri);
988 my $no_frag_uri = $uri->clone;
989 $no_frag_uri->uri_fragment (undef);
990 if ((defined $doc_uri and $doc_uri eq $no_frag_uri) or
991 (not defined $doc_uri and $no_frag_uri eq '')) {
992 my $fragid = $uri->uri_fragment;
993 if (defined $fragid) {
994 push @{$self->{$attr_name}}, [$fragid => $attr];
995 } else {
996 DOCEL: {
997 last DOCEL unless $attr_name eq 'template';
998
999 my $docel = $doc->document_element;
1000 if ($docel) {
1001 my $nsuri = $docel->namespace_uri;
1002 if (defined $nsuri and $nsuri eq $HTML_NS) {
1003 if ($docel->manakai_local_name eq 'datatemplate') {
1004 last DOCEL;
1005 }
1006 }
1007 }
1008
1009 $self->{onerror}->(node => $attr, type => 'template:not template',
1010 level => $self->{level}->{must});
1011 } # DOCEL
1012 }
1013 } else {
1014 ## TODO: An external document is referenced.
1015 ## The document MUST be an HTML or XML document.
1016 ## If there is a fragment identifier, it MUST point a part of the doc.
1017 ## If the attribute is |template|, the pointed part MUST be a
1018 ## |datatemplat| element.
1019 ## If no fragment identifier is specified, the root element MUST be
1020 ## a |datatemplate| element when the attribute is |template|.
1021 }
1022 }; # $HTMLRefOrTemplateAttrChecker
1023
1024 my $HTMLRepeatIndexAttrChecker = sub {
1025 my ($self, $attr) = @_;
1026
1027 if (defined $attr->namespace_uri) {
1028 my $oe = $attr->owner_element;
1029 my $oe_nsuri = $oe->namespace_uri;
1030 if (defined $oe_nsuri or $oe_nsuri eq $HTML_NS) { ## TODO: wrong?
1031 $self->{onerror}->(node => $attr, type => 'attribute not allowed',
1032 level => $self->{level}->{must});
1033 }
1034 }
1035
1036 $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 })->(@_);
1037 }; # $HTMLRepeatIndexAttrChecker
1038
1039 my $HTMLAttrChecker = {
1040 ## TODO: aria-* ## TODO: svg:*/@aria-* [HTML5ROLE] -> [STATES]
1041 id => sub {
1042 my ($self, $attr, $item, $element_state) = @_;
1043 my $value = $attr->value;
1044 if (length $value > 0) {
1045 if ($self->{id}->{$value}) {
1046 $self->{onerror}->(node => $attr, type => 'duplicate ID',
1047 level => $self->{level}->{must});
1048 push @{$self->{id}->{$value}}, $attr;
1049 } else {
1050 $self->{id}->{$value} = [$attr];
1051 $self->{id_type}->{$value} = $element_state->{id_type} || '';
1052 }
1053 if ($value =~ /[\x09\x0A\x0C\x0D\x20]/) {
1054 $self->{onerror}->(node => $attr, type => 'space in ID',
1055 level => $self->{level}->{must});
1056 }
1057 } else {
1058 ## NOTE: MUST contain at least one character
1059 $self->{onerror}->(node => $attr, type => 'empty attribute value',
1060 level => $self->{level}->{must});
1061 }
1062 },
1063 title => sub {}, ## NOTE: No conformance creteria
1064 lang => sub {
1065 my ($self, $attr) = @_;
1066 my $value = $attr->value;
1067 if ($value eq '') {
1068 #
1069 } else {
1070 require Whatpm::LangTag;
1071 Whatpm::LangTag->check_rfc3066_language_tag ($value, sub {
1072 $self->{onerror}->(@_, node => $attr);
1073 }, $self->{level});
1074 }
1075 ## ISSUE: RFC 4646 (3066bis)?
1076
1077 ## TODO: test data
1078
1079 ## NOTE: Inconsistency between |lang| and |xml:lang| attributes are
1080 ## non-conforming. Such errors are detected by the checkers of
1081 ## |{}xml:lang| and |{xml}:lang| attributes.
1082 },
1083 dir => $GetHTMLEnumeratedAttrChecker->({ltr => 1, rtl => 1}),
1084 class => sub {
1085 my ($self, $attr) = @_;
1086
1087 ## NOTE: "Unordered set of unique space-separated tokens".
1088
1089 my %word;
1090 for my $word (grep {length $_}
1091 split /[\x09\x0A\x0C\x0D\x20]+/, $attr->value) {
1092 unless ($word{$word}) {
1093 $word{$word} = 1;
1094 push @{$self->{return}->{class}->{$word}||=[]}, $attr;
1095 } else {
1096 $self->{onerror}->(node => $attr, type => 'duplicate token',
1097 value => $word,
1098 level => $self->{level}->{must});
1099 }
1100 }
1101 },
1102 contenteditable => $GetHTMLEnumeratedAttrChecker->({
1103 true => 1, false => 1, '' => 1,
1104 }),
1105 contextmenu => sub {
1106 my ($self, $attr) = @_;
1107 my $value = $attr->value;
1108 push @{$self->{idref}}, ['menu', $value => $attr];
1109 ## ISSUE: "The value must be the ID of a menu element in the DOM."
1110 ## What is "in the DOM"? A menu Element node that is not part
1111 ## of the Document tree is in the DOM? A menu Element node that
1112 ## belong to another Document tree is in the DOM?
1113 },
1114 hidden => $GetHTMLBooleanAttrChecker->('hidden'),
1115 irrelevant => $GetHTMLBooleanAttrChecker->('irrelevant'),
1116 ref => $HTMLRefOrTemplateAttrChecker,
1117 registrationmark => sub {
1118 my ($self, $attr, $item, $element_state) = @_;
1119
1120 ## NOTE: Any value is conforming.
1121
1122 if ($self->{flag}->{in_rule}) {
1123 my $el = $attr->owner_element;
1124 my $ln = $el->manakai_local_name;
1125 if ($ln eq 'nest' or
1126 ($ln eq 'rule' and not $element_state->{in_rule_original})) {
1127 my $nsuri = $el->namespace_uri;
1128 if (defined $nsuri and $nsuri eq $HTML_NS) {
1129 $self->{onerror}->(node => $attr, type => 'attribute not allowed',
1130 level => $self->{level}->{must});
1131 }
1132 }
1133 } else {
1134 $self->{onerror}->(node => $attr, type => 'attribute not allowed',
1135 level => $self->{level}->{must});
1136 }
1137 },
1138 repeat => sub {
1139 my ($self, $attr) = @_;
1140
1141 if (defined $attr->namespace_uri) {
1142 my $oe = $attr->owner_element;
1143 my $oe_nsuri = $oe->namespace_uri;
1144 if (defined $oe_nsuri or $oe_nsuri eq $HTML_NS) {
1145 $self->{onerror}->(node => $attr, type => 'attribute not allowed',
1146 level => $self->{level}->{must});
1147 }
1148 }
1149
1150 my $value = $attr->value;
1151 if ($value eq 'template') {
1152 #
1153 } elsif ($value =~ /\A-?[0-9]+\z/) {
1154 #
1155 } else {
1156 $self->{onerror}->(node => $attr, type => 'repeat:syntax error',
1157 level => $self->{level}->{must});
1158 }
1159
1160 ## ISSUE: "Repetition templates may occur anywhere." Does that mean
1161 ## that the attribute MAY be specified to any element, or that the
1162 ## element with that attribute (i.e. a repetition template) can be
1163 ## inserted anywhere in a document tree?
1164 },
1165 'repeat-min' => $HTMLRepeatIndexAttrChecker,
1166 'repeat-max' => $HTMLRepeatIndexAttrChecker,
1167 'repeat-start' => $HTMLRepeatIndexAttrChecker,
1168 'repeat-template' => sub {
1169 my ($self, $attr) = @_;
1170
1171 if (defined $attr->namespace_uri) {
1172 my $oe = $attr->owner_element;
1173 my $oe_nsuri = $oe->namespace_uri;
1174 if (defined $oe_nsuri or $oe_nsuri eq $HTML_NS) { ## TODO: This condition is wrong?
1175 $self->{onerror}->(node => $attr, type => 'attribute not allowed',
1176 level => $self->{level}->{must});
1177 }
1178 }
1179
1180 ## ISSUE: This attribute has no conformance requirement.
1181 ## ISSUE: Repetition blocks MAY have this attribute. Then, is the
1182 ## attribute allowed on an element that is not a repetition block?
1183 },
1184 ## TODO: role [HTML5ROLE] ## TODO: global @role [XHTML1ROLE]
1185 style => sub {
1186 my ($self, $attr) = @_;
1187
1188 $self->{onsubdoc}->({s => $attr->value,
1189 container_node => $attr,
1190 media_type => 'text/x-css-inline',
1191 is_char_string => 1});
1192
1193 ## NOTE: "... MUST still be comprehensible and usable if those
1194 ## attributes were removed" is a semantic requirement, it cannot
1195 ## be tested.
1196 },
1197 tabindex => $HTMLIntegerAttrChecker,
1198 template => $HTMLRefOrTemplateAttrChecker,
1199 'xml:lang' => sub {
1200 my ($self, $attr) = @_;
1201
1202 if ($attr->owner_document->manakai_is_html) {
1203 $self->{onerror}->(type => 'in HTML:xml:lang',
1204 level => $self->{level}->{info},
1205 node => $attr);
1206 ## NOTE: This is not an error, but the attribute will be ignored.
1207 } else {
1208 $self->{onerror}->(type => 'in XML:xml:lang',
1209 level => $self->{level}->{html5_no_may},
1210 node => $attr);
1211 ## TODO: We need to add test for this error.
1212 }
1213
1214 my $lang_attr = $attr->owner_element->get_attribute_node_ns
1215 (undef, 'lang');
1216 if ($lang_attr) {
1217 my $lang_attr_value = $lang_attr->value;
1218 $lang_attr_value =~ tr/A-Z/a-z/; ## ASCII case-insensitive
1219 my $value = $attr->value;
1220 $value =~ tr/A-Z/a-z/; ## ASCII case-insensitive
1221 if ($lang_attr_value ne $value) {
1222 $self->{onerror}->(type => 'xml:lang ne lang',
1223 level => $self->{level}->{must},
1224 node => $attr);
1225 }
1226 } else {
1227 $self->{onerror}->(type => 'xml:lang not allowed',
1228 level => $self->{level}->{must},
1229 node => $attr);
1230 ## TODO: We need to add test for <x {xml}:lang {}xml:lang>.
1231 }
1232 },
1233 xmlns => sub {
1234 my ($self, $attr) = @_;
1235 my $value = $attr->value;
1236 unless ($value eq $HTML_NS) {
1237 $self->{onerror}->(node => $attr, type => 'invalid attribute value',
1238 level => $self->{level}->{must});
1239 ## TODO: Should be new "bad namespace" error?
1240 }
1241 unless ($attr->owner_document->manakai_is_html) {
1242 $self->{onerror}->(node => $attr, type => 'in XML:xmlns',
1243 level => $self->{level}->{must});
1244 ## TODO: Test
1245 }
1246
1247 ## TODO: Should be resolved?
1248 push @{$self->{return}->{uri}->{$value} ||= []},
1249 {node => $attr, type => {namespace => 1}};
1250 },
1251 };
1252
1253 ## ISSUE: Shouldn't the same-origin policy applied to the datatemplate feature?
1254
1255 my %HTMLAttrStatus = (
1256 class => FEATURE_HTML5_DEFAULT,
1257 contenteditable => FEATURE_HTML5_DEFAULT,
1258 contextmenu => FEATURE_HTML5_WD,
1259 dir => FEATURE_HTML5_DEFAULT,
1260 draggable => FEATURE_HTML5_LC,
1261 hidden => FEATURE_HTML5_DEFAULT,
1262 id => FEATURE_HTML5_DEFAULT,
1263 irrelevant => FEATURE_HTML5_DROPPED,
1264 lang => FEATURE_HTML5_DEFAULT,
1265 ref => FEATURE_HTML5_AT_RISK,
1266 registrationmark => FEATURE_HTML5_AT_RISK,
1267 repeat => FEATURE_WF2,
1268 'repeat-max' => FEATURE_WF2,
1269 'repeat-min' => FEATURE_WF2,
1270 'repeat-start' => FEATURE_WF2,
1271 'repeat-template' => FEATURE_WF2,
1272 role => FEATURE_HTML5_ROLE,
1273 style => FEATURE_HTML5_DEFAULT,
1274 tabindex => FEATURE_HTML5_DEFAULT,
1275 template => FEATURE_HTML5_AT_RISK,
1276 title => FEATURE_HTML5_DEFAULT,
1277 xmlns => FEATURE_HTML5_DEFAULT,
1278 );
1279
1280 my %HTMLM12NCommonAttrStatus = (
1281 about => FEATURE_RDFA_CR,
1282 class => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
1283 content => FEATURE_RDFA_CR,
1284 datatype => FEATURE_RDFA_CR,
1285 dir => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
1286 href => FEATURE_RDFA_CR,
1287 id => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
1288 instanceof => FEATURE_RDFA_LC,
1289 onclick => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
1290 ondblclick => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
1291 onmousedown => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
1292 onmouseup => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
1293 onmouseover => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
1294 onmousemove => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
1295 onmouseout => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
1296 onkeypress => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
1297 onkeydown => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
1298 onkeyup => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
1299 property => FEATURE_RDFA_CR,
1300 rel => FEATURE_RDFA_CR,
1301 resource => FEATURE_RDFA_CR,
1302 rev => FEATURE_RDFA_CR,
1303 #style => FEATURE_HTML5_DEFAULT | FEATURE_XHTMLBASIC11_CR_DEPRECATED |
1304 # FEATURE_M12N10_REC,
1305 style => FEATURE_HTML5_DEFAULT | FEATURE_XHTMLBASIC11_CR |
1306 FEATURE_M12N10_REC,
1307 title => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
1308 typeof => FEATURE_RDFA_CR,
1309 );
1310
1311 my %XHTML2CommonAttrStatus = (
1312 ## Core
1313 class => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED,
1314 id => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED,
1315 #xml:id
1316 layout => FEATURE_XHTML2_ED,
1317 title => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED,
1318
1319 ## Hypertext
1320 cite => FEATURE_XHTML2_ED,
1321 href => FEATURE_XHTML2_ED,
1322 hreflang => FEATURE_XHTML2_ED,
1323 hrefmedia => FEATURE_XHTML2_ED,
1324 hreftype => FEATURE_XHTML2_ED,
1325 nextfocus => FEATURE_XHTML2_ED,
1326 prevfocus => FEATURE_XHTML2_ED,
1327 target => FEATURE_XHTML2_ED,
1328 #xml:base
1329
1330 ## I18N
1331 #xml:lang
1332
1333 ## Bi-directional
1334 dir => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED,
1335
1336 ## Edit
1337 edit => FEATURE_XHTML2_ED,
1338 datetime => FEATURE_XHTML2_ED,
1339
1340 ## Embedding
1341 encoding => FEATURE_XHTML2_ED,
1342 src => FEATURE_XHTML2_ED,
1343 srctype => FEATURE_XHTML2_ED,
1344
1345 ## Image Map
1346 usemap => FEATURE_XHTML2_ED,
1347 ismap => FEATURE_XHTML2_ED,
1348 shape => FEATURE_XHTML2_ED,
1349 coords => FEATURE_XHTML2_ED,
1350
1351 ## Media
1352 media => FEATURE_XHTML2_ED,
1353
1354 ## Metadata
1355 about => FEATURE_XHTML2_ED,
1356 content => FEATURE_XHTML2_ED,
1357 datatype => FEATURE_XHTML2_ED,
1358 instanceof => FEATURE_XHTML2_ED,
1359 property => FEATURE_XHTML2_ED,
1360 rel => FEATURE_XHTML2_ED,
1361 resource => FEATURE_XHTML2_ED,
1362 rev => FEATURE_XHTML2_ED,
1363
1364 ## Role
1365 role => FEATURE_HTML5_ROLE | FEATURE_XHTML2_ED,
1366
1367 ## Style
1368 style => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED, # "strongly discouraged"
1369 );
1370
1371 my %HTMLM12NXHTML2CommonAttrStatus = (
1372 %HTMLM12NCommonAttrStatus,
1373 %XHTML2CommonAttrStatus,
1374
1375 about => FEATURE_RDFA_CR | FEATURE_XHTML2_ED,
1376 class => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
1377 content => FEATURE_RDFA_CR | FEATURE_XHTML2_ED,
1378 datatype => FEATURE_RDFA_CR | FEATURE_XHTML2_ED,
1379 dir => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
1380 href => FEATURE_RDFA_CR,
1381 id => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
1382 instanceof => FEATURE_RDFA_LC | FEATURE_XHTML2_ED,
1383 property => FEATURE_RDFA_CR | FEATURE_XHTML2_ED,
1384 rel => FEATURE_RDFA_CR | FEATURE_XHTML2_ED,
1385 resource => FEATURE_RDFA_CR | FEATURE_XHTML2_ED,
1386 rev => FEATURE_RDFA_CR | FEATURE_XHTML2_ED,
1387 #style => FEATURE_HTML5_DEFAULT | FEATURE_XHTMLBASIC11_CR_DEPRECATED |
1388 # FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
1389 style => FEATURE_HTML5_DEFAULT | FEATURE_XHTMLBASIC11_CR |
1390 FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
1391 title => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
1392 typeof => FEATURE_RDFA_CR,
1393 );
1394
1395 for (qw/
1396 onabort onbeforeunload onblur onchange onclick oncontextmenu
1397 ondblclick ondrag ondragend ondragenter ondragleave ondragover
1398 ondragstart ondrop onerror onfocus onkeydown onkeypress
1399 onkeyup onload onmessage onmousedown onmousemove onmouseout
1400 onmouseover onmouseup onmousewheel onresize onscroll onselect
1401 onstorage onsubmit onunload
1402 /) {
1403 $HTMLAttrChecker->{$_} = $HTMLEventHandlerAttrChecker;
1404 $HTMLAttrStatus{$_} = FEATURE_HTML5_DEFAULT;
1405 }
1406
1407 ## NOTE: Non-standard global attributes in the HTML namespace.
1408 $AttrChecker->{$HTML_NS}->{''} = sub {}; # no syntactical checks
1409 $AttrStatus->{$HTML_NS}->{''} = 0; # disallowed and not part of any standard
1410
1411 $AttrStatus->{$HTML_NS}->{active} = FEATURE_HTML5_DROPPED;
1412 for (qw/repeat repeat-max repeat-min repeat-start repeat-template/) {
1413 $AttrChecker->{$HTML_NS}->{$_} = $HTMLAttrChecker->{$_};
1414 $AttrStatus->{$HTML_NS}->{$_} = FEATURE_WF2;
1415 }
1416
1417 for (qw/about content datatype property rel resource rev/) {
1418 $AttrStatus->{$HTML_NS}->{$_} = FEATURE_RDFA_CR | FEATURE_XHTML2_ED;
1419 }
1420 $AttrStatus->{$HTML_NS}->{instanceof} = FEATURE_RDFA_LC | FEATURE_XHTML2_ED;
1421 $AttrStatus->{$HTML_NS}->{typeof} = FEATURE_RDFA_CR;
1422 $AttrStatus->{$HTML_NS}->{role} = FEATURE_ROLE_LC;
1423 for (qw/cite coords datetime edit encoding href hreflang hrefmedia hreftype
1424 ismap layout media nextfocus prevfocus shape src srctype style
1425 target usemap/) {
1426 $AttrStatus->{$HTML_NS}->{$_} = FEATURE_XHTML2_ED;
1427 }
1428 for (qw/class dir id title/) {
1429 $AttrStatus->{$HTML_NS}->{$_} = FEATURE_M12N11_LC | FEATURE_XHTML2_ED;
1430 }
1431 for (qw/onclick ondblclick onmousedown onmouseup onmouseover onmousemove
1432 onmouseout onkeypress onkeydown onkeyup/) {
1433 $AttrStatus->{$HTML_NS}->{$_} = FEATURE_M12N11_LC;
1434 }
1435
1436 my $HTMLDatasetAttrChecker = sub {
1437 ## NOTE: "Authors should ... when the attributes are ignored and
1438 ## any associated CSS dropped, the page is still usable." (semantic
1439 ## constraint.)
1440 }; # $HTMLDatasetAttrChecker
1441
1442 my $HTMLDatasetAttrStatus = FEATURE_HTML5_DEFAULT;
1443
1444 my $GetHTMLAttrsChecker = sub {
1445 my $element_specific_checker = shift;
1446 my $element_specific_status = shift;
1447 return sub {
1448 my ($self, $item, $element_state) = @_;
1449 for my $attr (@{$item->{node}->attributes}) {
1450 my $attr_ns = $attr->namespace_uri;
1451 $attr_ns = '' unless defined $attr_ns;
1452 my $attr_ln = $attr->manakai_local_name;
1453 my $checker;
1454 my $status;
1455 if ($attr_ns eq '') {
1456 if ($attr_ln =~ /^data-\p{InXMLNCNameChar10}+\z/ and
1457 $attr_ln !~ /[A-Z]/) {
1458 $checker = $HTMLDatasetAttrChecker;
1459 $status = $HTMLDatasetAttrStatus;
1460 } else {
1461 $checker = $element_specific_checker->{$attr_ln}
1462 || $HTMLAttrChecker->{$attr_ln};
1463 $status = $element_specific_status->{$attr_ln};
1464 }
1465 }
1466 $checker ||= $AttrChecker->{$attr_ns}->{$attr_ln}
1467 || $AttrChecker->{$attr_ns}->{''};
1468 $status ||= $AttrStatus->{$attr_ns}->{$attr_ln}
1469 || $AttrStatus->{$attr_ns}->{''};
1470 $status = FEATURE_ALLOWED if not defined $status and length $attr_ns;
1471 if ($checker) {
1472 $checker->($self, $attr, $item, $element_state);
1473 } elsif ($attr_ns eq '' and not $element_specific_status->{$attr_ln}) {
1474 #
1475 } else {
1476 $self->{onerror}->(node => $attr,
1477 type => 'unknown attribute',
1478 level => $self->{level}->{uncertain});
1479 ## ISSUE: No conformance createria for unknown attributes in the spec
1480 }
1481 $self->_attr_status_info ($attr, $status);
1482 }
1483 };
1484 }; # $GetHTMLAttrsChecker
1485
1486 my %HTMLChecker = (
1487 %Whatpm::ContentChecker::AnyChecker,
1488 check_start => sub {
1489 my ($self, $item, $element_state) = @_;
1490
1491 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
1492 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
1493 },
1494 check_attrs => $GetHTMLAttrsChecker->({}, \%HTMLAttrStatus),
1495 );
1496
1497 my %HTMLEmptyChecker = (
1498 %HTMLChecker,
1499 check_child_element => sub {
1500 my ($self, $item, $child_el, $child_nsuri, $child_ln,
1501 $child_is_transparent, $element_state) = @_;
1502 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
1503 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
1504 $self->{onerror}->(node => $child_el,
1505 type => 'element not allowed:minus',
1506 level => $self->{level}->{must});
1507 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
1508 #
1509 } else {
1510 $self->{onerror}->(node => $child_el,
1511 type => 'element not allowed:empty',
1512 level => $self->{level}->{must});
1513 }
1514 },
1515 check_child_text => sub {
1516 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
1517 if ($has_significant) {
1518 $self->{onerror}->(node => $child_node,
1519 type => 'character not allowed:empty',
1520 level => $self->{level}->{must});
1521 }
1522 },
1523 );
1524
1525 my %HTMLTextChecker = (
1526 %HTMLChecker,
1527 check_child_element => sub {
1528 my ($self, $item, $child_el, $child_nsuri, $child_ln,
1529 $child_is_transparent, $element_state) = @_;
1530 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
1531 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
1532 $self->{onerror}->(node => $child_el,
1533 type => 'element not allowed:minus',
1534 level => $self->{level}->{must});
1535 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
1536 #
1537 } else {
1538 $self->{onerror}->(node => $child_el, type => 'element not allowed:text',
1539 level => $self->{level}->{must});
1540 }
1541 },
1542 );
1543
1544 my %HTMLFlowContentChecker = (
1545 %HTMLChecker,
1546 check_child_element => sub {
1547 my ($self, $item, $child_el, $child_nsuri, $child_ln,
1548 $child_is_transparent, $element_state) = @_;
1549 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
1550 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
1551 $self->{onerror}->(node => $child_el,
1552 type => 'element not allowed:minus',
1553 level => $self->{level}->{must});
1554 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
1555 #
1556 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'style') {
1557 if ($element_state->{has_non_style} or
1558 not $child_el->has_attribute_ns (undef, 'scoped')) {
1559 $self->{onerror}->(node => $child_el,
1560 type => 'element not allowed:flow style',
1561 level => $self->{level}->{must});
1562 }
1563 } elsif ($HTMLFlowContent->{$child_nsuri}->{$child_ln}) {
1564 $element_state->{has_non_style} = 1 unless $child_is_transparent;
1565 } else {
1566 $element_state->{has_non_style} = 1;
1567 $self->{onerror}->(node => $child_el,
1568 type => 'element not allowed:flow',
1569 level => $self->{level}->{must})
1570 }
1571 },
1572 check_child_text => sub {
1573 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
1574 if ($has_significant) {
1575 $element_state->{has_non_style} = 1;
1576 }
1577 },
1578 check_end => sub {
1579 my ($self, $item, $element_state) = @_;
1580 ## NOTE: A modified copy of the code below is in |datagrid| checker.
1581 if ($element_state->{has_significant}) {
1582 $item->{real_parent_state}->{has_significant} = 1;
1583 } elsif ($item->{transparent}) {
1584 #
1585 } else {
1586 $self->{onerror}->(node => $item->{node},
1587 level => $self->{level}->{should},
1588 type => 'no significant content');
1589 }
1590 },
1591 );
1592
1593 my %HTMLPhrasingContentChecker = (
1594 %HTMLChecker,
1595 check_child_element => sub {
1596 my ($self, $item, $child_el, $child_nsuri, $child_ln,
1597 $child_is_transparent, $element_state) = @_;
1598 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
1599 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
1600 $self->{onerror}->(node => $child_el,
1601 type => 'element not allowed:minus',
1602 level => $self->{level}->{must});
1603 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
1604 #
1605 } elsif ($HTMLPhrasingContent->{$child_nsuri}->{$child_ln}) {
1606 #
1607 } else {
1608 $self->{onerror}->(node => $child_el,
1609 type => 'element not allowed:phrasing',
1610 level => $self->{level}->{must});
1611 }
1612 },
1613 check_end => $HTMLFlowContentChecker{check_end},
1614 ## NOTE: The definition for |li| assumes that the only differences
1615 ## between flow and phrasing content checkers are |check_child_element|
1616 ## and |check_child_text|.
1617 );
1618
1619 my %HTMLTransparentChecker = %HTMLFlowContentChecker;
1620 ## ISSUE: Significant content rule should be applied to transparent element
1621 ## with parent?
1622
1623 our $Element;
1624 our $ElementDefault;
1625
1626 $Element->{$HTML_NS}->{''} = {
1627 %HTMLChecker,
1628 };
1629
1630 $Element->{$HTML_NS}->{html} = {
1631 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
1632 is_root => 1,
1633 check_attrs => $GetHTMLAttrsChecker->({
1634 manifest => $HTMLURIAttrChecker,
1635 version => sub {
1636 ## NOTE: According to HTML4 prose, this is a "cdata" attribute.
1637 ## Though DTDs of various versions of HTML define the attribute
1638 ## as |#FIXED|, this conformance checker does no check for
1639 ## the attribute value, since what kind of check should be done
1640 ## is unknown.
1641 },
1642 }, {
1643 %HTMLAttrStatus,
1644 %XHTML2CommonAttrStatus,
1645 class => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_HTML2X_RFC,
1646 dir => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
1647 id => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_XHTML10_REC,
1648 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
1649 manifest => FEATURE_HTML5_DEFAULT,
1650 sdaform => FEATURE_HTML20_RFC,
1651 version => FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
1652 }),
1653 check_start => sub {
1654 my ($self, $item, $element_state) = @_;
1655 $element_state->{phase} = 'before head';
1656
1657 $element_state->{uri_info}->{manifest}->{type}->{resource} = 1;
1658 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
1659 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
1660 },
1661 check_child_element => sub {
1662 my ($self, $item, $child_el, $child_nsuri, $child_ln,
1663 $child_is_transparent, $element_state) = @_;
1664 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
1665 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
1666 $self->{onerror}->(node => $child_el,
1667 type => 'element not allowed:minus',
1668 level => $self->{level}->{must});
1669 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
1670 #
1671 } elsif ($element_state->{phase} eq 'before head') {
1672 if ($child_nsuri eq $HTML_NS and $child_ln eq 'head') {
1673 $element_state->{phase} = 'after head';
1674 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'body') {
1675 $self->{onerror}->(node => $child_el,
1676 type => 'ps element missing',
1677 text => 'head',
1678 level => $self->{level}->{must});
1679 $element_state->{phase} = 'after body';
1680 } else {
1681 $self->{onerror}->(node => $child_el,
1682 type => 'element not allowed',
1683 level => $self->{level}->{must});
1684 }
1685 } elsif ($element_state->{phase} eq 'after head') {
1686 if ($child_nsuri eq $HTML_NS and $child_ln eq 'body') {
1687 $element_state->{phase} = 'after body';
1688 } else {
1689 $self->{onerror}->(node => $child_el,
1690 type => 'element not allowed',
1691 level => $self->{level}->{must});
1692 }
1693 } elsif ($element_state->{phase} eq 'after body') {
1694 $self->{onerror}->(node => $child_el,
1695 type => 'element not allowed',
1696 level => $self->{level}->{must});
1697 } else {
1698 die "check_child_element: Bad |html| phase: $element_state->{phase}";
1699 }
1700 },
1701 check_child_text => sub {
1702 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
1703 if ($has_significant) {
1704 $self->{onerror}->(node => $child_node,
1705 type => 'character not allowed',
1706 level => $self->{level}->{must});
1707 }
1708 },
1709 check_end => sub {
1710 my ($self, $item, $element_state) = @_;
1711 if ($element_state->{phase} eq 'after body') {
1712 #
1713 } elsif ($element_state->{phase} eq 'before head') {
1714 $self->{onerror}->(node => $item->{node},
1715 type => 'child element missing',
1716 text => 'head',
1717 level => $self->{level}->{must});
1718 $self->{onerror}->(node => $item->{node},
1719 type => 'child element missing',
1720 text => 'body',
1721 level => $self->{level}->{must});
1722 } elsif ($element_state->{phase} eq 'after head') {
1723 $self->{onerror}->(node => $item->{node},
1724 type => 'child element missing',
1725 text => 'body',
1726 level => $self->{level}->{must});
1727 } else {
1728 die "check_end: Bad |html| phase: $element_state->{phase}";
1729 }
1730
1731 $HTMLChecker{check_end}->(@_);
1732 },
1733 };
1734
1735 $Element->{$HTML_NS}->{head} = {
1736 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
1737 check_attrs => $GetHTMLAttrsChecker->({
1738 profile => $HTMLSpaceURIsAttrChecker, ## NOTE: MUST be profile URIs.
1739 }, {
1740 %HTMLAttrStatus,
1741 %XHTML2CommonAttrStatus,
1742 class => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_HTML2X_RFC,
1743 dir => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
1744 id => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_XHTML10_REC,
1745 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
1746 profile => FEATURE_HTML5_DROPPED | FEATURE_M12N10_REC,
1747 }),
1748 check_child_element => sub {
1749 my ($self, $item, $child_el, $child_nsuri, $child_ln,
1750 $child_is_transparent, $element_state) = @_;
1751 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
1752 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
1753 $self->{onerror}->(node => $child_el,
1754 type => 'element not allowed:minus',
1755 level => $self->{level}->{must});
1756 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
1757 #
1758 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'title') {
1759 unless ($element_state->{has_title}) {
1760 $element_state->{has_title} = 1;
1761 } else {
1762 $self->{onerror}->(node => $child_el,
1763 type => 'element not allowed:head title',
1764 level => $self->{level}->{must});
1765 }
1766 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'style') {
1767 if ($child_el->has_attribute_ns (undef, 'scoped')) {
1768 $self->{onerror}->(node => $child_el,
1769 type => 'element not allowed:head style',
1770 level => $self->{level}->{must});
1771 }
1772 } elsif ($HTMLMetadataContent->{$child_nsuri}->{$child_ln}) {
1773 #
1774
1775 ## NOTE: |meta| is a metadata content. However, strictly speaking,
1776 ## a |meta| element with none of |charset|, |name|,
1777 ## or |http-equiv| attribute is not allowed. It is non-conforming
1778 ## anyway.
1779
1780 ## TODO: |form| MUST be empty and in XML [WF2].
1781 } else {
1782 $self->{onerror}->(node => $child_el,
1783 type => 'element not allowed:metadata',
1784 level => $self->{level}->{must});
1785 }
1786 $element_state->{in_head_original} = $self->{flag}->{in_head};
1787 $self->{flag}->{in_head} = 1;
1788 },
1789 check_child_text => sub {
1790 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
1791 if ($has_significant) {
1792 $self->{onerror}->(node => $child_node, type => 'character not allowed',
1793 level => $self->{level}->{must});
1794 }
1795 },
1796 check_end => sub {
1797 my ($self, $item, $element_state) = @_;
1798 unless ($element_state->{has_title}) {
1799 $self->{onerror}->(node => $item->{node},
1800 type => 'child element missing',
1801 text => 'title',
1802 level => $self->{level}->{must});
1803 }
1804 $self->{flag}->{in_head} = $element_state->{in_head_original};
1805
1806 $HTMLChecker{check_end}->(@_);
1807 },
1808 };
1809
1810 $Element->{$HTML_NS}->{title} = {
1811 %HTMLTextChecker,
1812 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
1813 check_attrs => $GetHTMLAttrsChecker->({}, {
1814 %HTMLAttrStatus,
1815 %XHTML2CommonAttrStatus,
1816 class => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_HTML2X_RFC,
1817 dir => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
1818 id => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_XHTML10_REC,
1819 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
1820 sdaform => FEATURE_HTML20_RFC,
1821 }),
1822 };
1823
1824 $Element->{$HTML_NS}->{base} = {
1825 status => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
1826 %HTMLEmptyChecker,
1827 check_attrs => sub {
1828 my ($self, $item, $element_state) = @_;
1829
1830 if ($self->{has_base}) {
1831 $self->{onerror}->(node => $item->{node},
1832 type => 'element not allowed:base',
1833 level => $self->{level}->{must});
1834 } else {
1835 $self->{has_base} = 1;
1836 }
1837
1838 my $has_href = $item->{node}->has_attribute_ns (undef, 'href');
1839 my $has_target = $item->{node}->has_attribute_ns (undef, 'target');
1840
1841 if ($self->{has_uri_attr} and $has_href) {
1842 ## ISSUE: Are these examples conforming?
1843 ## <head profile="a b c"><base href> (except for |profile|'s
1844 ## non-conformance)
1845 ## <title xml:base="relative"/><base href/> (maybe it should be)
1846 ## <unknown xmlns="relative"/><base href/> (assuming that
1847 ## |{relative}:unknown| is allowed before XHTML |base| (unlikely, though))
1848 ## <style>@import 'relative';</style><base href>
1849 ## <script>location.href = 'relative';</script><base href>
1850 ## NOTE: <html manifest=".."><head><base href=""/> is conforming as
1851 ## an exception.
1852 $self->{onerror}->(node => $item->{node},
1853 type => 'basehref after URL attribute',
1854 level => $self->{level}->{must});
1855 }
1856 if ($self->{has_hyperlink_element} and $has_target) {
1857 ## ISSUE: Are these examples conforming?
1858 ## <head><title xlink:href=""/><base target="name"/></head>
1859 ## <xbl:xbl>...<svg:a href=""/>...</xbl:xbl><base target="name"/>
1860 ## (assuming that |xbl:xbl| is allowed before |base|)
1861 ## NOTE: These are non-conformant anyway because of |head|'s content model:
1862 ## <link href=""/><base target="name"/>
1863 ## <link rel=unknown href=""><base target=name>
1864 $self->{onerror}->(node => $item->{node},
1865 type => 'basetarget after hyperlink',
1866 level => $self->{level}->{must});
1867 }
1868
1869 if (not $has_href and not $has_target) {
1870 $self->{onerror}->(node => $item->{node},
1871 type => 'attribute missing:href|target',
1872 level => $self->{level}->{must});
1873 }
1874
1875 $element_state->{uri_info}->{href}->{type}->{base} = 1;
1876
1877 return $GetHTMLAttrsChecker->({
1878 href => $HTMLURIAttrChecker,
1879 target => $HTMLTargetAttrChecker,
1880 }, {
1881 %HTMLAttrStatus,
1882 href => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
1883 id => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
1884 target => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
1885 })->($self, $item, $element_state);
1886 },
1887 };
1888
1889 $Element->{$HTML_NS}->{link} = {
1890 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
1891 %HTMLEmptyChecker,
1892 check_attrs => sub {
1893 my ($self, $item, $element_state) = @_;
1894 my $sizes_attr;
1895 $GetHTMLAttrsChecker->({
1896 charset => sub {
1897 my ($self, $attr) = @_;
1898 $HTMLCharsetChecker->($attr->value, @_);
1899 },
1900 href => $HTMLURIAttrChecker,
1901 rel => sub { $HTMLLinkTypesAttrChecker->(0, $item, @_) },
1902 rev => $GetHTMLUnorderedUniqueSetOfSpaceSeparatedTokensAttrChecker->(),
1903 media => $HTMLMQAttrChecker,
1904 hreflang => $HTMLLanguageTagAttrChecker,
1905 sizes => sub {
1906 my ($self, $attr) = @_;
1907 $sizes_attr = $attr;
1908 my %word;
1909 for my $word (grep {length $_}
1910 split /[\x09\x0A\x0C\x0D\x20]+/, $attr->value) {
1911 unless ($word{$word}) {
1912 $word{$word} = 1;
1913 if ($word eq 'any' or $word =~ /\A[1-9][0-9]*x[1-9][0-9]*\z/) {
1914 #
1915 } else {
1916 $self->{onerror}->(node => $attr,
1917 type => 'sizes:syntax error',
1918 value => $word,
1919 level => $self->{level}->{must});
1920 }
1921 } else {
1922 $self->{onerror}->(node => $attr, type => 'duplicate token',
1923 value => $word,
1924 level => $self->{level}->{must});
1925 }
1926 }
1927 },
1928 target => $HTMLTargetAttrChecker,
1929 type => $HTMLIMTAttrChecker,
1930 ## NOTE: Though |title| has special semantics,
1931 ## syntactically same as the |title| as global attribute.
1932 }, {
1933 %HTMLAttrStatus,
1934 %HTMLM12NXHTML2CommonAttrStatus,
1935 charset => FEATURE_HTML5_DROPPED | FEATURE_M12N10_REC,
1936 ## NOTE: |charset| attribute had been part of HTML5 spec though
1937 ## it had been commented out.
1938 href => FEATURE_HTML5_DEFAULT | FEATURE_RDFA_CR | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
1939 hreflang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED |
1940 FEATURE_M12N10_REC,
1941 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
1942 media => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
1943 methods => FEATURE_HTML20_RFC,
1944 rel => FEATURE_HTML5_DEFAULT | FEATURE_RDFA_CR | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
1945 rev => FEATURE_RDFA_CR | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
1946 sdapref => FEATURE_HTML20_RFC,
1947 sizes => FEATURE_HTML5_DEFAULT,
1948 target => FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
1949 type => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
1950 urn => FEATURE_HTML20_RFC,
1951 })->($self, $item, $element_state);
1952
1953 if ($item->{node}->has_attribute_ns (undef, 'href')) {
1954 $self->{has_hyperlink_element} = 1 if $item->{has_hyperlink_link_type};
1955 } else {
1956 $self->{onerror}->(node => $item->{node},
1957 type => 'attribute missing',
1958 text => 'href',
1959 level => $self->{level}->{must});
1960 }
1961
1962 unless ($item->{node}->has_attribute_ns (undef, 'rel')) {
1963 $self->{onerror}->(node => $item->{node},
1964 type => 'attribute missing',
1965 text => 'rel',
1966 level => $self->{level}->{must});
1967 }
1968
1969 if ($sizes_attr and not $element_state->{link_rel}->{icon}) {
1970 $self->{onerror}->(node => $sizes_attr,
1971 type => 'attribute not allowed',
1972 level => $self->{level}->{must});
1973 }
1974
1975 if ($element_state->{link_rel}->{alternate} and
1976 $element_state->{link_rel}->{stylesheet}) {
1977 my $title_attr = $item->{node}->get_attribute_node_ns (undef, 'title');
1978 unless ($title_attr) {
1979 $self->{onerror}->(node => $item->{node},
1980 type => 'attribute missing',
1981 text => 'title',
1982 level => $self->{level}->{must});
1983 } elsif ($title_attr->value eq '') {
1984 $self->{onerror}->(node => $title_attr,
1985 type => 'empty style sheet title',
1986 level => $self->{level}->{must});
1987 }
1988 }
1989 },
1990 };
1991
1992 $Element->{$HTML_NS}->{meta} = {
1993 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
1994 %HTMLEmptyChecker,
1995 check_attrs => sub {
1996 my ($self, $item, $element_state) = @_;
1997 my $name_attr;
1998 my $http_equiv_attr;
1999 my $charset_attr;
2000 my $content_attr;
2001 for my $attr (@{$item->{node}->attributes}) {
2002 my $attr_ns = $attr->namespace_uri;
2003 $attr_ns = '' unless defined $attr_ns;
2004 my $attr_ln = $attr->manakai_local_name;
2005 my $checker;
2006 my $status;
2007 if ($attr_ns eq '') {
2008 $status = {
2009 %HTMLAttrStatus,
2010 %XHTML2CommonAttrStatus,
2011 charset => FEATURE_HTML5_DEFAULT,
2012 content => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED |
2013 FEATURE_M12N10_REC,
2014 dir => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED |
2015 FEATURE_M12N10_REC,
2016 'http-equiv' => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
2017 id => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED |
2018 FEATURE_XHTML10_REC,
2019 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
2020 name => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
2021 scheme => FEATURE_M12N10_REC,
2022 }->{$attr_ln};
2023
2024 if ($attr_ln eq 'content') {
2025 $content_attr = $attr;
2026 $checker = 1;
2027 } elsif ($attr_ln eq 'name') {
2028 $name_attr = $attr;
2029 $checker = 1;
2030 } elsif ($attr_ln eq 'http-equiv') {
2031 $http_equiv_attr = $attr;
2032 $checker = 1;
2033 } elsif ($attr_ln eq 'charset') {
2034 $charset_attr = $attr;
2035 $checker = 1;
2036 } elsif ($attr_ln eq 'scheme') {
2037 ## NOTE: <http://suika.fam.cx/2007/html/standards#html-meta-scheme>
2038 $checker = sub {};
2039 } elsif ($attr_ln =~ /^data-\p{InXMLNCNameChar10}+\z/ and
2040 $attr_ln !~ /[A-Z]/) {
2041 $checker = $HTMLDatasetAttrChecker;
2042 $status = $HTMLDatasetAttrStatus;
2043 } else {
2044 $checker = $HTMLAttrChecker->{$attr_ln}
2045 || $AttrChecker->{$attr_ns}->{$attr_ln}
2046 || $AttrChecker->{$attr_ns}->{''};
2047 }
2048 } else {
2049 $checker ||= $AttrChecker->{$attr_ns}->{$attr_ln}
2050 || $AttrChecker->{$attr_ns}->{''};
2051 $status ||= $AttrStatus->{$attr_ns}->{$attr_ln}
2052 || $AttrStatus->{$attr_ns}->{''};
2053 $status = FEATURE_ALLOWED if not defined $status;
2054 }
2055
2056 if ($checker) {
2057 $checker->($self, $attr, $item, $element_state) if ref $checker;
2058 } elsif ($attr_ns eq '' and not $status) {
2059 #
2060 } else {
2061 $self->{onerror}->(node => $attr,
2062 type => 'unknown attribute',
2063 level => $self->{level}->{uncertain});
2064 ## ISSUE: No conformance createria for unknown attributes in the spec
2065 }
2066
2067 $self->_attr_status_info ($attr, $status);
2068 }
2069
2070 if (defined $name_attr) {
2071 if (defined $http_equiv_attr) {
2072 $self->{onerror}->(node => $http_equiv_attr,
2073 type => 'attribute not allowed',
2074 level => $self->{level}->{must});
2075 } elsif (defined $charset_attr) {
2076 $self->{onerror}->(node => $charset_attr,
2077 type => 'attribute not allowed',
2078 level => $self->{level}->{must});
2079 }
2080 my $metadata_name = $name_attr->value;
2081 my $metadata_value;
2082 if (defined $content_attr) {
2083 $metadata_value = $content_attr->value;
2084 } else {
2085 $self->{onerror}->(node => $item->{node},
2086 type => 'attribute missing',
2087 text => 'content',
2088 level => $self->{level}->{must});
2089 $metadata_value = '';
2090 }
2091 } elsif (defined $http_equiv_attr) {
2092 if (defined $charset_attr) {
2093 $self->{onerror}->(node => $charset_attr,
2094 type => 'attribute not allowed',
2095 level => $self->{level}->{must});
2096 }
2097 unless (defined $content_attr) {
2098 $self->{onerror}->(node => $item->{node},
2099 type => 'attribute missing',
2100 text => 'content',
2101 level => $self->{level}->{must});
2102 }
2103 } elsif (defined $charset_attr) {
2104 if (defined $content_attr) {
2105 $self->{onerror}->(node => $content_attr,
2106 type => 'attribute not allowed',
2107 level => $self->{level}->{must});
2108 }
2109 } else {
2110 if (defined $content_attr) {
2111 $self->{onerror}->(node => $content_attr,
2112 type => 'attribute not allowed',
2113 level => $self->{level}->{must});
2114 $self->{onerror}->(node => $item->{node},
2115 type => 'attribute missing:name|http-equiv',
2116 level => $self->{level}->{must});
2117 } else {
2118 $self->{onerror}->(node => $item->{node},
2119 type => 'attribute missing:name|http-equiv|charset',
2120 level => $self->{level}->{must});
2121 }
2122 }
2123
2124 my $check_charset_decl = sub () {
2125 my $parent = $item->{node}->manakai_parent_element;
2126 if ($parent and $parent eq $parent->owner_document->manakai_head) {
2127 for my $el (@{$parent->child_nodes}) {
2128 next unless $el->node_type == 1; # ELEMENT_NODE
2129 unless ($el eq $item->{node}) {
2130 ## NOTE: Not the first child element.
2131 $self->{onerror}->(node => $item->{node},
2132 type => 'element not allowed:meta charset',
2133 level => $self->{level}->{must});
2134 }
2135 last;
2136 ## NOTE: Entity references are not supported.
2137 }
2138 } else {
2139 $self->{onerror}->(node => $item->{node},
2140 type => 'element not allowed:meta charset',
2141 level => $self->{level}->{must});
2142 }
2143
2144 unless ($item->{node}->owner_document->manakai_is_html) {
2145 $self->{onerror}->(node => $item->{node},
2146 type => 'in XML:charset',
2147 level => $self->{level}->{must});
2148 }
2149 }; # $check_charset_decl
2150
2151 my $check_charset = sub ($$) {
2152 my ($attr, $charset_value) = @_;
2153
2154 my $charset;
2155 ($charset, $charset_value)
2156 = $HTMLCharsetChecker->($charset_value, $self, $attr);
2157
2158 my $ic = $item->{node}->owner_document->input_encoding;
2159 if (defined $ic) {
2160 ## TODO: Test for this case
2161 my $ic_charset = $Message::Charset::Info::IANACharset->{$ic};
2162 if ($charset ne $ic_charset) {
2163 $self->{onerror}->(node => $attr,
2164 type => 'mismatched charset name',
2165 text => $ic,
2166 value => $charset_value,
2167 level => $self->{level}->{must});
2168 }
2169 } else {
2170 ## NOTE: MUST, but not checkable, since the document is not originally
2171 ## in serialized form (or the parser does not preserve the input
2172 ## encoding information).
2173 $self->{onerror}->(node => $attr,
2174 type => 'mismatched charset name not checked',
2175 value => $charset_value,
2176 level => $self->{level}->{uncertain});
2177 }
2178
2179 if ($attr->get_user_data ('manakai_has_reference')) {
2180 $self->{onerror}->(node => $attr,
2181 type => 'charref in charset',
2182 level => $self->{level}->{must},
2183 layer => 'syntax');
2184 }
2185 }; # $check_charset
2186
2187 ## TODO: metadata conformance
2188
2189 ## TODO: pragma conformance
2190 if (defined $http_equiv_attr) { ## An enumerated attribute
2191 my $keyword = lc $http_equiv_attr->value; ## TODO: ascii case?
2192
2193 if ($self->{has_http_equiv}->{$keyword}) {
2194 $self->{onerror}->(type => 'duplicate http-equiv', value => $keyword,
2195 node => $http_equiv_attr,
2196 level => $self->{level}->{must});
2197 } else {
2198 $self->{has_http_equiv}->{$keyword} = 1;
2199 }
2200
2201 if ($keyword eq 'content-type') {
2202 ## TODO: refs in "text/html; charset=" are not disallowed since rev.1275.
2203
2204 $check_charset_decl->();
2205 if ($content_attr) {
2206 my $content = $content_attr->value;
2207 if ($content =~ m!^[Tt][Ee][Xx][Tt]/[Hh][Tt][Mm][Ll];
2208 [\x09\x0A\x0C\x0D\x20]*[Cc][Hh][Aa][Rr][Ss][Ee][Tt]
2209 =(.+)\z!sx) {
2210 $check_charset->($content_attr, $1);
2211 } else {
2212 $self->{onerror}->(node => $content_attr,
2213 type => 'meta content-type syntax error',
2214 level => $self->{level}->{must});
2215 }
2216 }
2217 } elsif ($keyword eq 'default-style') {
2218 ## ISSUE: Not defined yet in the spec.
2219 } elsif ($keyword eq 'refresh') {
2220 if ($content_attr) {
2221 my $content = $content_attr->value;
2222 if ($content =~ /\A[0-9]+\z/) {
2223 ## NOTE: Valid non-negative integer.
2224 #
2225 } elsif ($content =~ s/\A[0-9]+;[\x09\x0A\x0C\x0D\x20]+[Uu][Rr][Ll]=//) {
2226 ## ISSUE: Relative references are allowed? (RFC 3987 "IRI" is an absolute reference with optional fragment identifier.)
2227 Whatpm::URIChecker->check_iri_reference ($content, sub {
2228 $self->{onerror}->(value => $content, @_, node => $content_attr);
2229 }, $self->{level});
2230 $self->{has_uri_attr} = 1; ## NOTE: One of "attributes with URIs".
2231
2232 $element_state->{uri_info}->{content}->{node} = $content_attr;
2233 $element_state->{uri_info}->{content}->{type}->{hyperlink} = 1;
2234 ## TODO: absolute
2235 push @{$self->{return}->{uri}->{$content} ||= []},
2236 $element_state->{uri_info}->{content};
2237 } else {
2238 $self->{onerror}->(node => $content_attr,
2239 type => 'refresh:syntax error',
2240 level => $self->{level}->{must});
2241 }
2242 }
2243 } else {
2244 $self->{onerror}->(node => $http_equiv_attr,
2245 type => 'enumerated:invalid',
2246 level => $self->{level}->{must});
2247 }
2248 }
2249
2250 if (defined $charset_attr) {
2251 $check_charset_decl->();
2252 $check_charset->($charset_attr, $charset_attr->value);
2253 }
2254 },
2255 };
2256
2257 $Element->{$HTML_NS}->{style} = {
2258 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
2259 %HTMLChecker,
2260 check_attrs => $GetHTMLAttrsChecker->({
2261 type => $HTMLIMTAttrChecker, ## TODO: MUST be a styling language
2262 media => $HTMLMQAttrChecker,
2263 scoped => $GetHTMLBooleanAttrChecker->('scoped'),
2264 ## NOTE: |title| has special semantics for |style|s, but is syntactically
2265 ## not different
2266 }, {
2267 %HTMLAttrStatus,
2268 %XHTML2CommonAttrStatus,
2269 dir => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
2270 disabled => FEATURE_XHTML2_ED,
2271 href => FEATURE_RDFA_PR | FEATURE_XHTML2_ED,
2272 id => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_XHTML10_REC,
2273 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
2274 media => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
2275 scoped => FEATURE_HTML5_DEFAULT,
2276 title => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
2277 type => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
2278 }),
2279 check_start => sub {
2280 my ($self, $item, $element_state) = @_;
2281
2282 ## NOTE: |html:style| itself has no conformance creteria on content model.
2283 my $type = $item->{node}->get_attribute_ns (undef, 'type');
2284 $type = 'text/css' unless defined $type;
2285 if ($type =~ m[\A(?>(?>\x0D\x0A)?[\x09\x20])*([\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+)(?>(?>\x0D\x0A)?[\x09\x20])*/(?>(?>\x0D\x0A)?[\x09\x20])*([\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+)(?>(?>\x0D\x0A)?[\x09\x20])*\z]) {
2286 $type = "$1/$2";
2287 $type =~ tr/A-Z/a-z/; ## NOTE: ASCII case-insensitive
2288 } else {
2289 ## NOTE: We don't know how parameters are handled by UAs. According to
2290 ## HTML5 specification, <style> with unknown parameters in |type=""|
2291 ## must be ignored.
2292 undef $type;
2293 }
2294 if (not defined $type) {
2295 $element_state->{allow_element} = 1; # invalid type=""
2296 } elsif ($type eq 'text/css') {
2297 $element_state->{allow_element} = 0;
2298 #} elsif ($type =~ m![/+][Xx][Mm][Ll]\z!) {
2299 # ## NOTE: There is no definition for "XML-based styling language" in HTML5
2300 # $element_state->{allow_element} = 1;
2301 } else {
2302 $element_state->{allow_element} = 1; # unknown
2303 }
2304 $element_state->{style_type} = $type;
2305
2306 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
2307 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
2308
2309 $element_state->{text} = '';
2310 },
2311 check_child_element => sub {
2312 my ($self, $item, $child_el, $child_nsuri, $child_ln,
2313 $child_is_transparent, $element_state) = @_;
2314 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
2315 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
2316 $self->{onerror}->(node => $child_el,
2317 type => 'element not allowed:minus',
2318 level => $self->{level}->{must});
2319 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
2320 #
2321 } elsif ($element_state->{allow_element}) {
2322 #
2323 } else {
2324 $self->{onerror}->(node => $child_el, type => 'element not allowed',
2325 level => $self->{level}->{must});
2326 }
2327 },
2328 check_child_text => sub {
2329 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
2330 $element_state->{text} .= $child_node->data;
2331 },
2332 check_end => sub {
2333 my ($self, $item, $element_state) = @_;
2334 if (not defined $element_state->{style_type}) {
2335 ## NOTE: Invalid type=""
2336 #
2337 } elsif ($element_state->{style_type} eq 'text/css') {
2338 $self->{onsubdoc}->({s => $element_state->{text},
2339 container_node => $item->{node},
2340 media_type => 'text/css', is_char_string => 1});
2341 } elsif ($element_state->{style_type} =~ m![+/][Xx][Mm][Ll]\z!) {
2342 ## NOTE: XML content should be checked by THIS instance of checker
2343 ## as part of normal tree validation. However, we don't know of any
2344 ## XML-based styling language that can be used in HTML <style> element,
2345 ## such that we throw a "style language not supported" error.
2346 $self->{onerror}->(node => $item->{node},
2347 type => 'XML style lang',
2348 text => $element_state->{style_type},
2349 level => $self->{level}->{uncertain});
2350 } else {
2351 ## NOTE: Should we raise some kind of error for,
2352 ## say, <style type="text/plaion">?
2353 $self->{onsubdoc}->({s => $element_state->{text},
2354 container_node => $item->{node},
2355 media_type => $element_state->{style_type},
2356 is_char_string => 1});
2357 }
2358
2359 $HTMLChecker{check_end}->(@_);
2360 },
2361 };
2362 ## ISSUE: Relationship to significant content check?
2363
2364 $Element->{$HTML_NS}->{body} = {
2365 %HTMLFlowContentChecker,
2366 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
2367 check_attrs => $GetHTMLAttrsChecker->({
2368 alink => $HTMLColorAttrChecker,
2369 background => $HTMLURIAttrChecker,
2370 bgcolor => $HTMLColorAttrChecker,
2371 link => $HTMLColorAttrChecker,
2372 text => $HTMLColorAttrChecker,
2373 vlink => $HTMLColorAttrChecker,
2374 }, {
2375 %HTMLAttrStatus,
2376 %HTMLM12NXHTML2CommonAttrStatus,
2377 alink => FEATURE_M12N10_REC_DEPRECATED,
2378 background => FEATURE_M12N10_REC_DEPRECATED,
2379 bgcolor => FEATURE_M12N10_REC_DEPRECATED,
2380 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
2381 link => FEATURE_M12N10_REC_DEPRECATED,
2382 onload => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
2383 onunload => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
2384 text => FEATURE_M12N10_REC_DEPRECATED,
2385 vlink => FEATURE_M12N10_REC_DEPRECATED,
2386 }),
2387 check_start => sub {
2388 my ($self, $item, $element_state) = @_;
2389
2390 $element_state->{uri_info}->{background}->{type}->{embedded} = 1;
2391 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
2392 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
2393 },
2394 };
2395
2396 $Element->{$HTML_NS}->{section} = {
2397 %HTMLFlowContentChecker,
2398 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED,
2399 check_attrs => $GetHTMLAttrsChecker->({
2400 }, {
2401 %HTMLAttrStatus,
2402 %XHTML2CommonAttrStatus,
2403 }),
2404 };
2405
2406 $Element->{$HTML_NS}->{nav} = {
2407 status => FEATURE_HTML5_DEFAULT,
2408 %HTMLFlowContentChecker,
2409 };
2410
2411 $Element->{$HTML_NS}->{article} = {
2412 status => FEATURE_HTML5_DEFAULT,
2413 %HTMLFlowContentChecker,
2414 };
2415
2416 $Element->{$HTML_NS}->{blockquote} = {
2417 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
2418 %HTMLFlowContentChecker,
2419 check_attrs => $GetHTMLAttrsChecker->({
2420 cite => $HTMLURIAttrChecker,
2421 }, {
2422 %HTMLAttrStatus,
2423 %HTMLM12NXHTML2CommonAttrStatus,
2424 align => FEATURE_HTML2X_RFC,
2425 cite => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
2426 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
2427 sdaform => FEATURE_HTML20_RFC,
2428 }),
2429 check_start => sub {
2430 my ($self, $item, $element_state) = @_;
2431
2432 $element_state->{uri_info}->{cite}->{type}->{cite} = 1;
2433 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
2434 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
2435 },
2436 };
2437
2438 $Element->{$HTML_NS}->{aside} = {
2439 status => FEATURE_HTML5_DEFAULT,
2440 %HTMLFlowContentChecker,
2441 };
2442
2443 $Element->{$HTML_NS}->{h1} = {
2444 %HTMLPhrasingContentChecker,
2445 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
2446 check_attrs => $GetHTMLAttrsChecker->({
2447 align => $GetHTMLEnumeratedAttrChecker->({
2448 left => 1, center => 1, right => 1, justify => 1,
2449 }),
2450 }, {
2451 %HTMLAttrStatus,
2452 %HTMLM12NXHTML2CommonAttrStatus,
2453 align => FEATURE_M12N10_REC_DEPRECATED,
2454 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
2455 sdaform => FEATURE_HTML20_RFC,
2456 }),
2457 check_start => sub {
2458 my ($self, $item, $element_state) = @_;
2459 $self->{flag}->{has_hn} = 1;
2460
2461 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
2462 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
2463 },
2464 };
2465
2466 $Element->{$HTML_NS}->{h2} = {%{$Element->{$HTML_NS}->{h1}}};
2467
2468 $Element->{$HTML_NS}->{h3} = {%{$Element->{$HTML_NS}->{h1}}};
2469
2470 $Element->{$HTML_NS}->{h4} = {%{$Element->{$HTML_NS}->{h1}}};
2471
2472 $Element->{$HTML_NS}->{h5} = {%{$Element->{$HTML_NS}->{h1}}};
2473
2474 $Element->{$HTML_NS}->{h6} = {%{$Element->{$HTML_NS}->{h1}}};
2475
2476 ## TODO: Explicit sectioning is "encouraged".
2477
2478 $Element->{$HTML_NS}->{header} = {
2479 status => FEATURE_HTML5_DEFAULT,
2480 %HTMLFlowContentChecker,
2481 check_start => sub {
2482 my ($self, $item, $element_state) = @_;
2483 $self->_add_minus_elements ($element_state,
2484 {$HTML_NS => {qw/header 1 footer 1/}},
2485 $HTMLSectioningContent);
2486 $element_state->{has_hn_original} = $self->{flag}->{has_hn};
2487 $self->{flag}->{has_hn} = 0;
2488
2489 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
2490 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
2491 },
2492 check_end => sub {
2493 my ($self, $item, $element_state) = @_;
2494 $self->_remove_minus_elements ($element_state);
2495 unless ($self->{flag}->{has_hn}) {
2496 $self->{onerror}->(node => $item->{node},
2497 type => 'element missing:hn',
2498 level => $self->{level}->{must});
2499 }
2500 $self->{flag}->{has_hn} ||= $element_state->{has_hn_original};
2501
2502 $HTMLFlowContentChecker{check_end}->(@_);
2503 },
2504 ## ISSUE: <header><del><h1>...</h1></del></header> is conforming?
2505 };
2506
2507 $Element->{$HTML_NS}->{footer} = {
2508 status => FEATURE_HTML5_DEFAULT,
2509 %HTMLFlowContentChecker,
2510 check_start => sub {
2511 my ($self, $item, $element_state) = @_;
2512 $self->_add_minus_elements ($element_state,
2513 {$HTML_NS => {footer => 1}},
2514 $HTMLSectioningContent,
2515 $HTMLHeadingContent);
2516
2517 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
2518 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
2519 },
2520 check_end => sub {
2521 my ($self, $item, $element_state) = @_;
2522 $self->_remove_minus_elements ($element_state);
2523
2524 $HTMLFlowContentChecker{check_end}->(@_);
2525 },
2526 };
2527
2528 $Element->{$HTML_NS}->{address} = {
2529 %HTMLFlowContentChecker,
2530 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
2531 check_attrs => $GetHTMLAttrsChecker->({
2532 ## TODO: add test
2533 #align => $GetHTMLEnumeratedAttrChecker->({
2534 # left => 1, center => 1, right => 1, justify => 1,
2535 #}),
2536 }, {
2537 %HTMLAttrStatus,
2538 %HTMLM12NXHTML2CommonAttrStatus,
2539 align => FEATURE_HTML2X_RFC,
2540 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
2541 sdaform => FEATURE_HTML20_RFC,
2542 sdapref => FEATURE_HTML20_RFC,
2543 }),
2544 check_start => sub {
2545 my ($self, $item, $element_state) = @_;
2546 $self->_add_minus_elements ($element_state,
2547 {$HTML_NS => {footer => 1, address => 1}},
2548 $HTMLSectioningContent, $HTMLHeadingContent);
2549
2550 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
2551 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
2552 },
2553 check_end => sub {
2554 my ($self, $item, $element_state) = @_;
2555 $self->_remove_minus_elements ($element_state);
2556
2557 $HTMLFlowContentChecker{check_end}->(@_);
2558 },
2559 };
2560
2561 $Element->{$HTML_NS}->{p} = {
2562 %HTMLPhrasingContentChecker,
2563 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
2564 check_attrs => $GetHTMLAttrsChecker->({
2565 align => $GetHTMLEnumeratedAttrChecker->({
2566 left => 1, center => 1, right => 1, justify => 1,
2567 }),
2568 }, {
2569 %HTMLAttrStatus,
2570 %HTMLM12NXHTML2CommonAttrStatus,
2571 align => FEATURE_M12N10_REC_DEPRECATED,
2572 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
2573 sdaform => FEATURE_HTML20_RFC,
2574 }),
2575 };
2576
2577 $Element->{$HTML_NS}->{hr} = {
2578 %HTMLEmptyChecker,
2579 status => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
2580 check_attrs => $GetHTMLAttrsChecker->({
2581 ## TODO: HTML4 |align|, |noshade|, |size|, |width|
2582 }, {
2583 %HTMLAttrStatus,
2584 %HTMLM12NCommonAttrStatus,
2585 align => FEATURE_M12N10_REC_DEPRECATED,
2586 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
2587 noshade => FEATURE_M12N10_REC_DEPRECATED,
2588 sdapref => FEATURE_HTML20_RFC,
2589 size => FEATURE_M12N10_REC_DEPRECATED,
2590 width => FEATURE_M12N10_REC_DEPRECATED,
2591 }),
2592 };
2593
2594 $Element->{$HTML_NS}->{br} = {
2595 %HTMLEmptyChecker,
2596 status => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
2597 check_attrs => $GetHTMLAttrsChecker->({
2598 clear => $GetHTMLEnumeratedAttrChecker->({
2599 left => 1, all => 1, right => 1, none => 1,
2600 }),
2601 }, {
2602 %HTMLAttrStatus,
2603 class => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
2604 clear => FEATURE_M12N10_REC_DEPRECATED,
2605 id => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
2606 sdapref => FEATURE_HTML20_RFC,
2607 style => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
2608 title => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
2609 }),
2610 ## NOTE: Blank line MUST NOT be used for presentation purpose.
2611 ## (This requirement is semantic so that we cannot check.)
2612 };
2613
2614 $Element->{$HTML_NS}->{dialog} = {
2615 status => FEATURE_HTML5_DEFAULT,
2616 %HTMLChecker,
2617 check_start => sub {
2618 my ($self, $item, $element_state) = @_;
2619 $element_state->{phase} = 'before dt';
2620
2621 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
2622 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
2623 },
2624 check_child_element => sub {
2625 my ($self, $item, $child_el, $child_nsuri, $child_ln,
2626 $child_is_transparent, $element_state) = @_;
2627 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
2628 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
2629 $self->{onerror}->(node => $child_el,
2630 type => 'element not allowed:minus',
2631 level => $self->{level}->{must});
2632 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
2633 #
2634 } elsif ($element_state->{phase} eq 'before dt') {
2635 if ($child_nsuri eq $HTML_NS and $child_ln eq 'dt') {
2636 $element_state->{phase} = 'before dd';
2637 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'dd') {
2638 $self->{onerror}
2639 ->(node => $child_el, type => 'ps element missing',
2640 text => 'dt',
2641 level => $self->{level}->{must});
2642 $element_state->{phase} = 'before dt';
2643 } else {
2644 $self->{onerror}->(node => $child_el, type => 'element not allowed',
2645 level => $self->{level}->{must});
2646 }
2647 } elsif ($element_state->{phase} eq 'before dd') {
2648 if ($child_nsuri eq $HTML_NS and $child_ln eq 'dd') {
2649 $element_state->{phase} = 'before dt';
2650 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'dt') {
2651 $self->{onerror}
2652 ->(node => $child_el, type => 'ps element missing',
2653 text => 'dd',
2654 level => $self->{level}->{must});
2655 $element_state->{phase} = 'before dd';
2656 } else {
2657 $self->{onerror}->(node => $child_el, type => 'element not allowed',
2658 level => $self->{level}->{must});
2659 }
2660 } else {
2661 die "check_child_element: Bad |dialog| phase: $element_state->{phase}";
2662 }
2663 },
2664 check_child_text => sub {
2665 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
2666 if ($has_significant) {
2667 $self->{onerror}->(node => $child_node, type => 'character not allowed',
2668 level => $self->{level}->{must});
2669 }
2670 },
2671 check_end => sub {
2672 my ($self, $item, $element_state) = @_;
2673 if ($element_state->{phase} eq 'before dd') {
2674 $self->{onerror}->(node => $item->{node},
2675 type => 'child element missing',
2676 text => 'dd',
2677 level => $self->{level}->{must});
2678 }
2679
2680 $HTMLChecker{check_end}->(@_);
2681 },
2682 };
2683
2684 $Element->{$HTML_NS}->{pre} = {
2685 %HTMLPhrasingContentChecker,
2686 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
2687 check_attrs => $GetHTMLAttrsChecker->({
2688 width => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }),
2689 }, {
2690 %HTMLAttrStatus,
2691 %HTMLM12NXHTML2CommonAttrStatus,
2692 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
2693 sdaform => FEATURE_HTML20_RFC,
2694 width => FEATURE_M12N10_REC_DEPRECATED,
2695 }),
2696 check_end => sub {
2697 my ($self, $item, $element_state) = @_;
2698
2699 ## TODO: Flag to enable/disable IDL checking?
2700 my $class = $item->{node}->get_attribute_ns (undef, 'class');
2701 if ($class =~ /\bidl(?>-code)?\b/) { ## TODO: use classList.has
2702 ## NOTE: pre.idl: WHATWG, XHR, Selectors API, CSSOM specs
2703 ## NOTE: pre.code > code.idl-code: WebIDL spec
2704 ## NOTE: pre.idl-code: DOM1 spec
2705 ## NOTE: div.idl-code > pre: DOM, ProgressEvent specs
2706 ## NOTE: pre.schema: ReSpec-generated specs
2707 $self->{onsubdoc}->({s => $item->{node}->text_content,
2708 container_node => $item->{node},
2709 media_type => 'text/x-webidl',
2710 is_char_string => 1});
2711 }
2712
2713 $HTMLPhrasingContentChecker{check_end}->(@_);
2714 },
2715 };
2716
2717 $Element->{$HTML_NS}->{ol} = {
2718 %HTMLChecker,
2719 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
2720 check_attrs => $GetHTMLAttrsChecker->({
2721 compact => $GetHTMLBooleanAttrChecker->('compact'),
2722 reversed => $GetHTMLBooleanAttrChecker->('reversed'),
2723 start => $HTMLIntegerAttrChecker,
2724 ## TODO: HTML4 |type|
2725 }, {
2726 %HTMLAttrStatus,
2727 %HTMLM12NXHTML2CommonAttrStatus,
2728 align => FEATURE_HTML2X_RFC,
2729 compact => FEATURE_M12N10_REC_DEPRECATED,
2730 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
2731 reversed => FEATURE_HTML5_DEFAULT,
2732 sdaform => FEATURE_HTML20_RFC,
2733 #start => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC_DEPRECATED,
2734 start => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
2735 type => FEATURE_M12N10_REC_DEPRECATED,
2736 }),
2737 check_child_element => sub {
2738 my ($self, $item, $child_el, $child_nsuri, $child_ln,
2739 $child_is_transparent, $element_state) = @_;
2740 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
2741 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
2742 $self->{onerror}->(node => $child_el,
2743 type => 'element not allowed:minus',
2744 level => $self->{level}->{must});
2745 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
2746 #
2747 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'li') {
2748 #
2749 } else {
2750 $self->{onerror}->(node => $child_el, type => 'element not allowed',
2751 level => $self->{level}->{must});
2752 }
2753 },
2754 check_child_text => sub {
2755 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
2756 if ($has_significant) {
2757 $self->{onerror}->(node => $child_node, type => 'character not allowed',
2758 level => $self->{level}->{must});
2759 }
2760 },
2761 };
2762
2763 $Element->{$HTML_NS}->{ul} = {
2764 %{$Element->{$HTML_NS}->{ol}},
2765 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
2766 check_attrs => $GetHTMLAttrsChecker->({
2767 compact => $GetHTMLBooleanAttrChecker->('compact'),
2768 ## TODO: HTML4 |type|
2769 ## TODO: sdaform, align
2770 }, {
2771 %HTMLAttrStatus,
2772 %HTMLM12NXHTML2CommonAttrStatus,
2773 align => FEATURE_HTML2X_RFC,
2774 compact => FEATURE_M12N10_REC_DEPRECATED,
2775 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
2776 sdaform => FEATURE_HTML20_RFC,
2777 type => FEATURE_M12N10_REC_DEPRECATED,
2778 }),
2779 };
2780
2781 $Element->{$HTML_NS}->{dir} = {
2782 ## TODO: %block; is not allowed [HTML4] ## TODO: Empty list allowed?
2783 %{$Element->{$HTML_NS}->{ul}},
2784 status => FEATURE_M12N10_REC_DEPRECATED,
2785 check_attrs => $GetHTMLAttrsChecker->({
2786 compact => $GetHTMLBooleanAttrChecker->('compact'),
2787 }, {
2788 %HTMLAttrStatus,
2789 %HTMLM12NCommonAttrStatus,
2790 align => FEATURE_HTML2X_RFC,
2791 compact => FEATURE_M12N10_REC_DEPRECATED,
2792 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
2793 sdaform => FEATURE_HTML20_RFC,
2794 sdapref => FEATURE_HTML20_RFC,
2795 }),
2796 };
2797
2798 $Element->{$HTML_NS}->{li} = {
2799 %HTMLFlowContentChecker,
2800 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
2801 check_attrs => $GetHTMLAttrsChecker->({
2802 ## TODO: HTML4 |type|
2803 value => sub {
2804 my ($self, $attr) = @_;
2805 my $parent = $attr->owner_element->manakai_parent_element;
2806 if (defined $parent) {
2807 my $parent_ns = $parent->namespace_uri;
2808 $parent_ns = '' unless defined $parent_ns;
2809 my $parent_ln = $parent->manakai_local_name;
2810 unless ($parent_ns eq $HTML_NS and $parent_ln eq 'ol') {
2811 ## ISSUE: ...
2812 $self->{onerror}->(node => $attr,
2813 type => 'unknown attribute',
2814 level => $self->{level}->{uncertain});
2815 }
2816 }
2817 $HTMLIntegerAttrChecker->($self, $attr);
2818 },
2819 }, {
2820 %HTMLAttrStatus,
2821 %HTMLM12NXHTML2CommonAttrStatus,
2822 align => FEATURE_HTML2X_RFC,
2823 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
2824 sdaform => FEATURE_HTML20_RFC,
2825 type => FEATURE_M12N10_REC_DEPRECATED,
2826 #value => FEATURE_HTML5_DEFAULT | FEATURE_XHTMLBASIC11_CR |
2827 # FEATURE_M12N10_REC_DEPRECATED,
2828 value => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED |
2829 FEATURE_XHTMLBASIC11_CR | FEATURE_M12N10_REC,
2830 }),
2831 check_child_element => sub {
2832 my ($self, $item, $child_el, $child_nsuri, $child_ln,
2833 $child_is_transparent, $element_state) = @_;
2834 if ($self->{flag}->{in_menu}) {
2835 $HTMLPhrasingContentChecker{check_child_element}->(@_);
2836 } else {
2837 $HTMLFlowContentChecker{check_child_element}->(@_);
2838 }
2839 },
2840 check_child_text => sub {
2841 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
2842 if ($self->{flag}->{in_menu}) {
2843 $HTMLPhrasingContentChecker{check_child_text}->(@_);
2844 } else {
2845 $HTMLFlowContentChecker{check_child_text}->(@_);
2846 }
2847 },
2848 };
2849
2850 $Element->{$HTML_NS}->{dl} = {
2851 %HTMLChecker,
2852 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
2853 check_attrs => $GetHTMLAttrsChecker->({
2854 compact => $GetHTMLBooleanAttrChecker->('compact'),
2855 }, {
2856 %HTMLAttrStatus,
2857 %HTMLM12NXHTML2CommonAttrStatus,
2858 compact => FEATURE_M12N10_REC_DEPRECATED,
2859 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
2860 sdaform => FEATURE_HTML20_RFC,
2861 sdapref => FEATURE_HTML20_RFC,
2862 type => FEATURE_M12N10_REC_DEPRECATED,
2863 }),
2864 check_start => sub {
2865 my ($self, $item, $element_state) = @_;
2866 $element_state->{phase} = 'before dt';
2867
2868 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
2869 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
2870 },
2871 check_child_element => sub {
2872 my ($self, $item, $child_el, $child_nsuri, $child_ln,
2873 $child_is_transparent, $element_state) = @_;
2874 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
2875 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
2876 $self->{onerror}->(node => $child_el,
2877 type => 'element not allowed:minus',
2878 level => $self->{level}->{must});
2879 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
2880 #
2881 } elsif ($element_state->{phase} eq 'in dds') {
2882 if ($child_nsuri eq $HTML_NS and $child_ln eq 'dd') {
2883 #$element_state->{phase} = 'in dds';
2884 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'dt') {
2885 $element_state->{phase} = 'in dts';
2886 } else {
2887 $self->{onerror}->(node => $child_el, type => 'element not allowed',
2888 level => $self->{level}->{must});
2889 }
2890 } elsif ($element_state->{phase} eq 'in dts') {
2891 if ($child_nsuri eq $HTML_NS and $child_ln eq 'dt') {
2892 #$element_state->{phase} = 'in dts';
2893 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'dd') {
2894 $element_state->{phase} = 'in dds';
2895 } else {
2896 $self->{onerror}->(node => $child_el, type => 'element not allowed',
2897 level => $self->{level}->{must});
2898 }
2899 } elsif ($element_state->{phase} eq 'before dt') {
2900 if ($child_nsuri eq $HTML_NS and $child_ln eq 'dt') {
2901 $element_state->{phase} = 'in dts';
2902 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'dd') {
2903 $self->{onerror}
2904 ->(node => $child_el, type => 'ps element missing',
2905 text => 'dt',
2906 level => $self->{level}->{must});
2907 $element_state->{phase} = 'in dds';
2908 } else {
2909 $self->{onerror}->(node => $child_el, type => 'element not allowed',
2910 level => $self->{level}->{must});
2911 }
2912 } else {
2913 die "check_child_element: Bad |dl| phase: $element_state->{phase}";
2914 }
2915 },
2916 check_child_text => sub {
2917 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
2918 if ($has_significant) {
2919 $self->{onerror}->(node => $child_node, type => 'character not allowed',
2920 level => $self->{level}->{must});
2921 }
2922 },
2923 check_end => sub {
2924 my ($self, $item, $element_state) = @_;
2925 if ($element_state->{phase} eq 'in dts') {
2926 $self->{onerror}->(node => $item->{node},
2927 type => 'child element missing',
2928 text => 'dd',
2929 level => $self->{level}->{must});
2930 }
2931
2932 $HTMLChecker{check_end}->(@_);
2933 },
2934 };
2935
2936 $Element->{$HTML_NS}->{dt} = {
2937 %HTMLPhrasingContentChecker,
2938 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
2939 check_attrs => $GetHTMLAttrsChecker->({}, {
2940 %HTMLAttrStatus,
2941 %HTMLM12NXHTML2CommonAttrStatus,
2942 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
2943 sdaform => FEATURE_HTML20_RFC,
2944 }),
2945 };
2946
2947 $Element->{$HTML_NS}->{dd} = {
2948 %HTMLFlowContentChecker,
2949 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
2950 check_attrs => $GetHTMLAttrsChecker->({}, {
2951 %HTMLAttrStatus,
2952 %HTMLM12NXHTML2CommonAttrStatus,
2953 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
2954 sdaform => FEATURE_HTML20_RFC,
2955 }),
2956 };
2957
2958 $Element->{$HTML_NS}->{a} = {
2959 %HTMLTransparentChecker,
2960 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
2961 check_attrs => sub {
2962 my ($self, $item, $element_state) = @_;
2963 my %attr;
2964 for my $attr (@{$item->{node}->attributes}) {
2965 my $attr_ns = $attr->namespace_uri;
2966 $attr_ns = '' unless defined $attr_ns;
2967 my $attr_ln = $attr->manakai_local_name;
2968 my $checker;
2969 my $status;
2970 if ($attr_ns eq '') {
2971 $status = {
2972 %HTMLAttrStatus,
2973 %HTMLM12NXHTML2CommonAttrStatus,
2974 accesskey => FEATURE_M12N10_REC,
2975 charset => FEATURE_M12N10_REC,
2976 coords => FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
2977 cryptopts => FEATURE_RFC2659,
2978 dn => FEATURE_RFC2659,
2979 href => FEATURE_HTML5_DEFAULT | FEATURE_RDFA_CR | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
2980 hreflang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
2981 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
2982 media => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED,
2983 methods => FEATURE_HTML20_RFC,
2984 name => FEATURE_M12N10_REC_DEPRECATED,
2985 nonce => FEATURE_RFC2659,
2986 onblur => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
2987 onfocus => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
2988 ping => FEATURE_HTML5_DEFAULT,
2989 rel => FEATURE_HTML5_DEFAULT | FEATURE_RDFA_CR | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
2990 rev => FEATURE_RDFA_CR | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
2991 sdapref => FEATURE_HTML20_RFC,
2992 shape => FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
2993 tabindex => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
2994 target => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
2995 type => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
2996 urn => FEATURE_HTML20_RFC,
2997 }->{$attr_ln};
2998
2999 $checker = {
3000 accesskey => $HTMLAccesskeyAttrChecker,
3001 charset => sub {
3002 my ($self, $attr) = @_;
3003 $HTMLCharsetChecker->($attr->value, @_);
3004 },
3005 ## TODO: HTML4 |coords|
3006 target => $HTMLTargetAttrChecker,
3007 href => $HTMLURIAttrChecker,
3008 ping => $HTMLSpaceURIsAttrChecker,
3009 rel => sub { $HTMLLinkTypesAttrChecker->(1, $item, @_) },
3010 rev => $GetHTMLUnorderedUniqueSetOfSpaceSeparatedTokensAttrChecker->(),
3011 ## TODO: HTML4 |shape|
3012 media => $HTMLMQAttrChecker,
3013 ## TODO: HTML4/XHTML1 |name|
3014 hreflang => $HTMLLanguageTagAttrChecker,
3015 type => $HTMLIMTAttrChecker,
3016 }->{$attr_ln};
3017 if ($checker) {
3018 $attr{$attr_ln} = $attr;
3019 } elsif ($attr_ln =~ /^data-\p{InXMLNCNameChar10}+\z/ and
3020 $attr_ln !~ /[A-Z]/) {
3021 $checker = $HTMLDatasetAttrChecker;
3022 $status = $HTMLDatasetAttrStatus;
3023 } else {
3024 $checker = $HTMLAttrChecker->{$attr_ln};
3025 }
3026 }
3027 $checker ||= $AttrChecker->{$attr_ns}->{$attr_ln}
3028 || $AttrChecker->{$attr_ns}->{''};
3029 $status ||= $AttrStatus->{$attr_ns}->{$attr_ln}
3030 || $AttrStatus->{$attr_ns}->{''};
3031 $status = FEATURE_ALLOWED if not defined $status and length $attr_ns;
3032
3033 if ($checker) {
3034 $checker->($self, $attr, $item, $element_state) if ref $checker;
3035 } elsif ($attr_ns eq '' and not $status) {
3036 #
3037 } else {
3038 $self->{onerror}->(node => $attr,
3039 type => 'unknown attribute',
3040 level => $self->{level}->{uncertain});
3041 ## ISSUE: No conformance createria for unknown attributes in the spec
3042 }
3043
3044 $self->_attr_status_info ($attr, $status);
3045 }
3046
3047 $element_state->{in_a_href_original} = $self->{flag}->{in_a_href};
3048 if (defined $attr{href}) {
3049 $self->{has_hyperlink_element} = 1;
3050 $self->{flag}->{in_a_href} = 1;
3051 } else {
3052 for (qw/target ping rel media hreflang type/) {
3053 if (defined $attr{$_}) {
3054 $self->{onerror}->(node => $attr{$_},
3055 type => 'attribute not allowed',
3056 level => $self->{level}->{must});
3057 }
3058 }
3059 }
3060
3061 $element_state->{uri_info}->{href}->{type}->{hyperlink} = 1;
3062 },
3063 check_start => sub {
3064 my ($self, $item, $element_state) = @_;
3065 $self->_add_minus_elements ($element_state, $HTMLInteractiveContent);
3066
3067 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
3068 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
3069 },
3070 check_end => sub {
3071 my ($self, $item, $element_state) = @_;
3072 $self->_remove_minus_elements ($element_state);
3073 delete $self->{flag}->{in_a_href}
3074 unless $element_state->{in_a_href_original};
3075
3076 $HTMLTransparentChecker{check_end}->(@_);
3077 },
3078 };
3079
3080 $Element->{$HTML_NS}->{q} = {
3081 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
3082 %HTMLPhrasingContentChecker,
3083 check_attrs => $GetHTMLAttrsChecker->({
3084 cite => $HTMLURIAttrChecker,
3085 }, {
3086 %HTMLAttrStatus,
3087 %HTMLM12NXHTML2CommonAttrStatus,
3088 cite => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
3089 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
3090 sdapref => FEATURE_HTML2X_RFC,
3091 sdasuff => FEATURE_HTML2X_RFC,
3092 }),
3093 check_start => sub {
3094 my ($self, $item, $element_state) = @_;
3095
3096 $element_state->{uri_info}->{cite}->{type}->{cite} = 1;
3097 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
3098 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
3099 },
3100 };
3101 ## TODO: "Quotation punctuation (such as quotation marks), if any, must be
3102 ## placed inside the <code>q</code> element." Though we cannot test the
3103 ## element against this requirement since it incluides a semantic bit,
3104 ## it might be possible to inform of the existence of quotation marks OUTSIDE
3105 ## the |q| element.
3106
3107 $Element->{$HTML_NS}->{cite} = {
3108 %HTMLPhrasingContentChecker,
3109 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
3110 check_attrs => $GetHTMLAttrsChecker->({}, {
3111 %HTMLAttrStatus,
3112 %HTMLM12NXHTML2CommonAttrStatus,
3113 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
3114 sdaform => FEATURE_HTML20_RFC,
3115 }),
3116 };
3117
3118 $Element->{$HTML_NS}->{em} = {
3119 %HTMLPhrasingContentChecker,
3120 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
3121 check_attrs => $GetHTMLAttrsChecker->({}, {
3122 %HTMLAttrStatus,
3123 %HTMLM12NXHTML2CommonAttrStatus,
3124 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
3125 sdaform => FEATURE_HTML20_RFC,
3126 }),
3127 };
3128
3129 $Element->{$HTML_NS}->{strong} = {
3130 %HTMLPhrasingContentChecker,
3131 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
3132 check_attrs => $GetHTMLAttrsChecker->({}, {
3133 %HTMLAttrStatus,
3134 %HTMLM12NXHTML2CommonAttrStatus,
3135 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
3136 sdaform => FEATURE_HTML20_RFC,
3137 }),
3138 };
3139
3140 $Element->{$HTML_NS}->{small} = {
3141 %HTMLPhrasingContentChecker,
3142 status => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
3143 check_attrs => $GetHTMLAttrsChecker->({}, {
3144 %HTMLAttrStatus,
3145 %HTMLM12NCommonAttrStatus,
3146 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
3147 }),
3148 };
3149
3150 $Element->{$HTML_NS}->{big} = {
3151 %HTMLPhrasingContentChecker,
3152 status => FEATURE_M12N10_REC,
3153 check_attrs => $GetHTMLAttrsChecker->({}, {
3154 %HTMLAttrStatus,
3155 %HTMLM12NCommonAttrStatus,
3156 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
3157 }),
3158 };
3159
3160 $Element->{$HTML_NS}->{mark} = {
3161 status => FEATURE_HTML5_DEFAULT,
3162 %HTMLPhrasingContentChecker,
3163 };
3164
3165 $Element->{$HTML_NS}->{dfn} = {
3166 %HTMLPhrasingContentChecker,
3167 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
3168 check_attrs => $GetHTMLAttrsChecker->({}, {
3169 %HTMLAttrStatus,
3170 %HTMLM12NXHTML2CommonAttrStatus,
3171 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
3172 }),
3173 check_start => sub {
3174 my ($self, $item, $element_state) = @_;
3175 $self->_add_minus_elements ($element_state, {$HTML_NS => {dfn => 1}});
3176
3177 my $node = $item->{node};
3178 my $term = $node->get_attribute_ns (undef, 'title');
3179 unless (defined $term) {
3180 for my $child (@{$node->child_nodes}) {
3181 if ($child->node_type == 1) { # ELEMENT_NODE
3182 if (defined $term) {
3183 undef $term;
3184 last;
3185 } elsif ($child->manakai_local_name eq 'abbr') {
3186 my $nsuri = $child->namespace_uri;
3187 if (defined $nsuri and $nsuri eq $HTML_NS) {
3188 my $attr = $child->get_attribute_node_ns (undef, 'title');
3189 if ($attr) {
3190 $term = $attr->value;
3191 }
3192 }
3193 }
3194 } elsif ($child->node_type == 3 or $child->node_type == 4) {
3195 ## TEXT_NODE or CDATA_SECTION_NODE
3196 if ($child->data =~ /\A[\x09\x0A\x0C\x0D\x20]+\z/) { # Inter-element whitespace
3197 next;
3198 }
3199 undef $term;
3200 last;
3201 }
3202 }
3203 unless (defined $term) {
3204 $term = $node->text_content;
3205 }
3206 }
3207 if ($self->{term}->{$term}) {
3208 push @{$self->{term}->{$term}}, $node;
3209 } else {
3210 $self->{term}->{$term} = [$node];
3211 }
3212 ## ISSUE: The HTML5 definition for the defined term does not work with
3213 ## |ruby| unless |dfn| has |title|.
3214
3215 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
3216 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
3217 },
3218 check_end => sub {
3219 my ($self, $item, $element_state) = @_;
3220 $self->_remove_minus_elements ($element_state);
3221
3222 $HTMLPhrasingContentChecker{check_end}->(@_);
3223 },
3224 };
3225
3226 $Element->{$HTML_NS}->{abbr} = {
3227 %HTMLPhrasingContentChecker,
3228 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
3229 check_attrs => $GetHTMLAttrsChecker->({}, {
3230 %HTMLAttrStatus,
3231 %HTMLM12NXHTML2CommonAttrStatus,
3232 full => FEATURE_XHTML2_ED,
3233 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
3234 }),
3235 ## NOTE: "If an abbreviation is pluralised, the expansion's grammatical
3236 ## number (plural vs singular) must match the grammatical number of the
3237 ## contents of the element." Though this can be checked by machine,
3238 ## it requires language-specific knowledge and dictionary, such that
3239 ## we don't support the check of the requirement.
3240 ## ISSUE: Is <abbr title="Cascading Style Sheets">CSS</abbr> conforming?
3241 };
3242
3243 $Element->{$HTML_NS}->{acronym} = {
3244 %HTMLPhrasingContentChecker,
3245 status => FEATURE_M12N10_REC,
3246 check_attrs => $GetHTMLAttrsChecker->({}, {
3247 %HTMLAttrStatus,
3248 %HTMLM12NCommonAttrStatus,
3249 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
3250 }),
3251 };
3252
3253 $Element->{$HTML_NS}->{time} = {
3254 status => FEATURE_HTML5_DEFAULT,
3255 %HTMLPhrasingContentChecker,
3256 check_attrs => $GetHTMLAttrsChecker->({
3257 datetime => sub { 1 }, # checked in |checker|
3258 }, {
3259 %HTMLAttrStatus,
3260 %HTMLM12NCommonAttrStatus,
3261 datetime => FEATURE_HTML5_FD,
3262 }),
3263 ## TODO: Write tests
3264 check_end => sub {
3265 my ($self, $item, $element_state) = @_;
3266
3267 my $attr = $item->{node}->get_attribute_node_ns (undef, 'datetime');
3268 my $input;
3269 my $reg_sp;
3270 my $input_node;
3271 if ($attr) {
3272 $input = $attr->value;
3273 $reg_sp = qr/[\x09\x0A\x0C\x0D\x20]*/;
3274 $input_node = $attr;
3275 } else {
3276 $input = $item->{node}->text_content;
3277 $reg_sp = qr/\p{WhiteSpace}*/;
3278 $input_node = $item->{node};
3279
3280 ## ISSUE: What is the definition for "successfully extracts a date
3281 ## or time"? If the algorithm says the string is invalid but
3282 ## return some date or time, is it "successfully"?
3283 }
3284
3285 my $hour;
3286 my $minute;
3287 my $second;
3288 if ($input =~ /
3289 \A
3290 $reg_sp
3291 ([0-9]+) # 1
3292 (?>
3293 -([0-9]+) # 2
3294 -((?>[0-9]+)) # 3 # Use (?>) such that yyyy-mm-ddhh:mm does not match
3295 $reg_sp
3296 (?>
3297 T
3298 $reg_sp
3299 )?
3300 ([0-9]+) # 4
3301 :([0-9]+) # 5
3302 (?>
3303 :([0-9]+(?>\.[0-9]*)?|\.[0-9]*) # 6
3304 )?
3305 $reg_sp
3306 (?>
3307 Z
3308 $reg_sp
3309 |
3310 [+-]([0-9]+):([0-9]+) # 7, 8
3311 $reg_sp
3312 )?
3313 \z
3314 |
3315 :([0-9]+) # 9
3316 (?>
3317 :([0-9]+(?>\.[0-9]*)?|\.[0-9]*) # 10
3318 )?
3319 $reg_sp
3320 \z
3321 )
3322 /x) {
3323 if (defined $2) { ## YYYY-MM-DD T? hh:mm
3324 if (length $1 != 4 or length $2 != 2 or length $3 != 2 or
3325 length $4 != 2 or length $5 != 2) {
3326 $self->{onerror}->(node => $input_node,
3327 type => 'dateortime:syntax error',
3328 level => $self->{level}->{must});
3329 }
3330
3331 if (1 <= $2 and $2 <= 12) {
3332 $self->{onerror}->(node => $input_node, type => 'datetime:bad day',
3333 level => $self->{level}->{must})
3334 if $3 < 1 or
3335 $3 > [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]->[$2];
3336 $self->{onerror}->(node => $input_node, type => 'datetime:bad day',
3337 level => $self->{level}->{must})
3338 if $2 == 2 and $3 == 29 and
3339 not ($1 % 400 == 0 or ($1 % 4 == 0 and $1 % 100 != 0));
3340 } else {
3341 $self->{onerror}->(node => $input_node,
3342 type => 'datetime:bad month',
3343 level => $self->{level}->{must});
3344 }
3345
3346 ($hour, $minute, $second) = ($4, $5, $6);
3347
3348 if (defined $7) { ## [+-]hh:mm
3349 if (length $7 != 2 or length $8 != 2) {
3350 $self->{onerror}->(node => $input_node,
3351 type => 'dateortime:syntax error',
3352 level => $self->{level}->{must});
3353 }
3354
3355 $self->{onerror}->(node => $input_node,
3356 type => 'datetime:bad timezone hour',
3357 level => $self->{level}->{must})
3358 if $7 > 23;
3359 $self->{onerror}->(node => $input_node,
3360 type => 'datetime:bad timezone minute',
3361 level => $self->{level}->{must})
3362 if $8 > 59;
3363 }
3364 } else { ## hh:mm
3365 if (length $1 != 2 or length $9 != 2) {
3366 $self->{onerror}->(node => $input_node,
3367 type => qq'dateortime:syntax error',
3368 level => $self->{level}->{must});
3369 }
3370
3371 ($hour, $minute, $second) = ($1, $9, $10);
3372 }
3373
3374 $self->{onerror}->(node => $input_node, type => 'datetime:bad hour',
3375 level => $self->{level}->{must}) if $hour > 23;
3376 $self->{onerror}->(node => $input_node, type => 'datetime:bad minute',
3377 level => $self->{level}->{must}) if $minute > 59;
3378
3379 if (defined $second) { ## s
3380 ## NOTE: Integer part of second don't have to have length of two.
3381
3382 if (substr ($second, 0, 1) eq '.') {
3383 $self->{onerror}->(node => $input_node,
3384 type => 'dateortime:syntax error',
3385 level => $self->{level}->{must});
3386 }
3387
3388 $self->{onerror}->(node => $input_node, type => 'datetime:bad second',
3389 level => $self->{level}->{must}) if $second >= 60;
3390 }
3391 } else {
3392 $self->{onerror}->(node => $input_node,
3393 type => 'dateortime:syntax error',
3394 level => $self->{level}->{must});
3395 }
3396
3397 $HTMLPhrasingContentChecker{check_end}->(@_);
3398 },
3399 };
3400
3401 $Element->{$HTML_NS}->{meter} = { ## TODO: "The recommended way of giving the value is to include it as contents of the element"
3402 ## TODO: value inequalities (HTML5 revision 1463)
3403 ## TODO: content checking
3404 ## TODO: content or value must contain number (rev 2053)
3405 status => FEATURE_HTML5_DEFAULT,
3406 %HTMLPhrasingContentChecker,
3407 check_attrs => $GetHTMLAttrsChecker->({
3408 value => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
3409 min => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
3410 low => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
3411 high => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
3412 max => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
3413 optimum => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
3414 }, {
3415 %HTMLAttrStatus,
3416 high => FEATURE_HTML5_DEFAULT,
3417 low => FEATURE_HTML5_DEFAULT,
3418 max => FEATURE_HTML5_DEFAULT,
3419 min => FEATURE_HTML5_DEFAULT,
3420 optimum => FEATURE_HTML5_DEFAULT,
3421 value => FEATURE_HTML5_DEFAULT,
3422 }),
3423 };
3424
3425 $Element->{$HTML_NS}->{progress} = { ## TODO: recommended to use content
3426 status => FEATURE_HTML5_DEFAULT,
3427 %HTMLPhrasingContentChecker,
3428 check_attrs => $GetHTMLAttrsChecker->({
3429 value => $GetHTMLFloatingPointNumberAttrChecker->(sub { shift >= 0 }),
3430 max => $GetHTMLFloatingPointNumberAttrChecker->(sub { shift > 0 }),
3431 }, {
3432 %HTMLAttrStatus,
3433 max => FEATURE_HTML5_DEFAULT,
3434 value => FEATURE_HTML5_DEFAULT,
3435 }),
3436 };
3437
3438 $Element->{$HTML_NS}->{code} = {
3439 %HTMLPhrasingContentChecker,
3440 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
3441 check_attrs => $GetHTMLAttrsChecker->({}, {
3442 %HTMLAttrStatus,
3443 %HTMLM12NXHTML2CommonAttrStatus,
3444 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
3445 sdaform => FEATURE_HTML20_RFC,
3446 }),
3447 };
3448
3449 $Element->{$HTML_NS}->{var} = {
3450 %HTMLPhrasingContentChecker,
3451 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
3452 check_attrs => $GetHTMLAttrsChecker->({}, {
3453 %HTMLAttrStatus,
3454 %HTMLM12NXHTML2CommonAttrStatus,
3455 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
3456 sdaform => FEATURE_HTML20_RFC,
3457 }),
3458 };
3459
3460 $Element->{$HTML_NS}->{samp} = {
3461 %HTMLPhrasingContentChecker,
3462 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
3463 check_attrs => $GetHTMLAttrsChecker->({}, {
3464 %HTMLAttrStatus,
3465 %HTMLM12NXHTML2CommonAttrStatus,
3466 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
3467 sdaform => FEATURE_HTML20_RFC,
3468 }),
3469 };
3470
3471 $Element->{$HTML_NS}->{kbd} = {
3472 %HTMLPhrasingContentChecker,
3473 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
3474 check_attrs => $GetHTMLAttrsChecker->({}, {
3475 %HTMLAttrStatus,
3476 %HTMLM12NXHTML2CommonAttrStatus,
3477 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
3478 sdaform => FEATURE_HTML20_RFC,
3479 }),
3480 };
3481
3482 $Element->{$HTML_NS}->{sub} = {
3483 %HTMLPhrasingContentChecker,
3484 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
3485 check_attrs => $GetHTMLAttrsChecker->({}, {
3486 %HTMLAttrStatus,
3487 %HTMLM12NXHTML2CommonAttrStatus,
3488 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
3489 sdapref => FEATURE_HTML2X_RFC,
3490 }),
3491 };
3492
3493 $Element->{$HTML_NS}->{sup} = $Element->{$HTML_NS}->{sub};
3494
3495 $Element->{$HTML_NS}->{span} = {
3496 %HTMLPhrasingContentChecker,
3497 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
3498 check_attrs => $GetHTMLAttrsChecker->({}, {
3499 %HTMLAttrStatus,
3500 %HTMLM12NXHTML2CommonAttrStatus,
3501 datafld => FEATURE_HTML4_REC_RESERVED,
3502 dataformatas => FEATURE_HTML4_REC_RESERVED,
3503 datasrc => FEATURE_HTML4_REC_RESERVED,
3504 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
3505 sdaform => FEATURE_HTML2X_RFC,
3506 }),
3507 };
3508
3509 $Element->{$HTML_NS}->{i} = {
3510 %HTMLPhrasingContentChecker,
3511 status => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
3512 check_attrs => $GetHTMLAttrsChecker->({}, {
3513 %HTMLAttrStatus,
3514 %HTMLM12NCommonAttrStatus,
3515 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
3516 sdaform => FEATURE_HTML20_RFC,
3517 }),
3518 };
3519
3520 $Element->{$HTML_NS}->{b} = $Element->{$HTML_NS}->{i};
3521
3522 $Element->{$HTML_NS}->{tt} = {
3523 %HTMLPhrasingContentChecker,
3524 status => FEATURE_M12N10_REC,
3525 check_attrs => $GetHTMLAttrsChecker->({}, {
3526 %HTMLAttrStatus,
3527 %HTMLM12NCommonAttrStatus,
3528 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
3529 sdaform => FEATURE_HTML20_RFC,
3530 }),
3531 };
3532
3533 $Element->{$HTML_NS}->{s} = {
3534 %HTMLPhrasingContentChecker,
3535 status => FEATURE_M12N10_REC_DEPRECATED,
3536 check_attrs => $GetHTMLAttrsChecker->({}, {
3537 %HTMLAttrStatus,
3538 %HTMLM12NCommonAttrStatus,
3539 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
3540 }),
3541 };
3542
3543 $Element->{$HTML_NS}->{strike} = $Element->{$HTML_NS}->{s};
3544
3545 $Element->{$HTML_NS}->{u} = $Element->{$HTML_NS}->{s};
3546
3547 $Element->{$HTML_NS}->{bdo} = {
3548 %HTMLPhrasingContentChecker,
3549 status => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
3550 check_attrs => sub {
3551 my ($self, $item, $element_state) = @_;
3552 $GetHTMLAttrsChecker->({}, {
3553 %HTMLAttrStatus,
3554 class => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
3555 dir => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
3556 id => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
3557 style => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
3558 title => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
3559 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
3560 sdapref => FEATURE_HTML2X_RFC,
3561 sdasuff => FEATURE_HTML2X_RFC,
3562 })->($self, $item, $element_state);
3563 unless ($item->{node}->has_attribute_ns (undef, 'dir')) {
3564 $self->{onerror}->(node => $item->{node},
3565 type => 'attribute missing',
3566 text => 'dir',
3567 level => $self->{level}->{must});
3568 }
3569 },
3570 ## ISSUE: The spec does not directly say that |dir| is a enumerated attr.
3571 };
3572
3573 $Element->{$HTML_NS}->{ruby} = {
3574 %HTMLPhrasingContentChecker,
3575 status => FEATURE_HTML5_DEFAULT | FEATURE_RUBY_REC,
3576 check_attrs => $GetHTMLAttrsChecker->({}, {
3577 %HTMLAttrStatus,
3578 %HTMLM12NXHTML2CommonAttrStatus, # XHTML 1.1 & XHTML 2.0 & XHTML+RDFa 1.0
3579 lang => FEATURE_HTML5_DEFAULT,
3580 }),
3581 check_start => sub {
3582 my ($self, $item, $element_state) = @_;
3583
3584 $element_state->{phase} = 'before-rb';
3585 #$element_state->{has_sig}
3586
3587 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
3588 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
3589 },
3590 ## NOTE: (phrasing, (rt | (rp, rt, rp)))+
3591 check_child_element => sub {
3592 my ($self, $item, $child_el, $child_nsuri, $child_ln,
3593 $child_is_transparent, $element_state) = @_;
3594 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
3595 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
3596 $self->{onerror}->(node => $child_el,
3597 type => 'element not allowed:minus',
3598 level => $self->{level}->{must});
3599 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
3600 #
3601 } elsif ($element_state->{phase} eq 'before-rb') {
3602 if ($HTMLPhrasingContent->{$child_nsuri}->{$child_ln}) {
3603 $element_state->{phase} = 'in-rb';
3604 } elsif ($child_ln eq 'rt' and $child_nsuri eq $HTML_NS) {
3605 $self->{onerror}->(node => $child_el,
3606 level => $self->{level}->{should},
3607 type => 'no significant content before');
3608 $element_state->{phase} = 'after-rt';
3609 } elsif ($child_ln eq 'rp' and $child_nsuri eq $HTML_NS) {
3610 $self->{onerror}->(node => $child_el,
3611 level => $self->{level}->{should},
3612 type => 'no significant content before');
3613 $element_state->{phase} = 'after-rp1';
3614 } else {
3615 $self->{onerror}->(node => $child_el,
3616 type => 'element not allowed:ruby base',
3617 level => $self->{level}->{must});
3618 $element_state->{phase} = 'in-rb';
3619 }
3620 } elsif ($element_state->{phase} eq 'in-rb') {
3621 if ($HTMLPhrasingContent->{$child_nsuri}->{$child_ln}) {
3622 #$element_state->{phase} = 'in-rb';
3623 } elsif ($child_ln eq 'rt' and $child_nsuri eq $HTML_NS) {
3624 unless ($element_state->{has_significant}) {
3625 $self->{onerror}->(node => $child_el,
3626 level => $self->{level}->{should},
3627 type => 'no significant content before');
3628 }
3629 $element_state->{phase} = 'after-rt';
3630 } elsif ($child_ln eq 'rp' and $child_nsuri eq $HTML_NS) {
3631 unless ($element_state->{has_significant}) {
3632 $self->{onerror}->(node => $child_el,
3633 level => $self->{level}->{should},
3634 type => 'no significant content before');
3635 }
3636 $element_state->{phase} = 'after-rp1';
3637 } else {
3638 $self->{onerror}->(node => $child_el,
3639 type => 'element not allowed:ruby base',
3640 level => $self->{level}->{must});
3641 #$element_state->{phase} = 'in-rb';
3642 }
3643 } elsif ($element_state->{phase} eq 'after-rt') {
3644 if ($HTMLPhrasingContent->{$child_nsuri}->{$child_ln}) {
3645 if ($element_state->{has_significant}) {
3646 $element_state->{has_sig} = 1;
3647 delete $element_state->{has_significant};
3648 }
3649 $element_state->{phase} = 'in-rb';
3650 } elsif ($child_ln eq 'rp' and $child_nsuri eq $HTML_NS) {
3651 $self->{onerror}->(node => $child_el,
3652 level => $self->{level}->{should},
3653 type => 'no significant content before');
3654 $element_state->{phase} = 'after-rp1';
3655 } elsif ($child_ln eq 'rt' and $child_nsuri eq $HTML_NS) {
3656 $self->{onerror}->(node => $child_el,
3657 level => $self->{level}->{should},
3658 type => 'no significant content before');
3659 #$element_state->{phase} = 'after-rt';
3660 } else {
3661 $self->{onerror}->(node => $child_el,
3662 type => 'element not allowed:ruby base',
3663 level => $self->{level}->{must});
3664 if ($element_state->{has_significant}) {
3665 $element_state->{has_sig} = 1;
3666 delete $element_state->{has_significant};
3667 }
3668 $element_state->{phase} = 'in-rb';
3669 }
3670 } elsif ($element_state->{phase} eq 'after-rp1') {
3671 if ($child_ln eq 'rt' and $child_nsuri eq $HTML_NS) {
3672 $element_state->{phase} = 'after-rp-rt';
3673 } elsif ($child_ln eq 'rp' and $child_nsuri eq $HTML_NS) {
3674 $self->{onerror}->(node => $child_el,
3675 type => 'ps element missing',
3676 text => 'rt',
3677 level => $self->{level}->{must});
3678 $element_state->{phase} = 'after-rp2';
3679 } else {
3680 $self->{onerror}->(node => $child_el,
3681 type => 'ps element missing',
3682 text => 'rt',
3683 level => $self->{level}->{must});
3684 $self->{onerror}->(node => $child_el,
3685 type => 'ps element missing',
3686 text => 'rp',
3687 level => $self->{level}->{must});
3688 unless ($HTMLPhrasingContent->{$child_nsuri}->{$child_ln}) {
3689 $self->{onerror}->(node => $child_el,
3690 type => 'element not allowed:ruby base',
3691 level => $self->{level}->{must});
3692 }
3693 if ($element_state->{has_significant}) {
3694 $element_state->{has_sig} = 1;
3695 delete $element_state->{has_significant};
3696 }
3697 $element_state->{phase} = 'in-rb';
3698 }
3699 } elsif ($element_state->{phase} eq 'after-rp-rt') {
3700 if ($child_ln eq 'rp' and $child_nsuri eq $HTML_NS) {
3701 $element_state->{phase} = 'after-rp2';
3702 } elsif ($child_ln eq 'rt' and $child_nsuri eq $HTML_NS) {
3703 $self->{onerror}->(node => $child_el,
3704 type => 'ps element missing',
3705 text => 'rp',
3706 level => $self->{level}->{must});
3707 $self->{onerror}->(node => $child_el,
3708 level => $self->{level}->{should},
3709 type => 'no significant content before');
3710 $element_state->{phase} = 'after-rt';
3711 } else {
3712 $self->{onerror}->(node => $child_el,
3713 type => 'ps element missing',
3714 text => 'rp',
3715 level => $self->{level}->{must});
3716 unless ($HTMLPhrasingContent->{$child_nsuri}->{$child_ln}) {
3717 $self->{onerror}->(node => $child_el,
3718 type => 'element not allowed:ruby base',
3719 level => $self->{level}->{must});
3720 }
3721 if ($element_state->{has_significant}) {
3722 $element_state->{has_sig} = 1;
3723 delete $element_state->{has_significant};
3724 }
3725 $element_state->{phase} = 'in-rb';
3726 }
3727 } elsif ($element_state->{phase} eq 'after-rp2') {
3728 if ($HTMLPhrasingContent->{$child_nsuri}->{$child_ln}) {
3729 if ($element_state->{has_significant}) {
3730 $element_state->{has_sig} = 1;
3731 delete $element_state->{has_significant};
3732 }
3733 $element_state->{phase} = 'in-rb';
3734 } elsif ($child_ln eq 'rt' and $child_nsuri eq $HTML_NS) {
3735 $self->{onerror}->(node => $child_el,
3736 level => $self->{level}->{should},
3737 type => 'no significant content before');
3738 $element_state->{phase} = 'after-rt';
3739 } elsif ($child_ln eq 'rp' and $child_nsuri eq $HTML_NS) {
3740 $self->{onerror}->(node => $child_el,
3741 level => $self->{level}->{should},
3742 type => 'no significant content before');
3743 $element_state->{phase} = 'after-rp1';
3744 } else {
3745 $self->{onerror}->(node => $child_el,
3746 type => 'element not allowed:ruby base',
3747 level => $self->{level}->{must});
3748 if ($element_state->{has_significant}) {
3749 $element_state->{has_sig} = 1;
3750 delete $element_state->{has_significant};
3751 }
3752 $element_state->{phase} = 'in-rb';
3753 }
3754 } else {
3755 die "check_child_element: Bad |ruby| phase: $element_state->{phase}";
3756 }
3757 },
3758 check_child_text => sub {
3759 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
3760 if ($has_significant) {
3761 if ($element_state->{phase} eq 'before-rb') {
3762 $element_state->{phase} = 'in-rb';
3763 } elsif ($element_state->{phase} eq 'in-rb') {
3764 #
3765 } elsif ($element_state->{phase} eq 'after-rt' or
3766 $element_state->{phase} eq 'after-rp2') {
3767 $element_state->{phase} = 'in-rb';
3768 } elsif ($element_state->{phase} eq 'after-rp1') {
3769 $self->{onerror}->(node => $child_node,
3770 type => 'ps element missing',
3771 text => 'rt',
3772 level => $self->{level}->{must});
3773 $self->{onerror}->(node => $child_node,
3774 type => 'ps element missing',
3775 text => 'rp',
3776 level => $self->{level}->{must});
3777 $element_state->{phase} = 'in-rb';
3778 } elsif ($element_state->{phase} eq 'after-rp-rt') {
3779 $self->{onerror}->(node => $child_node,
3780 type => 'ps element missing',
3781 text => 'rp',
3782 level => $self->{level}->{must});
3783 $element_state->{phase} = 'in-rb';
3784 } else {
3785 die "check_child_text: Bad |ruby| phase: $element_state->{phase}";
3786 }
3787 }
3788 },
3789 check_end => sub {
3790 my ($self, $item, $element_state) = @_;
3791 $self->_remove_minus_elements ($element_state);
3792
3793 if ($element_state->{phase} eq 'before-rb') {
3794 $self->{onerror}->(node => $item->{node},
3795 level => $self->{level}->{should},
3796 type => 'no significant content');
3797 $self->{onerror}->(node => $item->{node},
3798 type => 'element missing',
3799 text => 'rt',
3800 level => $self->{level}->{must});
3801 } elsif ($element_state->{phase} eq 'in-rb') {
3802 unless ($element_state->{has_significant}) {
3803 $self->{onerror}->(node => $item->{node},
3804 level => $self->{level}->{should},
3805 type => 'no significant content at the end');
3806 }
3807 $self->{onerror}->(node => $item->{node},
3808 type => 'element missing',
3809 text => 'rt',
3810 level => $self->{level}->{must});
3811 } elsif ($element_state->{phase} eq 'after-rt' or
3812 $element_state->{phase} eq 'after-rp2') {
3813 #
3814 } elsif ($element_state->{phase} eq 'after-rp1') {
3815 $self->{onerror}->(node => $item->{node},
3816 type => 'element missing',
3817 text => 'rt',
3818 level => $self->{level}->{must});
3819 $self->{onerror}->(node => $item->{node},
3820 type => 'element missing',
3821 text => 'rp',
3822 level => $self->{level}->{must});
3823 } elsif ($element_state->{phase} eq 'after-rp-rt') {
3824 $self->{onerror}->(node => $item->{node},
3825 type => 'element missing',
3826 text => 'rp',
3827 level => $self->{level}->{must});
3828 } else {
3829 die "check_child_text: Bad |ruby| phase: $element_state->{phase}";
3830 }
3831
3832 ## NOTE: A modified version of |check_end| of %AnyChecker.
3833 if ($element_state->{has_significant} or $element_state->{has_sig}) {
3834 $item->{real_parent_state}->{has_significant} = 1;
3835 }
3836 },
3837 };
3838
3839 $Element->{$HTML_NS}->{rt} = {
3840 %HTMLPhrasingContentChecker,
3841 status => FEATURE_HTML5_DEFAULT | FEATURE_RUBY_REC,
3842 check_attrs => $GetHTMLAttrsChecker->({}, {
3843 %HTMLAttrStatus,
3844 %HTMLM12NXHTML2CommonAttrStatus,
3845 lang => FEATURE_HTML5_DEFAULT,
3846 }),
3847 };
3848
3849 $Element->{$HTML_NS}->{rp} = {
3850 %HTMLTextChecker,
3851 status => FEATURE_HTML5_DEFAULT | FEATURE_RUBY_REC,
3852 check_attrs => $GetHTMLAttrsChecker->({}, {
3853 %HTMLAttrStatus,
3854 %HTMLM12NXHTML2CommonAttrStatus,
3855 lang => FEATURE_HTML5_DEFAULT,
3856 }),
3857 check_start => sub {
3858 my ($self, $item, $element_state) = @_;
3859 $element_state->{text} = '';
3860
3861 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
3862 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
3863 },
3864 check_child_text => sub {
3865 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
3866 if ($has_significant) {
3867 $element_state->{text} .= $child_node->data;
3868 ## NOTE: |<rp> <!---->(</rp>| is allowed.
3869 }
3870 },
3871 check_end => sub {
3872 my ($self, $item, $element_state) = @_;
3873
3874 my $p_class = ($item->{parent_state} and
3875 $item->{parent_state}->{phase} and
3876 $item->{parent_state}->{phase} eq 'after-rp2')
3877 ? qr/\p{Pe}/ : qr/\p{Ps}/;
3878 if ($element_state->{text} =~ /\A$p_class\z/) {
3879 #=~ /\A[\x09\x0A\x0C\x0D\x20]*${p_class}[\x09\x0A\x0C\x0D\x20]*\z/) {
3880 #
3881 } else {
3882 $self->{onerror}->(node => $item->{node},
3883 type => 'rp:syntax error',
3884 level => $self->{level}->{must});
3885 }
3886
3887 $HTMLTextChecker{check_end}->(@_);
3888 },
3889 };
3890
3891 =pod
3892
3893 ## TODO:
3894
3895 +
3896 + <p>Partly because of the confusion described above, authors are
3897 + strongly recommended to always mark up all paragraphs with the
3898 + <code>p</code> element, and to not have any <code>ins</code> or
3899 + <code>del</code> elements that cross across any <span
3900 + title="paragraph">implied paragraphs</span>.</p>
3901 +
3902 (An informative note)
3903
3904 <p><code>ins</code> elements should not cross <span
3905 + title="paragraph">implied paragraph</span> boundaries.</p>
3906 (normative)
3907
3908 + <p><code>del</code> elements should not cross <span
3909 + title="paragraph">implied paragraph</span> boundaries.</p>
3910 (normative)
3911
3912 =cut
3913
3914 $Element->{$HTML_NS}->{ins} = {
3915 %HTMLTransparentChecker,
3916 status => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
3917 check_attrs => $GetHTMLAttrsChecker->({
3918 cite => $HTMLURIAttrChecker,
3919 datetime => $HTMLDatetimeAttrChecker,
3920 }, {
3921 %HTMLAttrStatus,
3922 %HTMLM12NCommonAttrStatus,
3923 cite => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
3924 datetime => FEATURE_HTML5_FD | FEATURE_M12N10_REC,
3925 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
3926 }),
3927 check_start => sub {
3928 my ($self, $item, $element_state) = @_;
3929
3930 $element_state->{uri_info}->{cite}->{type}->{cite} = 1;
3931 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
3932 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
3933 },
3934 };
3935
3936 $Element->{$HTML_NS}->{del} = {
3937 %HTMLTransparentChecker,
3938 status => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
3939 check_attrs => $GetHTMLAttrsChecker->({
3940 cite => $HTMLURIAttrChecker,
3941 datetime => $HTMLDatetimeAttrChecker,
3942 }, {
3943 %HTMLAttrStatus,
3944 %HTMLM12NCommonAttrStatus,
3945 cite => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
3946 datetime => FEATURE_HTML5_FD | FEATURE_M12N10_REC,
3947 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
3948 }),
3949 check_end => sub {
3950 my ($self, $item, $element_state) = @_;
3951 if ($element_state->{has_significant}) {
3952 ## NOTE: Significantness flag does not propagate.
3953 } elsif ($item->{transparent}) {
3954 #
3955 } else {
3956 $self->{onerror}->(node => $item->{node},
3957 level => $self->{level}->{should},
3958 type => 'no significant content');
3959 }
3960 },
3961 check_start => sub {
3962 my ($self, $item, $element_state) = @_;
3963
3964 $element_state->{uri_info}->{cite}->{type}->{cite} = 1;
3965 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
3966 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
3967 },
3968 };
3969
3970 $Element->{$HTML_NS}->{figure} = {
3971 %HTMLFlowContentChecker,
3972 status => FEATURE_HTML5_FD,
3973 ## NOTE: legend, Flow | Flow, legend?
3974 check_child_element => sub {
3975 my ($self, $item, $child_el, $child_nsuri, $child_ln,
3976 $child_is_transparent, $element_state) = @_;
3977 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
3978 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
3979 $self->{onerror}->(node => $child_el,
3980 type => 'element not allowed:minus',
3981 level => $self->{level}->{must});
3982 $element_state->{has_non_legend} = 1;
3983 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
3984 #
3985 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'legend') {
3986 if ($element_state->{has_legend_at_first}) {
3987 $self->{onerror}->(node => $child_el,
3988 type => 'element not allowed:figure legend',
3989 level => $self->{level}->{must});
3990 } elsif ($element_state->{has_legend}) {
3991 $self->{onerror}->(node => $element_state->{has_legend},
3992 type => 'element not allowed:figure legend',
3993 level => $self->{level}->{must});
3994 $element_state->{has_legend} = $child_el;
3995 } elsif ($element_state->{has_non_legend}) {
3996 $element_state->{has_legend} = $child_el;
3997 } else {
3998 $element_state->{has_legend_at_first} = 1;
3999 }
4000 delete $element_state->{has_non_legend};
4001 } else {
4002 $HTMLFlowContentChecker{check_child_element}->(@_);
4003 $element_state->{has_non_legend} = 1 unless $child_is_transparent;
4004 }
4005 },
4006 check_child_text => sub {
4007 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
4008 if ($has_significant) {
4009 $element_state->{has_non_legend} = 1;
4010 }
4011 },
4012 check_end => sub {
4013 my ($self, $item, $element_state) = @_;
4014
4015 if ($element_state->{has_legend_at_first}) {
4016 #
4017 } elsif ($element_state->{has_legend}) {
4018 if ($element_state->{has_non_legend}) {
4019 $self->{onerror}->(node => $element_state->{has_legend},
4020 type => 'element not allowed:figure legend',
4021 level => $self->{level}->{must});
4022 }
4023 }
4024
4025 $HTMLFlowContentChecker{check_end}->(@_);
4026 ## ISSUE: |<figure><legend>aa</legend></figure>| should be an error?
4027 },
4028 };
4029 ## TODO: Test for <nest/> in <figure/>
4030
4031 my $AttrCheckerNotImplemented = sub {
4032 my ($self, $attr) = @_;
4033 $self->{onerror}->(node => $attr,
4034 type => 'unknown attribute',
4035 level => $self->{level}->{uncertain});
4036 };
4037
4038 $Element->{$HTML_NS}->{img} = {
4039 %HTMLEmptyChecker,
4040 status => FEATURE_HTML5_WD | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
4041 check_attrs => sub {
4042 my ($self, $item, $element_state) = @_;
4043 $GetHTMLAttrsChecker->({
4044 align => $GetHTMLEnumeratedAttrChecker->({
4045 bottom => 1, middle => 1, top => 1, left => 1, right => 1,
4046 }),
4047 alt => sub { }, ## NOTE: No syntactical requirement
4048 border => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }),
4049 src => $HTMLURIAttrChecker,
4050 usemap => $HTMLUsemapAttrChecker,
4051 hspace => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }),
4052 ismap => sub {
4053 my ($self, $attr, $parent_item) = @_;
4054 if (not $self->{flag}->{in_a_href}) {
4055 $self->{onerror}->(node => $attr,
4056 type => 'attribute not allowed:ismap',
4057 level => $self->{level}->{must});
4058 }
4059 $GetHTMLBooleanAttrChecker->('ismap')->($self, $attr, $parent_item);
4060 },
4061 longdesc => $HTMLURIAttrChecker,
4062 ## TODO: HTML4 |name|
4063 height => $AttrCheckerNotImplemented, ## TODO: spec does not define yet
4064 vspace => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }),
4065 width => $AttrCheckerNotImplemented, ## TODO: spec does not define yet
4066 }, {
4067 %HTMLAttrStatus,
4068 %HTMLM12NXHTML2CommonAttrStatus,
4069 align => FEATURE_M12N10_REC_DEPRECATED,
4070 alt => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
4071 border => FEATURE_M12N10_REC_DEPRECATED,
4072 height => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
4073 hspace => FEATURE_M12N10_REC_DEPRECATED,
4074 ismap => FEATURE_HTML5_WD | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
4075 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
4076 longdesc => FEATURE_M12N10_REC,
4077 name => FEATURE_M12N10_REC_DEPRECATED,
4078 sdapref => FEATURE_HTML20_RFC,
4079 src => FEATURE_HTML5_WD | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
4080 usemap => FEATURE_HTML5_WD | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
4081 vspace => FEATURE_M12N10_REC_DEPRECATED,
4082 width => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
4083 })->($self, $item, $element_state);
4084 unless ($item->{node}->has_attribute_ns (undef, 'alt')) {
4085 $self->{onerror}->(node => $item->{node},
4086 type => 'attribute missing',
4087 text => 'alt',
4088 level => $self->{level}->{should});
4089 ## TODO: ...
4090 }
4091 unless ($item->{node}->has_attribute_ns (undef, 'src')) {
4092 $self->{onerror}->(node => $item->{node},
4093 type => 'attribute missing',
4094 text => 'src',
4095 level => $self->{level}->{must});
4096 }
4097
4098 ## TODO: external resource check
4099
4100 $element_state->{uri_info}->{src}->{type}->{embedded} = 1;
4101 $element_state->{uri_info}->{lowsrc}->{type}->{embedded} = 1;
4102 $element_state->{uri_info}->{dynsrc}->{type}->{embedded} = 1;
4103 $element_state->{uri_info}->{longdesc}->{type}->{cite} = 1;
4104 },
4105 };
4106
4107 $Element->{$HTML_NS}->{iframe} = {
4108 %HTMLTextChecker,
4109 status => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
4110 ## NOTE: Not part of M12N10 Strict
4111 check_attrs => $GetHTMLAttrsChecker->({
4112 height => $AttrCheckerNotImplemented, ## TODO: spec does not define yet
4113 name => $HTMLBrowsingContextNameAttrChecker,
4114 sandbox => $GetHTMLUnorderedUniqueSetOfSpaceSeparatedTokensAttrChecker->({
4115 'allow-same-origin' => 1, 'allow-forms' => 1, 'allow-scripts' => 1,
4116 }),
4117 seemless => $GetHTMLBooleanAttrChecker->('seemless'),
4118 src => $HTMLURIAttrChecker,
4119 width => $AttrCheckerNotImplemented, ## TODO: spec does not define yet
4120 }, {
4121 %HTMLAttrStatus,
4122 %HTMLM12NCommonAttrStatus,
4123 align => FEATURE_XHTML10_REC,
4124 class => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4125 frameborder => FEATURE_M12N10_REC,
4126 height => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4127 id => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4128 longdesc => FEATURE_M12N10_REC,
4129 marginheight => FEATURE_M12N10_REC,
4130 marginwidth => FEATURE_M12N10_REC,
4131 #name => FEATURE_HTML5_WD | FEATURE_M12N10_REC_DEPRECATED,
4132 name => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
4133 sandbox => FEATURE_HTML5_WD,
4134 scrolling => FEATURE_M12N10_REC,
4135 seemless => FEATURE_HTML5_WD,
4136 src => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
4137 title => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4138 width => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4139 }),
4140 check_start => sub {
4141 my ($self, $item, $element_state) = @_;
4142
4143 $element_state->{uri_info}->{src}->{type}->{embedded} = 1;
4144 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
4145 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
4146 },
4147 };
4148
4149 $Element->{$HTML_NS}->{embed} = {
4150 %HTMLEmptyChecker,
4151 status => FEATURE_HTML5_WD,
4152 check_attrs => sub {
4153 my ($self, $item, $element_state) = @_;
4154 my $has_src;
4155 for my $attr (@{$item->{node}->attributes}) {
4156 my $attr_ns = $attr->namespace_uri;
4157 $attr_ns = '' unless defined $attr_ns;
4158 my $attr_ln = $attr->manakai_local_name;
4159 my $checker;
4160
4161 my $status = {
4162 %HTMLAttrStatus,
4163 height => FEATURE_HTML5_DEFAULT,
4164 src => FEATURE_HTML5_WD,
4165 type => FEATURE_HTML5_WD,
4166 width => FEATURE_HTML5_DEFAULT,
4167 }->{$attr_ln};
4168
4169 if ($attr_ns eq '') {
4170 if ($attr_ln eq 'src') {
4171 $checker = $HTMLURIAttrChecker;
4172 $has_src = 1;
4173 } elsif ($attr_ln eq 'type') {
4174 $checker = $HTMLIMTAttrChecker;
4175 } elsif ($attr_ln eq 'width' or $attr_ln eq 'height') {
4176 $checker = $AttrCheckerNotImplemented; ## TODO: because spec does not define them yet.
4177 } elsif ($attr_ln =~ /^data-\p{InXMLNCNameChar10}+\z/ and
4178 $attr_ln !~ /[A-Z]/) {
4179 $checker = $HTMLDatasetAttrChecker;
4180 $status = $HTMLDatasetAttrStatus;
4181 } elsif ($attr_ln !~ /^[Xx][Mm][Ll]/ and
4182 $attr_ln !~ /[A-Z]/ and
4183 $attr_ln =~ /\A\p{InXML_NCNameStartChar10}\p{InXMLNCNameChar10}*\z/) {
4184 $checker = $HTMLAttrChecker->{$attr_ln}
4185 || sub { }; ## NOTE: Any local attribute is ok.
4186 $status = FEATURE_HTML5_WD | FEATURE_ALLOWED;
4187 } else {
4188 $checker = $HTMLAttrChecker->{$attr_ln};
4189 }
4190 }
4191 $checker ||= $AttrChecker->{$attr_ns}->{$attr_ln}
4192 || $AttrChecker->{$attr_ns}->{''};
4193 $status ||= $AttrStatus->{$attr_ns}->{$attr_ln}
4194 || $AttrStatus->{$attr_ns}->{''};
4195 $status = FEATURE_ALLOWED if not defined $status and length $attr_ns;
4196
4197 if ($checker) {
4198 $checker->($self, $attr, $item, $element_state);
4199 } elsif ($attr_ns eq '' and not $status) {
4200 #
4201 } else {
4202 $self->{onerror}->(node => $attr,
4203 type => 'unknown attribute',
4204 level => $self->{level}->{uncertain});
4205 ## ISSUE: No conformance createria for global attributes in the spec
4206 }
4207
4208 $self->_attr_status_info ($attr, $status);
4209 }
4210
4211 unless ($has_src) {
4212 $self->{onerror}->(node => $item->{node},
4213 type => 'attribute missing',
4214 text => 'src',
4215 level => $self->{level}->{info});
4216 ## NOTE: <embed> without src="" is allowed since revision 1929.
4217 ## We issues an informational message since <embed> w/o src=""
4218 ## is likely an authoring error.
4219 }
4220
4221 ## TODO: external resource check
4222
4223 $element_state->{uri_info}->{src}->{type}->{embedded} = 1;
4224 },
4225 };
4226
4227 ## TODO:
4228 ## {applet} FEATURE_M12N10_REC_DEPRECATED
4229 ## class, id, title, alt, archive, code, codebase, height, object, width name style,hspace,vspace(xhtml10)
4230
4231 $Element->{$HTML_NS}->{object} = {
4232 %HTMLTransparentChecker,
4233 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
4234 check_attrs => sub {
4235 my ($self, $item, $element_state) = @_;
4236 $GetHTMLAttrsChecker->({
4237 align => $GetHTMLEnumeratedAttrChecker->({
4238 bottom => 1, middle => 1, top => 1, left => 1, right => 1,
4239 }),
4240 archive => $HTMLSpaceURIsAttrChecker,
4241 ## TODO: Relative to @codebase
4242 border => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }),
4243 classid => $HTMLURIAttrChecker,
4244 codebase => $HTMLURIAttrChecker,
4245 codetype => $HTMLIMTAttrChecker,
4246 ## TODO: "RECOMMENDED when |classid| is specified" [HTML4]
4247 data => $HTMLURIAttrChecker,
4248 declare => $GetHTMLBooleanAttrChecker->('declare'),
4249 ## NOTE: "The object MUST be instantiated by a subsequent OBJECT ..."
4250 ## [HTML4] but we don't know how to test this.
4251 hspace => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }),
4252 name => $HTMLBrowsingContextNameAttrChecker,
4253 standby => sub {}, ## NOTE: %Text; in HTML4
4254 type => $HTMLIMTAttrChecker,
4255 usemap => $HTMLUsemapAttrChecker,
4256 height => $AttrCheckerNotImplemented, ## TODO: spec does not define yet
4257 vspace => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }),
4258 width => $AttrCheckerNotImplemented, ## TODO: spec does not define yet
4259 }, {
4260 %HTMLAttrStatus,
4261 %HTMLM12NXHTML2CommonAttrStatus,
4262 align => FEATURE_XHTML10_REC,
4263 archive => FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
4264 border => FEATURE_XHTML10_REC,
4265 classid => FEATURE_M12N10_REC,
4266 codebase => FEATURE_M12N10_REC,
4267 codetype => FEATURE_M12N10_REC,
4268 'content-length' => FEATURE_XHTML2_ED,
4269 data => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4270 datafld => FEATURE_HTML4_REC_RESERVED,
4271 dataformatas => FEATURE_HTML4_REC_RESERVED,
4272 datasrc => FEATURE_HTML4_REC_RESERVED,
4273 declare => FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
4274 height => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4275 hspace => FEATURE_XHTML10_REC,
4276 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
4277 name => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4278 standby => FEATURE_M12N10_REC,
4279 tabindex => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4280 type => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4281 usemap => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4282 vspace => FEATURE_XHTML10_REC,
4283 width => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4284 })->($self, $item, $element_state);
4285 unless ($item->{node}->has_attribute_ns (undef, 'data')) {
4286 unless ($item->{node}->has_attribute_ns (undef, 'type')) {
4287 $self->{onerror}->(node => $item->{node},
4288 type => 'attribute missing:data|type',
4289 level => $self->{level}->{must});
4290 }
4291 }
4292
4293 $element_state->{uri_info}->{data}->{type}->{embedded} = 1;
4294 $element_state->{uri_info}->{classid}->{type}->{embedded} = 1;
4295 $element_state->{uri_info}->{codebase}->{type}->{base} = 1;
4296 ## TODO: archive
4297 $element_state->{uri_info}->{datasrc}->{type}->{resource} = 1;
4298 },
4299 ## NOTE: param*, transparent (Flow)
4300 check_child_element => sub {
4301 my ($self, $item, $child_el, $child_nsuri, $child_ln,
4302 $child_is_transparent, $element_state) = @_;
4303 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
4304 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
4305 $self->{onerror}->(node => $child_el,
4306 type => 'element not allowed:minus',
4307 level => $self->{level}->{must});
4308 $element_state->{has_non_legend} = 1;
4309 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
4310 #
4311 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'param') {
4312 if ($element_state->{has_non_param}) {
4313 $self->{onerror}->(node => $child_el,
4314 type => 'element not allowed:flow',
4315 level => $self->{level}->{must});
4316 }
4317 } else {
4318 $HTMLFlowContentChecker{check_child_element}->(@_);
4319 $element_state->{has_non_param} = 1;
4320 }
4321 },
4322 check_child_text => sub {
4323 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
4324 if ($has_significant) {
4325 $element_state->{has_non_param} = 1;
4326 }
4327 },
4328 check_end => sub {
4329 my ($self, $item, $element_state) = @_;
4330 if ($element_state->{has_significant}) {
4331 $item->{real_parent_state}->{has_significant} = 1;
4332 } elsif ($item->{node}->manakai_parent_element) {
4333 ## NOTE: Transparent.
4334 } else {
4335 $self->{onerror}->(node => $item->{node},
4336 level => $self->{level}->{should},
4337 type => 'no significant content');
4338 }
4339 },
4340 ## TODO: Tests for <nest/> in <object/>
4341 };
4342 ## ISSUE: Is |<menu><object data><li>aa</li></object></menu>| conforming?
4343 ## What about |<section><object data><style scoped></style>x</object></section>|?
4344 ## |<section><ins></ins><object data><style scoped></style>x</object></section>|?
4345
4346 $Element->{$HTML_NS}->{param} = {
4347 %HTMLEmptyChecker,
4348 status => FEATURE_HTML5_LC | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
4349 check_attrs => sub {
4350 my ($self, $item, $element_state) = @_;
4351 $GetHTMLAttrsChecker->({
4352 name => sub { },
4353 type => $HTMLIMTAttrChecker,
4354 value => sub { },
4355 valuetype => $GetHTMLEnumeratedAttrChecker->({
4356 data => 1, ref => 1, object => 1,
4357 }),
4358 }, {
4359 %HTMLAttrStatus,
4360 href => FEATURE_RDFA_PR,
4361 id => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4362 name => FEATURE_HTML5_LC | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
4363 type => FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
4364 value => FEATURE_HTML5_LC | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
4365 valuetype => FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
4366 })->(@_);
4367 unless ($item->{node}->has_attribute_ns (undef, 'name')) {
4368 $self->{onerror}->(node => $item->{node},
4369 type => 'attribute missing',
4370 text => 'name',
4371 level => $self->{level}->{must});
4372 }
4373 unless ($item->{node}->has_attribute_ns (undef, 'value')) {
4374 $self->{onerror}->(node => $item->{node},
4375 type => 'attribute missing',
4376 text => 'value',
4377 level => $self->{level}->{must});
4378 }
4379 },
4380 };
4381
4382 $Element->{$HTML_NS}->{video} = {
4383 %HTMLTransparentChecker,
4384 status => FEATURE_HTML5_LC,
4385 check_attrs => $GetHTMLAttrsChecker->({
4386 src => $HTMLURIAttrChecker,
4387 ## TODO: start, loopstart, loopend, end
4388 ## ISSUE: they MUST be "value time offset"s. Value?
4389 ## ISSUE: playcount has no conformance creteria
4390 autoplay => $GetHTMLBooleanAttrChecker->('autoplay'),
4391 controls => $GetHTMLBooleanAttrChecker->('controls'),
4392 poster => $HTMLURIAttrChecker,
4393 height => $AttrCheckerNotImplemented, ## TODO: spec does not define yet
4394 width => $AttrCheckerNotImplemented, ## TODO: spec does not define yet
4395 }, {
4396 %HTMLAttrStatus,
4397 autoplay => FEATURE_HTML5_LC,
4398 controls => FEATURE_HTML5_LC,
4399 end => FEATURE_HTML5_LC,
4400 height => FEATURE_HTML5_LC,
4401 loopend => FEATURE_HTML5_LC,
4402 loopstart => FEATURE_HTML5_LC,
4403 playcount => FEATURE_HTML5_LC,
4404 poster => FEATURE_HTML5_LC,
4405 src => FEATURE_HTML5_LC,
4406 start => FEATURE_HTML5_LC,
4407 width => FEATURE_HTML5_LC,
4408 }),
4409 check_start => sub {
4410 my ($self, $item, $element_state) = @_;
4411 $element_state->{allow_source}
4412 = not $item->{node}->has_attribute_ns (undef, 'src');
4413 $element_state->{has_source} ||= $element_state->{allow_source} * -1;
4414 ## NOTE: It might be set true by |check_element|.
4415
4416 $element_state->{uri_info}->{src}->{type}->{embedded} = 1;
4417 $element_state->{uri_info}->{poster}->{type}->{embedded} = 1;
4418 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
4419 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
4420 },
4421 check_child_element => sub {
4422 my ($self, $item, $child_el, $child_nsuri, $child_ln,
4423 $child_is_transparent, $element_state) = @_;
4424 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
4425 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
4426 $self->{onerror}->(node => $child_el,
4427 type => 'element not allowed:minus',
4428 level => $self->{level}->{must});
4429 delete $element_state->{allow_source};
4430 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
4431 #
4432 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'source') {
4433 unless ($element_state->{allow_source}) {
4434 $self->{onerror}->(node => $child_el,
4435 type => 'element not allowed:flow',
4436 level => $self->{level}->{must});
4437 }
4438 $element_state->{has_source} = 1;
4439 } else {
4440 delete $element_state->{allow_source};
4441 $HTMLFlowContentChecker{check_child_element}->(@_);
4442 }
4443 },
4444 check_child_text => sub {
4445 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
4446 if ($has_significant) {
4447 delete $element_state->{allow_source};
4448 }
4449 $HTMLFlowContentChecker{check_child_text}->(@_);
4450 },
4451 check_end => sub {
4452 my ($self, $item, $element_state) = @_;
4453 if ($element_state->{has_source} == -1) {
4454 $self->{onerror}->(node => $item->{node},
4455 type => 'child element missing',
4456 text => 'source',
4457 level => $self->{level}->{must});
4458 }
4459
4460 $Element->{$HTML_NS}->{object}->{check_end}->(@_);
4461 },
4462 };
4463
4464 $Element->{$HTML_NS}->{audio} = {
4465 %{$Element->{$HTML_NS}->{video}},
4466 status => FEATURE_HTML5_LC,
4467 check_attrs => $GetHTMLAttrsChecker->({
4468 src => $HTMLURIAttrChecker,
4469 ## TODO: start, loopstart, loopend, end
4470 ## ISSUE: they MUST be "value time offset"s. Value?
4471 ## ISSUE: playcount has no conformance creteria
4472 autoplay => $GetHTMLBooleanAttrChecker->('autoplay'),
4473 controls => $GetHTMLBooleanAttrChecker->('controls'),
4474 }, {
4475 %HTMLAttrStatus,
4476 autoplay => FEATURE_HTML5_LC,
4477 controls => FEATURE_HTML5_LC,
4478 end => FEATURE_HTML5_LC,
4479 loopend => FEATURE_HTML5_LC,
4480 loopstart => FEATURE_HTML5_LC,
4481 playcount => FEATURE_HTML5_LC,
4482 src => FEATURE_HTML5_LC,
4483 start => FEATURE_HTML5_LC,
4484 }),
4485 };
4486
4487 $Element->{$HTML_NS}->{source} = {
4488 %HTMLEmptyChecker,
4489 status => FEATURE_HTML5_DEFAULT,
4490 check_attrs => sub {
4491 my ($self, $item, $element_state) = @_;
4492 $GetHTMLAttrsChecker->({
4493 media => $HTMLMQAttrChecker,
4494 pixelratio => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
4495 src => $HTMLURIAttrChecker, ## ISSUE: Negative or zero pixelratio=""
4496 type => $HTMLIMTAttrChecker,
4497 }, {
4498 %HTMLAttrStatus,
4499 media => FEATURE_HTML5_DEFAULT,
4500 pixelratio => FEATURE_HTML5_DEFAULT,
4501 src => FEATURE_HTML5_DEFAULT,
4502 type => FEATURE_HTML5_DEFAULT,
4503 })->(@_);
4504 unless ($item->{node}->has_attribute_ns (undef, 'src')) {
4505 $self->{onerror}->(node => $item->{node},
4506 type => 'attribute missing',
4507 text => 'src',
4508 level => $self->{level}->{must});
4509 }
4510
4511 $element_state->{uri_info}->{src}->{type}->{embedded} = 1;
4512 },
4513 };
4514
4515 $Element->{$HTML_NS}->{canvas} = {
4516 %HTMLTransparentChecker,
4517 status => FEATURE_HTML5_COMPLETE,
4518 check_attrs => $GetHTMLAttrsChecker->({
4519 height => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }),
4520 width => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }),
4521 }, {
4522 %HTMLAttrStatus,
4523 height => FEATURE_HTML5_COMPLETE,
4524 width => FEATURE_HTML5_COMPLETE,
4525 }),
4526 };
4527
4528 $Element->{$HTML_NS}->{map} = {
4529 %HTMLFlowContentChecker,
4530 status => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4531 check_attrs => sub {
4532 my ($self, $item, $element_state) = @_;
4533 my $has_name;
4534 $GetHTMLAttrsChecker->({
4535 name => sub {
4536 my ($self, $attr) = @_;
4537 my $value = $attr->value;
4538 if (length $value) {
4539 ## NOTE: Duplication is not non-conforming.
4540 ## NOTE: Space characters are not non-conforming.
4541 #
4542 } else {
4543 $self->{onerror}->(node => $attr,
4544 type => 'empty attribute value',
4545 level => $self->{level}->{must});
4546 }
4547 $self->{map}->{$value} ||= $attr;
4548 $has_name = [$value, $attr];
4549 },
4550 }, {
4551 %HTMLAttrStatus,
4552 class => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4553 dir => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4554 id => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4555 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
4556 #name => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC_DEPRECATED,
4557 name => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4558 onclick => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4559 ondblclick => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4560 onmousedown => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4561 onmouseup => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4562 onmouseover => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4563 onmousemove => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4564 onmouseout => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4565 onkeypress => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4566 onkeydown => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4567 onkeyup => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4568 title => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4569 })->(@_);
4570
4571 if ($has_name) {
4572 my $id = $item->{node}->get_attribute_ns (undef, 'id');
4573 if (defined $id and $has_name->[0] ne $id) {
4574 $self->{onerror}->(node => $item->{node}->get_attribute_node ('id'),
4575 type => 'id ne name',
4576 level => $self->{level}->{must});
4577 }
4578 } else {
4579 $self->{onerror}->(node => $item->{node},
4580 type => 'attribute missing',
4581 text => 'name',
4582 level => $self->{level}->{must});
4583 }
4584 },
4585 check_start => sub {
4586 my ($self, $item, $element_state) = @_;
4587 $element_state->{in_map_original} = $self->{flag}->{in_map};
4588 $self->{flag}->{in_map} = [@{$self->{flag}->{in_map} or []}, {}];
4589 ## NOTE: |{in_map}| is a reference to the array which contains
4590 ## hash references. Hashes are corresponding to the opening
4591 ## |map| elements and each of them contains the key-value
4592 ## pairs corresponding to the absolute URLs for the processed
4593 ## |area| elements in the |map| element corresponding to the
4594 ## hash. The key represents the resource (## TODO: use
4595 ## absolute URL), while the value represents whether there is
4596 ## an |area| element whose |alt| attribute is specified to a
4597 ## non-empty value. If there IS such an |area| element for
4598 ## the resource specified by the key, then the value is set to
4599 ## zero (|0|). Otherwise, if there is no such an |area|
4600 ## element but there is any |area| element with the empty
4601 ## |alt=""| attribute, then the value contains an array
4602 ## reference that contains all of such |area| elements.
4603
4604 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
4605 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
4606 },
4607 check_end => sub {
4608 my ($self, $item, $element_state) = @_;
4609
4610 for (keys %{$self->{flag}->{in_map}->[-1]}) {
4611 my $nodes = $self->{flag}->{in_map}->[-1]->{$_};
4612 next unless $nodes;
4613 for (@$nodes) {
4614 $self->{onerror}->(type => 'empty area alt',
4615 node => $_,
4616 level => $self->{level}->{html5_no_may});
4617 }
4618 }
4619
4620 $self->{flag}->{in_map} = $element_state->{in_map_original};
4621
4622 $HTMLFlowContentChecker{check_end}->(@_);
4623 },
4624 };
4625
4626 $Element->{$HTML_NS}->{area} = {
4627 %HTMLEmptyChecker,
4628 status => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4629 check_attrs => sub {
4630 my ($self, $item, $element_state) = @_;
4631 my %attr;
4632 my $coords;
4633 for my $attr (@{$item->{node}->attributes}) {
4634 my $attr_ns = $attr->namespace_uri;
4635 $attr_ns = '' unless defined $attr_ns;
4636 my $attr_ln = $attr->manakai_local_name;
4637 my $checker;
4638 my $status;
4639 if ($attr_ns eq '') {
4640 $status = {
4641 %HTMLAttrStatus,
4642 %HTMLM12NCommonAttrStatus,
4643 accesskey => FEATURE_M12N10_REC,
4644 alt => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4645 coords => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4646 href => FEATURE_HTML5_DEFAULT | FEATURE_RDFA_CR | FEATURE_M12N10_REC,
4647 hreflang => FEATURE_HTML5_DEFAULT,
4648 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
4649 media => FEATURE_HTML5_DEFAULT,
4650 nohref => FEATURE_M12N10_REC,
4651 onblur => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4652 onfocus => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4653 ping => FEATURE_HTML5_DEFAULT,
4654 rel => FEATURE_HTML5_DEFAULT | FEATURE_RDFA_CR,
4655 shape => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4656 tabindex => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4657 target => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4658 type => FEATURE_HTML5_DEFAULT,
4659 }->{$attr_ln};
4660
4661 $checker = {
4662 accesskey => $HTMLAccesskeyAttrChecker,
4663 alt => sub { },
4664 ## NOTE: |alt| value has no conformance creteria.
4665 shape => $GetHTMLEnumeratedAttrChecker->({
4666 circ => -1, circle => 1,
4667 default => 1,
4668 poly => 1, polygon => -1,
4669 rect => 1, rectangle => -1,
4670 }),
4671 coords => sub {
4672 my ($self, $attr) = @_;
4673 my $value = $attr->value;
4674 if ($value =~ /\A-?[0-9]+(?>,-?[0-9]+)*\z/) {
4675 $coords = [split /,/, $value];
4676 } else {
4677 $self->{onerror}->(node => $attr,
4678 type => 'coords:syntax error',
4679 level => $self->{level}->{must});
4680 }
4681 },
4682 nohref => $GetHTMLBooleanAttrChecker->('nohref'),
4683 target => $HTMLTargetAttrChecker,
4684 href => $HTMLURIAttrChecker,
4685 ping => $HTMLSpaceURIsAttrChecker,
4686 rel => sub { $HTMLLinkTypesAttrChecker->(1, $item, @_) },
4687 media => $HTMLMQAttrChecker,
4688 hreflang => $HTMLLanguageTagAttrChecker,
4689 type => $HTMLIMTAttrChecker,
4690 }->{$attr_ln};
4691 if ($checker) {
4692 $attr{$attr_ln} = $attr;
4693 } elsif ($attr_ln =~ /^data-\p{InXMLNCNameChar10}+\z/ and
4694 $attr_ln !~ /[A-Z]/) {
4695 $checker = $HTMLDatasetAttrChecker;
4696 $status = $HTMLDatasetAttrStatus;
4697 } else {
4698 $checker = $HTMLAttrChecker->{$attr_ln};
4699 }
4700 }
4701 $checker ||= $AttrChecker->{$attr_ns}->{$attr_ln}
4702 || $AttrChecker->{$attr_ns}->{''};
4703 $status ||= $AttrStatus->{$attr_ns}->{$attr_ln}
4704 || $AttrStatus->{$attr_ns}->{''};
4705 $status = FEATURE_ALLOWED if not defined $status and length $attr_ns;
4706
4707 if ($checker) {
4708 $checker->($self, $attr, $item, $element_state) if ref $checker;
4709 } elsif ($attr_ns eq '' and not $status) {
4710 #
4711 } else {
4712 $self->{onerror}->(node => $attr,
4713 type => 'unknown attribute',
4714 level => $self->{level}->{uncertain});
4715 ## ISSUE: No comformance createria for unknown attributes in the spec
4716 }
4717
4718 $self->_attr_status_info ($attr, $status);
4719 }
4720
4721 if (defined $attr{href}) {
4722 $self->{has_hyperlink_element} = 1;
4723 if (defined $attr{alt}) {
4724 my $url = $attr{href}->value; ## TODO: resolve
4725 if (length $attr{alt}->value) {
4726 for (@{$self->{flag}->{in_map} or []}) {
4727 $_->{$url} = 0;
4728 }
4729 } else {
4730 ## NOTE: Empty |alt=""|. If there is another |area| element
4731 ## with the same |href=""| and that |area| elemnet's
4732 ## |alt=""| attribute is not an empty string, then this
4733 ## is conforming.
4734 for (@{$self->{flag}->{in_map} or []}) {
4735 push @{$_->{$url} ||= []}, $attr{alt}
4736 unless exists $_->{$url} and not $_->{$url};
4737 }
4738 }
4739 } else {
4740 $self->{onerror}->(node => $item->{node},
4741 type => 'attribute missing',
4742 text => 'alt',
4743 level => $self->{level}->{must});
4744 }
4745 } else {
4746 for (qw/target ping rel media hreflang type alt/) {
4747 if (defined $attr{$_}) {
4748 $self->{onerror}->(node => $attr{$_},
4749 type => 'attribute not allowed',
4750 level => $self->{level}->{must});
4751 }
4752 }
4753 }
4754
4755 my $shape = 'rectangle';
4756 if (defined $attr{shape}) {
4757 $shape = {
4758 circ => 'circle', circle => 'circle',
4759 default => 'default',
4760 poly => 'polygon', polygon => 'polygon',
4761 rect => 'rectangle', rectangle => 'rectangle',
4762 }->{lc $attr{shape}->value} || 'rectangle';
4763 ## TODO: ASCII lowercase?
4764 }
4765
4766 if ($shape eq 'circle') {
4767 if (defined $attr{coords}) {
4768 if (defined $coords) {
4769 if (@$coords == 3) {
4770 if ($coords->[2] < 0) {
4771 $self->{onerror}->(node => $attr{coords},
4772 type => 'coords:out of range',
4773 index => 2,
4774 value => $coords->[2],
4775 level => $self->{level}->{must});
4776 }
4777 } else {
4778 $self->{onerror}->(node => $attr{coords},
4779 type => 'coords:number not 3',
4780 text => 0+@$coords,
4781 level => $self->{level}->{must});
4782 }
4783 } else {
4784 ## NOTE: A syntax error has been reported.
4785 }
4786 } else {
4787 $self->{onerror}->(node => $item->{node},
4788 type => 'attribute missing',
4789 text => 'coords',
4790 level => $self->{level}->{must});
4791 }
4792 } elsif ($shape eq 'default') {
4793 if (defined $attr{coords}) {
4794 $self->{onerror}->(node => $attr{coords},
4795 type => 'attribute not allowed',
4796 level => $self->{level}->{must});
4797 }
4798 } elsif ($shape eq 'polygon') {
4799 if (defined $attr{coords}) {
4800 if (defined $coords) {
4801 if (@$coords >= 6) {
4802 unless (@$coords % 2 == 0) {
4803 $self->{onerror}->(node => $attr{coords},
4804 type => 'coords:number not even',
4805 text => 0+@$coords,
4806 level => $self->{level}->{must});
4807 }
4808 } else {
4809 $self->{onerror}->(node => $attr{coords},
4810 type => 'coords:number lt 6',
4811 text => 0+@$coords,
4812 level => $self->{level}->{must});
4813 }
4814 } else {
4815 ## NOTE: A syntax error has been reported.
4816 }
4817 } else {
4818 $self->{onerror}->(node => $item->{node},
4819 type => 'attribute missing',
4820 text => 'coords',
4821 level => $self->{level}->{must});
4822 }
4823 } elsif ($shape eq 'rectangle') {
4824 if (defined $attr{coords}) {
4825 if (defined $coords) {
4826 if (@$coords == 4) {
4827 unless ($coords->[0] < $coords->[2]) {
4828 $self->{onerror}->(node => $attr{coords},
4829 type => 'coords:out of range',
4830 index => 0,
4831 value => $coords->[0],
4832 level => $self->{level}->{must});
4833 }
4834 unless ($coords->[1] < $coords->[3]) {
4835 $self->{onerror}->(node => $attr{coords},
4836 type => 'coords:out of range',
4837 index => 1,
4838 value => $coords->[1],
4839 level => $self->{level}->{must});
4840 }
4841 } else {
4842 $self->{onerror}->(node => $attr{coords},
4843 type => 'coords:number not 4',
4844 text => 0+@$coords,
4845 level => $self->{level}->{must});
4846 }
4847 } else {
4848 ## NOTE: A syntax error has been reported.
4849 }
4850 } else {
4851 $self->{onerror}->(node => $item->{node},
4852 type => 'attribute missing',
4853 text => 'coords',
4854 level => $self->{level}->{must});
4855 }
4856 }
4857
4858 $element_state->{uri_info}->{href}->{type}->{hyperlink} = 1;
4859 },
4860 check_start => sub {
4861 my ($self, $item, $element_state) = @_;
4862 unless ($self->{flag}->{in_map} or
4863 not $item->{node}->manakai_parent_element) {
4864 $self->{onerror}->(node => $item->{node},
4865 type => 'element not allowed:area',
4866 level => $self->{level}->{must});
4867 }
4868
4869 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
4870 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
4871 },
4872 };
4873
4874 $Element->{$HTML_NS}->{table} = {
4875 %HTMLChecker,
4876 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
4877 check_attrs => $GetHTMLAttrsChecker->({
4878 cellpadding => $HTMLLengthAttrChecker,
4879 cellspacing => $HTMLLengthAttrChecker,
4880 frame => $GetHTMLEnumeratedAttrChecker->({
4881 void => 1, above => 1, below => 1, hsides => 1, vsides => 1,
4882 lhs => 1, rhs => 1, box => 1, border => 1,
4883 }),
4884 rules => $GetHTMLEnumeratedAttrChecker->({
4885 none => 1, groups => 1, rows => 1, cols => 1, all => 1,
4886 }),
4887 summary => sub {}, ## NOTE: %Text; in HTML4.
4888 width => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }), ## %Pixels;
4889 }, {
4890 %HTMLAttrStatus,
4891 %HTMLM12NXHTML2CommonAttrStatus,
4892 align => FEATURE_M12N10_REC_DEPRECATED,
4893 bgcolor => FEATURE_M12N10_REC_DEPRECATED,
4894 border => FEATURE_M12N10_REC,
4895 cellpadding => FEATURE_M12N10_REC,
4896 cellspacing => FEATURE_M12N10_REC,
4897 cols => FEATURE_RFC1942,
4898 datafld => FEATURE_HTML4_REC_RESERVED,
4899 dataformatas => FEATURE_HTML4_REC_RESERVED,
4900 datapagesize => FEATURE_M12N10_REC,
4901 datasrc => FEATURE_HTML4_REC_RESERVED,
4902 frame => FEATURE_M12N10_REC,
4903 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
4904 rules => FEATURE_M12N10_REC,
4905 summary => FEATURE_M12N10_REC,
4906 width => FEATURE_M12N10_REC,
4907 }),
4908 check_start => sub {
4909 my ($self, $item, $element_state) = @_;
4910 $element_state->{phase} = 'before caption';
4911
4912 $element_state->{uri_info}->{datasrc}->{type}->{resource} = 1;
4913 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
4914 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
4915 },
4916 check_child_element => sub {
4917 my ($self, $item, $child_el, $child_nsuri, $child_ln,
4918 $child_is_transparent, $element_state) = @_;
4919 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
4920 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
4921 $self->{onerror}->(node => $child_el,
4922 type => 'element not allowed:minus',
4923 level => $self->{level}->{must});
4924 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
4925 #
4926 } elsif ($element_state->{phase} eq 'in tbodys') {
4927 if ($child_nsuri eq $HTML_NS and $child_ln eq 'tbody') {
4928 #$element_state->{phase} = 'in tbodys';
4929 } elsif (not $element_state->{has_tfoot} and
4930 $child_nsuri eq $HTML_NS and $child_ln eq 'tfoot') {
4931 $element_state->{phase} = 'after tfoot';
4932 $element_state->{has_tfoot} = 1;
4933 } else {
4934 $self->{onerror}->(node => $child_el, type => 'element not allowed',
4935 level => $self->{level}->{must});
4936 }
4937 } elsif ($element_state->{phase} eq 'in trs') {
4938 if ($child_nsuri eq $HTML_NS and $child_ln eq 'tr') {
4939 #$element_state->{phase} = 'in trs';
4940 } elsif (not $element_state->{has_tfoot} and
4941 $child_nsuri eq $HTML_NS and $child_ln eq 'tfoot') {
4942 $element_state->{phase} = 'after tfoot';
4943 $element_state->{has_tfoot} = 1;
4944 } else {
4945 $self->{onerror}->(node => $child_el, type => 'element not allowed',
4946 level => $self->{level}->{must});
4947 }
4948 } elsif ($element_state->{phase} eq 'after thead') {
4949 if ($child_nsuri eq $HTML_NS and $child_ln eq 'tbody') {
4950 $element_state->{phase} = 'in tbodys';
4951 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'tr') {
4952 $element_state->{phase} = 'in trs';
4953 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'tfoot') {
4954 $element_state->{phase} = 'in tbodys';
4955 $element_state->{has_tfoot} = 1;
4956 } else {
4957 $self->{onerror}->(node => $child_el, type => 'element not allowed',
4958 level => $self->{level}->{must});
4959 }
4960 } elsif ($element_state->{phase} eq 'in colgroup') {
4961 if ($child_nsuri eq $HTML_NS and $child_ln eq 'colgroup') {
4962 $element_state->{phase} = 'in colgroup';
4963 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'thead') {
4964 $element_state->{phase} = 'after thead';
4965 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'tbody') {
4966 $element_state->{phase} = 'in tbodys';
4967 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'tr') {
4968 $element_state->{phase} = 'in trs';
4969 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'tfoot') {
4970 $element_state->{phase} = 'in tbodys';
4971 $element_state->{has_tfoot} = 1;
4972 } else {
4973 $self->{onerror}->(node => $child_el, type => 'element not allowed',
4974 level => $self->{level}->{must});
4975 }
4976 } elsif ($element_state->{phase} eq 'before caption') {
4977 if ($child_nsuri eq $HTML_NS and $child_ln eq 'caption') {
4978 $element_state->{phase} = 'in colgroup';
4979 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'colgroup') {
4980 $element_state->{phase} = 'in colgroup';
4981 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'thead') {
4982 $element_state->{phase} = 'after thead';
4983 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'tbody') {
4984 $element_state->{phase} = 'in tbodys';
4985 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'tr') {
4986 $element_state->{phase} = 'in trs';
4987 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'tfoot') {
4988 $element_state->{phase} = 'in tbodys';
4989 $element_state->{has_tfoot} = 1;
4990 } else {
4991 $self->{onerror}->(node => $child_el, type => 'element not allowed',
4992 level => $self->{level}->{must});
4993 }
4994 } elsif ($element_state->{phase} eq 'after tfoot') {
4995 $self->{onerror}->(node => $child_el, type => 'element not allowed',
4996 level => $self->{level}->{must});
4997 } else {
4998 die "check_child_element: Bad |table| phase: $element_state->{phase}";
4999 }
5000 },
5001 check_child_text => sub {
5002 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
5003 if ($has_significant) {
5004 $self->{onerror}->(node => $child_node, type => 'character not allowed',
5005 level => $self->{level}->{must});
5006 }
5007 },
5008 check_end => sub {
5009 my ($self, $item, $element_state) = @_;
5010
5011 ## Table model errors
5012 require Whatpm::HTMLTable;
5013 my $table = Whatpm::HTMLTable->form_table ($item->{node}, sub {
5014 $self->{onerror}->(@_);
5015 }, $self->{level});
5016 Whatpm::HTMLTable->assign_header
5017 ($table, $self->{onerror}, $self->{level});
5018 push @{$self->{return}->{table}}, $table;
5019
5020 $HTMLChecker{check_end}->(@_);
5021 },
5022 };
5023
5024 $Element->{$HTML_NS}->{caption} = {
5025 %HTMLPhrasingContentChecker,
5026 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5027 check_attrs => $GetHTMLAttrsChecker->({
5028 align => $GetHTMLEnumeratedAttrChecker->({
5029 top => 1, bottom => 1, left => 1, right => 1,
5030 }),
5031 }, {
5032 %HTMLAttrStatus,
5033 %HTMLM12NXHTML2CommonAttrStatus,
5034 align => FEATURE_M12N10_REC_DEPRECATED,
5035 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
5036 }),
5037 };
5038
5039 my %cellalign = (
5040 ## HTML4 %cellhalign;
5041 align => $GetHTMLEnumeratedAttrChecker->({
5042 left => 1, center => 1, right => 1, justify => 1, char => 1,
5043 }),
5044 char => sub {
5045 my ($self, $attr) = @_;
5046
5047 ## NOTE: "character" or |%Character;| in HTML4.
5048
5049 my $value = $attr->value;
5050 if (length $value != 1) {
5051 $self->{onerror}->(node => $attr, type => 'char:syntax error',
5052 level => $self->{level}->{html4_fact});
5053 }
5054 },
5055 charoff => $HTMLLengthAttrChecker,
5056
5057 ## HTML4 %cellvalign;
5058 valign => $GetHTMLEnumeratedAttrChecker->({
5059 top => 1, middle => 1, bottom => 1, baseline => 1,
5060 }),
5061 );
5062
5063 $Element->{$HTML_NS}->{colgroup} = {
5064 %HTMLEmptyChecker,
5065 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5066 check_attrs => $GetHTMLAttrsChecker->({
5067 %cellalign,
5068 span => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
5069 ## NOTE: Defined only if "the |colgroup| element contains no |col| elements"
5070 ## TODO: "attribute not supported" if |col|.
5071 ## ISSUE: MUST NOT if any |col|?
5072 ## ISSUE: MUST NOT for |<colgroup span="1"><any><col/></any></colgroup>| (though non-conforming)?
5073 }, {
5074 %HTMLAttrStatus,
5075 %HTMLM12NXHTML2CommonAttrStatus,
5076 align => FEATURE_M12N10_REC,
5077 char => FEATURE_M12N10_REC,
5078 charoff => FEATURE_M12N10_REC,
5079 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
5080 span => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5081 valign => FEATURE_M12N10_REC,
5082 width => FEATURE_M12N10_REC,
5083 }),
5084 check_child_element => sub {
5085 my ($self, $item, $child_el, $child_nsuri, $child_ln,
5086 $child_is_transparent, $element_state) = @_;
5087 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
5088 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
5089 $self->{onerror}->(node => $child_el,
5090 type => 'element not allowed:minus',
5091 level => $self->{level}->{must});
5092 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
5093 #
5094 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'col') {
5095 #
5096 } else {
5097 $self->{onerror}->(node => $child_el, type => 'element not allowed',
5098 level => $self->{level}->{must});
5099 }
5100 },
5101 check_child_text => sub {
5102 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
5103 if ($has_significant) {
5104 $self->{onerror}->(node => $child_node, type => 'character not allowed',
5105 level => $self->{level}->{must});
5106 }
5107 },
5108 };
5109
5110 $Element->{$HTML_NS}->{col} = {
5111 %HTMLEmptyChecker,
5112 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5113 check_attrs => $GetHTMLAttrsChecker->({
5114 %cellalign,
5115 span => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
5116 }, {
5117 %HTMLAttrStatus,
5118 %HTMLM12NXHTML2CommonAttrStatus,
5119 align => FEATURE_M12N10_REC,
5120 char => FEATURE_M12N10_REC,
5121 charoff => FEATURE_M12N10_REC,
5122 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
5123 span => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5124 valign => FEATURE_M12N10_REC,
5125 width => FEATURE_M12N10_REC,
5126 }),
5127 };
5128
5129 $Element->{$HTML_NS}->{tbody} = {
5130 %HTMLChecker,
5131 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5132 check_attrs => $GetHTMLAttrsChecker->({
5133 %cellalign,
5134 }, {
5135 %HTMLAttrStatus,
5136 %HTMLM12NXHTML2CommonAttrStatus,
5137 align => FEATURE_M12N10_REC,
5138 char => FEATURE_M12N10_REC,
5139 charoff => FEATURE_M12N10_REC,
5140 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
5141 valign => FEATURE_M12N10_REC,
5142 }),
5143 check_child_element => sub {
5144 my ($self, $item, $child_el, $child_nsuri, $child_ln,
5145 $child_is_transparent, $element_state) = @_;
5146 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
5147 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
5148 $self->{onerror}->(node => $child_el,
5149 type => 'element not allowed:minus',
5150 level => $self->{level}->{must});
5151 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
5152 #
5153 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'tr') {
5154 #
5155 } else {
5156 $self->{onerror}->(node => $child_el, type => 'element not allowed',
5157 level => $self->{level}->{must});
5158 }
5159 },
5160 check_child_text => sub {
5161 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
5162 if ($has_significant) {
5163 $self->{onerror}->(node => $child_node, type => 'character not allowed',
5164 level => $self->{level}->{must});
5165 }
5166 },
5167 };
5168
5169 $Element->{$HTML_NS}->{thead} = {
5170 %{$Element->{$HTML_NS}->{tbody}},
5171 };
5172
5173 $Element->{$HTML_NS}->{tfoot} = {
5174 %{$Element->{$HTML_NS}->{tbody}},
5175 };
5176
5177 $Element->{$HTML_NS}->{tr} = {
5178 %HTMLChecker,
5179 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5180 check_attrs => $GetHTMLAttrsChecker->({
5181 %cellalign,
5182 bgcolor => $HTMLColorAttrChecker,
5183 }, {
5184 %HTMLAttrStatus,
5185 %HTMLM12NXHTML2CommonAttrStatus,
5186 align => FEATURE_M12N10_REC,
5187 bgcolor => FEATURE_M12N10_REC_DEPRECATED,
5188 char => FEATURE_M12N10_REC,
5189 charoff => FEATURE_M12N10_REC,
5190 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
5191 valign => FEATURE_M12N10_REC,
5192 }),
5193 check_child_element => sub {
5194 my ($self, $item, $child_el, $child_nsuri, $child_ln,
5195 $child_is_transparent, $element_state) = @_;
5196 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
5197 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
5198 $self->{onerror}->(node => $child_el,
5199 type => 'element not allowed:minus',
5200 level => $self->{level}->{must});
5201 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
5202 #
5203 } elsif ($child_nsuri eq $HTML_NS and
5204 ($child_ln eq 'td' or $child_ln eq 'th')) {
5205 #
5206 } else {
5207 $self->{onerror}->(node => $child_el, type => 'element not allowed',
5208 level => $self->{level}->{must});
5209 }
5210 },
5211 check_child_text => sub {
5212 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
5213 if ($has_significant) {
5214 $self->{onerror}->(node => $child_node, type => 'character not allowed',
5215 level => $self->{level}->{must});
5216 }
5217 },
5218 };
5219
5220 $Element->{$HTML_NS}->{td} = {
5221 %HTMLFlowContentChecker,
5222 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5223 check_attrs => $GetHTMLAttrsChecker->({
5224 %cellalign,
5225 abbr => sub {}, ## NOTE: HTML4 %Text; and SHOULD be short.
5226 axis => sub {}, ## NOTE: HTML4 "cdata", comma-separated
5227 bgcolor => $HTMLColorAttrChecker,
5228 colspan => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
5229 headers => sub {
5230 ## NOTE: Will be checked by Whatpm::HTMLTable->assign_header.
5231 ## Though that method does not check the |headers| attribute of a
5232 ## |td| element if the element does not form a table, in that case
5233 ## the |td| element is non-conforming anyway.
5234 },
5235 nowrap => $GetHTMLBooleanAttrChecker->('nowrap'),
5236 rowspan => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
5237 scope => $GetHTMLEnumeratedAttrChecker
5238 ->({row => 1, col => 1, rowgroup => 1, colgroup => 1}),
5239 }, {
5240 %HTMLAttrStatus,
5241 %HTMLM12NXHTML2CommonAttrStatus,
5242 abbr => FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5243 align => FEATURE_M12N10_REC,
5244 axis => FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5245 bgcolor => FEATURE_M12N10_REC_DEPRECATED,
5246 char => FEATURE_M12N10_REC,
5247 charoff => FEATURE_M12N10_REC,
5248 colspan => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5249 headers => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5250 height => FEATURE_M12N10_REC_DEPRECATED,
5251 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
5252 nowrap => FEATURE_M12N10_REC_DEPRECATED,
5253 rowspan => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5254 scope => FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5255 valign => FEATURE_M12N10_REC,
5256 width => FEATURE_M12N10_REC_DEPRECATED,
5257 }),
5258 };
5259
5260 $Element->{$HTML_NS}->{th} = {
5261 %HTMLPhrasingContentChecker,
5262 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5263 check_attrs => $GetHTMLAttrsChecker->({
5264 %cellalign,
5265 abbr => sub {}, ## NOTE: HTML4 %Text; and SHOULD be short.
5266 axis => sub {}, ## NOTE: HTML4 "cdata", comma-separated
5267 bgcolor => $HTMLColorAttrChecker,
5268 colspan => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
5269 ## TODO: HTML4(?) |headers|
5270 nowrap => $GetHTMLBooleanAttrChecker->('nowrap'),
5271 rowspan => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
5272 scope => $GetHTMLEnumeratedAttrChecker
5273 ->({row => 1, col => 1, rowgroup => 1, colgroup => 1}),
5274 }, {
5275 %HTMLAttrStatus,
5276 %HTMLM12NXHTML2CommonAttrStatus,
5277 abbr => FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5278 align => FEATURE_M12N10_REC,
5279 axis => FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5280 bgcolor => FEATURE_M12N10_REC_DEPRECATED,
5281 char => FEATURE_M12N10_REC,
5282 charoff => FEATURE_M12N10_REC,
5283 colspan => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5284 headers => FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5285 height => FEATURE_M12N10_REC_DEPRECATED,
5286 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
5287 nowrap => FEATURE_M12N10_REC_DEPRECATED,
5288 rowspan => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5289 scope => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5290 valign => FEATURE_M12N10_REC,
5291 width => FEATURE_M12N10_REC_DEPRECATED,
5292 }),
5293 };
5294
5295 $Element->{$HTML_NS}->{form} = {
5296 %HTMLFlowContentChecker,
5297 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
5298 check_attrs => $GetHTMLAttrsChecker->({
5299 accept => $AttrCheckerNotImplemented, ## TODO: ContentTypes [WF2]
5300 'accept-charset' => $HTMLCharsetsAttrChecker,
5301 action => $HTMLURIAttrChecker, ## TODO: "User agent behavior for a value other than HTTP URI is undefined" [HTML4]
5302 data => $HTMLURIAttrChecker, ## TODO: MUST point ... [WF2]
5303 enctype => $HTMLIMTAttrChecker, ## TODO: "multipart/form-data" should be used when type=file is used [HTML4] ## TODO: MUST NOT parameter [WF2]
5304 method => $GetHTMLEnumeratedAttrChecker->({
5305 get => 1, post => 1, put => 1, delete => 1,
5306 }),
5307 ## NOTE: "get" SHOULD be used for idempotent submittion,
5308 ## "post" SHOULD be used otherwise [HTML4]. This cannot be tested.
5309 name => sub {
5310 my ($self, $attr) = @_;
5311
5312 my $value = $attr->value;
5313 if ($value eq '') {
5314 $self->{onerror}->(type => 'empty form name',
5315 node => $attr,
5316 level => $self->{level}->{must});
5317 } else {
5318 if ($self->{form}->{$value}) {
5319 $self->{onerror}->(type => 'duplicate form name',
5320 node => $attr,
5321 value => $value,
5322 level => $self->{level}->{must});
5323 } else {
5324 $self->{form}->{$value} = 1;
5325 }
5326 }
5327 },
5328 onformchange => $HTMLEventHandlerAttrChecker,
5329 onforminput => $HTMLEventHandlerAttrChecker,
5330 onreceived => $HTMLEventHandlerAttrChecker,
5331 replace => $GetHTMLEnumeratedAttrChecker->({document => 1, values => 1}),
5332 target => $HTMLTargetAttrChecker,
5333 ## TODO: Warn for combination whose behavior is not defined in HTML4/WF2.
5334 }, {
5335 %HTMLAttrStatus,
5336 %HTMLM12NCommonAttrStatus,
5337 accept => FEATURE_WF2X | FEATURE_M12N10_REC,
5338 'accept-charset' => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5339 action => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
5340 data => FEATURE_WF2,
5341 enctype => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
5342 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
5343 method => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
5344 #name => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC_DEPRECATED,
5345 name => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5346 onformchange => FEATURE_WF2_INFORMATIVE,
5347 onforminput => FEATURE_WF2_INFORMATIVE,
5348 onreceived => FEATURE_WF2,
5349 onreset => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5350 onsubmit => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5351 replace => FEATURE_WF2,
5352 sdapref => FEATURE_HTML20_RFC,
5353 sdasuff => FEATURE_HTML20_RFC,
5354 target => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5355 }),
5356 check_start => sub {
5357 my ($self, $item, $element_state) = @_;
5358 $self->_add_minus_elements ($element_state, {$HTML_NS => {form => 1}});
5359
5360 $element_state->{uri_info}->{action}->{type}->{action} = 1;
5361 $element_state->{uri_info}->{data}->{type}->{resource} = 1;
5362 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
5363 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
5364 $element_state->{id_type} = 'form';
5365 },
5366 check_end => sub {
5367 my ($self, $item, $element_state) = @_;
5368 $self->_remove_minus_elements ($element_state);
5369
5370 $HTMLFlowContentChecker{check_end}->(@_);
5371 },
5372 };
5373
5374 $Element->{$HTML_NS}->{fieldset} = {
5375 %HTMLFlowContentChecker,
5376 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
5377 check_attrs => $GetHTMLAttrsChecker->({
5378 disabled => $GetHTMLBooleanAttrChecker->('disabled'),
5379 form => $HTMLFormAttrChecker,
5380 ## TODO: name [HTML5]
5381 }, {
5382 %HTMLAttrStatus,
5383 %HTMLM12NCommonAttrStatus,
5384 disabled => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5385 form => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5386 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
5387 name => FEATURE_HTML5_DEFAULT,
5388 }),
5389 ## NOTE: legend, Flow
5390 check_child_element => sub {
5391 my ($self, $item, $child_el, $child_nsuri, $child_ln,
5392 $child_is_transparent, $element_state) = @_;
5393 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
5394 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
5395 $self->{onerror}->(node => $child_el,
5396 type => 'element not allowed:minus',
5397 level => $self->{level}->{must});
5398 $element_state->{has_non_legend} = 1;
5399 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
5400 #
5401 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'legend') {
5402 if ($element_state->{has_non_legend}) {
5403 $self->{onerror}->(node => $child_el,
5404 type => 'element not allowed:details legend',
5405 level => $self->{level}->{must});
5406 }
5407 $element_state->{has_legend} = 1;
5408 $element_state->{has_non_legend} = 1;
5409 } else {
5410 $HTMLFlowContentChecker{check_child_element}->(@_);
5411 $element_state->{has_non_legend} = 1 unless $child_is_transparent;
5412 ## TODO:
5413 ## |<details><object><legend>xx</legend></object>..</details>|
5414 ## should be an error, since |object| is allowed as flow,
5415 ## therefore |details| part of the content model does not match.
5416 }
5417 },
5418 check_child_text => sub {
5419 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
5420 if ($has_significant) {
5421 $element_state->{has_non_legend} = 1;
5422 }
5423 },
5424 check_end => sub {
5425 my ($self, $item, $element_state) = @_;
5426
5427 unless ($element_state->{has_legend}) {
5428 $self->{onerror}->(node => $item->{node},
5429 type => 'child element missing',
5430 text => 'legend',
5431 level => $self->{level}->{must});
5432 }
5433
5434 $HTMLFlowContentChecker{check_end}->(@_);
5435 ## ISSUE: |<details><legend>aa</legend></details>| error?
5436 },
5437 ## NOTE: This definition is partially reused by |details| element's
5438 ## checker.
5439 ## TODO: Tests for <nest/> in <fieldset>
5440 };
5441
5442 $Element->{$HTML_NS}->{input} = {
5443 %HTMLEmptyChecker,
5444 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
5445 check_attrs => sub {
5446 my ($self, $item, $element_state) = @_;
5447
5448 my $state = $item->{node}->get_attribute_ns (undef, 'type');
5449 $state = 'text' unless defined $state;
5450 $state =~ tr/A-Z/a-z/; ## ASCII case-insensitive
5451
5452 for my $attr (@{$item->{node}->attributes}) {
5453 my $attr_ns = $attr->namespace_uri;
5454 $attr_ns = '' unless defined $attr_ns;
5455 my $attr_ln = $attr->manakai_local_name;
5456 my $checker;
5457 my $status;
5458 if ($attr_ns eq '') {
5459 $status =
5460 {
5461 %HTMLAttrStatus,
5462 %HTMLM12NCommonAttrStatus,
5463 accept => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
5464 'accept-charset' => FEATURE_HTML2X_RFC,
5465 accesskey => FEATURE_M12N10_REC,
5466 action => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5467 align => FEATURE_M12N10_REC_DEPRECATED,
5468 alt => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5469 autocomplete => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5470 autofocus => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5471 checked => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5472 datafld => FEATURE_HTML4_REC_RESERVED,
5473 dataformatas => FEATURE_HTML4_REC_RESERVED,
5474 datasrc => FEATURE_HTML4_REC_RESERVED,
5475 disabled => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
5476 enctype => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5477 form => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5478 inputmode => FEATURE_HTML5_DROPPED | FEATURE_WF2X |
5479 FEATURE_XHTMLBASIC11_CR,
5480 ismap => FEATURE_M12N10_REC,
5481 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
5482 list => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5483 max => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5484 maxlength => FEATURE_HTML5_DEFAULT | FEATURE_WF2X |
5485 FEATURE_M12N10_REC,
5486 method => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5487 min => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5488 name => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5489 onblur => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5490 onchange => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5491 onfocus => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5492 onformchange => FEATURE_WF2_INFORMATIVE,
5493 onforminput => FEATURE_WF2_INFORMATIVE,
5494 oninput => FEATURE_WF2,
5495 oninvalid => FEATURE_WF2,
5496 onselect => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5497 pattern => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5498 readonly => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
5499 replace => FEATURE_WF2,
5500 required => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5501 sdapref => FEATURE_HTML20_RFC,
5502 size => FEATURE_HTML5_DEFAULT | FEATURE_WF2X_DEPRECATED |
5503 FEATURE_M12N10_REC,
5504 src => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5505 step => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5506 tabindex => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5507 target => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5508 template => FEATURE_HTML5_AT_RISK | FEATURE_WF2,
5509 type => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5510 usemap => FEATURE_HTML5_DROPPED | FEATURE_M12N10_REC,
5511 value => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5512 }->{$attr_ln};
5513
5514 $checker =
5515 {
5516 ## NOTE: Value of an empty string means that the attribute is only
5517 ## applicable for a specific set of states.
5518 accept => '',
5519 'accept-charset' => $HTMLCharsetsAttrChecker,
5520 ## NOTE: To which states it applies is not defined in RFC 2070.
5521 accesskey => '', ## NOTE: Not applied to |hidden| [WF2].
5522 action => '',
5523 align => '',
5524 alt => '',
5525 autocomplete => '',
5526 autofocus => $GetHTMLBooleanAttrChecker->('autofocus'),
5527 ## ISSUE: No HTML5 definition yet. ## TODO: Tests
5528 checked => '',
5529 disabled => $GetHTMLBooleanAttrChecker->('disabled'),
5530 ## ISSUE: No HTML5 definition yet. ## TODO: Tests
5531 enctype => '',
5532 form => $HTMLFormAttrChecker,
5533 inputmode => '',
5534 ismap => '', ## NOTE: "MUST" be type=image [HTML4]
5535 list => '',
5536 max => '',
5537 maxlength => '',
5538 method => '',
5539 min => '',
5540 name => sub {
5541 ## ISSUE: No HTML5 definition yet.
5542 ## TODO: tests
5543 },
5544 onformchange => $HTMLEventHandlerAttrChecker, # [WF2]
5545 onforminput => $HTMLEventHandlerAttrChecker, # [WF2]
5546 oninput => $HTMLEventHandlerAttrChecker, # [WF2]
5547 oninvalid => $HTMLEventHandlerAttrChecker, # [WF2]
5548 ## TODO: tests for four attributes above
5549 pattern => '',
5550 readonly => '',
5551 replace => '',
5552 required => '',
5553 size => '',
5554 src => '',
5555 step => '',
5556 target => '',
5557 type => $GetHTMLEnumeratedAttrChecker->({
5558 hidden => 1, text => 1, email => 1, url => 1, password => 1,
5559 datetime => 1, date => 1, month => 1, week => 1, time => 1,
5560 'datetime-local' => 1, number => 1, range => 1, checkbox => 1,
5561 radio => 1, file => 1, submit => 1, image => 1, reset => 1,
5562 button => 1,
5563 }),
5564 value => '',
5565 }->{$attr_ln};
5566
5567 ## State-dependent checkers
5568 unless ($checker) {
5569 if ($state eq 'hidden') {
5570 $checker =
5571 {
5572 value => sub {
5573 my ($self, $attr, $item, $element_state) = @_;
5574 my $name = $item->{node}->get_attribute_ns (undef, 'name');
5575 if (defined $name and $name eq '_charset_') { ## case-sensitive
5576 $self->{onerror}->(node => $attr,
5577 type => '_charset_ value',
5578 level => $self->{level}->{must});
5579 }
5580 },
5581 }->{$attr_ln} || $checker;
5582 ## TODO: Warn if no name attribute?
5583 ## TODO: Warn if name!=_charset_ and no value attribute?
5584 } elsif ($state eq 'datetime') {
5585 $checker =
5586 {
5587 accesskey => $HTMLAccesskeyAttrChecker,
5588 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5589 on => 1, off => 1,
5590 }),
5591 ## TODO: list
5592 ## TODO: max
5593 ## TODO: min
5594 ## TODO: min <= max
5595 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
5596 required => $GetHTMLBooleanAttrChecker->('required'),
5597 step => $StepAttrChecker,
5598 value => sub {
5599 ## NOTE: No restriction.
5600 ## TODO: Warn if not a valid UTC date and time string.
5601 ## TODO: Warn unless min <= value <= max
5602 },
5603 }->{$attr_ln} || $checker;
5604 } elsif ($state eq 'date') {
5605 $checker =
5606 {
5607 accesskey => $HTMLAccesskeyAttrChecker,
5608 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5609 on => 1, off => 1,
5610 }),
5611 ## TODO: list
5612 ## TODO: max
5613 ## TODO: min
5614 ## TODO: min <= max
5615 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
5616 required => $GetHTMLBooleanAttrChecker->('required'),
5617 step => $StepAttrChecker,
5618 value => sub {
5619 ## NOTE: No restriction.
5620 ## TODO: Warn if not a valid date string.
5621 ## TODO: Warn unless min <= value <= max
5622 },
5623 }->{$attr_ln} || $checker;
5624 } elsif ($state eq 'month') {
5625 $checker =
5626 {
5627 accesskey => $HTMLAccesskeyAttrChecker,
5628 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5629 on => 1, off => 1,
5630 }),
5631 ## TODO: list
5632 ## TODO: max
5633 ## TODO: min
5634 ## TODO: min <= max
5635 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
5636 required => $GetHTMLBooleanAttrChecker->('required'),
5637 step => $StepAttrChecker,
5638 value => sub {
5639 ## NOTE: No restriction.
5640 ## TODO: Warn if not a valid month string.
5641 ## TODO: Warn unless min <= value <= max
5642 },
5643 }->{$attr_ln} || $checker;
5644 } elsif ($state eq 'week') {
5645 $checker =
5646 {
5647 accesskey => $HTMLAccesskeyAttrChecker,
5648 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5649 on => 1, off => 1,
5650 }),
5651 ## TODO: list
5652 ## TODO: max
5653 ## TODO: min
5654 ## TODO: min <= max
5655 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
5656 required => $GetHTMLBooleanAttrChecker->('required'),
5657 step => $StepAttrChecker,
5658 value => sub {
5659 ## NOTE: No restriction.
5660 ## TODO: Warn if not a valid week string.
5661 ## TODO: Warn unless min <= value <= max
5662 },
5663 }->{$attr_ln} || $checker;
5664 } elsif ($state eq 'time') {
5665 $checker =
5666 {
5667 accesskey => $HTMLAccesskeyAttrChecker,
5668 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5669 on => 1, off => 1,
5670 }),
5671 ## TODO: list
5672 ## TODO: max
5673 ## TODO: min
5674 ## TODO: min <= max
5675 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
5676 required => $GetHTMLBooleanAttrChecker->('required'),
5677 step => $StepAttrChecker,
5678 value => sub {
5679 ## NOTE: No restriction.
5680 ## TODO: Warn if not a valid time string.
5681 ## TODO: Warn unless min <= value <= max
5682 },
5683 }->{$attr_ln} || $checker;
5684 } elsif ($state eq 'datetime-local') {
5685 $checker =
5686 {
5687 accesskey => $HTMLAccesskeyAttrChecker,
5688 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5689 on => 1, off => 1,
5690 }),
5691 ## TODO: list
5692 ## TODO: max
5693 ## TODO: min
5694 ## TODO: min <= max
5695 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
5696 required => $GetHTMLBooleanAttrChecker->('required'),
5697 step => $StepAttrChecker,
5698 value => sub {
5699 ## NOTE: No restriction.
5700 ## TODO: Warn if not a valid local date and time string.
5701 ## TODO: Warn unless min <= value <= max
5702 },
5703 }->{$attr_ln} || $checker;
5704 } elsif ($state eq 'number') {
5705 $checker =
5706 {
5707 accesskey => $HTMLAccesskeyAttrChecker,
5708 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5709 on => 1, off => 1,
5710 }),
5711 ## TODO: list
5712 max => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
5713 min => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
5714 ## TODO: min & max tests
5715 ## TODO: min <= max
5716 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
5717 required => $GetHTMLBooleanAttrChecker->('required'),
5718 step => $StepAttrChecker,
5719 value => sub {
5720 ## NOTE: No restriction.
5721 ## TODO: Warn if not a valid floating point number.
5722 ## TODO: Warn unless min <= value <= max
5723 },
5724 }->{$attr_ln} || $checker;
5725 } elsif ($state eq 'range') {
5726 $checker =
5727 {
5728 accesskey => $HTMLAccesskeyAttrChecker,
5729 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5730 on => 1, off => 1,
5731 }),
5732 ## TODO: list
5733 max => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
5734 min => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
5735 ## TODO: min & max tests
5736 ## TODO: min <= max
5737 step => $StepAttrChecker,
5738 value => sub {
5739 ## NOTE: No restriction.
5740 ## TODO: Warn if not a valid floating point number.
5741 ## TODO: Warn unless min <= value <= max
5742 },
5743 }->{$attr_ln} || $checker;
5744 } elsif ($state eq 'checkbox' or $state eq 'radio') {
5745 $checker =
5746 {
5747 accesskey => $HTMLAccesskeyAttrChecker,
5748 checked => $GetHTMLBooleanAttrChecker->('checked'),
5749 ## ISSUE: checked value not (yet?) defined.
5750 ## TODO: tests
5751 required => $GetHTMLBooleanAttrChecker->('required'),
5752 value => sub { }, ## NOTE: No restriction.
5753 }->{$attr_ln} || $checker;
5754 ## TODO: There MUST be another input type=radio with same
5755 ## name (Radio state).
5756 ## ISSUE: There should be exactly one type=radio with checked?
5757 } elsif ($state eq 'file') {
5758 $checker =
5759 {
5760 ## TODO: accept
5761 accesskey => $HTMLAccesskeyAttrChecker,
5762 required => $GetHTMLBooleanAttrChecker->('required'),
5763 }->{$attr_ln} || $checker;
5764 } elsif ($state eq 'submit') {
5765 $checker =
5766 {
5767 accesskey => $HTMLAccesskeyAttrChecker,
5768 action => $HTMLURIAttrChecker,
5769 ## ISSUE: Not yet defined.
5770 ## TODO: tests
5771 enctype => $HTMLIMTAttrChecker,
5772 ## ISSUE: Not yet defined.
5773 ## TODO: tests
5774 method => $GetHTMLEnumeratedAttrChecker->({
5775 get => 1, post => 1, put => 1, delete => 1,
5776 }),
5777 ## ISSUE: Not yet defined.
5778 ## TODO: tests
5779 replace => $GetHTMLEnumeratedAttrChecker->({
5780 document => 1, values => 1,
5781 }),
5782 target => $HTMLTargetAttrChecker,
5783 ## ISSUE: Not yet defined.
5784 ## TODO: tests
5785 value => sub { }, ## NOTE: No restriction.
5786 }->{$attr_ln} || $checker;
5787 } elsif ($state eq 'image') {
5788 $checker =
5789 {
5790 accesskey => $HTMLAccesskeyAttrChecker,
5791 action => $HTMLURIAttrChecker,
5792 ## ISSUE: Not yet defined.
5793 ## TODO: tests
5794 align => $GetHTMLEnumeratedAttrChecker->({
5795 top => 1, middle => 1, bottom => 1, left => 1, right => 1,
5796 }),
5797 alt => sub {
5798 my ($self, $attr) = @_;
5799 my $value = $attr->value;
5800 unless (length $value) {
5801 $self->{onerror}->(node => $attr,
5802 type => 'empty anchor image alt',
5803 level => $self->{level}->{must});
5804 }
5805 },
5806 enctype => $HTMLIMTAttrChecker,
5807 ## ISSUE: Not yet defined.
5808 ## TODO: tests
5809 ismap => $GetHTMLBooleanAttrChecker->('ismap'),
5810 ## TODO: tests
5811 method => $GetHTMLEnumeratedAttrChecker->({
5812 get => 1, post => 1, put => 1, delete => 1,
5813 }),
5814 ## ISSUE: Not yet defined.
5815 ## TODO: tests
5816 replace => $GetHTMLEnumeratedAttrChecker->({
5817 document => 1, values => 1,
5818 }),
5819 src => $HTMLURIAttrChecker,
5820 ## TODO: There is requirements on the referenced resource.
5821 ## TODO: tests
5822 target => $HTMLTargetAttrChecker,
5823 ## ISSUE: Not yet defined.
5824 ## TODO: tests
5825 usemap => $HTMLUsemapAttrChecker,
5826 ## TODO: tests
5827 }->{$attr_ln} || $checker;
5828 ## TODO: alt & src are required.
5829 } elsif ({
5830 reset => 1, button => 1,
5831 ## NOTE: From Web Forms 2.0:
5832 remove => 1, 'move-up' => 1, 'move-down' => 1,
5833 add => 1,
5834 }->{$state}) {
5835 $checker =
5836 {
5837 accesskey => $HTMLAccesskeyAttrChecker,
5838 ## NOTE: According to Web Forms 2.0, |input| attribute
5839 ## has |template| attribute to support the |add| button
5840 ## type (as part of the repetition template feature). It
5841 ## conflicts with the |template| global attribute
5842 ## introduced as part of the data template feature.
5843 ## NOTE: |template| attribute as defined in Web Forms 2.0
5844 ## has no author requirement.
5845 ## TODO: template tests
5846 value => sub { }, ## NOTE: No restriction.
5847 }->{$attr_ln} || $checker;
5848 } else { # Text, E-mail, URL, Password
5849 $checker =
5850 {
5851 accesskey => $HTMLAccesskeyAttrChecker,
5852 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5853 on => 1, off => 1,
5854 }),
5855 ## TODO: inputmode [WF2]
5856 ## TODO: list
5857 maxlength => sub {
5858 my ($self, $attr, $item, $element_state) = @_;
5859
5860 $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 })->(@_);
5861
5862 if ($attr->value =~ /^[\x09\x0A\x0D\x20]*([0-9]+)/) {
5863 ## NOTE: Applying the rules for parsing non-negative
5864 ## integers results in a number.
5865 my $max_allowed_value_length = 0+$1;
5866
5867 my $value = $item->{node}->get_attribute_ns (undef, 'value');
5868 if (defined $value) {
5869 my $codepoint_length = length $value;
5870 $codepoint_length++
5871 while $value =~ /[\x{10000}-\x{10FFFF}]/g;
5872
5873 if ($codepoint_length > $max_allowed_value_length) {
5874 $self->{onerror}
5875 ->(node => $item->{node}
5876 ->get_attribute_node_ns (undef, 'value'),
5877 type => 'value too long',
5878 level => $self->{level}->{must});
5879 }
5880 }
5881 }
5882 },
5883 ## TODO: pattern
5884 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
5885 required => $GetHTMLBooleanAttrChecker->('required'),
5886 size => $GetHTMLNonNegativeIntegerAttrChecker->(sub {shift > 0}),
5887 value => sub {
5888 ## NOTE: No restriction.
5889 ## TODO: Warn if it contains CR or LF?
5890 ## TODO: Warn if not addr-spec (E-Mail state)?
5891 ## TODO: Warn if not a URL (URL state)?
5892 },
5893 }->{$attr_ln} || $checker;
5894 $checker = '' if $state eq 'password' and $attr_ln eq 'list';
5895 }
5896 }
5897
5898 if (defined $checker) {
5899 if ($checker eq '') {
5900 $checker = sub {
5901 my ($self, $attr) = @_;
5902 $self->{onerror}->(node => $attr,
5903 type => 'input attr not applicable',
5904 text => $state,
5905 level => $self->{level}->{must});
5906 };
5907 }
5908 } elsif ($attr_ln =~ /^data-\p{InXMLNCNameChar10}+\z/ and
5909 $attr_ln !~ /[A-Z]/) {
5910 $checker = $HTMLDatasetAttrChecker;
5911 $status = $HTMLDatasetAttrStatus;
5912 } else {
5913 $checker = $HTMLAttrChecker->{$attr_ln};
5914 }
5915 }
5916 $checker ||= $AttrChecker->{$attr_ns}->{$attr_ln}
5917 || $AttrChecker->{$attr_ns}->{''};
5918 $status ||= $AttrStatus->{$attr_ns}->{$attr_ln}
5919 || $AttrStatus->{$attr_ns}->{''};
5920 $status = FEATURE_ALLOWED if not defined $status and length $attr_ns;
5921
5922 if ($checker) {
5923 $checker->($self, $attr, $item, $element_state) if ref $checker;
5924 } elsif ($attr_ns eq '' and not $status) {
5925 #
5926 } else {
5927 $self->{onerror}->(node => $attr,
5928 type => 'unknown attribute',
5929 level => $self->{level}->{uncertain});
5930 ## ISSUE: No comformance createria for unknown attributes in the spec
5931 }
5932
5933 $self->_attr_status_info ($attr, $status);
5934 }
5935
5936 $element_state->{uri_info}->{action}->{type}->{action} = 1;
5937 $element_state->{uri_info}->{datasrc}->{type}->{resource} = 1;
5938 $element_state->{uri_info}->{src}->{type}->{embedded} = 1;
5939 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
5940 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
5941 },
5942 ## TODO: Tests for <nest/> in <input>
5943 check_start => sub {
5944 my ($self, $item, $element_state) = @_;
5945 if ($self->{flag}->{has_label} and $self->{flag}->{has_labelable}) {
5946 $self->{onerror}->(node => $item->{node},
5947 type => 'multiple labelable fae',
5948 level => $self->{level}->{must});
5949 } else {
5950 $self->{flag}->{has_labelable} = 2;
5951 }
5952
5953 $element_state->{id_type} = 'labelable';
5954 },
5955 };
5956
5957 ## TODO: Form |name| attributes: MUST NOT conflict with RFC 3106 [WF2]
5958
5959 ## NOTE: "authors who are nesting repetition blocks should position such
5960 ## [repetition-block-related] buttons carefully to make clear which block a
5961 ## button applies to." [WF2]: I have no idea how this can be tested.
5962
5963 $Element->{$HTML_NS}->{button} = {
5964 %HTMLPhrasingContentChecker, ## ISSUE: -interactive?
5965 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
5966 check_attrs => $GetHTMLAttrsChecker->({
5967 accesskey => $HTMLAccesskeyAttrChecker,
5968 action => $HTMLURIAttrChecker,
5969 autofocus => $GetHTMLBooleanAttrChecker->('autofocus'),
5970 disabled => $GetHTMLBooleanAttrChecker->('disabled'),
5971 form => $HTMLFormAttrChecker,
5972 method => $GetHTMLEnumeratedAttrChecker->({
5973 get => 1, post => 1, put => 1, delete => 1,
5974 }),
5975 name => sub {}, ## NOTE: CDATA [M12N]
5976 onformchange => $HTMLEventHandlerAttrChecker,
5977 onforminput => $HTMLEventHandlerAttrChecker,
5978 replace => $GetHTMLEnumeratedAttrChecker->({document => 1, values => 1}),
5979 target => $HTMLTargetAttrChecker,
5980 ## NOTE: According to Web Forms 2.0, |button| attribute has |template|
5981 ## attribute to support the |add| button type (as part of repetition
5982 ## template feature). It conflicts with the |template| global attribute
5983 ## introduced as part of the data template feature.
5984 ## NOTE: |template| attribute as defined in Web Forms 2.0 has no
5985 ## author requirement.
5986 type => $GetHTMLEnumeratedAttrChecker->({
5987 button => 1, submit => 1, reset => 1,
5988 ## [WF2]
5989 add => 1, remove => 1, 'move-up' => 1, 'move-down' => 1,
5990 }),
5991 value => sub {}, ## NOTE: CDATA [M12N]
5992 }, {
5993 %HTMLAttrStatus,
5994 %HTMLM12NCommonAttrStatus,
5995 accesskey => FEATURE_M12N10_REC,
5996 action => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5997 autofocus => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5998 datafld => FEATURE_HTML4_REC_RESERVED,
5999 dataformatas => FEATURE_HTML4_REC_RESERVED,
6000 datasrc => FEATURE_HTML4_REC_RESERVED,
6001 disabled => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6002 enctype => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6003 form => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6004 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
6005 method => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6006 name => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6007 onblur => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6008 onfocus => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6009 onformchange => FEATURE_WF2_INFORMATIVE,
6010 onforminput => FEATURE_WF2_INFORMATIVE,
6011 replace => FEATURE_WF2,
6012 tabindex => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6013 target => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6014 template => FEATURE_HTML5_AT_RISK | FEATURE_WF2,
6015 type => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6016 value => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6017 }),
6018 check_start => sub {
6019 my ($self, $item, $element_state) = @_;
6020 if ($self->{flag}->{has_label} and $self->{flag}->{has_labelable}) {
6021 $self->{onerror}->(node => $item->{node},
6022 type => 'multiple labelable fae',
6023 level => $self->{level}->{must});
6024 } else {
6025 $self->{flag}->{has_labelable} = 2;
6026 }
6027
6028 $element_state->{uri_info}->{action}->{type}->{action} = 1;
6029 $element_state->{uri_info}->{datasrc}->{type}->{resource} = 1;
6030 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6031 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6032
6033 $element_state->{id_type} = 'labelable';
6034 },
6035 };
6036
6037 $Element->{$HTML_NS}->{label} = {
6038 %HTMLPhrasingContentChecker,
6039 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC
6040 | FEATURE_XHTML2_ED,
6041 check_attrs => $GetHTMLAttrsChecker->({
6042 accesskey => $HTMLAccesskeyAttrChecker,
6043 for => sub {
6044 my ($self, $attr) = @_;
6045
6046 ## NOTE: MUST be an ID of a labelable element.
6047
6048 push @{$self->{idref}}, ['labelable', $attr->value, $attr];
6049 },
6050 form => $HTMLFormAttrChecker,
6051 }, {
6052 %HTMLAttrStatus,
6053 %HTMLM12NXHTML2CommonAttrStatus,
6054 accesskey => FEATURE_WF2 | FEATURE_M12N10_REC,
6055 for => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6056 form => FEATURE_HTML5_DEFAULT,
6057 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
6058 onblur => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6059 onfocus => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6060 }),
6061 check_start => sub {
6062 my ($self, $item, $element_state) = @_;
6063 $self->_add_minus_elements ($element_state, {$HTML_NS => {label => 1}});
6064
6065 $element_state->{has_label_original} = $self->{flag}->{has_label};
6066 $self->{flag}->{has_label} = 1;
6067 $element_state->{has_labelable_original} = $self->{flag}->{has_labelable};
6068 $self->{flag}->{has_labelable} = $item->{node}->has_attribute ('for')?1:0;
6069
6070 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6071 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6072 },
6073 check_end => sub {
6074 my ($self, $item, $element_state) = @_;
6075 $self->_remove_minus_elements ($element_state);
6076
6077 if ($self->{flag}->{has_labelable} == 1) { # has for="" but no labelable
6078 $self->{flag}->{has_labelable}
6079 = $element_state->{has_labelable_original};
6080 }
6081 delete $self->{flag}->{has_label}
6082 unless $element_state->{has_label_original};
6083 ## TODO: Warn if no labelable descendant? <input type=hidden>?
6084
6085 ## NOTE: |<label for=a><input id=a></label>| is non-conforming.
6086
6087 $HTMLPhrasingContentChecker{check_end}->(@_);
6088 },
6089 ## TODO: Tests for <nest/> in <label>
6090 };
6091
6092 $Element->{$HTML_NS}->{select} = {
6093 %HTMLChecker,
6094 ## TODO: author should SELECTED at least one OPTION in non-MULTIPLE case [HTML4].
6095 ## TODO: more than one OPTION with SELECTED in non-MULTIPLE case is "error" [HTML4]
6096 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6097 is_root => 1, ## TODO: SHOULD NOT in application/xhtml+xml [WF2]
6098 check_attrs => $GetHTMLAttrsChecker->({
6099 accesskey => $HTMLAccesskeyAttrChecker,
6100 autofocus => $GetHTMLBooleanAttrChecker->('autofocus'),
6101 disabled => $GetHTMLBooleanAttrChecker->('disabled'),
6102 data => $HTMLURIAttrChecker, ## TODO: MUST point ... [WF2]
6103 form => $HTMLFormAttrChecker,
6104 multiple => $GetHTMLBooleanAttrChecker->('multiple'),
6105 name => sub {}, ## NOTE: CDATA [M12N]
6106 onformchange => $HTMLEventHandlerAttrChecker,
6107 onforminput => $HTMLEventHandlerAttrChecker,
6108 oninput => $HTMLEventHandlerAttrChecker,
6109 oninvalid => $HTMLEventHandlerAttrChecker,
6110 size => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }),
6111 }, {
6112 %HTMLAttrStatus,
6113 %HTMLM12NCommonAttrStatus,
6114 accesskey => FEATURE_WF2,
6115 autofocus => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6116 data => FEATURE_WF2,
6117 datafld => FEATURE_HTML4_REC_RESERVED,
6118 dataformatas => FEATURE_HTML4_REC_RESERVED,
6119 datasrc => FEATURE_HTML4_REC_RESERVED,
6120 disabled => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6121 form => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6122 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
6123 multiple => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6124 name => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6125 onblur => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6126 onchange => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6127 onformchange => FEATURE_WF2_INFORMATIVE,
6128 onforminput => FEATURE_WF2_INFORMATIVE,
6129 onfocus => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6130 oninput => FEATURE_WF2,
6131 oninvalid => FEATURE_WF2,
6132 sdaform => FEATURE_HTML20_RFC,
6133 sdapref => FEATURE_HTML20_RFC,
6134 size => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6135 tabindex => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6136 }),
6137 check_start => sub {
6138 my ($self, $item, $element_state) = @_;
6139 if ($self->{flag}->{has_label} and $self->{flag}->{has_labelable}) {
6140 $self->{onerror}->(node => $item->{node},
6141 type => 'multiple labelable fae',
6142 level => $self->{level}->{must});
6143 } else {
6144 $self->{flag}->{has_labelable} = 2;
6145 }
6146
6147 $element_state->{uri_info}->{data}->{type}->{resource} = 1;
6148 $element_state->{uri_info}->{datasrc}->{type}->{resource} = 1;
6149 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6150 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6151
6152 $element_state->{id_type} = 'labelable';
6153 },
6154 check_child_element => sub {
6155 my ($self, $item, $child_el, $child_nsuri, $child_ln,
6156 $child_is_transparent, $element_state) = @_;
6157 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
6158 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
6159 $self->{onerror}->(node => $child_el,
6160 type => 'element not allowed:minus',
6161 level => $self->{level}->{must});
6162 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
6163 #
6164 } elsif ($child_nsuri eq $HTML_NS and
6165 {
6166 option => 1, optgroup => 1,
6167 }->{$child_ln}) {
6168 #
6169 } else {
6170 $self->{onerror}->(node => $child_el, type => 'element not allowed',
6171 level => $self->{level}->{must});
6172 }
6173 },
6174 check_child_text => sub {
6175 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
6176 if ($has_significant) {
6177 $self->{onerror}->(node => $child_node, type => 'character not allowed',
6178 level => $self->{level}->{must});
6179 }
6180 },
6181 };
6182
6183 $Element->{$HTML_NS}->{datalist} = {
6184 %HTMLPhrasingContentChecker,
6185 ## TODO: |option| child MUST be empty [WF2]
6186 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6187 check_attrs => $GetHTMLAttrsChecker->({
6188 data => $HTMLURIAttrChecker, ## TODO: MUST point ... [WF2]
6189 }, {
6190 %HTMLAttrStatus,
6191 data => FEATURE_WF2,
6192 }),
6193 check_start => sub {
6194 my ($self, $item, $element_state) = @_;
6195
6196 $element_state->{phase} = 'any'; # any | phrasing | option
6197
6198 $element_state->{uri_info}->{data}->{type}->{resource} = 1;
6199 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6200 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6201 },
6202 ## NOTE: phrasing | option*
6203 check_child_element => sub {
6204 my ($self, $item, $child_el, $child_nsuri, $child_ln,
6205 $child_is_transparent, $element_state) = @_;
6206 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
6207 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
6208 $self->{onerror}->(node => $child_el,
6209 type => 'element not allowed:minus',
6210 level => $self->{level}->{must});
6211 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
6212 #
6213 } elsif ($element_state->{phase} eq 'phrasing') {
6214 if ($HTMLPhrasingContent->{$child_nsuri}->{$child_ln}) {
6215 #
6216 } else {
6217 $self->{onerror}->(node => $child_el,
6218 type => 'element not allowed:phrasing',
6219 level => $self->{level}->{must});
6220 }
6221 } elsif ($element_state->{phase} eq 'option') {
6222 if ($child_nsuri eq $HTML_NS and $child_ln eq 'option') {
6223 #
6224 } else {
6225 $self->{onerror}->(node => $child_el,
6226 type => 'element not allowed',
6227 level => $self->{level}->{must});
6228 }
6229 } elsif ($element_state->{phase} eq 'any') {
6230 if ($HTMLPhrasingContent->{$child_nsuri}->{$child_ln}) {
6231 $element_state->{phase} = 'phrasing';
6232 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'option') {
6233 $element_state->{phase} = 'option';
6234 } else {
6235 $self->{onerror}->(node => $child_el,
6236 type => 'element not allowed',
6237 level => $self->{level}->{must});
6238 }
6239 } else {
6240 die "check_child_element: Bad |datalist| phase: $element_state->{phase}";
6241 }
6242 },
6243 check_child_text => sub {
6244 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
6245 if ($has_significant) {
6246 if ($element_state->{phase} eq 'phrasing') {
6247 #
6248 } elsif ($element_state->{phase} eq 'any') {
6249 $element_state->{phase} = 'phrasing';
6250 } else {
6251 $self->{onerror}->(node => $child_node,
6252 type => 'character not allowed',
6253 level => $self->{level}->{must});
6254 }
6255 }
6256 },
6257 check_end => sub {
6258 my ($self, $item, $element_state) = @_;
6259 if ($element_state->{phase} eq 'phrasing') {
6260 if ($element_state->{has_significant}) {
6261 $item->{real_parent_state}->{has_significant} = 1;
6262 } elsif ($item->{transparent}) {
6263 #
6264 } else {
6265 $self->{onerror}->(node => $item->{node},
6266 type => 'no significant content',
6267 level => $self->{level}->{should});
6268 }
6269 } else {
6270 ## NOTE: Since the content model explicitly allows a |datalist| element
6271 ## being empty, we don't raise "no significant content" error for this
6272 ## element when there is no element. (We should raise an error for
6273 ## |<datalist><br></datalist>|, however.)
6274 ## NOTE: As a side-effect, when the |datalist| element only contains
6275 ## non-conforming content, then the |phase| flag has not changed from
6276 ## |any|, no "no significant content" error is raised neither.
6277 $HTMLChecker{check_end}->(@_);
6278 }
6279 },
6280 };
6281
6282 $Element->{$HTML_NS}->{optgroup} = {
6283 %HTMLChecker,
6284 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6285 check_attrs => $GetHTMLAttrsChecker->({
6286 disabled => $GetHTMLBooleanAttrChecker->('disabled'),
6287 label => sub {}, ## NOTE: Text [M12N] ## TODO: required
6288 }, {
6289 %HTMLAttrStatus,
6290 %HTMLM12NCommonAttrStatus,
6291 disabled => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6292 label => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6293 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
6294 }),
6295 check_child_element => sub {
6296 my ($self, $item, $child_el, $child_nsuri, $child_ln,
6297 $child_is_transparent, $element_state) = @_;
6298 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
6299 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
6300 $self->{onerror}->(node => $child_el,
6301 type => 'element not allowed:minus',
6302 level => $self->{level}->{must});
6303 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
6304 #
6305 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'option') {
6306 #
6307 } else {
6308 $self->{onerror}->(node => $child_el, type => 'element not allowed',
6309 level => $self->{level}->{must});
6310 }
6311 },
6312 check_child_text => sub {
6313 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
6314 if ($has_significant) {
6315 $self->{onerror}->(node => $child_node, type => 'character not allowed',
6316 level => $self->{level}->{must});
6317 }
6318 },
6319 };
6320
6321 $Element->{$HTML_NS}->{option} = {
6322 %HTMLTextChecker,
6323 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6324 check_attrs => $GetHTMLAttrsChecker->({
6325 disabled => $GetHTMLBooleanAttrChecker->('disabled'),
6326 label => sub {}, ## NOTE: Text [M12N]
6327 selected => $GetHTMLBooleanAttrChecker->('selected'),
6328 value => sub {}, ## NOTE: CDATA [M12N]
6329 }, {
6330 %HTMLAttrStatus,
6331 %HTMLM12NCommonAttrStatus,
6332 disabled => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6333 label => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6334 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
6335 sdaform => FEATURE_HTML20_RFC,
6336 sdapref => FEATURE_HTML20_RFC,
6337 selected => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6338 value => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6339 }),
6340 };
6341
6342 $Element->{$HTML_NS}->{textarea} = {
6343 %HTMLTextChecker,
6344 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6345 check_attrs => $GetHTMLAttrsChecker->({
6346 accept => $HTMLIMTAttrChecker, ## TODO: MUST be a text-based type
6347 accesskey => $HTMLAccesskeyAttrChecker,
6348 autofocus => $GetHTMLBooleanAttrChecker->('autofocus'),
6349 cols => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }), ## TODO: SHOULD if wrap=hard [WF2]
6350 disabled => $GetHTMLBooleanAttrChecker->('disabled'),
6351 form => $HTMLFormAttrChecker,
6352 ## TODO: inputmode [WF2]
6353 maxlength => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }),
6354 name => sub {}, ## NOTE: CDATA [M12N]
6355 onformchange => $HTMLEventHandlerAttrChecker,
6356 onforminput => $HTMLEventHandlerAttrChecker,
6357 oninput => $HTMLEventHandlerAttrChecker,
6358 ## TODO: pattern [WF2] ## TODO: |title| special semantics
6359 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
6360 required => $GetHTMLBooleanAttrChecker->('required'),
6361 rows => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }),
6362 oninput => $HTMLEventHandlerAttrChecker,
6363 oninvalid => $HTMLEventHandlerAttrChecker,
6364 wrap => $GetHTMLEnumeratedAttrChecker->({soft => 1, hard => 1}),
6365 }, {
6366 %HTMLAttrStatus,
6367 %HTMLM12NCommonAttrStatus,
6368 accept => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6369 'accept-charset' => FEATURE_HTML2X_RFC,
6370 accesskey => FEATURE_M12N10_REC,
6371 autofocus => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6372 cols => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6373 datafld => FEATURE_HTML4_REC_RESERVED,
6374 dataformatas => FEATURE_HTML4_REC_RESERVED,
6375 datasrc => FEATURE_HTML4_REC_RESERVED,
6376 disabled => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6377 form => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6378 inputmode => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_XHTMLBASIC11_CR,
6379 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
6380 maxlength => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6381 name => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6382 onblur => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6383 onchange => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6384 onfocus => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6385 onformchange => FEATURE_WF2_INFORMATIVE,
6386 onforminput => FEATURE_WF2_INFORMATIVE,
6387 oninput => FEATURE_WF2,
6388 oninvalid => FEATURE_WF2,
6389 onselect => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6390 pattern => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6391 readonly => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6392 required => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6393 rows => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6394 sdaform => FEATURE_HTML20_RFC,
6395 sdapref => FEATURE_HTML20_RFC,
6396 tabindex => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6397 wrap => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6398 }),
6399 check_start => sub {
6400 my ($self, $item, $element_state) = @_;
6401 if ($self->{flag}->{has_label} and $self->{flag}->{has_labelable}) {
6402 $self->{onerror}->(node => $item->{node},
6403 type => 'multiple labelable fae',
6404 level => $self->{level}->{must});
6405 } else {
6406 $self->{flag}->{has_labelable} = 2;
6407 }
6408
6409 $element_state->{uri_info}->{data}->{type}->{resource} = 1;
6410 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6411 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6412
6413 $element_state->{id_type} = 'labelable';
6414 },
6415 };
6416
6417 $Element->{$HTML_NS}->{output} = {
6418 %HTMLPhrasingContentChecker,
6419 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6420 check_attrs => $GetHTMLAttrsChecker->({
6421 ## TODO: for [WF2]
6422 form => $HTMLFormAttrChecker,
6423 ## TODO: name [WF2]
6424 onformchange => $HTMLEventHandlerAttrChecker,
6425 onforminput => $HTMLEventHandlerAttrChecker,
6426 }, {
6427 %HTMLAttrStatus,
6428 for => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6429 form => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6430 name => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6431 onchange => FEATURE_HTML5_DEFAULT | FEATURE_WF2,
6432 onformchange => FEATURE_WF2,
6433 onforminput => FEATURE_WF2,
6434 }),
6435 ## NOTE: "The output element should be used when ..." [WF2]
6436 };
6437
6438 $Element->{$HTML_NS}->{isindex} = {
6439 %HTMLEmptyChecker,
6440 status => FEATURE_M12N10_REC_DEPRECATED |
6441 Whatpm::ContentChecker::FEATURE_DEPRECATED_SHOULD, ## [HTML4]
6442 check_attrs => $GetHTMLAttrsChecker->({
6443 prompt => sub {}, ## NOTE: Text [M12N]
6444 }, {
6445 %HTMLAttrStatus,
6446 class => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6447 dir => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6448 id => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6449 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
6450 prompt => FEATURE_M12N10_REC_DEPRECATED,
6451 sdapref => FEATURE_HTML20_RFC,
6452 style => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
6453 title => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6454 }),
6455 ## TODO: Tests
6456 ## TODO: Tests for <nest/> in <isindex>
6457 check_start => sub {
6458 my ($self, $item, $element_state) = @_;
6459
6460 $element_state->{uri_info}->{action}->{type}->{action} = 1;
6461 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6462 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6463 },
6464 };
6465
6466 $Element->{$HTML_NS}->{script} = {
6467 %HTMLChecker,
6468 status => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6469 check_attrs => $GetHTMLAttrsChecker->({
6470 charset => sub {
6471 my ($self, $attr) = @_;
6472
6473 unless ($attr->owner_element->has_attribute_ns (undef, 'src')) {
6474 $self->{onerror}->(type => 'attribute not allowed',
6475 node => $attr,
6476 level => $self->{level}->{must});
6477 }
6478
6479 $HTMLCharsetChecker->($attr->value, @_);
6480 },
6481 language => sub {}, ## NOTE: No syntax constraint according to HTML4.
6482 src => $HTMLURIAttrChecker, ## TODO: pointed resource MUST be in type of type="" (resource error)
6483 defer => $GetHTMLBooleanAttrChecker->('defer'),
6484 async => $GetHTMLBooleanAttrChecker->('async'),
6485 type => $HTMLIMTAttrChecker, ## TODO: MUST NOT: |charset=""| parameter
6486 }, {
6487 %HTMLAttrStatus,
6488 async => FEATURE_HTML5_DEFAULT,
6489 charset => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6490 defer => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6491 event => FEATURE_HTML4_REC_RESERVED,
6492 for => FEATURE_HTML4_REC_RESERVED,
6493 href => FEATURE_RDFA_PR,
6494 id => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
6495 language => FEATURE_M12N10_REC_DEPRECATED,
6496 src => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6497 type => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6498 }),
6499 check_start => sub {
6500 my ($self, $item, $element_state) = @_;
6501
6502 if ($item->{node}->has_attribute_ns (undef, 'src')) {
6503 $element_state->{must_be_empty} = 1;
6504 } else {
6505 ## NOTE: No content model conformance in HTML5 spec.
6506 my $type = $item->{node}->get_attribute_ns (undef, 'type');
6507 my $language = $item->{node}->get_attribute_ns (undef, 'language');
6508 if ((defined $type and $type eq '') or
6509 (defined $language and $language eq '')) {
6510 $type = 'text/javascript';
6511 } elsif (defined $type) {
6512 #
6513 } elsif (defined $language) {
6514 $type = 'text/' . $language;
6515 } else {
6516 $type = 'text/javascript';
6517 }
6518
6519 if ($type =~ m[\A(?>(?>\x0D\x0A)?[\x09\x20])*([\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+)(?>(?>\x0D\x0A)?[\x09\x20])*/(?>(?>\x0D\x0A)?[\x09\x20])*([\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+)(?>(?>\x0D\x0A)?[\x09\x20])*(?>;|\z)]) {
6520 $type = "$1/$2";
6521 $type =~ tr/A-Z/a-z/; ## NOTE: ASCII case-insensitive
6522 ## TODO: Though we strip prameter here, it should not be ignored for the purpose of conformance checking...
6523 }
6524 $element_state->{script_type} = $type;
6525 }
6526
6527 $element_state->{uri_info}->{src}->{type}->{resource} = 1;
6528 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6529 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6530
6531 $element_state->{text} = '';
6532 },
6533 check_child_element => sub {
6534 my ($self, $item, $child_el, $child_nsuri, $child_ln,
6535 $child_is_transparent, $element_state) = @_;
6536 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
6537 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
6538 $self->{onerror}->(node => $child_el,
6539 type => 'element not allowed:minus',
6540 level => $self->{level}->{must});
6541 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
6542 #
6543 } else {
6544 if ($element_state->{must_be_empty}) {
6545 $self->{onerror}->(node => $child_el,
6546 type => 'element not allowed:empty',
6547 level => $self->{level}->{must});
6548 }
6549 }
6550 },
6551 check_child_text => sub {
6552 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
6553 if ($has_significant and
6554 $element_state->{must_be_empty}) {
6555 $self->{onerror}->(node => $child_node,
6556 type => 'character not allowed:empty',
6557 level => $self->{level}->{must});
6558 }
6559 $element_state->{text} .= $child_node->data;
6560 },
6561 check_end => sub {
6562 my ($self, $item, $element_state) = @_;
6563 unless ($element_state->{must_be_empty}) {
6564 if ($element_state->{script_type} =~ m![+/][Xx][Mm][Ll]\z!) {
6565 ## NOTE: XML content should be checked by THIS instance of checker
6566 ## as part of normal tree validation.
6567 $self->{onerror}->(node => $item->{node},
6568 type => 'XML script lang',
6569 text => $element_state->{script_type},
6570 level => $self->{level}->{uncertain});
6571 ## ISSUE: Should we raise some kind of error for
6572 ## <script type="text/xml">aaaaa</script>?
6573 ## NOTE: ^^^ This is why we throw an "uncertain" error.
6574 } else {
6575 $self->{onsubdoc}->({s => $element_state->{text},
6576 container_node => $item->{node},
6577 media_type => $element_state->{script_type},
6578 is_char_string => 1});
6579 }
6580
6581 $HTMLChecker{check_end}->(@_);
6582 }
6583 },
6584 ## TODO: There MUST be |type| unless the script type is JavaScript. (resource error)
6585 ## NOTE: "When used to include script data, the script data must be embedded
6586 ## inline, the format of the data must be given using the type attribute,
6587 ## and the src attribute must not be specified." - not testable.
6588 ## TODO: It would be possible to err <script type=text/plain src=...>
6589 };
6590 ## ISSUE: Significant check and text child node
6591
6592 ## NOTE: When script is disabled.
6593 $Element->{$HTML_NS}->{noscript} = {
6594 %HTMLTransparentChecker,
6595 status => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6596 check_attrs => $GetHTMLAttrsChecker->({}, {
6597 %HTMLAttrStatus,
6598 %HTMLM12NCommonAttrStatus,
6599 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
6600 }),
6601 check_start => sub {
6602 my ($self, $item, $element_state) = @_;
6603
6604 unless ($item->{node}->owner_document->manakai_is_html) {
6605 $self->{onerror}->(node => $item->{node}, type => 'in XML:noscript',
6606 level => $self->{level}->{must});
6607 }
6608
6609 unless ($self->{flag}->{in_head}) {
6610 $self->_add_minus_elements ($element_state,
6611 {$HTML_NS => {noscript => 1}});
6612 }
6613
6614 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6615 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6616 },
6617 check_child_element => sub {
6618 my ($self, $item, $child_el, $child_nsuri, $child_ln,
6619 $child_is_transparent, $element_state) = @_;
6620 if ($self->{flag}->{in_head}) {
6621 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
6622 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
6623 $self->{onerror}->(node => $child_el,
6624 type => 'element not allowed:minus',
6625 level => $self->{level}->{must});
6626 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
6627 #
6628 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'link') {
6629 #
6630 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'style') {
6631 if ($child_el->has_attribute_ns (undef, 'scoped')) {
6632 $self->{onerror}->(node => $child_el,
6633 type => 'element not allowed:head noscript',
6634 level => $self->{level}->{must});
6635 }
6636 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'meta') {
6637 my $http_equiv_attr
6638 = $child_el->get_attribute_node_ns (undef, 'http-equiv');
6639 if ($http_equiv_attr) {
6640 ## TODO: case
6641 if (lc $http_equiv_attr->value eq 'content-type') {
6642 $self->{onerror}->(node => $child_el,
6643 type => 'element not allowed:head noscript',
6644 level => $self->{level}->{must});
6645 } else {
6646 #
6647 }
6648 } else {
6649 $self->{onerror}->(node => $child_el,
6650 type => 'element not allowed:head noscript',
6651 level => $self->{level}->{must});
6652 }
6653 } else {
6654 $self->{onerror}->(node => $child_el,
6655 type => 'element not allowed:head noscript',
6656 level => $self->{level}->{must});
6657 }
6658 } else {
6659 $HTMLTransparentChecker{check_child_element}->(@_);
6660 }
6661 },
6662 check_child_text => sub {
6663 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
6664 if ($self->{flag}->{in_head}) {
6665 if ($has_significant) {
6666 $self->{onerror}->(node => $child_node,
6667 type => 'character not allowed',
6668 level => $self->{level}->{must});
6669 }
6670 } else {
6671 $HTMLTransparentChecker{check_child_text}->(@_);
6672 }
6673 },
6674 check_end => sub {
6675 my ($self, $item, $element_state) = @_;
6676 $self->_remove_minus_elements ($element_state);
6677 if ($self->{flag}->{in_head}) {
6678 $HTMLChecker{check_end}->(@_);
6679 } else {
6680 $HTMLPhrasingContentChecker{check_end}->(@_);
6681 }
6682 },
6683 };
6684 ## ISSUE: Scripting is disabled: <head><noscript><html a></noscript></head>
6685
6686 $Element->{$HTML_NS}->{'event-source'} = {
6687 %HTMLEmptyChecker,
6688 status => FEATURE_HTML5_LC_DROPPED,
6689 check_attrs => $GetHTMLAttrsChecker->({
6690 src => $HTMLURIAttrChecker,
6691 }, {
6692 %HTMLAttrStatus,
6693 src => FEATURE_HTML5_LC_DROPPED,
6694 }),
6695 check_start => sub {
6696 my ($self, $item, $element_state) = @_;
6697
6698 $element_state->{uri_info}->{src}->{type}->{resource} = 1;
6699 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6700 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6701 },
6702 };
6703
6704 $Element->{$HTML_NS}->{eventsource} = {
6705 %HTMLEmptyChecker,
6706 status => FEATURE_HTML5_DEFAULT,
6707 check_attrs => $GetHTMLAttrsChecker->({
6708 src => $HTMLURIAttrChecker,
6709 }, {
6710 %HTMLAttrStatus,
6711 src => FEATURE_HTML5_DEFAULT,
6712 }),
6713 check_start => sub {
6714 my ($self, $item, $element_state) = @_;
6715
6716 $element_state->{uri_info}->{src}->{type}->{resource} = 1;
6717 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6718 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6719 },
6720 };
6721
6722 $Element->{$HTML_NS}->{details} = {
6723 %{$Element->{$HTML_NS}->{fieldset}},
6724 status => FEATURE_HTML5_WD,
6725 check_attrs => $GetHTMLAttrsChecker->({
6726 open => $GetHTMLBooleanAttrChecker->('open'),
6727 }, {
6728 %HTMLAttrStatus,
6729 open => FEATURE_HTML5_WD,
6730 }),
6731 };
6732
6733 $Element->{$HTML_NS}->{datagrid} = {
6734 %HTMLFlowContentChecker,
6735 status => FEATURE_HTML5_WD,
6736 check_attrs => $GetHTMLAttrsChecker->({
6737 disabled => $GetHTMLBooleanAttrChecker->('disabled'),
6738 multiple => $GetHTMLBooleanAttrChecker->('multiple'),
6739 }, {
6740 %HTMLAttrStatus,
6741 disabled => FEATURE_HTML5_WD,
6742 multiple => FEATURE_HTML5_WD,
6743 }),
6744 check_start => sub {
6745 my ($self, $item, $element_state) = @_;
6746
6747 $self->_add_minus_elements ($element_state,
6748 {$HTML_NS => {a => 1, datagrid => 1}});
6749 $element_state->{phase} = 'any';
6750 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6751 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6752 },
6753 ## NOTE: Flow -(text* (table|select|datalist) Flow*) | table | select |
6754 ## datalist | Empty
6755 check_child_element => sub {
6756 my ($self, $item, $child_el, $child_nsuri, $child_ln,
6757 $child_is_transparent, $element_state) = @_;
6758 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
6759 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
6760 $self->{onerror}->(node => $child_el,
6761 type => 'element not allowed:minus',
6762 level => $self->{level}->{must});
6763 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
6764 #
6765 } elsif ($element_state->{phase} eq 'flow') {
6766 if ($HTMLFlowContent->{$child_nsuri}->{$child_ln}) {
6767 if (not $element_state->{has_element} and
6768 $child_nsuri eq $HTML_NS and
6769 {
6770 table => 1, select => 1, datalist => 1,
6771 }->{$child_ln}) {
6772 $self->{onerror}->(node => $child_el,
6773 type => 'element not allowed',
6774 level => $self->{level}->{must});
6775 } else {
6776 #
6777 }
6778 } else {
6779 $self->{onerror}->(node => $child_el,
6780 type => 'element not allowed', ## TODO: :flow
6781 level => $self->{level}->{must});
6782 }
6783 $element_state->{has_element} = 1;
6784 } elsif ($element_state->{phase} eq 'any') {
6785 if ($child_nsuri eq $HTML_NS and
6786 {table => 1, select => 1, datalist => 1}->{$child_ln}) {
6787 $element_state->{phase} = 'none';
6788 } elsif ($HTMLFlowContent->{$child_nsuri}->{$child_ln}) {
6789 $element_state->{has_element} = 1;
6790 $element_state->{phase} = 'flow';
6791 } else {
6792 $self->{onerror}->(node => $child_el,
6793 type => 'element not allowed',
6794 level => $self->{level}->{must});
6795 }
6796 } elsif ($element_state->{phase} eq 'none') {
6797 $self->{onerror}->(node => $child_el,
6798 type => 'element not allowed',
6799 level => $self->{level}->{must});
6800 } else {
6801 die "check_child_element: Bad |datagrid| phase: $element_state->{phase}";
6802 }
6803 },
6804 check_child_text => sub {
6805 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
6806 if ($has_significant) {
6807 if ($element_state->{phase} eq 'flow') {
6808 #
6809 } elsif ($element_state->{phase} eq 'any') {
6810 $element_state->{phase} = 'flow';
6811 } else {
6812 $self->{onerror}->(node => $child_node,
6813 type => 'character not allowed',
6814 level => $self->{level}->{must});
6815 }
6816 }
6817 },
6818 check_end => sub {
6819 my ($self, $item, $element_state) = @_;
6820 $self->_remove_minus_elements ($element_state);
6821
6822 if ($element_state->{phase} eq 'flow') {
6823 if ($element_state->{has_significant}) {
6824 $item->{real_parent_state}->{has_significant} = 1;
6825 } elsif ($item->{transparent}) {
6826 #
6827 } else {
6828 $self->{onerror}->(node => $item->{node},
6829 type => 'no significant content',
6830 level => $self->{level}->{should});
6831 }
6832 } else {
6833 ## NOTE: Since the content model explicitly allows a |datagird| element
6834 ## being empty, we don't raise "no significant content" error for this
6835 ## element when there is no element. (We should raise an error for
6836 ## |<datagrid><br></datagrid>|, however.)
6837 ## NOTE: As a side-effect, when the |datagrid| element only contains
6838 ## non-conforming content, then the |phase| flag has not changed from
6839 ## |any|, no "no significant content" error is raised neither.
6840 ## NOTE: Another side-effect of the current implementation:
6841 ## |<daragrid><datagrid/></datagrid>| has no "no significant content"
6842 ## error at all.
6843 $HTMLChecker{check_end}->(@_);
6844 }
6845 },
6846 };
6847
6848 $Element->{$HTML_NS}->{command} = {
6849 %HTMLEmptyChecker,
6850 status => FEATURE_HTML5_WD,
6851 check_attrs => $GetHTMLAttrsChecker->({
6852 checked => $GetHTMLBooleanAttrChecker->('checked'),
6853 default => $GetHTMLBooleanAttrChecker->('default'),
6854 disabled => $GetHTMLBooleanAttrChecker->('disabled'),
6855 icon => $HTMLURIAttrChecker,
6856 label => sub { }, ## NOTE: No conformance creteria
6857 radiogroup => sub { }, ## NOTE: No conformance creteria
6858 type => sub {
6859 my ($self, $attr) = @_;
6860 my $value = $attr->value;
6861 unless ({command => 1, checkbox => 1, radio => 1}->{$value}) {
6862 $self->{onerror}->(node => $attr, type => 'invalid attribute value',
6863 level => $self->{level}->{must});
6864 }
6865 },
6866 }, {
6867 %HTMLAttrStatus,
6868 checked => FEATURE_HTML5_WD,
6869 default => FEATURE_HTML5_WD,
6870 disabled => FEATURE_HTML5_WD,
6871 icon => FEATURE_HTML5_WD,
6872 label => FEATURE_HTML5_WD,
6873 radiogroup => FEATURE_HTML5_WD,
6874 type => FEATURE_HTML5_WD,
6875 }),
6876 check_start => sub {
6877 my ($self, $item, $element_state) = @_;
6878
6879 $element_state->{uri_info}->{icon}->{type}->{embedded} = 1;
6880 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6881 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6882 },
6883 };
6884
6885 $Element->{$HTML_NS}->{bb} = {
6886 %HTMLPhrasingContentChecker,
6887 status => FEATURE_HTML5_DEFAULT,
6888 check_attrs => $GetHTMLAttrsChecker->({
6889 type => $GetHTMLEnumeratedAttrChecker->({makeapp => 1}),
6890 }, {
6891 %HTMLAttrStatus,
6892 type => FEATURE_HTML5_DEFAULT,
6893 }),
6894 check_start => sub {
6895 my ($self, $item, $element_state) = @_;
6896 $self->_add_minus_elements ($element_state, $HTMLInteractiveContent);
6897
6898 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6899 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6900 },
6901 check_end => sub {
6902 my ($self, $item, $element_state) = @_;
6903 $self->_remove_minus_elements ($element_state);
6904
6905 $HTMLTransparentChecker{check_end}->(@_);
6906 },
6907 };
6908
6909 $Element->{$HTML_NS}->{menu} = {
6910 %HTMLPhrasingContentChecker,
6911 #status => FEATURE_M12N10_REC_DEPRECATED | FEATURE_HTML5_WD,
6912 status => FEATURE_M12N10_REC | FEATURE_HTML5_WD,
6913 ## NOTE: We don't want any |menu| element warned as deprecated.
6914 check_attrs => $GetHTMLAttrsChecker->({
6915 autosubmit => $GetHTMLBooleanAttrChecker->('autosubmit'),
6916 compact => $GetHTMLBooleanAttrChecker->('compact'),
6917 ## ISSUE: <menu id=""><p contextmenu=""> match? (In the current
6918 ## implementation, it does not match.)
6919 label => sub { }, ## NOTE: No conformance creteria
6920 type => $GetHTMLEnumeratedAttrChecker->({context => 1, toolbar => 1}),
6921 }, {
6922 %HTMLAttrStatus,
6923 %HTMLM12NCommonAttrStatus,
6924 align => FEATURE_HTML2X_RFC,
6925 autosubmit => FEATURE_HTML5_DROPPED,
6926 compat => FEATURE_M12N10_REC_DEPRECATED,
6927 label => FEATURE_HTML5_WD,
6928 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
6929 sdaform => FEATURE_HTML20_RFC,
6930 sdapref => FEATURE_HTML20_RFC,
6931 type => FEATURE_HTML5_WD,
6932 }),
6933 check_start => sub {
6934 my ($self, $item, $element_state) = @_;
6935 $element_state->{phase} = 'li or phrasing';
6936 $element_state->{in_menu_original} = $self->{flag}->{in_menu};
6937 $self->{flag}->{in_menu} = 1;
6938
6939 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6940 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6941 $element_state->{id_type} = 'menu';
6942 },
6943 check_child_element => sub {
6944 my ($self, $item, $child_el, $child_nsuri, $child_ln,
6945 $child_is_transparent, $element_state) = @_;
6946 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
6947 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
6948 $self->{onerror}->(node => $child_el,
6949 type => 'element not allowed:minus',
6950 level => $self->{level}->{must});
6951 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
6952 #
6953 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'li') {
6954 if ($element_state->{phase} eq 'li') {
6955 #
6956 } elsif ($element_state->{phase} eq 'li or phrasing') {
6957 $element_state->{phase} = 'li';
6958 } else {
6959 $self->{onerror}->(node => $child_el, type => 'element not allowed',
6960 level => $self->{level}->{must});
6961 }
6962 } elsif ($HTMLPhrasingContent->{$child_nsuri}->{$child_ln}) {
6963 if ($element_state->{phase} eq 'phrasing') {
6964 #
6965 } elsif ($element_state->{phase} eq 'li or phrasing') {
6966 $element_state->{phase} = 'phrasing';
6967 } else {
6968 $self->{onerror}->(node => $child_el, type => 'element not allowed',
6969 level => $self->{level}->{must});
6970 }
6971 } else {
6972 $self->{onerror}->(node => $child_el, type => 'element not allowed',
6973 level => $self->{level}->{must});
6974 }
6975 },
6976 check_child_text => sub {
6977 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
6978 if ($has_significant) {
6979 if ($element_state->{phase} eq 'phrasing') {
6980 #
6981 } elsif ($element_state->{phase} eq 'li or phrasing') {
6982 $element_state->{phase} = 'phrasing';
6983 } else {
6984 $self->{onerror}->(node => $child_node,
6985 type => 'character not allowed',
6986 level => $self->{level}->{must});
6987 }
6988 }
6989 },
6990 check_end => sub {
6991 my ($self, $item, $element_state) = @_;
6992 delete $self->{flag}->{in_menu} unless $element_state->{in_menu_original};
6993
6994 if ($element_state->{phase} eq 'li') {
6995 $HTMLChecker{check_end}->(@_);
6996 } else { # 'phrasing' or 'li or phrasing'
6997 $HTMLPhrasingContentChecker{check_end}->(@_);
6998 }
6999 },
7000 };
7001
7002 $Element->{$HTML_NS}->{datatemplate} = {
7003 %HTMLChecker,
7004 status => FEATURE_HTML5_AT_RISK,
7005 check_child_element => sub {
7006 my ($self, $item, $child_el, $child_nsuri, $child_ln,
7007 $child_is_transparent, $element_state) = @_;
7008 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
7009 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
7010 $self->{onerror}->(node => $child_el,
7011 type => 'element not allowed:minus',
7012 level => $self->{level}->{must});
7013 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
7014 #
7015 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'rule') {
7016 #
7017 } else {
7018 $self->{onerror}->(node => $child_el,
7019 type => 'element not allowed:datatemplate',
7020 level => $self->{level}->{must});
7021 }
7022 },
7023 check_child_text => sub {
7024 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
7025 if ($has_significant) {
7026 $self->{onerror}->(node => $child_node, type => 'character not allowed',
7027 level => $self->{level}->{must});
7028 }
7029 },
7030 is_xml_root => 1,
7031 };
7032
7033 $Element->{$HTML_NS}->{rule} = {
7034 %HTMLChecker,
7035 status => FEATURE_HTML5_AT_RISK,
7036 check_attrs => $GetHTMLAttrsChecker->({
7037 condition => $HTMLSelectorsAttrChecker,
7038 mode => $GetHTMLUnorderedUniqueSetOfSpaceSeparatedTokensAttrChecker->(),
7039 }, {
7040 %HTMLAttrStatus,
7041 condition => FEATURE_HTML5_AT_RISK,
7042 mode => FEATURE_HTML5_AT_RISK,
7043 }),
7044 check_start => sub {
7045 my ($self, $item, $element_state) = @_;
7046
7047 $self->_add_plus_elements ($element_state, {$HTML_NS => {nest => 1}});
7048 $element_state->{in_rule_original} = $self->{flag}->{in_rule};
7049 $self->{flag}->{in_rule} = 1;
7050
7051 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
7052 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
7053 },
7054 check_child_element => sub { },
7055 check_child_text => sub { },
7056 check_end => sub {
7057 my ($self, $item, $element_state) = @_;
7058
7059 $self->_remove_plus_elements ($element_state);
7060 delete $self->{flag}->{in_rule} unless $element_state->{in_rule_original};
7061
7062 $HTMLChecker{check_end}->(@_);
7063 },
7064 ## NOTE: "MAY be anything that, when the parent |datatemplate|
7065 ## is applied to some conforming data, results in a conforming DOM tree.":
7066 ## We don't check against this.
7067 };
7068
7069 $Element->{$HTML_NS}->{nest} = {
7070 %HTMLEmptyChecker,
7071 status => FEATURE_HTML5_AT_RISK,
7072 check_attrs => $GetHTMLAttrsChecker->({
7073 filter => $HTMLSelectorsAttrChecker,
7074 mode => sub {
7075 my ($self, $attr) = @_;
7076 my $value = $attr->value;
7077 if ($value !~ /\A[^\x09\x0A\x0C\x0D\x20]+\z/) {
7078 $self->{onerror}->(node => $attr, type => 'mode:syntax error',
7079 level => $self->{level}->{must});
7080 }
7081 },
7082 }, {
7083 %HTMLAttrStatus,
7084 filter => FEATURE_HTML5_AT_RISK,
7085 mode => FEATURE_HTML5_AT_RISK,
7086 }),
7087 };
7088
7089 $Element->{$HTML_NS}->{legend} = {
7090 %HTMLPhrasingContentChecker,
7091 status => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
7092 check_attrs => $GetHTMLAttrsChecker->({
7093 accesskey => $HTMLAccesskeyAttrChecker,
7094 # align => $GetHTMLEnumeratedAttrChecker->({
7095 # top => 1, bottom => 1, left => 1, right => 1,
7096 # }),
7097 }, {
7098 %HTMLAttrStatus,
7099 %HTMLM12NCommonAttrStatus,
7100 accesskey => FEATURE_M12N10_REC,
7101 align => FEATURE_M12N10_REC_DEPRECATED,
7102 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
7103 }),
7104 };
7105
7106 $Element->{$HTML_NS}->{div} = {
7107 %HTMLFlowContentChecker,
7108 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
7109 check_attrs => $GetHTMLAttrsChecker->({
7110 align => $GetHTMLEnumeratedAttrChecker->({
7111 left => 1, center => 1, right => 1, justify => 1,
7112 }),
7113 }, {
7114 %HTMLAttrStatus,
7115 %HTMLM12NXHTML2CommonAttrStatus,
7116 align => FEATURE_M12N10_REC_DEPRECATED,
7117 datafld => FEATURE_HTML4_REC_RESERVED,
7118 dataformatas => FEATURE_HTML4_REC_RESERVED,
7119 datasrc => FEATURE_HTML4_REC_RESERVED,
7120 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
7121 }),
7122 check_start => sub {
7123 my ($self, $item, $element_state) = @_;
7124
7125 $element_state->{uri_info}->{datasrc}->{type}->{resource} = 1;
7126 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
7127 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
7128 },
7129 };
7130
7131 $Element->{$HTML_NS}->{center} = {
7132 %HTMLFlowContentChecker,
7133 status => FEATURE_M12N10_REC_DEPRECATED,
7134 check_attrs => $GetHTMLAttrsChecker->({}, {
7135 %HTMLAttrStatus,
7136 %HTMLM12NCommonAttrStatus,
7137 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
7138 }),
7139 };
7140
7141 $Element->{$HTML_NS}->{font} = {
7142 %HTMLTransparentChecker,
7143 status => FEATURE_HTML5_DROPPED | FEATURE_M12N10_REC_DEPRECATED,
7144 check_attrs => $GetHTMLAttrsChecker->({
7145 ## TODO: HTML4 |size|, |color|, |face|
7146 }, {
7147 %HTMLAttrStatus,
7148 class => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
7149 color => FEATURE_M12N10_REC_DEPRECATED,
7150 dir => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
7151 face => FEATURE_M12N10_REC_DEPRECATED,
7152 id => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
7153 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
7154 size => FEATURE_M12N10_REC_DEPRECATED,
7155 style => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
7156 title => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
7157 }),
7158 ## NOTE: When the |font| element was defined in the HTML5 specification,
7159 ## it is allowed only in a document with the WYSIWYG signature. The
7160 ## checker does not check whether there is the signature, since the
7161 ## signature is dropped, too, and has never been implemented. (In addition,
7162 ## for any |font| element an "element not defined" error is raised anyway,
7163 ## such that we don't have to raise an additional error.)
7164 };
7165
7166 $Element->{$HTML_NS}->{basefont} = {
7167 %HTMLEmptyChecker,
7168 status => FEATURE_M12N10_REC_DEPRECATED,
7169 check_attrs => $GetHTMLAttrsChecker->({
7170 ## TODO: color, face, size
7171 }, {
7172 %HTMLAttrStatus,
7173 color => FEATURE_M12N10_REC_DEPRECATED,
7174 face => FEATURE_M12N10_REC_DEPRECATED,
7175 #id => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC_DEPRECATED,
7176 id => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
7177 size => FEATURE_M12N10_REC_DEPRECATED,
7178 }),
7179 };
7180
7181 ## TODO: frameset FEATURE_M12N10_REC
7182 ## class title id cols rows onload onunload style(x10)
7183 ## frame frameborder longdesc marginheight marginwidth noresize scrolling src name(deprecated) class,id,title,style(x10)
7184 ## noframes Common, lang(xhtml10)
7185
7186 ## TODO: CR: rbc rtc @rbspan (M12NXHTML2Common)
7187
7188 ## TODO: xmp, listing, plaintext FEATURE_HTML32_REC_OBSOLETE
7189 ## TODO: ^^^ lang, dir, id, class [HTML 2.x] sdaform [HTML 2.0]
7190 ## xmp, listing sdapref[HTML2,0]
7191
7192 =pod
7193
7194 WF2: Documents MUST comply to [CHARMOD].
7195 WF2: Vencor extensions MUST NOT be used.
7196
7197 HTML 2.0 nextid @n
7198
7199 RFC 2659: CERTS CRYPTOPTS
7200
7201 ISO-HTML: pre-html, divN
7202
7203 XHTML2: blockcode (Common), h (Common), separator (Common), l (Common),
7204 di (Common), nl (Common), handler (Common, type), standby (Common),
7205 summary (Common)
7206
7207 Access & XHTML2: access (LC)
7208
7209 XML Events & XForms (for XHTML2 support; very, very low priority)
7210
7211 =cut
7212
7213 ## NOTE: Where RFC 2659 allows additional attributes is unclear.
7214 ## We added them only to |a|. |link| and |form| might also allow them
7215 ## in theory.
7216
7217 $Whatpm::ContentChecker::Namespace->{$HTML_NS}->{loaded} = 1;
7218
7219 1;

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24