/[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.167 - (hide annotations) (download)
Sat Dec 13 04:49:04 2008 UTC (16 years, 7 months ago) by wakaba
Branch: MAIN
Changes since 1.166: +10 -3 lines
++ whatpm/t/ChangeLog	13 Dec 2008 04:48:14 -0000
2008-12-13  Wakaba  <wakaba@suika.fam.cx>

	* content-model-7.dat: Added an example from HTML5 spec.

++ whatpm/t/dom-conformance/ChangeLog	13 Dec 2008 04:48:48 -0000
	* html-form-label.dat, html-forms-1.dat: Added test data for
	form="".

2008-12-13  Wakaba  <wakaba@suika.fam.cx>

++ whatpm/Whatpm/ContentChecker/ChangeLog	13 Dec 2008 04:47:22 -0000
	* HTML.pm: Implemented <object form> and <legend form>.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24