/[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.197 - (hide annotations) (download)
Sat Aug 22 10:13:39 2009 UTC (15 years, 11 months ago) by wakaba
Branch: MAIN
Changes since 1.196: +5 -20 lines
++ whatpm/t/dom-conformance/ChangeLog	22 Aug 2009 10:12:51 -0000
	* html-interactive-1.dat, html-datatemplate.dat,
	html-form-label.dat, html-links-1.dat: Revised test data on
	|datagrid| (HTML5 revision 2962, HTML5 revision 3555).

2009-08-22  Wakaba  <wakaba@suika.fam.cx>

++ whatpm/Whatpm/ContentChecker/ChangeLog	22 Aug 2009 10:13:28 -0000
	* HTML.pm: The |datagrid| element has been commented out in the
	spec (HTML5 revision 2962, HTML5 revision 3555).

2009-08-22  Wakaba  <wakaba@suika.fam.cx>

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24