/[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.192 - (hide annotations) (download)
Sat Aug 22 05:05:15 2009 UTC (17 years, 1 month ago) by wakaba
Branch: MAIN
Changes since 1.191: +81 -45 lines
++ whatpm/t/dom-conformance/ChangeLog	22 Aug 2009 05:05:07 -0000
2009-08-22  Wakaba  <wakaba@suika.fam.cx>

	* html-form-label.dat: Added tests on |<label for=a><input
	id=a></label>|-handlings (cf. HTML5 revision 2614).

++ whatpm/Whatpm/ContentChecker/ChangeLog	22 Aug 2009 05:04:33 -0000
2009-08-22  Wakaba  <wakaba@suika.fam.cx>

	* HTML.pm: Allows |<label for=a><input id=a></label>| (HTML5
	revision 2614).

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24