/[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.149 - (show annotations) (download)
Mon Oct 6 13:41:25 2008 UTC (18 years ago) by wakaba
Branch: MAIN
Changes since 1.148: +61 -33 lines
++ whatpm/t/dom-conformance/ChangeLog	6 Oct 2008 12:27:56 -0000
	* html-global-1.dat: A test on <input type=hidden tabindex> is
	added (it was not applied in WF2).

2008-10-06  Wakaba  <wakaba@suika.fam.cx>

++ whatpm/Whatpm/ContentChecker/ChangeLog	6 Oct 2008 13:40:56 -0000
	* HTML.pm: Place checkers for obsolete attributes appropriately.

2008-10-06  Wakaba  <wakaba@suika.fam.cx>

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_DEFAULT | FEATURE_WF2X | FEATURE_XHTMLBASIC11_CR,
5479 ismap => FEATURE_M12N10_REC,
5480 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
5481 list => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5482 max => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5483 maxlength => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
5484 method => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5485 min => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5486 name => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5487 onblur => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5488 onchange => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5489 onfocus => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5490 onformchange => FEATURE_WF2_INFORMATIVE,
5491 onforminput => FEATURE_WF2_INFORMATIVE,
5492 oninput => FEATURE_WF2,
5493 oninvalid => FEATURE_WF2,
5494 onselect => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5495 pattern => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5496 readonly => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
5497 replace => FEATURE_WF2,
5498 required => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5499 sdapref => FEATURE_HTML20_RFC,
5500 size => FEATURE_HTML5_DEFAULT | FEATURE_WF2X_DEPRECATED | FEATURE_M12N10_REC,
5501 src => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5502 step => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5503 tabindex => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5504 target => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5505 template => FEATURE_HTML5_AT_RISK | FEATURE_WF2,
5506 type => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5507 usemap => FEATURE_HTML5_DROPPED | FEATURE_M12N10_REC,
5508 value => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5509 }->{$attr_ln};
5510
5511 $checker =
5512 {
5513 ## NOTE: Value of an empty string means that the attribute is only
5514 ## applicable for a specific set of states.
5515 accept => '',
5516 'accept-charset' => $HTMLCharsetsAttrChecker,
5517 ## NOTE: To which states it applies is not defined in RFC 2070.
5518 ## TODO: tests
5519 accesskey => $HTMLAccesskeyAttrChecker,
5520 ## NOTE: Not applied to |hidden| [WF2].
5521 ## TODO: tests
5522 action => '',
5523 alt => '',
5524 autocomplete => '',
5525 autofocus => $GetHTMLBooleanAttrChecker->('autofocus'),
5526 ## ISSUE: No HTML5 definition yet. ## TODO: Tests
5527 checked => '',
5528 disabled => $GetHTMLBooleanAttrChecker->('disabled'),
5529 ## ISSUE: No HTML5 definition yet. ## TODO: Tests
5530 enctype => '',
5531 form => $HTMLFormAttrChecker,
5532 ismap => '', ## NOTE: "MUST" [HTML4]
5533 list => '',
5534 max => '',
5535 maxlength => '',
5536 method => '',
5537 min => '',
5538 name => sub {
5539 ## ISSUE: No HTML5 definition yet.
5540 ## TODO: tests
5541 },
5542 onformchange => $HTMLEventHandlerAttrChecker, # [WF2]
5543 onforminput => $HTMLEventHandlerAttrChecker, # [WF2]
5544 oninput => $HTMLEventHandlerAttrChecker, # [WF2]
5545 oninvalid => $HTMLEventHandlerAttrChecker, # [WF2]
5546 ## TODO: tests for four attributes above
5547 pattern => '',
5548 readonly => '',
5549 required => '',
5550 size => '',
5551 src => '',
5552 step => '',
5553 target => '',
5554 type => $GetHTMLEnumeratedAttrChecker->({
5555 hidden => 1, text => 1, email => 1, url => 1, password => 1,
5556 datetime => 1, date => 1, month => 1, week => 1, time => 1,
5557 'datetime-local' => 1, number => 1, range => 1, checkbox => 1,
5558 radio => 1, file => 1, submit => 1, image => 1, reset => 1,
5559 button => 1,
5560 }),
5561 value => '',
5562 }->{$attr_ln};
5563
5564 ## State-dependent checkers
5565 unless ($checker) {
5566 if ($state eq 'hidden') {
5567 $checker =
5568 {
5569 value => sub {
5570 my ($self, $attr, $item, $element_state) = @_;
5571 my $name = $item->{node}->get_attribute_ns (undef, 'name');
5572 if (defined $name and $name eq '_charset_') { ## case-sensitive
5573 $self->{onerror}->(node => $attr,
5574 type => '_charset_ value',
5575 level => $self->{level}->{must});
5576 }
5577 },
5578 }->{$attr_ln} || $checker;
5579 $checker = '' if $attr_ln eq 'accesskey';
5580 ## TODO: Warn if no name attribute?
5581 ## TODO: Warn if name!=_charset_ and no value attribute?
5582 } elsif ($state eq 'datetime') {
5583 $checker =
5584 {
5585 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5586 on => 1, off => 1,
5587 }),
5588 ## TODO: list
5589 ## TODO: max
5590 ## TODO: min
5591 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
5592 required => $GetHTMLBooleanAttrChecker->('required'),
5593 step => $StepAttrChecker,
5594 value => sub {
5595 ## NOTE: No restriction.
5596 ## TODO: Warn if not a valid UTC date and time string.
5597 },
5598 }->{$attr_ln} || $checker;
5599 } elsif ($state eq 'date') {
5600 $checker =
5601 {
5602 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5603 on => 1, off => 1,
5604 }),
5605 ## TODO: list
5606 ## TODO: max
5607 ## TODO: min
5608 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
5609 required => $GetHTMLBooleanAttrChecker->('required'),
5610 step => $StepAttrChecker,
5611 value => sub {
5612 ## NOTE: No restriction.
5613 ## TODO: Warn if not a valid date string.
5614 },
5615 }->{$attr_ln} || $checker;
5616 } elsif ($state eq 'month') {
5617 $checker =
5618 {
5619 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5620 on => 1, off => 1,
5621 }),
5622 ## TODO: list
5623 ## TODO: max
5624 ## TODO: min
5625 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
5626 required => $GetHTMLBooleanAttrChecker->('required'),
5627 step => $StepAttrChecker,
5628 value => sub {
5629 ## NOTE: No restriction.
5630 ## TODO: Warn if not a valid month string.
5631 },
5632 }->{$attr_ln} || $checker;
5633 } elsif ($state eq 'week') {
5634 $checker =
5635 {
5636 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5637 on => 1, off => 1,
5638 }),
5639 ## TODO: list
5640 ## TODO: max
5641 ## TODO: min
5642 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
5643 required => $GetHTMLBooleanAttrChecker->('required'),
5644 step => $StepAttrChecker,
5645 value => sub {
5646 ## NOTE: No restriction.
5647 ## TODO: Warn if not a valid week string.
5648 },
5649 }->{$attr_ln} || $checker;
5650 } elsif ($state eq 'time') {
5651 $checker =
5652 {
5653 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5654 on => 1, off => 1,
5655 }),
5656 ## TODO: list
5657 ## TODO: max
5658 ## TODO: min
5659 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
5660 required => $GetHTMLBooleanAttrChecker->('required'),
5661 step => $StepAttrChecker,
5662 value => sub {
5663 ## NOTE: No restriction.
5664 ## TODO: Warn if not a valid time string.
5665 },
5666 }->{$attr_ln} || $checker;
5667 } elsif ($state eq 'datetime-local') {
5668 $checker =
5669 {
5670 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5671 on => 1, off => 1,
5672 }),
5673 ## TODO: list
5674 ## TODO: max
5675 ## TODO: min
5676 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
5677 required => $GetHTMLBooleanAttrChecker->('required'),
5678 step => $StepAttrChecker,
5679 value => sub {
5680 ## NOTE: No restriction.
5681 ## TODO: Warn if not a valid local date and time string.
5682 },
5683 }->{$attr_ln} || $checker;
5684 } elsif ($state eq 'number') {
5685 $checker =
5686 {
5687 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5688 on => 1, off => 1,
5689 }),
5690 ## TODO: list
5691 max => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
5692 min => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
5693 ## TODO: min & max tests
5694 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
5695 required => $GetHTMLBooleanAttrChecker->('required'),
5696 step => $StepAttrChecker,
5697 value => sub {
5698 ## NOTE: No restriction.
5699 ## TODO: Warn if not a valid floating point number.
5700 },
5701 }->{$attr_ln} || $checker;
5702 } elsif ($state eq 'range') {
5703 $checker =
5704 {
5705 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5706 on => 1, off => 1,
5707 }),
5708 ## TODO: list
5709 max => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
5710 min => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
5711 ## TODO: min & max tests
5712 step => $StepAttrChecker,
5713 value => sub {
5714 ## NOTE: No restriction.
5715 ## TODO: Warn if not a valid floating point number.
5716 },
5717 }->{$attr_ln} || $checker;
5718 } elsif ($state eq 'checkbox' or $state eq 'radio') {
5719 $checker =
5720 {
5721 checked => $GetHTMLBooleanAttrChecker->('checked'),
5722 ## ISSUE: checked value not (yet?) defined.
5723 ## TODO: tests
5724 required => $GetHTMLBooleanAttrChecker->('required'),
5725 value => sub { }, ## NOTE: No restriction.
5726 }->{$attr_ln} || $checker;
5727 ## TODO: There MUST be another input type=radio with same
5728 ## name (Radio state).
5729 ## ISSUE: There should be exactly one type=radio with checked?
5730 } elsif ($state eq 'file') {
5731 $checker =
5732 {
5733 ## TODO: accept
5734 required => $GetHTMLBooleanAttrChecker->('required'),
5735 }->{$attr_ln} || $checker;
5736 } elsif ($state eq 'submit') {
5737 $checker =
5738 {
5739 action => $HTMLURIAttrChecker,
5740 ## ISSUE: Not yet defined.
5741 ## TODO: tests
5742 enctype => $HTMLIMTAttrChecker,
5743 ## ISSUE: Not yet defined.
5744 ## TODO: tests
5745 method => $GetHTMLEnumeratedAttrChecker->({
5746 get => 1, post => 1, put => 1, delete => 1,
5747 }),
5748 ## ISSUE: Not yet defined.
5749 ## TODO: tests
5750 replace => $GetHTMLEnumeratedAttrChecker->({
5751 document => 1, values => 1,
5752 }),
5753 ## TODO: tests
5754 target => $HTMLTargetAttrChecker,
5755 ## ISSUE: Not yet defined.
5756 ## TODO: tests
5757 value => sub { }, ## NOTE: No restriction.
5758 }->{$attr_ln} || $checker;
5759 } elsif ($state eq 'image') {
5760 $checker =
5761 {
5762 action => $HTMLURIAttrChecker,
5763 ## ISSUE: Not yet defined.
5764 ## TODO: tests
5765 align => $GetHTMLEnumeratedAttrChecker->({
5766 top => 1, middle => 1, bottom => 1, left => 1, right => 1,
5767 }),
5768 ## TODO: tests
5769 alt => sub {
5770 my ($self, $attr) = @_;
5771 my $value = $attr->value;
5772 unless (length $value) {
5773 $self->{onerror}->(node => $attr,
5774 type => 'empty anchor image alt',
5775 level => $self->{level}->{must});
5776 }
5777 },
5778 enctype => $HTMLIMTAttrChecker,
5779 ## ISSUE: Not yet defined.
5780 ## TODO: tests
5781 ismap => $GetHTMLBooleanAttrChecker->('ismap'),
5782 ## TODO: tests
5783 method => $GetHTMLEnumeratedAttrChecker->({
5784 get => 1, post => 1, put => 1, delete => 1,
5785 }),
5786 ## ISSUE: Not yet defined.
5787 ## TODO: tests
5788 replace => $GetHTMLEnumeratedAttrChecker->({
5789 document => 1, values => 1,
5790 }),
5791 ## TODO: tests
5792 src => $HTMLURIAttrChecker,
5793 ## TODO: There is requirements on the referenced resource.
5794 ## TODO: tests
5795 target => $HTMLTargetAttrChecker,
5796 ## ISSUE: Not yet defined.
5797 ## TODO: tests
5798 usemap => $HTMLUsemapAttrChecker,
5799 ## TODO: tests
5800 }->{$attr_ln} || $checker;
5801 ## TODO: alt & src are required.
5802 } elsif ({
5803 reset => 1, button => 1,
5804 ## NOTE: From Web Forms 2.0:
5805 remove => 1, 'move-up' => 1, 'move-down' => 1,
5806 add => 1,
5807 }->{$state}) {
5808 $checker =
5809 {
5810 ## NOTE: According to Web Forms 2.0, |input| attribute
5811 ## has |template| attribute to support the |add| button
5812 ## type (as part of the repetition template feature). It
5813 ## conflicts with the |template| global attribute
5814 ## introduced as part of the data template feature.
5815 ## NOTE: |template| attribute as defined in Web Forms 2.0
5816 ## has no author requirement.
5817 ## TODO: template tests
5818 value => sub { }, ## NOTE: No restriction.
5819 }->{$attr_ln} || $checker;
5820 } else { # Text, E-mail, URL, Password
5821 $checker =
5822 {
5823 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5824 on => 1, off => 1,
5825 }),
5826 ## TODO: inputmode [WF2]
5827 ## TODO: list
5828 maxlength => sub {
5829 my ($self, $attr, $item, $element_state) = @_;
5830
5831 $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 })->(@_);
5832
5833 if ($attr->value =~ /^[\x09\x0A\x0D\x20]*([0-9]+)/) {
5834 ## NOTE: Applying the rules for parsing non-negative
5835 ## integers results in a number.
5836 my $max_allowed_value_length = 0+$1;
5837
5838 my $value = $item->{node}->get_attribute_ns (undef, 'value');
5839 if (defined $value) {
5840 my $codepoint_length = length $value;
5841 $codepoint_length++
5842 while $value =~ /[\x{10000}-\x{10FFFF}]/g;
5843
5844 if ($codepoint_length > $max_allowed_value_length) {
5845 $self->{onerror}
5846 ->(node => $item->{node}
5847 ->get_attribute_node_ns (undef, 'value'),
5848 type => 'value too long',
5849 level => $self->{level}->{must});
5850 }
5851 }
5852 }
5853 },
5854 ## TODO: pattern
5855 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
5856 required => $GetHTMLBooleanAttrChecker->('required'),
5857 size => $GetHTMLNonNegativeIntegerAttrChecker->(sub {shift > 0}),
5858 value => sub {
5859 ## NOTE: No restriction.
5860 ## TODO: Warn if it contains CR or LF?
5861 ## TODO: Warn if not addr-spec (E-Mail state)?
5862 ## TODO: Warn if not a URL (URL state)?
5863 },
5864 }->{$attr_ln} || $checker;
5865 $checker = '' if $state eq 'password' and $attr_ln eq 'list';
5866 $state = 'text';
5867 }
5868 }
5869
5870 if (defined $checker) {
5871 if ($checker eq '') {
5872 $checker = sub {
5873 my ($self, $attr) = @_;
5874 $self->{onerror}->(node => $attr,
5875 type => 'input attr not applicable',
5876 text => $state,
5877 level => $self->{level}->{must});
5878 };
5879 }
5880 } elsif ($attr_ln =~ /^data-\p{InXMLNCNameChar10}+\z/ and
5881 $attr_ln !~ /[A-Z]/) {
5882 $checker = $HTMLDatasetAttrChecker;
5883 $status = $HTMLDatasetAttrStatus;
5884 } else {
5885 $checker = $HTMLAttrChecker->{$attr_ln};
5886 }
5887 }
5888 $checker ||= $AttrChecker->{$attr_ns}->{$attr_ln}
5889 || $AttrChecker->{$attr_ns}->{''};
5890 $status ||= $AttrStatus->{$attr_ns}->{$attr_ln}
5891 || $AttrStatus->{$attr_ns}->{''};
5892 $status = FEATURE_ALLOWED if not defined $status and length $attr_ns;
5893
5894 if ($checker) {
5895 $checker->($self, $attr, $item, $element_state) if ref $checker;
5896 } elsif ($attr_ns eq '' and not $status) {
5897 #
5898 } else {
5899 $self->{onerror}->(node => $attr,
5900 type => 'unknown attribute',
5901 level => $self->{level}->{uncertain});
5902 ## ISSUE: No comformance createria for unknown attributes in the spec
5903 }
5904
5905 $self->_attr_status_info ($attr, $status);
5906 }
5907 },
5908 ## TODO: Tests
5909 ## TODO: Tests for <nest/> in <input>
5910 check_start => sub {
5911 my ($self, $item, $element_state) = @_;
5912 if ($self->{flag}->{has_label} and $self->{flag}->{has_labelable}) {
5913 $self->{onerror}->(node => $item->{node},
5914 type => 'multiple labelable fae',
5915 level => $self->{level}->{must});
5916 } else {
5917 $self->{flag}->{has_labelable} = 2;
5918 }
5919
5920 $element_state->{uri_info}->{action}->{type}->{action} = 1;
5921 $element_state->{uri_info}->{datasrc}->{type}->{resource} = 1;
5922 $element_state->{uri_info}->{src}->{type}->{embedded} = 1;
5923 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
5924 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
5925
5926 $element_state->{id_type} = 'labelable';
5927 },
5928 };
5929
5930 ## TODO: Form |name| attributes: MUST NOT conflict with RFC 3106 [WF2]
5931
5932 ## NOTE: "authors who are nesting repetition blocks should position such
5933 ## [repetition-block-related] buttons carefully to make clear which block a
5934 ## button applies to.": I have no idea how this can be tested.
5935
5936 $Element->{$HTML_NS}->{button} = {
5937 %HTMLPhrasingContentChecker, ## ISSUE: -interactive?
5938 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
5939 check_attrs => $GetHTMLAttrsChecker->({
5940 accesskey => $HTMLAccesskeyAttrChecker,
5941 action => $HTMLURIAttrChecker,
5942 autofocus => $GetHTMLBooleanAttrChecker->('autofocus'),
5943 disabled => $GetHTMLBooleanAttrChecker->('disabled'),
5944 form => $HTMLFormAttrChecker,
5945 method => $GetHTMLEnumeratedAttrChecker->({
5946 get => 1, post => 1, put => 1, delete => 1,
5947 }),
5948 name => sub {}, ## NOTE: CDATA [M12N]
5949 onformchange => $HTMLEventHandlerAttrChecker,
5950 onforminput => $HTMLEventHandlerAttrChecker,
5951 replace => $GetHTMLEnumeratedAttrChecker->({document => 1, values => 1}),
5952 target => $HTMLTargetAttrChecker,
5953 ## NOTE: According to Web Forms 2.0, |button| attribute has |template|
5954 ## attribute to support the |add| button type (as part of repetition
5955 ## template feature). It conflicts with the |template| global attribute
5956 ## introduced as part of the data template feature.
5957 ## NOTE: |template| attribute as defined in Web Forms 2.0 has no
5958 ## author requirement.
5959 type => $GetHTMLEnumeratedAttrChecker->({
5960 button => 1, submit => 1, reset => 1,
5961 ## [WF2]
5962 add => 1, remove => 1, 'move-up' => 1, 'move-down' => 1,
5963 }),
5964 value => sub {}, ## NOTE: CDATA [M12N]
5965 }, {
5966 %HTMLAttrStatus,
5967 %HTMLM12NCommonAttrStatus,
5968 accesskey => FEATURE_M12N10_REC,
5969 action => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5970 autofocus => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5971 datafld => FEATURE_HTML4_REC_RESERVED,
5972 dataformatas => FEATURE_HTML4_REC_RESERVED,
5973 datasrc => FEATURE_HTML4_REC_RESERVED,
5974 disabled => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
5975 enctype => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5976 form => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5977 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
5978 method => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5979 name => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5980 onblur => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5981 onfocus => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5982 onformchange => FEATURE_WF2_INFORMATIVE,
5983 onforminput => FEATURE_WF2_INFORMATIVE,
5984 replace => FEATURE_WF2,
5985 tabindex => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5986 target => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5987 template => FEATURE_WF2,
5988 type => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5989 value => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5990 }),
5991 check_start => sub {
5992 my ($self, $item, $element_state) = @_;
5993 if ($self->{flag}->{has_label} and $self->{flag}->{has_labelable}) {
5994 $self->{onerror}->(node => $item->{node},
5995 type => 'multiple labelable fae',
5996 level => $self->{level}->{must});
5997 } else {
5998 $self->{flag}->{has_labelable} = 2;
5999 }
6000
6001 $element_state->{uri_info}->{action}->{type}->{action} = 1;
6002 $element_state->{uri_info}->{datasrc}->{type}->{resource} = 1;
6003 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6004 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6005
6006 $element_state->{id_type} = 'labelable';
6007 },
6008 };
6009
6010 $Element->{$HTML_NS}->{label} = {
6011 %HTMLPhrasingContentChecker,
6012 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC
6013 | FEATURE_XHTML2_ED,
6014 check_attrs => $GetHTMLAttrsChecker->({
6015 accesskey => $HTMLAccesskeyAttrChecker,
6016 for => sub {
6017 my ($self, $attr) = @_;
6018
6019 ## NOTE: MUST be an ID of a labelable element.
6020
6021 push @{$self->{idref}}, ['labelable', $attr->value, $attr];
6022 },
6023 form => $HTMLFormAttrChecker,
6024 }, {
6025 %HTMLAttrStatus,
6026 %HTMLM12NXHTML2CommonAttrStatus,
6027 accesskey => FEATURE_WF2 | FEATURE_M12N10_REC,
6028 for => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6029 form => FEATURE_HTML5_DEFAULT,
6030 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
6031 onblur => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6032 onfocus => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6033 }),
6034 check_start => sub {
6035 my ($self, $item, $element_state) = @_;
6036 $self->_add_minus_elements ($element_state, {$HTML_NS => {label => 1}});
6037
6038 $element_state->{has_label_original} = $self->{flag}->{has_label};
6039 $self->{flag}->{has_label} = 1;
6040 $element_state->{has_labelable_original} = $self->{flag}->{has_labelable};
6041 $self->{flag}->{has_labelable} = $item->{node}->has_attribute ('for')?1:0;
6042
6043 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6044 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6045 },
6046 check_end => sub {
6047 my ($self, $item, $element_state) = @_;
6048 $self->_remove_minus_elements ($element_state);
6049
6050 if ($self->{flag}->{has_labelable} == 1) { # has for="" but no labelable
6051 $self->{flag}->{has_labelable}
6052 = $element_state->{has_labelable_original};
6053 }
6054 delete $self->{flag}->{has_label}
6055 unless $element_state->{has_label_original};
6056 ## TODO: Warn if no labelable descendant? <input type=hidden>?
6057
6058 ## NOTE: |<label for=a><input id=a></label>| is non-conforming.
6059
6060 $HTMLPhrasingContentChecker{check_end}->(@_);
6061 },
6062 ## TODO: Tests for <nest/> in <label>
6063 };
6064
6065 $Element->{$HTML_NS}->{select} = {
6066 %HTMLChecker,
6067 ## TODO: author should SELECTED at least one OPTION in non-MULTIPLE case [HTML4].
6068 ## TODO: more than one OPTION with SELECTED in non-MULTIPLE case is "error" [HTML4]
6069 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6070 is_root => 1, ## TODO: SHOULD NOT in application/xhtml+xml [WF2]
6071 check_attrs => $GetHTMLAttrsChecker->({
6072 accesskey => $HTMLAccesskeyAttrChecker,
6073 autofocus => $GetHTMLBooleanAttrChecker->('autofocus'),
6074 disabled => $GetHTMLBooleanAttrChecker->('disabled'),
6075 data => $HTMLURIAttrChecker, ## TODO: MUST point ... [WF2]
6076 form => $HTMLFormAttrChecker,
6077 multiple => $GetHTMLBooleanAttrChecker->('multiple'),
6078 name => sub {}, ## NOTE: CDATA [M12N]
6079 onformchange => $HTMLEventHandlerAttrChecker,
6080 onforminput => $HTMLEventHandlerAttrChecker,
6081 oninput => $HTMLEventHandlerAttrChecker,
6082 oninvalid => $HTMLEventHandlerAttrChecker,
6083 size => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }),
6084 }, {
6085 %HTMLAttrStatus,
6086 %HTMLM12NCommonAttrStatus,
6087 accesskey => FEATURE_WF2,
6088 autofocus => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6089 data => FEATURE_WF2,
6090 datafld => FEATURE_HTML4_REC_RESERVED,
6091 dataformatas => FEATURE_HTML4_REC_RESERVED,
6092 datasrc => FEATURE_HTML4_REC_RESERVED,
6093 disabled => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6094 form => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6095 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
6096 multiple => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6097 name => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6098 onblur => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6099 onchange => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6100 onformchange => FEATURE_WF2_INFORMATIVE,
6101 onforminput => FEATURE_WF2_INFORMATIVE,
6102 onfocus => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6103 oninput => FEATURE_WF2,
6104 oninvalid => FEATURE_WF2,
6105 sdaform => FEATURE_HTML20_RFC,
6106 sdapref => FEATURE_HTML20_RFC,
6107 size => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6108 tabindex => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6109 }),
6110 check_start => sub {
6111 my ($self, $item, $element_state) = @_;
6112 if ($self->{flag}->{has_label} and $self->{flag}->{has_labelable}) {
6113 $self->{onerror}->(node => $item->{node},
6114 type => 'multiple labelable fae',
6115 level => $self->{level}->{must});
6116 } else {
6117 $self->{flag}->{has_labelable} = 2;
6118 }
6119
6120 $element_state->{uri_info}->{data}->{type}->{resource} = 1;
6121 $element_state->{uri_info}->{datasrc}->{type}->{resource} = 1;
6122 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6123 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6124
6125 $element_state->{id_type} = 'labelable';
6126 },
6127 check_child_element => sub {
6128 my ($self, $item, $child_el, $child_nsuri, $child_ln,
6129 $child_is_transparent, $element_state) = @_;
6130 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
6131 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
6132 $self->{onerror}->(node => $child_el,
6133 type => 'element not allowed:minus',
6134 level => $self->{level}->{must});
6135 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
6136 #
6137 } elsif ($child_nsuri eq $HTML_NS and
6138 {
6139 option => 1, optgroup => 1,
6140 }->{$child_ln}) {
6141 #
6142 } else {
6143 $self->{onerror}->(node => $child_el, type => 'element not allowed',
6144 level => $self->{level}->{must});
6145 }
6146 },
6147 check_child_text => sub {
6148 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
6149 if ($has_significant) {
6150 $self->{onerror}->(node => $child_node, type => 'character not allowed',
6151 level => $self->{level}->{must});
6152 }
6153 },
6154 };
6155
6156 $Element->{$HTML_NS}->{datalist} = {
6157 %HTMLPhrasingContentChecker,
6158 ## TODO: |option| child MUST be empty [WF2]
6159 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6160 check_attrs => $GetHTMLAttrsChecker->({
6161 data => $HTMLURIAttrChecker, ## TODO: MUST point ... [WF2]
6162 }, {
6163 %HTMLAttrStatus,
6164 data => FEATURE_WF2,
6165 }),
6166 check_start => sub {
6167 my ($self, $item, $element_state) = @_;
6168
6169 $element_state->{phase} = 'any'; # any | phrasing | option
6170
6171 $element_state->{uri_info}->{data}->{type}->{resource} = 1;
6172 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6173 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6174 },
6175 ## NOTE: phrasing | option*
6176 check_child_element => sub {
6177 my ($self, $item, $child_el, $child_nsuri, $child_ln,
6178 $child_is_transparent, $element_state) = @_;
6179 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
6180 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
6181 $self->{onerror}->(node => $child_el,
6182 type => 'element not allowed:minus',
6183 level => $self->{level}->{must});
6184 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
6185 #
6186 } elsif ($element_state->{phase} eq 'phrasing') {
6187 if ($HTMLPhrasingContent->{$child_nsuri}->{$child_ln}) {
6188 #
6189 } else {
6190 $self->{onerror}->(node => $child_el,
6191 type => 'element not allowed:phrasing',
6192 level => $self->{level}->{must});
6193 }
6194 } elsif ($element_state->{phase} eq 'option') {
6195 if ($child_nsuri eq $HTML_NS and $child_ln eq 'option') {
6196 #
6197 } else {
6198 $self->{onerror}->(node => $child_el,
6199 type => 'element not allowed',
6200 level => $self->{level}->{must});
6201 }
6202 } elsif ($element_state->{phase} eq 'any') {
6203 if ($HTMLPhrasingContent->{$child_nsuri}->{$child_ln}) {
6204 $element_state->{phase} = 'phrasing';
6205 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'option') {
6206 $element_state->{phase} = 'option';
6207 } else {
6208 $self->{onerror}->(node => $child_el,
6209 type => 'element not allowed',
6210 level => $self->{level}->{must});
6211 }
6212 } else {
6213 die "check_child_element: Bad |datalist| phase: $element_state->{phase}";
6214 }
6215 },
6216 check_child_text => sub {
6217 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
6218 if ($has_significant) {
6219 if ($element_state->{phase} eq 'phrasing') {
6220 #
6221 } elsif ($element_state->{phase} eq 'any') {
6222 $element_state->{phase} = 'phrasing';
6223 } else {
6224 $self->{onerror}->(node => $child_node,
6225 type => 'character not allowed',
6226 level => $self->{level}->{must});
6227 }
6228 }
6229 },
6230 check_end => sub {
6231 my ($self, $item, $element_state) = @_;
6232 if ($element_state->{phase} eq 'phrasing') {
6233 if ($element_state->{has_significant}) {
6234 $item->{real_parent_state}->{has_significant} = 1;
6235 } elsif ($item->{transparent}) {
6236 #
6237 } else {
6238 $self->{onerror}->(node => $item->{node},
6239 type => 'no significant content',
6240 level => $self->{level}->{should});
6241 }
6242 } else {
6243 ## NOTE: Since the content model explicitly allows a |datalist| element
6244 ## being empty, we don't raise "no significant content" error for this
6245 ## element when there is no element. (We should raise an error for
6246 ## |<datalist><br></datalist>|, however.)
6247 ## NOTE: As a side-effect, when the |datalist| element only contains
6248 ## non-conforming content, then the |phase| flag has not changed from
6249 ## |any|, no "no significant content" error is raised neither.
6250 $HTMLChecker{check_end}->(@_);
6251 }
6252 },
6253 };
6254
6255 $Element->{$HTML_NS}->{optgroup} = {
6256 %HTMLChecker,
6257 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6258 check_attrs => $GetHTMLAttrsChecker->({
6259 disabled => $GetHTMLBooleanAttrChecker->('disabled'),
6260 label => sub {}, ## NOTE: Text [M12N] ## TODO: required
6261 }, {
6262 %HTMLAttrStatus,
6263 %HTMLM12NCommonAttrStatus,
6264 disabled => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6265 label => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6266 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
6267 }),
6268 check_child_element => sub {
6269 my ($self, $item, $child_el, $child_nsuri, $child_ln,
6270 $child_is_transparent, $element_state) = @_;
6271 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
6272 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
6273 $self->{onerror}->(node => $child_el,
6274 type => 'element not allowed:minus',
6275 level => $self->{level}->{must});
6276 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
6277 #
6278 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'option') {
6279 #
6280 } else {
6281 $self->{onerror}->(node => $child_el, type => 'element not allowed',
6282 level => $self->{level}->{must});
6283 }
6284 },
6285 check_child_text => sub {
6286 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
6287 if ($has_significant) {
6288 $self->{onerror}->(node => $child_node, type => 'character not allowed',
6289 level => $self->{level}->{must});
6290 }
6291 },
6292 };
6293
6294 $Element->{$HTML_NS}->{option} = {
6295 %HTMLTextChecker,
6296 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6297 check_attrs => $GetHTMLAttrsChecker->({
6298 disabled => $GetHTMLBooleanAttrChecker->('disabled'),
6299 label => sub {}, ## NOTE: Text [M12N]
6300 selected => $GetHTMLBooleanAttrChecker->('selected'),
6301 value => sub {}, ## NOTE: CDATA [M12N]
6302 }, {
6303 %HTMLAttrStatus,
6304 %HTMLM12NCommonAttrStatus,
6305 disabled => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6306 label => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6307 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
6308 sdaform => FEATURE_HTML20_RFC,
6309 sdapref => FEATURE_HTML20_RFC,
6310 selected => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6311 value => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6312 }),
6313 };
6314
6315 $Element->{$HTML_NS}->{textarea} = {
6316 %HTMLTextChecker,
6317 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6318 check_attrs => $GetHTMLAttrsChecker->({
6319 accept => $HTMLIMTAttrChecker, ## TODO: MUST be a text-based type
6320 accesskey => $HTMLAccesskeyAttrChecker,
6321 autofocus => $GetHTMLBooleanAttrChecker->('autofocus'),
6322 cols => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }), ## TODO: SHOULD if wrap=hard [WF2]
6323 disabled => $GetHTMLBooleanAttrChecker->('disabled'),
6324 form => $HTMLFormAttrChecker,
6325 ## TODO: inputmode [WF2]
6326 maxlength => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }),
6327 name => sub {}, ## NOTE: CDATA [M12N]
6328 onformchange => $HTMLEventHandlerAttrChecker,
6329 onforminput => $HTMLEventHandlerAttrChecker,
6330 oninput => $HTMLEventHandlerAttrChecker,
6331 ## TODO: pattern [WF2] ## TODO: |title| special semantics
6332 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
6333 required => $GetHTMLBooleanAttrChecker->('required'),
6334 rows => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }),
6335 oninput => $HTMLEventHandlerAttrChecker,
6336 oninvalid => $HTMLEventHandlerAttrChecker,
6337 wrap => $GetHTMLEnumeratedAttrChecker->({soft => 1, hard => 1}),
6338 }, {
6339 %HTMLAttrStatus,
6340 %HTMLM12NCommonAttrStatus,
6341 accept => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6342 'accept-charset' => FEATURE_HTML2X_RFC,
6343 accesskey => FEATURE_M12N10_REC,
6344 autofocus => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6345 cols => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6346 datafld => FEATURE_HTML4_REC_RESERVED,
6347 dataformatas => FEATURE_HTML4_REC_RESERVED,
6348 datasrc => FEATURE_HTML4_REC_RESERVED,
6349 disabled => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6350 form => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6351 inputmode => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_XHTMLBASIC11_CR,
6352 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
6353 maxlength => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6354 name => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6355 onblur => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6356 onchange => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6357 onfocus => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6358 onformchange => FEATURE_WF2_INFORMATIVE,
6359 onforminput => FEATURE_WF2_INFORMATIVE,
6360 oninput => FEATURE_WF2,
6361 oninvalid => FEATURE_WF2,
6362 onselect => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6363 pattern => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6364 readonly => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6365 required => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6366 rows => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6367 sdaform => FEATURE_HTML20_RFC,
6368 sdapref => FEATURE_HTML20_RFC,
6369 tabindex => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6370 wrap => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6371 }),
6372 check_start => sub {
6373 my ($self, $item, $element_state) = @_;
6374 if ($self->{flag}->{has_label} and $self->{flag}->{has_labelable}) {
6375 $self->{onerror}->(node => $item->{node},
6376 type => 'multiple labelable fae',
6377 level => $self->{level}->{must});
6378 } else {
6379 $self->{flag}->{has_labelable} = 2;
6380 }
6381
6382 $element_state->{uri_info}->{data}->{type}->{resource} = 1;
6383 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6384 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6385
6386 $element_state->{id_type} = 'labelable';
6387 },
6388 };
6389
6390 $Element->{$HTML_NS}->{output} = {
6391 %HTMLPhrasingContentChecker,
6392 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6393 check_attrs => $GetHTMLAttrsChecker->({
6394 ## TODO: for [WF2]
6395 form => $HTMLFormAttrChecker,
6396 ## TODO: name [WF2]
6397 onformchange => $HTMLEventHandlerAttrChecker,
6398 onforminput => $HTMLEventHandlerAttrChecker,
6399 }, {
6400 %HTMLAttrStatus,
6401 for => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6402 form => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6403 name => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6404 onchange => FEATURE_HTML5_DEFAULT | FEATURE_WF2,
6405 onformchange => FEATURE_WF2,
6406 onforminput => FEATURE_WF2,
6407 }),
6408 ## NOTE: "The output element should be used when ..." [WF2]
6409 };
6410
6411 $Element->{$HTML_NS}->{isindex} = {
6412 %HTMLEmptyChecker,
6413 status => FEATURE_M12N10_REC_DEPRECATED |
6414 Whatpm::ContentChecker::FEATURE_DEPRECATED_SHOULD, ## [HTML4]
6415 check_attrs => $GetHTMLAttrsChecker->({
6416 prompt => sub {}, ## NOTE: Text [M12N]
6417 }, {
6418 %HTMLAttrStatus,
6419 class => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6420 dir => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6421 id => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6422 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
6423 prompt => FEATURE_M12N10_REC_DEPRECATED,
6424 sdapref => FEATURE_HTML20_RFC,
6425 style => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
6426 title => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6427 }),
6428 ## TODO: Tests
6429 ## TODO: Tests for <nest/> in <isindex>
6430 check_start => sub {
6431 my ($self, $item, $element_state) = @_;
6432
6433 $element_state->{uri_info}->{action}->{type}->{action} = 1;
6434 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6435 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6436 },
6437 };
6438
6439 $Element->{$HTML_NS}->{script} = {
6440 %HTMLChecker,
6441 status => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6442 check_attrs => $GetHTMLAttrsChecker->({
6443 charset => sub {
6444 my ($self, $attr) = @_;
6445
6446 unless ($attr->owner_element->has_attribute_ns (undef, 'src')) {
6447 $self->{onerror}->(type => 'attribute not allowed',
6448 node => $attr,
6449 level => $self->{level}->{must});
6450 }
6451
6452 $HTMLCharsetChecker->($attr->value, @_);
6453 },
6454 language => sub {}, ## NOTE: No syntax constraint according to HTML4.
6455 src => $HTMLURIAttrChecker, ## TODO: pointed resource MUST be in type of type="" (resource error)
6456 defer => $GetHTMLBooleanAttrChecker->('defer'),
6457 async => $GetHTMLBooleanAttrChecker->('async'),
6458 type => $HTMLIMTAttrChecker, ## TODO: MUST NOT: |charset=""| parameter
6459 }, {
6460 %HTMLAttrStatus,
6461 async => FEATURE_HTML5_DEFAULT,
6462 charset => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6463 defer => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6464 event => FEATURE_HTML4_REC_RESERVED,
6465 for => FEATURE_HTML4_REC_RESERVED,
6466 href => FEATURE_RDFA_PR,
6467 id => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
6468 language => FEATURE_M12N10_REC_DEPRECATED,
6469 src => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6470 type => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6471 }),
6472 check_start => sub {
6473 my ($self, $item, $element_state) = @_;
6474
6475 if ($item->{node}->has_attribute_ns (undef, 'src')) {
6476 $element_state->{must_be_empty} = 1;
6477 } else {
6478 ## NOTE: No content model conformance in HTML5 spec.
6479 my $type = $item->{node}->get_attribute_ns (undef, 'type');
6480 my $language = $item->{node}->get_attribute_ns (undef, 'language');
6481 if ((defined $type and $type eq '') or
6482 (defined $language and $language eq '')) {
6483 $type = 'text/javascript';
6484 } elsif (defined $type) {
6485 #
6486 } elsif (defined $language) {
6487 $type = 'text/' . $language;
6488 } else {
6489 $type = 'text/javascript';
6490 }
6491
6492 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)]) {
6493 $type = "$1/$2";
6494 $type =~ tr/A-Z/a-z/; ## NOTE: ASCII case-insensitive
6495 ## TODO: Though we strip prameter here, it should not be ignored for the purpose of conformance checking...
6496 }
6497 $element_state->{script_type} = $type;
6498 }
6499
6500 $element_state->{uri_info}->{src}->{type}->{resource} = 1;
6501 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6502 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6503
6504 $element_state->{text} = '';
6505 },
6506 check_child_element => sub {
6507 my ($self, $item, $child_el, $child_nsuri, $child_ln,
6508 $child_is_transparent, $element_state) = @_;
6509 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
6510 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
6511 $self->{onerror}->(node => $child_el,
6512 type => 'element not allowed:minus',
6513 level => $self->{level}->{must});
6514 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
6515 #
6516 } else {
6517 if ($element_state->{must_be_empty}) {
6518 $self->{onerror}->(node => $child_el,
6519 type => 'element not allowed:empty',
6520 level => $self->{level}->{must});
6521 }
6522 }
6523 },
6524 check_child_text => sub {
6525 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
6526 if ($has_significant and
6527 $element_state->{must_be_empty}) {
6528 $self->{onerror}->(node => $child_node,
6529 type => 'character not allowed:empty',
6530 level => $self->{level}->{must});
6531 }
6532 $element_state->{text} .= $child_node->data;
6533 },
6534 check_end => sub {
6535 my ($self, $item, $element_state) = @_;
6536 unless ($element_state->{must_be_empty}) {
6537 if ($element_state->{script_type} =~ m![+/][Xx][Mm][Ll]\z!) {
6538 ## NOTE: XML content should be checked by THIS instance of checker
6539 ## as part of normal tree validation.
6540 $self->{onerror}->(node => $item->{node},
6541 type => 'XML script lang',
6542 text => $element_state->{script_type},
6543 level => $self->{level}->{uncertain});
6544 ## ISSUE: Should we raise some kind of error for
6545 ## <script type="text/xml">aaaaa</script>?
6546 ## NOTE: ^^^ This is why we throw an "uncertain" error.
6547 } else {
6548 $self->{onsubdoc}->({s => $element_state->{text},
6549 container_node => $item->{node},
6550 media_type => $element_state->{script_type},
6551 is_char_string => 1});
6552 }
6553
6554 $HTMLChecker{check_end}->(@_);
6555 }
6556 },
6557 ## TODO: There MUST be |type| unless the script type is JavaScript. (resource error)
6558 ## NOTE: "When used to include script data, the script data must be embedded
6559 ## inline, the format of the data must be given using the type attribute,
6560 ## and the src attribute must not be specified." - not testable.
6561 ## TODO: It would be possible to err <script type=text/plain src=...>
6562 };
6563 ## ISSUE: Significant check and text child node
6564
6565 ## NOTE: When script is disabled.
6566 $Element->{$HTML_NS}->{noscript} = {
6567 %HTMLTransparentChecker,
6568 status => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6569 check_attrs => $GetHTMLAttrsChecker->({}, {
6570 %HTMLAttrStatus,
6571 %HTMLM12NCommonAttrStatus,
6572 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
6573 }),
6574 check_start => sub {
6575 my ($self, $item, $element_state) = @_;
6576
6577 unless ($item->{node}->owner_document->manakai_is_html) {
6578 $self->{onerror}->(node => $item->{node}, type => 'in XML:noscript',
6579 level => $self->{level}->{must});
6580 }
6581
6582 unless ($self->{flag}->{in_head}) {
6583 $self->_add_minus_elements ($element_state,
6584 {$HTML_NS => {noscript => 1}});
6585 }
6586
6587 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6588 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6589 },
6590 check_child_element => sub {
6591 my ($self, $item, $child_el, $child_nsuri, $child_ln,
6592 $child_is_transparent, $element_state) = @_;
6593 if ($self->{flag}->{in_head}) {
6594 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
6595 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
6596 $self->{onerror}->(node => $child_el,
6597 type => 'element not allowed:minus',
6598 level => $self->{level}->{must});
6599 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
6600 #
6601 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'link') {
6602 #
6603 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'style') {
6604 if ($child_el->has_attribute_ns (undef, 'scoped')) {
6605 $self->{onerror}->(node => $child_el,
6606 type => 'element not allowed:head noscript',
6607 level => $self->{level}->{must});
6608 }
6609 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'meta') {
6610 my $http_equiv_attr
6611 = $child_el->get_attribute_node_ns (undef, 'http-equiv');
6612 if ($http_equiv_attr) {
6613 ## TODO: case
6614 if (lc $http_equiv_attr->value eq 'content-type') {
6615 $self->{onerror}->(node => $child_el,
6616 type => 'element not allowed:head noscript',
6617 level => $self->{level}->{must});
6618 } else {
6619 #
6620 }
6621 } else {
6622 $self->{onerror}->(node => $child_el,
6623 type => 'element not allowed:head noscript',
6624 level => $self->{level}->{must});
6625 }
6626 } else {
6627 $self->{onerror}->(node => $child_el,
6628 type => 'element not allowed:head noscript',
6629 level => $self->{level}->{must});
6630 }
6631 } else {
6632 $HTMLTransparentChecker{check_child_element}->(@_);
6633 }
6634 },
6635 check_child_text => sub {
6636 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
6637 if ($self->{flag}->{in_head}) {
6638 if ($has_significant) {
6639 $self->{onerror}->(node => $child_node,
6640 type => 'character not allowed',
6641 level => $self->{level}->{must});
6642 }
6643 } else {
6644 $HTMLTransparentChecker{check_child_text}->(@_);
6645 }
6646 },
6647 check_end => sub {
6648 my ($self, $item, $element_state) = @_;
6649 $self->_remove_minus_elements ($element_state);
6650 if ($self->{flag}->{in_head}) {
6651 $HTMLChecker{check_end}->(@_);
6652 } else {
6653 $HTMLPhrasingContentChecker{check_end}->(@_);
6654 }
6655 },
6656 };
6657 ## ISSUE: Scripting is disabled: <head><noscript><html a></noscript></head>
6658
6659 $Element->{$HTML_NS}->{'event-source'} = {
6660 %HTMLEmptyChecker,
6661 status => FEATURE_HTML5_LC_DROPPED,
6662 check_attrs => $GetHTMLAttrsChecker->({
6663 src => $HTMLURIAttrChecker,
6664 }, {
6665 %HTMLAttrStatus,
6666 src => FEATURE_HTML5_LC_DROPPED,
6667 }),
6668 check_start => sub {
6669 my ($self, $item, $element_state) = @_;
6670
6671 $element_state->{uri_info}->{src}->{type}->{resource} = 1;
6672 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6673 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6674 },
6675 };
6676
6677 $Element->{$HTML_NS}->{eventsource} = {
6678 %HTMLEmptyChecker,
6679 status => FEATURE_HTML5_DEFAULT,
6680 check_attrs => $GetHTMLAttrsChecker->({
6681 src => $HTMLURIAttrChecker,
6682 }, {
6683 %HTMLAttrStatus,
6684 src => FEATURE_HTML5_DEFAULT,
6685 }),
6686 check_start => sub {
6687 my ($self, $item, $element_state) = @_;
6688
6689 $element_state->{uri_info}->{src}->{type}->{resource} = 1;
6690 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6691 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6692 },
6693 };
6694
6695 $Element->{$HTML_NS}->{details} = {
6696 %{$Element->{$HTML_NS}->{fieldset}},
6697 status => FEATURE_HTML5_WD,
6698 check_attrs => $GetHTMLAttrsChecker->({
6699 open => $GetHTMLBooleanAttrChecker->('open'),
6700 }, {
6701 %HTMLAttrStatus,
6702 open => FEATURE_HTML5_WD,
6703 }),
6704 };
6705
6706 $Element->{$HTML_NS}->{datagrid} = {
6707 %HTMLFlowContentChecker,
6708 status => FEATURE_HTML5_WD,
6709 check_attrs => $GetHTMLAttrsChecker->({
6710 disabled => $GetHTMLBooleanAttrChecker->('disabled'),
6711 multiple => $GetHTMLBooleanAttrChecker->('multiple'),
6712 }, {
6713 %HTMLAttrStatus,
6714 disabled => FEATURE_HTML5_WD,
6715 multiple => FEATURE_HTML5_WD,
6716 }),
6717 check_start => sub {
6718 my ($self, $item, $element_state) = @_;
6719
6720 $self->_add_minus_elements ($element_state,
6721 {$HTML_NS => {a => 1, datagrid => 1}});
6722 $element_state->{phase} = 'any';
6723 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6724 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6725 },
6726 ## NOTE: Flow -(text* (table|select|datalist) Flow*) | table | select |
6727 ## datalist | Empty
6728 check_child_element => sub {
6729 my ($self, $item, $child_el, $child_nsuri, $child_ln,
6730 $child_is_transparent, $element_state) = @_;
6731 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
6732 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
6733 $self->{onerror}->(node => $child_el,
6734 type => 'element not allowed:minus',
6735 level => $self->{level}->{must});
6736 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
6737 #
6738 } elsif ($element_state->{phase} eq 'flow') {
6739 if ($HTMLFlowContent->{$child_nsuri}->{$child_ln}) {
6740 if (not $element_state->{has_element} and
6741 $child_nsuri eq $HTML_NS and
6742 {
6743 table => 1, select => 1, datalist => 1,
6744 }->{$child_ln}) {
6745 $self->{onerror}->(node => $child_el,
6746 type => 'element not allowed',
6747 level => $self->{level}->{must});
6748 } else {
6749 #
6750 }
6751 } else {
6752 $self->{onerror}->(node => $child_el,
6753 type => 'element not allowed', ## TODO: :flow
6754 level => $self->{level}->{must});
6755 }
6756 $element_state->{has_element} = 1;
6757 } elsif ($element_state->{phase} eq 'any') {
6758 if ($child_nsuri eq $HTML_NS and
6759 {table => 1, select => 1, datalist => 1}->{$child_ln}) {
6760 $element_state->{phase} = 'none';
6761 } elsif ($HTMLFlowContent->{$child_nsuri}->{$child_ln}) {
6762 $element_state->{has_element} = 1;
6763 $element_state->{phase} = 'flow';
6764 } else {
6765 $self->{onerror}->(node => $child_el,
6766 type => 'element not allowed',
6767 level => $self->{level}->{must});
6768 }
6769 } elsif ($element_state->{phase} eq 'none') {
6770 $self->{onerror}->(node => $child_el,
6771 type => 'element not allowed',
6772 level => $self->{level}->{must});
6773 } else {
6774 die "check_child_element: Bad |datagrid| phase: $element_state->{phase}";
6775 }
6776 },
6777 check_child_text => sub {
6778 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
6779 if ($has_significant) {
6780 if ($element_state->{phase} eq 'flow') {
6781 #
6782 } elsif ($element_state->{phase} eq 'any') {
6783 $element_state->{phase} = 'flow';
6784 } else {
6785 $self->{onerror}->(node => $child_node,
6786 type => 'character not allowed',
6787 level => $self->{level}->{must});
6788 }
6789 }
6790 },
6791 check_end => sub {
6792 my ($self, $item, $element_state) = @_;
6793 $self->_remove_minus_elements ($element_state);
6794
6795 if ($element_state->{phase} eq 'flow') {
6796 if ($element_state->{has_significant}) {
6797 $item->{real_parent_state}->{has_significant} = 1;
6798 } elsif ($item->{transparent}) {
6799 #
6800 } else {
6801 $self->{onerror}->(node => $item->{node},
6802 type => 'no significant content',
6803 level => $self->{level}->{should});
6804 }
6805 } else {
6806 ## NOTE: Since the content model explicitly allows a |datagird| element
6807 ## being empty, we don't raise "no significant content" error for this
6808 ## element when there is no element. (We should raise an error for
6809 ## |<datagrid><br></datagrid>|, however.)
6810 ## NOTE: As a side-effect, when the |datagrid| element only contains
6811 ## non-conforming content, then the |phase| flag has not changed from
6812 ## |any|, no "no significant content" error is raised neither.
6813 ## NOTE: Another side-effect of the current implementation:
6814 ## |<daragrid><datagrid/></datagrid>| has no "no significant content"
6815 ## error at all.
6816 $HTMLChecker{check_end}->(@_);
6817 }
6818 },
6819 };
6820
6821 $Element->{$HTML_NS}->{command} = {
6822 %HTMLEmptyChecker,
6823 status => FEATURE_HTML5_WD,
6824 check_attrs => $GetHTMLAttrsChecker->({
6825 checked => $GetHTMLBooleanAttrChecker->('checked'),
6826 default => $GetHTMLBooleanAttrChecker->('default'),
6827 disabled => $GetHTMLBooleanAttrChecker->('disabled'),
6828 icon => $HTMLURIAttrChecker,
6829 label => sub { }, ## NOTE: No conformance creteria
6830 radiogroup => sub { }, ## NOTE: No conformance creteria
6831 type => sub {
6832 my ($self, $attr) = @_;
6833 my $value = $attr->value;
6834 unless ({command => 1, checkbox => 1, radio => 1}->{$value}) {
6835 $self->{onerror}->(node => $attr, type => 'invalid attribute value',
6836 level => $self->{level}->{must});
6837 }
6838 },
6839 }, {
6840 %HTMLAttrStatus,
6841 checked => FEATURE_HTML5_WD,
6842 default => FEATURE_HTML5_WD,
6843 disabled => FEATURE_HTML5_WD,
6844 icon => FEATURE_HTML5_WD,
6845 label => FEATURE_HTML5_WD,
6846 radiogroup => FEATURE_HTML5_WD,
6847 type => FEATURE_HTML5_WD,
6848 }),
6849 check_start => sub {
6850 my ($self, $item, $element_state) = @_;
6851
6852 $element_state->{uri_info}->{icon}->{type}->{embedded} = 1;
6853 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6854 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6855 },
6856 };
6857
6858 $Element->{$HTML_NS}->{bb} = {
6859 %HTMLPhrasingContentChecker,
6860 status => FEATURE_HTML5_DEFAULT,
6861 check_attrs => $GetHTMLAttrsChecker->({
6862 type => $GetHTMLEnumeratedAttrChecker->({makeapp => 1}),
6863 }, {
6864 %HTMLAttrStatus,
6865 type => FEATURE_HTML5_DEFAULT,
6866 }),
6867 check_start => sub {
6868 my ($self, $item, $element_state) = @_;
6869 $self->_add_minus_elements ($element_state, $HTMLInteractiveContent);
6870
6871 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6872 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6873 },
6874 check_end => sub {
6875 my ($self, $item, $element_state) = @_;
6876 $self->_remove_minus_elements ($element_state);
6877
6878 $HTMLTransparentChecker{check_end}->(@_);
6879 },
6880 };
6881
6882 $Element->{$HTML_NS}->{menu} = {
6883 %HTMLPhrasingContentChecker,
6884 #status => FEATURE_M12N10_REC_DEPRECATED | FEATURE_HTML5_WD,
6885 status => FEATURE_M12N10_REC | FEATURE_HTML5_WD,
6886 ## NOTE: We don't want any |menu| element warned as deprecated.
6887 check_attrs => $GetHTMLAttrsChecker->({
6888 autosubmit => $GetHTMLBooleanAttrChecker->('autosubmit'),
6889 compact => $GetHTMLBooleanAttrChecker->('compact'),
6890 ## ISSUE: <menu id=""><p contextmenu=""> match? (In the current
6891 ## implementation, it does not match.)
6892 label => sub { }, ## NOTE: No conformance creteria
6893 type => $GetHTMLEnumeratedAttrChecker->({context => 1, toolbar => 1}),
6894 }, {
6895 %HTMLAttrStatus,
6896 %HTMLM12NCommonAttrStatus,
6897 align => FEATURE_HTML2X_RFC,
6898 autosubmit => FEATURE_HTML5_DROPPED,
6899 compat => FEATURE_M12N10_REC_DEPRECATED,
6900 label => FEATURE_HTML5_WD,
6901 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
6902 sdaform => FEATURE_HTML20_RFC,
6903 sdapref => FEATURE_HTML20_RFC,
6904 type => FEATURE_HTML5_WD,
6905 }),
6906 check_start => sub {
6907 my ($self, $item, $element_state) = @_;
6908 $element_state->{phase} = 'li or phrasing';
6909 $element_state->{in_menu_original} = $self->{flag}->{in_menu};
6910 $self->{flag}->{in_menu} = 1;
6911
6912 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6913 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6914 $element_state->{id_type} = 'menu';
6915 },
6916 check_child_element => sub {
6917 my ($self, $item, $child_el, $child_nsuri, $child_ln,
6918 $child_is_transparent, $element_state) = @_;
6919 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
6920 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
6921 $self->{onerror}->(node => $child_el,
6922 type => 'element not allowed:minus',
6923 level => $self->{level}->{must});
6924 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
6925 #
6926 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'li') {
6927 if ($element_state->{phase} eq 'li') {
6928 #
6929 } elsif ($element_state->{phase} eq 'li or phrasing') {
6930 $element_state->{phase} = 'li';
6931 } else {
6932 $self->{onerror}->(node => $child_el, type => 'element not allowed',
6933 level => $self->{level}->{must});
6934 }
6935 } elsif ($HTMLPhrasingContent->{$child_nsuri}->{$child_ln}) {
6936 if ($element_state->{phase} eq 'phrasing') {
6937 #
6938 } elsif ($element_state->{phase} eq 'li or phrasing') {
6939 $element_state->{phase} = 'phrasing';
6940 } else {
6941 $self->{onerror}->(node => $child_el, type => 'element not allowed',
6942 level => $self->{level}->{must});
6943 }
6944 } else {
6945 $self->{onerror}->(node => $child_el, type => 'element not allowed',
6946 level => $self->{level}->{must});
6947 }
6948 },
6949 check_child_text => sub {
6950 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
6951 if ($has_significant) {
6952 if ($element_state->{phase} eq 'phrasing') {
6953 #
6954 } elsif ($element_state->{phase} eq 'li or phrasing') {
6955 $element_state->{phase} = 'phrasing';
6956 } else {
6957 $self->{onerror}->(node => $child_node,
6958 type => 'character not allowed',
6959 level => $self->{level}->{must});
6960 }
6961 }
6962 },
6963 check_end => sub {
6964 my ($self, $item, $element_state) = @_;
6965 delete $self->{flag}->{in_menu} unless $element_state->{in_menu_original};
6966
6967 if ($element_state->{phase} eq 'li') {
6968 $HTMLChecker{check_end}->(@_);
6969 } else { # 'phrasing' or 'li or phrasing'
6970 $HTMLPhrasingContentChecker{check_end}->(@_);
6971 }
6972 },
6973 };
6974
6975 $Element->{$HTML_NS}->{datatemplate} = {
6976 %HTMLChecker,
6977 status => FEATURE_HTML5_AT_RISK,
6978 check_child_element => sub {
6979 my ($self, $item, $child_el, $child_nsuri, $child_ln,
6980 $child_is_transparent, $element_state) = @_;
6981 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
6982 $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
6983 $self->{onerror}->(node => $child_el,
6984 type => 'element not allowed:minus',
6985 level => $self->{level}->{must});
6986 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
6987 #
6988 } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'rule') {
6989 #
6990 } else {
6991 $self->{onerror}->(node => $child_el,
6992 type => 'element not allowed:datatemplate',
6993 level => $self->{level}->{must});
6994 }
6995 },
6996 check_child_text => sub {
6997 my ($self, $item, $child_node, $has_significant, $element_state) = @_;
6998 if ($has_significant) {
6999 $self->{onerror}->(node => $child_node, type => 'character not allowed',
7000 level => $self->{level}->{must});
7001 }
7002 },
7003 is_xml_root => 1,
7004 };
7005
7006 $Element->{$HTML_NS}->{rule} = {
7007 %HTMLChecker,
7008 status => FEATURE_HTML5_AT_RISK,
7009 check_attrs => $GetHTMLAttrsChecker->({
7010 condition => $HTMLSelectorsAttrChecker,
7011 mode => $GetHTMLUnorderedUniqueSetOfSpaceSeparatedTokensAttrChecker->(),
7012 }, {
7013 %HTMLAttrStatus,
7014 condition => FEATURE_HTML5_AT_RISK,
7015 mode => FEATURE_HTML5_AT_RISK,
7016 }),
7017 check_start => sub {
7018 my ($self, $item, $element_state) = @_;
7019
7020 $self->_add_plus_elements ($element_state, {$HTML_NS => {nest => 1}});
7021 $element_state->{in_rule_original} = $self->{flag}->{in_rule};
7022 $self->{flag}->{in_rule} = 1;
7023
7024 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
7025 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
7026 },
7027 check_child_element => sub { },
7028 check_child_text => sub { },
7029 check_end => sub {
7030 my ($self, $item, $element_state) = @_;
7031
7032 $self->_remove_plus_elements ($element_state);
7033 delete $self->{flag}->{in_rule} unless $element_state->{in_rule_original};
7034
7035 $HTMLChecker{check_end}->(@_);
7036 },
7037 ## NOTE: "MAY be anything that, when the parent |datatemplate|
7038 ## is applied to some conforming data, results in a conforming DOM tree.":
7039 ## We don't check against this.
7040 };
7041
7042 $Element->{$HTML_NS}->{nest} = {
7043 %HTMLEmptyChecker,
7044 status => FEATURE_HTML5_AT_RISK,
7045 check_attrs => $GetHTMLAttrsChecker->({
7046 filter => $HTMLSelectorsAttrChecker,
7047 mode => sub {
7048 my ($self, $attr) = @_;
7049 my $value = $attr->value;
7050 if ($value !~ /\A[^\x09\x0A\x0C\x0D\x20]+\z/) {
7051 $self->{onerror}->(node => $attr, type => 'mode:syntax error',
7052 level => $self->{level}->{must});
7053 }
7054 },
7055 }, {
7056 %HTMLAttrStatus,
7057 filter => FEATURE_HTML5_AT_RISK,
7058 mode => FEATURE_HTML5_AT_RISK,
7059 }),
7060 };
7061
7062 $Element->{$HTML_NS}->{legend} = {
7063 %HTMLPhrasingContentChecker,
7064 status => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
7065 check_attrs => $GetHTMLAttrsChecker->({
7066 accesskey => $HTMLAccesskeyAttrChecker,
7067 # align => $GetHTMLEnumeratedAttrChecker->({
7068 # top => 1, bottom => 1, left => 1, right => 1,
7069 # }),
7070 }, {
7071 %HTMLAttrStatus,
7072 %HTMLM12NCommonAttrStatus,
7073 accesskey => FEATURE_M12N10_REC,
7074 align => FEATURE_M12N10_REC_DEPRECATED,
7075 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
7076 }),
7077 };
7078
7079 $Element->{$HTML_NS}->{div} = {
7080 %HTMLFlowContentChecker,
7081 status => FEATURE_HTML5_DEFAULT | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
7082 check_attrs => $GetHTMLAttrsChecker->({
7083 align => $GetHTMLEnumeratedAttrChecker->({
7084 left => 1, center => 1, right => 1, justify => 1,
7085 }),
7086 }, {
7087 %HTMLAttrStatus,
7088 %HTMLM12NXHTML2CommonAttrStatus,
7089 align => FEATURE_M12N10_REC_DEPRECATED,
7090 datafld => FEATURE_HTML4_REC_RESERVED,
7091 dataformatas => FEATURE_HTML4_REC_RESERVED,
7092 datasrc => FEATURE_HTML4_REC_RESERVED,
7093 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
7094 }),
7095 check_start => sub {
7096 my ($self, $item, $element_state) = @_;
7097
7098 $element_state->{uri_info}->{datasrc}->{type}->{resource} = 1;
7099 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
7100 $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
7101 },
7102 };
7103
7104 $Element->{$HTML_NS}->{center} = {
7105 %HTMLFlowContentChecker,
7106 status => FEATURE_M12N10_REC_DEPRECATED,
7107 check_attrs => $GetHTMLAttrsChecker->({}, {
7108 %HTMLAttrStatus,
7109 %HTMLM12NCommonAttrStatus,
7110 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
7111 }),
7112 };
7113
7114 $Element->{$HTML_NS}->{font} = {
7115 %HTMLTransparentChecker,
7116 status => FEATURE_HTML5_DROPPED | FEATURE_M12N10_REC_DEPRECATED,
7117 check_attrs => $GetHTMLAttrsChecker->({
7118 ## TODO: HTML4 |size|, |color|, |face|
7119 }, {
7120 %HTMLAttrStatus,
7121 class => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
7122 color => FEATURE_M12N10_REC_DEPRECATED,
7123 dir => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
7124 face => FEATURE_M12N10_REC_DEPRECATED,
7125 id => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
7126 lang => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
7127 size => FEATURE_M12N10_REC_DEPRECATED,
7128 style => FEATURE_HTML5_DEFAULT | FEATURE_XHTML10_REC,
7129 title => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
7130 }),
7131 ## NOTE: When the |font| element was defined in the HTML5 specification,
7132 ## it is allowed only in a document with the WYSIWYG signature. The
7133 ## checker does not check whether there is the signature, since the
7134 ## signature is dropped, too, and has never been implemented. (In addition,
7135 ## for any |font| element an "element not defined" error is raised anyway,
7136 ## such that we don't have to raise an additional error.)
7137 };
7138
7139 $Element->{$HTML_NS}->{basefont} = {
7140 %HTMLEmptyChecker,
7141 status => FEATURE_M12N10_REC_DEPRECATED,
7142 check_attrs => $GetHTMLAttrsChecker->({
7143 ## TODO: color, face, size
7144 }, {
7145 %HTMLAttrStatus,
7146 color => FEATURE_M12N10_REC_DEPRECATED,
7147 face => FEATURE_M12N10_REC_DEPRECATED,
7148 #id => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC_DEPRECATED,
7149 id => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
7150 size => FEATURE_M12N10_REC_DEPRECATED,
7151 }),
7152 };
7153
7154 ## TODO: frameset FEATURE_M12N10_REC
7155 ## class title id cols rows onload onunload style(x10)
7156 ## frame frameborder longdesc marginheight marginwidth noresize scrolling src name(deprecated) class,id,title,style(x10)
7157 ## noframes Common, lang(xhtml10)
7158
7159 ## TODO: CR: rbc rtc @rbspan (M12NXHTML2Common)
7160
7161 ## TODO: xmp, listing, plaintext FEATURE_HTML32_REC_OBSOLETE
7162 ## TODO: ^^^ lang, dir, id, class [HTML 2.x] sdaform [HTML 2.0]
7163 ## xmp, listing sdapref[HTML2,0]
7164
7165 =pod
7166
7167 WF2: Documents MUST comply to [CHARMOD].
7168 WF2: Vencor extensions MUST NOT be used.
7169
7170 HTML 2.0 nextid @n
7171
7172 RFC 2659: CERTS CRYPTOPTS
7173
7174 ISO-HTML: pre-html, divN
7175
7176 XHTML2: blockcode (Common), h (Common), separator (Common), l (Common),
7177 di (Common), nl (Common), handler (Common, type), standby (Common),
7178 summary (Common)
7179
7180 Access & XHTML2: access (LC)
7181
7182 XML Events & XForms (for XHTML2 support; very, very low priority)
7183
7184 =cut
7185
7186 ## NOTE: Where RFC 2659 allows additional attributes is unclear.
7187 ## We added them only to |a|. |link| and |form| might also allow them
7188 ## in theory.
7189
7190 $Whatpm::ContentChecker::Namespace->{$HTML_NS}->{loaded} = 1;
7191
7192 1;

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24