/[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.165 - (hide annotations) (download)
Fri Dec 12 12:59:17 2008 UTC (17 years, 9 months ago) by wakaba
Branch: MAIN
Changes since 1.164: +64 -23 lines
++ whatpm/t/dom-conformance/ChangeLog	12 Dec 2008 12:59:07 -0000
	* html-form-input.dat: Added test data for <input disabled> and
	<input autofocus>.

	* html-forms-1.dat: Added test data for <fieldset disabled>,
	<output for>, and <{formcontrol} name>.  Added test data for
	<{formcontrol} autofocus="">'s uniqueness.

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

++ whatpm/Whatpm/ChangeLog	12 Dec 2008 12:54:19 -0000
	* ContentChecker.pm: Introduced new flag |has_autofocus|.  Added
	support for new |id_type| value of |any|, which means any kind of
	element is allowed as a referenced element.

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

++ whatpm/Whatpm/ContentChecker/ChangeLog	12 Dec 2008 12:56:56 -0000
	* HTML.pm: Added checking for the uniqueness of |autofocus=""|
	attribute.  Implemented |name=""| attribute for form controls.
	Added U+000C into character class of space characters (it should
	have been, but was not in some of regular expressions).  Updated
	definitions for |output| elements.

2008-12-12  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     hspace => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }),
4364 wakaba 1.76 name => $HTMLBrowsingContextNameAttrChecker,
4365 wakaba 1.70 standby => sub {}, ## NOTE: %Text; in HTML4
4366 wakaba 1.1 type => $HTMLIMTAttrChecker,
4367     usemap => $HTMLUsemapAttrChecker,
4368 wakaba 1.92 height => $AttrCheckerNotImplemented, ## TODO: spec does not define yet
4369 wakaba 1.70 vspace => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }),
4370 wakaba 1.92 width => $AttrCheckerNotImplemented, ## TODO: spec does not define yet
4371 wakaba 1.49 }, {
4372     %HTMLAttrStatus,
4373 wakaba 1.82 %HTMLM12NXHTML2CommonAttrStatus,
4374 wakaba 1.49 align => FEATURE_XHTML10_REC,
4375 wakaba 1.82 archive => FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
4376 wakaba 1.49 border => FEATURE_XHTML10_REC,
4377     classid => FEATURE_M12N10_REC,
4378     codebase => FEATURE_M12N10_REC,
4379     codetype => FEATURE_M12N10_REC,
4380 wakaba 1.82 'content-length' => FEATURE_XHTML2_ED,
4381 wakaba 1.153 data => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
4382 wakaba 1.49 datafld => FEATURE_HTML4_REC_RESERVED,
4383     dataformatas => FEATURE_HTML4_REC_RESERVED,
4384     datasrc => FEATURE_HTML4_REC_RESERVED,
4385 wakaba 1.82 declare => FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
4386 wakaba 1.153 height => FEATURE_HTML5_LC | FEATURE_M12N10_REC,
4387 wakaba 1.49 hspace => FEATURE_XHTML10_REC,
4388 wakaba 1.153 lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
4389     name => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
4390 wakaba 1.49 standby => FEATURE_M12N10_REC,
4391 wakaba 1.50 tabindex => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4392 wakaba 1.153 type => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
4393     usemap => FEATURE_HTML5_LC | FEATURE_M12N10_REC,
4394 wakaba 1.49 vspace => FEATURE_XHTML10_REC,
4395 wakaba 1.153 width => FEATURE_HTML5_LC | FEATURE_M12N10_REC,
4396 wakaba 1.66 })->($self, $item, $element_state);
4397 wakaba 1.40 unless ($item->{node}->has_attribute_ns (undef, 'data')) {
4398     unless ($item->{node}->has_attribute_ns (undef, 'type')) {
4399     $self->{onerror}->(node => $item->{node},
4400 wakaba 1.104 type => 'attribute missing:data|type',
4401     level => $self->{level}->{must});
4402 wakaba 1.1 }
4403     }
4404 wakaba 1.66
4405     $element_state->{uri_info}->{data}->{type}->{embedded} = 1;
4406     $element_state->{uri_info}->{classid}->{type}->{embedded} = 1;
4407     $element_state->{uri_info}->{codebase}->{type}->{base} = 1;
4408     ## TODO: archive
4409     $element_state->{uri_info}->{datasrc}->{type}->{resource} = 1;
4410 wakaba 1.1 },
4411 wakaba 1.72 ## NOTE: param*, transparent (Flow)
4412 wakaba 1.41 check_child_element => sub {
4413     my ($self, $item, $child_el, $child_nsuri, $child_ln,
4414     $child_is_transparent, $element_state) = @_;
4415 wakaba 1.130 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
4416     $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
4417 wakaba 1.41 $self->{onerror}->(node => $child_el,
4418     type => 'element not allowed:minus',
4419 wakaba 1.104 level => $self->{level}->{must});
4420 wakaba 1.41 $element_state->{has_non_legend} = 1;
4421     } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
4422     #
4423     } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'param') {
4424     if ($element_state->{has_non_param}) {
4425 wakaba 1.104 $self->{onerror}->(node => $child_el,
4426 wakaba 1.72 type => 'element not allowed:flow',
4427 wakaba 1.104 level => $self->{level}->{must});
4428 wakaba 1.39 }
4429 wakaba 1.41 } else {
4430 wakaba 1.72 $HTMLFlowContentChecker{check_child_element}->(@_);
4431 wakaba 1.41 $element_state->{has_non_param} = 1;
4432 wakaba 1.39 }
4433 wakaba 1.25 },
4434 wakaba 1.41 check_child_text => sub {
4435     my ($self, $item, $child_node, $has_significant, $element_state) = @_;
4436     if ($has_significant) {
4437     $element_state->{has_non_param} = 1;
4438     }
4439 wakaba 1.42 },
4440     check_end => sub {
4441     my ($self, $item, $element_state) = @_;
4442     if ($element_state->{has_significant}) {
4443 wakaba 1.46 $item->{real_parent_state}->{has_significant} = 1;
4444 wakaba 1.42 } elsif ($item->{node}->manakai_parent_element) {
4445     ## NOTE: Transparent.
4446     } else {
4447     $self->{onerror}->(node => $item->{node},
4448 wakaba 1.104 level => $self->{level}->{should},
4449 wakaba 1.42 type => 'no significant content');
4450     }
4451     },
4452 wakaba 1.8 ## TODO: Tests for <nest/> in <object/>
4453 wakaba 1.1 };
4454 wakaba 1.41 ## ISSUE: Is |<menu><object data><li>aa</li></object></menu>| conforming?
4455     ## What about |<section><object data><style scoped></style>x</object></section>|?
4456     ## |<section><ins></ins><object data><style scoped></style>x</object></section>|?
4457 wakaba 1.1
4458     $Element->{$HTML_NS}->{param} = {
4459 wakaba 1.40 %HTMLEmptyChecker,
4460 wakaba 1.153 status => FEATURE_HTML5_WD | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
4461 wakaba 1.40 check_attrs => sub {
4462     my ($self, $item, $element_state) = @_;
4463 wakaba 1.1 $GetHTMLAttrsChecker->({
4464     name => sub { },
4465 wakaba 1.70 type => $HTMLIMTAttrChecker,
4466 wakaba 1.1 value => sub { },
4467 wakaba 1.70 valuetype => $GetHTMLEnumeratedAttrChecker->({
4468     data => 1, ref => 1, object => 1,
4469     }),
4470 wakaba 1.49 }, {
4471     %HTMLAttrStatus,
4472 wakaba 1.154 href => FEATURE_RDFA_REC,
4473 wakaba 1.153 id => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
4474     name => FEATURE_HTML5_WD | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
4475 wakaba 1.82 type => FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
4476 wakaba 1.153 value => FEATURE_HTML5_WD | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
4477 wakaba 1.82 valuetype => FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
4478 wakaba 1.66 })->(@_);
4479 wakaba 1.40 unless ($item->{node}->has_attribute_ns (undef, 'name')) {
4480     $self->{onerror}->(node => $item->{node},
4481 wakaba 1.104 type => 'attribute missing',
4482     text => 'name',
4483     level => $self->{level}->{must});
4484 wakaba 1.1 }
4485 wakaba 1.40 unless ($item->{node}->has_attribute_ns (undef, 'value')) {
4486     $self->{onerror}->(node => $item->{node},
4487 wakaba 1.104 type => 'attribute missing',
4488     text => 'value',
4489     level => $self->{level}->{must});
4490 wakaba 1.1 }
4491     },
4492     };
4493    
4494     $Element->{$HTML_NS}->{video} = {
4495 wakaba 1.40 %HTMLTransparentChecker,
4496 wakaba 1.48 status => FEATURE_HTML5_LC,
4497 wakaba 1.40 check_attrs => $GetHTMLAttrsChecker->({
4498 wakaba 1.1 src => $HTMLURIAttrChecker,
4499     ## TODO: start, loopstart, loopend, end
4500     ## ISSUE: they MUST be "value time offset"s. Value?
4501 wakaba 1.11 ## ISSUE: playcount has no conformance creteria
4502 wakaba 1.1 autoplay => $GetHTMLBooleanAttrChecker->('autoplay'),
4503     controls => $GetHTMLBooleanAttrChecker->('controls'),
4504 wakaba 1.59 poster => $HTMLURIAttrChecker,
4505 wakaba 1.92 height => $AttrCheckerNotImplemented, ## TODO: spec does not define yet
4506     width => $AttrCheckerNotImplemented, ## TODO: spec does not define yet
4507 wakaba 1.50 }, {
4508     %HTMLAttrStatus,
4509     autoplay => FEATURE_HTML5_LC,
4510     controls => FEATURE_HTML5_LC,
4511 wakaba 1.153 end => FEATURE_HTML5_AT_RISK,
4512 wakaba 1.50 height => FEATURE_HTML5_LC,
4513 wakaba 1.153 loopend => FEATURE_HTML5_AT_RISK,
4514     loopstart => FEATURE_HTML5_AT_RISK,
4515     playcount => FEATURE_HTML5_AT_RISK,
4516 wakaba 1.50 poster => FEATURE_HTML5_LC,
4517     src => FEATURE_HTML5_LC,
4518 wakaba 1.153 start => FEATURE_HTML5_AT_RISK,
4519 wakaba 1.50 width => FEATURE_HTML5_LC,
4520 wakaba 1.1 }),
4521 wakaba 1.42 check_start => sub {
4522     my ($self, $item, $element_state) = @_;
4523     $element_state->{allow_source}
4524     = not $item->{node}->has_attribute_ns (undef, 'src');
4525     $element_state->{has_source} ||= $element_state->{allow_source} * -1;
4526     ## NOTE: It might be set true by |check_element|.
4527 wakaba 1.66
4528     $element_state->{uri_info}->{src}->{type}->{embedded} = 1;
4529     $element_state->{uri_info}->{poster}->{type}->{embedded} = 1;
4530 wakaba 1.79 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
4531     $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
4532 wakaba 1.42 },
4533     check_child_element => sub {
4534     my ($self, $item, $child_el, $child_nsuri, $child_ln,
4535     $child_is_transparent, $element_state) = @_;
4536 wakaba 1.130 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
4537     $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
4538 wakaba 1.42 $self->{onerror}->(node => $child_el,
4539     type => 'element not allowed:minus',
4540 wakaba 1.104 level => $self->{level}->{must});
4541 wakaba 1.42 delete $element_state->{allow_source};
4542     } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
4543     #
4544     } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'source') {
4545 wakaba 1.45 unless ($element_state->{allow_source}) {
4546 wakaba 1.104 $self->{onerror}->(node => $child_el,
4547 wakaba 1.72 type => 'element not allowed:flow',
4548 wakaba 1.104 level => $self->{level}->{must});
4549 wakaba 1.42 }
4550 wakaba 1.45 $element_state->{has_source} = 1;
4551 wakaba 1.1 } else {
4552 wakaba 1.42 delete $element_state->{allow_source};
4553 wakaba 1.72 $HTMLFlowContentChecker{check_child_element}->(@_);
4554 wakaba 1.42 }
4555     },
4556     check_child_text => sub {
4557     my ($self, $item, $child_node, $has_significant, $element_state) = @_;
4558     if ($has_significant) {
4559     delete $element_state->{allow_source};
4560     }
4561 wakaba 1.72 $HTMLFlowContentChecker{check_child_text}->(@_);
4562 wakaba 1.42 },
4563     check_end => sub {
4564     my ($self, $item, $element_state) = @_;
4565     if ($element_state->{has_source} == -1) {
4566     $self->{onerror}->(node => $item->{node},
4567 wakaba 1.104 type => 'child element missing',
4568     text => 'source',
4569     level => $self->{level}->{must});
4570 wakaba 1.1 }
4571 wakaba 1.42
4572     $Element->{$HTML_NS}->{object}->{check_end}->(@_);
4573 wakaba 1.1 },
4574     };
4575    
4576     $Element->{$HTML_NS}->{audio} = {
4577 wakaba 1.40 %{$Element->{$HTML_NS}->{video}},
4578 wakaba 1.48 status => FEATURE_HTML5_LC,
4579 wakaba 1.42 check_attrs => $GetHTMLAttrsChecker->({
4580     src => $HTMLURIAttrChecker,
4581     ## TODO: start, loopstart, loopend, end
4582     ## ISSUE: they MUST be "value time offset"s. Value?
4583     ## ISSUE: playcount has no conformance creteria
4584     autoplay => $GetHTMLBooleanAttrChecker->('autoplay'),
4585     controls => $GetHTMLBooleanAttrChecker->('controls'),
4586 wakaba 1.50 }, {
4587     %HTMLAttrStatus,
4588     autoplay => FEATURE_HTML5_LC,
4589     controls => FEATURE_HTML5_LC,
4590 wakaba 1.153 end => FEATURE_HTML5_AT_RISK,
4591     loopend => FEATURE_HTML5_AT_RISK,
4592     loopstart => FEATURE_HTML5_AT_RISK,
4593     playcount => FEATURE_HTML5_AT_RISK,
4594 wakaba 1.50 src => FEATURE_HTML5_LC,
4595 wakaba 1.153 start => FEATURE_HTML5_AT_RISK,
4596 wakaba 1.42 }),
4597 wakaba 1.1 };
4598    
4599     $Element->{$HTML_NS}->{source} = {
4600 wakaba 1.40 %HTMLEmptyChecker,
4601 wakaba 1.153 status => FEATURE_HTML5_LC,
4602 wakaba 1.40 check_attrs => sub {
4603     my ($self, $item, $element_state) = @_;
4604 wakaba 1.1 $GetHTMLAttrsChecker->({
4605 wakaba 1.90 media => $HTMLMQAttrChecker,
4606     pixelratio => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
4607     src => $HTMLURIAttrChecker, ## ISSUE: Negative or zero pixelratio=""
4608 wakaba 1.1 type => $HTMLIMTAttrChecker,
4609 wakaba 1.50 }, {
4610     %HTMLAttrStatus,
4611 wakaba 1.153 media => FEATURE_HTML5_LC,
4612     pixelratio => FEATURE_HTML5_LC,
4613     src => FEATURE_HTML5_LC,
4614     type => FEATURE_HTML5_LC,
4615 wakaba 1.66 })->(@_);
4616 wakaba 1.40 unless ($item->{node}->has_attribute_ns (undef, 'src')) {
4617     $self->{onerror}->(node => $item->{node},
4618 wakaba 1.104 type => 'attribute missing',
4619     text => 'src',
4620     level => $self->{level}->{must});
4621 wakaba 1.1 }
4622 wakaba 1.66
4623     $element_state->{uri_info}->{src}->{type}->{embedded} = 1;
4624 wakaba 1.1 },
4625     };
4626    
4627     $Element->{$HTML_NS}->{canvas} = {
4628 wakaba 1.40 %HTMLTransparentChecker,
4629 wakaba 1.89 status => FEATURE_HTML5_COMPLETE,
4630 wakaba 1.40 check_attrs => $GetHTMLAttrsChecker->({
4631 wakaba 1.1 height => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }),
4632     width => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }),
4633 wakaba 1.50 }, {
4634     %HTMLAttrStatus,
4635 wakaba 1.89 height => FEATURE_HTML5_COMPLETE,
4636     width => FEATURE_HTML5_COMPLETE,
4637 wakaba 1.1 }),
4638     };
4639    
4640     $Element->{$HTML_NS}->{map} = {
4641 wakaba 1.72 %HTMLFlowContentChecker,
4642 wakaba 1.153 status => FEATURE_HTML5_LC | FEATURE_M12N10_REC,
4643 wakaba 1.40 check_attrs => sub {
4644     my ($self, $item, $element_state) = @_;
4645 wakaba 1.100 my $has_name;
4646 wakaba 1.4 $GetHTMLAttrsChecker->({
4647 wakaba 1.100 name => sub {
4648     my ($self, $attr) = @_;
4649     my $value = $attr->value;
4650     if (length $value) {
4651     ## NOTE: Duplication is not non-conforming.
4652     ## NOTE: Space characters are not non-conforming.
4653     #
4654     } else {
4655     $self->{onerror}->(node => $attr,
4656     type => 'empty attribute value',
4657 wakaba 1.104 level => $self->{level}->{must});
4658 wakaba 1.100 }
4659 wakaba 1.4 $self->{map}->{$value} ||= $attr;
4660 wakaba 1.100 $has_name = [$value, $attr];
4661 wakaba 1.4 },
4662 wakaba 1.49 }, {
4663     %HTMLAttrStatus,
4664 wakaba 1.153 class => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
4665     dir => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
4666     id => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
4667     lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
4668     #name => FEATURE_HTML5_LC | FEATURE_M12N10_REC_DEPRECATED,
4669     name => FEATURE_HTML5_LC | FEATURE_M12N10_REC,
4670 wakaba 1.50 onclick => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4671     ondblclick => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4672     onmousedown => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4673     onmouseup => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4674     onmouseover => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4675     onmousemove => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4676     onmouseout => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4677     onkeypress => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4678     onkeydown => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4679     onkeyup => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4680 wakaba 1.153 title => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
4681 wakaba 1.66 })->(@_);
4682 wakaba 1.100
4683 wakaba 1.135 if ($has_name) {
4684 wakaba 1.145 my $id = $item->{node}->get_attribute_ns (undef, 'id');
4685 wakaba 1.135 if (defined $id and $has_name->[0] ne $id) {
4686 wakaba 1.155 $self->{onerror}
4687     ->(node => $item->{node}->get_attribute_node_ns (undef, 'id'),
4688     type => 'id ne name',
4689     level => $self->{level}->{must});
4690 wakaba 1.100 }
4691 wakaba 1.135 } else {
4692 wakaba 1.100 $self->{onerror}->(node => $item->{node},
4693 wakaba 1.104 type => 'attribute missing',
4694     text => 'name',
4695     level => $self->{level}->{must});
4696 wakaba 1.100 }
4697 wakaba 1.4 },
4698 wakaba 1.59 check_start => sub {
4699     my ($self, $item, $element_state) = @_;
4700     $element_state->{in_map_original} = $self->{flag}->{in_map};
4701 wakaba 1.137 $self->{flag}->{in_map} = [@{$self->{flag}->{in_map} or []}, {}];
4702     ## NOTE: |{in_map}| is a reference to the array which contains
4703     ## hash references. Hashes are corresponding to the opening
4704     ## |map| elements and each of them contains the key-value
4705     ## pairs corresponding to the absolute URLs for the processed
4706     ## |area| elements in the |map| element corresponding to the
4707     ## hash. The key represents the resource (## TODO: use
4708     ## absolute URL), while the value represents whether there is
4709     ## an |area| element whose |alt| attribute is specified to a
4710     ## non-empty value. If there IS such an |area| element for
4711     ## the resource specified by the key, then the value is set to
4712     ## zero (|0|). Otherwise, if there is no such an |area|
4713     ## element but there is any |area| element with the empty
4714     ## |alt=""| attribute, then the value contains an array
4715     ## reference that contains all of such |area| elements.
4716 wakaba 1.79
4717     $element_state->{uri_info}->{template}->{type}->{resource} = 1;
4718     $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
4719 wakaba 1.59 },
4720     check_end => sub {
4721     my ($self, $item, $element_state) = @_;
4722 wakaba 1.137
4723     for (keys %{$self->{flag}->{in_map}->[-1]}) {
4724     my $nodes = $self->{flag}->{in_map}->[-1]->{$_};
4725     next unless $nodes;
4726     for (@$nodes) {
4727     $self->{onerror}->(type => 'empty area alt',
4728     node => $_,
4729     level => $self->{level}->{html5_no_may});
4730     }
4731     }
4732    
4733     $self->{flag}->{in_map} = $element_state->{in_map_original};
4734    
4735 wakaba 1.72 $HTMLFlowContentChecker{check_end}->(@_);
4736 wakaba 1.59 },
4737 wakaba 1.1 };
4738    
4739     $Element->{$HTML_NS}->{area} = {
4740 wakaba 1.40 %HTMLEmptyChecker,
4741 wakaba 1.153 status => FEATURE_HTML5_LC | FEATURE_M12N10_REC,
4742 wakaba 1.40 check_attrs => sub {
4743     my ($self, $item, $element_state) = @_;
4744 wakaba 1.1 my %attr;
4745     my $coords;
4746 wakaba 1.40 for my $attr (@{$item->{node}->attributes}) {
4747 wakaba 1.1 my $attr_ns = $attr->namespace_uri;
4748     $attr_ns = '' unless defined $attr_ns;
4749     my $attr_ln = $attr->manakai_local_name;
4750     my $checker;
4751 wakaba 1.73 my $status;
4752 wakaba 1.1 if ($attr_ns eq '') {
4753 wakaba 1.73 $status = {
4754     %HTMLAttrStatus,
4755     %HTMLM12NCommonAttrStatus,
4756     accesskey => FEATURE_M12N10_REC,
4757 wakaba 1.153 alt => FEATURE_HTML5_LC | FEATURE_M12N10_REC,
4758     coords => FEATURE_HTML5_LC | FEATURE_M12N10_REC,
4759 wakaba 1.154 href => FEATURE_HTML5_WD | FEATURE_RDFA_REC | FEATURE_M12N10_REC,
4760 wakaba 1.153 hreflang => FEATURE_HTML5_WD,
4761     lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
4762 wakaba 1.154 media => FEATURE_HTML5_WD,
4763 wakaba 1.73 nohref => FEATURE_M12N10_REC,
4764     onblur => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4765     onfocus => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4766 wakaba 1.153 ping => FEATURE_HTML5_WD,
4767 wakaba 1.154 rel => FEATURE_HTML5_WD | FEATURE_RDFA_REC,
4768 wakaba 1.153 shape => FEATURE_HTML5_LC | FEATURE_M12N10_REC,
4769 wakaba 1.73 tabindex => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
4770 wakaba 1.153 target => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
4771     type => FEATURE_HTML5_WD,
4772 wakaba 1.73 }->{$attr_ln};
4773    
4774 wakaba 1.1 $checker = {
4775 wakaba 1.66 accesskey => $HTMLAccesskeyAttrChecker,
4776 wakaba 1.153 alt => sub {
4777     ## NOTE: Checked later.
4778     },
4779 wakaba 1.1 shape => $GetHTMLEnumeratedAttrChecker->({
4780     circ => -1, circle => 1,
4781     default => 1,
4782     poly => 1, polygon => -1,
4783     rect => 1, rectangle => -1,
4784     }),
4785     coords => sub {
4786     my ($self, $attr) = @_;
4787     my $value = $attr->value;
4788     if ($value =~ /\A-?[0-9]+(?>,-?[0-9]+)*\z/) {
4789     $coords = [split /,/, $value];
4790     } else {
4791     $self->{onerror}->(node => $attr,
4792 wakaba 1.104 type => 'coords:syntax error',
4793     level => $self->{level}->{must});
4794 wakaba 1.1 }
4795     },
4796 wakaba 1.70 nohref => $GetHTMLBooleanAttrChecker->('nohref'),
4797     target => $HTMLTargetAttrChecker,
4798 wakaba 1.1 href => $HTMLURIAttrChecker,
4799     ping => $HTMLSpaceURIsAttrChecker,
4800 wakaba 1.40 rel => sub { $HTMLLinkTypesAttrChecker->(1, $item, @_) },
4801 wakaba 1.1 media => $HTMLMQAttrChecker,
4802     hreflang => $HTMLLanguageTagAttrChecker,
4803     type => $HTMLIMTAttrChecker,
4804     }->{$attr_ln};
4805     if ($checker) {
4806     $attr{$attr_ln} = $attr;
4807 wakaba 1.122 } elsif ($attr_ln =~ /^data-\p{InXMLNCNameChar10}+\z/ and
4808     $attr_ln !~ /[A-Z]/) {
4809 wakaba 1.73 $checker = $HTMLDatasetAttrChecker;
4810     $status = $HTMLDatasetAttrStatus;
4811 wakaba 1.1 } else {
4812     $checker = $HTMLAttrChecker->{$attr_ln};
4813     }
4814     }
4815     $checker ||= $AttrChecker->{$attr_ns}->{$attr_ln}
4816 wakaba 1.82 || $AttrChecker->{$attr_ns}->{''};
4817     $status ||= $AttrStatus->{$attr_ns}->{$attr_ln}
4818     || $AttrStatus->{$attr_ns}->{''};
4819     $status = FEATURE_ALLOWED if not defined $status and length $attr_ns;
4820 wakaba 1.62
4821 wakaba 1.1 if ($checker) {
4822 wakaba 1.66 $checker->($self, $attr, $item, $element_state) if ref $checker;
4823 wakaba 1.62 } elsif ($attr_ns eq '' and not $status) {
4824 wakaba 1.54 #
4825 wakaba 1.1 } else {
4826 wakaba 1.104 $self->{onerror}->(node => $attr,
4827     type => 'unknown attribute',
4828     level => $self->{level}->{uncertain});
4829 wakaba 1.1 ## ISSUE: No comformance createria for unknown attributes in the spec
4830     }
4831 wakaba 1.49
4832 wakaba 1.82 $self->_attr_status_info ($attr, $status);
4833 wakaba 1.1 }
4834    
4835     if (defined $attr{href}) {
4836 wakaba 1.4 $self->{has_hyperlink_element} = 1;
4837 wakaba 1.137 if (defined $attr{alt}) {
4838     my $url = $attr{href}->value; ## TODO: resolve
4839     if (length $attr{alt}->value) {
4840     for (@{$self->{flag}->{in_map} or []}) {
4841     $_->{$url} = 0;
4842     }
4843     } else {
4844     ## NOTE: Empty |alt=""|. If there is another |area| element
4845     ## with the same |href=""| and that |area| elemnet's
4846     ## |alt=""| attribute is not an empty string, then this
4847     ## is conforming.
4848     for (@{$self->{flag}->{in_map} or []}) {
4849     push @{$_->{$url} ||= []}, $attr{alt}
4850     unless exists $_->{$url} and not $_->{$url};
4851     }
4852     }
4853     } else {
4854 wakaba 1.40 $self->{onerror}->(node => $item->{node},
4855 wakaba 1.104 type => 'attribute missing',
4856     text => 'alt',
4857     level => $self->{level}->{must});
4858 wakaba 1.1 }
4859     } else {
4860     for (qw/target ping rel media hreflang type alt/) {
4861     if (defined $attr{$_}) {
4862     $self->{onerror}->(node => $attr{$_},
4863 wakaba 1.104 type => 'attribute not allowed',
4864     level => $self->{level}->{must});
4865 wakaba 1.1 }
4866     }
4867     }
4868    
4869     my $shape = 'rectangle';
4870     if (defined $attr{shape}) {
4871     $shape = {
4872     circ => 'circle', circle => 'circle',
4873     default => 'default',
4874     poly => 'polygon', polygon => 'polygon',
4875     rect => 'rectangle', rectangle => 'rectangle',
4876     }->{lc $attr{shape}->value} || 'rectangle';
4877     ## TODO: ASCII lowercase?
4878     }
4879    
4880     if ($shape eq 'circle') {
4881     if (defined $attr{coords}) {
4882     if (defined $coords) {
4883     if (@$coords == 3) {
4884     if ($coords->[2] < 0) {
4885     $self->{onerror}->(node => $attr{coords},
4886 wakaba 1.104 type => 'coords:out of range',
4887     index => 2,
4888     value => $coords->[2],
4889     level => $self->{level}->{must});
4890 wakaba 1.1 }
4891     } else {
4892     $self->{onerror}->(node => $attr{coords},
4893 wakaba 1.104 type => 'coords:number not 3',
4894     text => 0+@$coords,
4895     level => $self->{level}->{must});
4896 wakaba 1.1 }
4897     } else {
4898     ## NOTE: A syntax error has been reported.
4899     }
4900     } else {
4901 wakaba 1.40 $self->{onerror}->(node => $item->{node},
4902 wakaba 1.104 type => 'attribute missing',
4903     text => 'coords',
4904     level => $self->{level}->{must});
4905 wakaba 1.1 }
4906     } elsif ($shape eq 'default') {
4907     if (defined $attr{coords}) {
4908     $self->{onerror}->(node => $attr{coords},
4909 wakaba 1.104 type => 'attribute not allowed',
4910     level => $self->{level}->{must});
4911 wakaba 1.1 }
4912     } elsif ($shape eq 'polygon') {
4913     if (defined $attr{coords}) {
4914     if (defined $coords) {
4915     if (@$coords >= 6) {
4916     unless (@$coords % 2 == 0) {
4917     $self->{onerror}->(node => $attr{coords},
4918 wakaba 1.104 type => 'coords:number not even',
4919     text => 0+@$coords,
4920     level => $self->{level}->{must});
4921 wakaba 1.1 }
4922     } else {
4923     $self->{onerror}->(node => $attr{coords},
4924 wakaba 1.104 type => 'coords:number lt 6',
4925     text => 0+@$coords,
4926     level => $self->{level}->{must});
4927 wakaba 1.1 }
4928     } else {
4929     ## NOTE: A syntax error has been reported.
4930     }
4931     } else {
4932 wakaba 1.40 $self->{onerror}->(node => $item->{node},
4933 wakaba 1.104 type => 'attribute missing',
4934     text => 'coords',
4935     level => $self->{level}->{must});
4936 wakaba 1.1 }
4937     } elsif ($shape eq 'rectangle') {
4938     if (defined $attr{coords}) {
4939     if (defined $coords) {
4940     if (@$coords == 4) {
4941     unless ($coords->[0] < $coords->[2]) {
4942     $self->{onerror}->(node => $attr{coords},
4943 wakaba 1.104 type => 'coords:out of range',
4944     index => 0,
4945     value => $coords->[0],
4946     level => $self->{level}->{must});
4947 wakaba 1.1 }
4948     unless ($coords->[1] < $coords->[3]) {
4949     $self->{onerror}->(node => $attr{coords},
4950 wakaba 1.104 type => 'coords:out of range',
4951     index => 1,
4952     value => $coords->[1],
4953     level => $self->{level}->{must});
4954 wakaba 1.1 }
4955     } else {
4956     $self->{onerror}->(node => $attr{coords},
4957 wakaba 1.104 type => 'coords:number not 4',
4958     text => 0+@$coords,
4959     level => $self->{level}->{must});
4960 wakaba 1.1 }
4961     } else {
4962     ## NOTE: A syntax error has been reported.
4963     }
4964     } else {
4965 wakaba 1.40 $self->{onerror}->(node => $item->{node},
4966 wakaba 1.104 type => 'attribute missing',
4967     text => 'coords',
4968     level => $self->{level}->{must});
4969 wakaba 1.1 }
4970     }
4971 wakaba 1.66
4972     $element_state->{uri_info}->{href}->{type}->{hyperlink} = 1;
4973 wakaba 1.1 },
4974 wakaba 1.59 check_start => sub {
4975     my ($self, $item, $element_state) = @_;
4976     unless ($self->{flag}->{in_map} or
4977     not $item->{node}->manakai_parent_element) {
4978     $self->{onerror}->(node => $item->{node},
4979     type => 'element not allowed:area',
4980 wakaba 1.104 level => $self->{level}->{must});
4981 wakaba 1.59 }
4982 wakaba 1.79
4983     $element_state->{uri_info}->{template}->{type}->{resource} = 1;
4984     $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
4985 wakaba 1.59 },
4986 wakaba 1.1 };
4987    
4988     $Element->{$HTML_NS}->{table} = {
4989 wakaba 1.40 %HTMLChecker,
4990 wakaba 1.153 status => FEATURE_HTML5_LC | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
4991 wakaba 1.69 check_attrs => $GetHTMLAttrsChecker->({
4992 wakaba 1.86 cellpadding => $HTMLLengthAttrChecker,
4993     cellspacing => $HTMLLengthAttrChecker,
4994 wakaba 1.69 frame => $GetHTMLEnumeratedAttrChecker->({
4995     void => 1, above => 1, below => 1, hsides => 1, vsides => 1,
4996     lhs => 1, rhs => 1, box => 1, border => 1,
4997     }),
4998     rules => $GetHTMLEnumeratedAttrChecker->({
4999     none => 1, groups => 1, rows => 1, cols => 1, all => 1,
5000     }),
5001     summary => sub {}, ## NOTE: %Text; in HTML4.
5002     width => $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 }), ## %Pixels;
5003     }, {
5004 wakaba 1.49 %HTMLAttrStatus,
5005 wakaba 1.82 %HTMLM12NXHTML2CommonAttrStatus,
5006 wakaba 1.49 align => FEATURE_M12N10_REC_DEPRECATED,
5007     bgcolor => FEATURE_M12N10_REC_DEPRECATED,
5008     border => FEATURE_M12N10_REC,
5009     cellpadding => FEATURE_M12N10_REC,
5010     cellspacing => FEATURE_M12N10_REC,
5011 wakaba 1.61 cols => FEATURE_RFC1942,
5012 wakaba 1.49 datafld => FEATURE_HTML4_REC_RESERVED,
5013     dataformatas => FEATURE_HTML4_REC_RESERVED,
5014     datapagesize => FEATURE_M12N10_REC,
5015     datasrc => FEATURE_HTML4_REC_RESERVED,
5016     frame => FEATURE_M12N10_REC,
5017 wakaba 1.153 lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
5018 wakaba 1.49 rules => FEATURE_M12N10_REC,
5019     summary => FEATURE_M12N10_REC,
5020     width => FEATURE_M12N10_REC,
5021     }),
5022 wakaba 1.40 check_start => sub {
5023     my ($self, $item, $element_state) = @_;
5024     $element_state->{phase} = 'before caption';
5025 wakaba 1.66
5026     $element_state->{uri_info}->{datasrc}->{type}->{resource} = 1;
5027 wakaba 1.79 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
5028     $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
5029 wakaba 1.40 },
5030     check_child_element => sub {
5031     my ($self, $item, $child_el, $child_nsuri, $child_ln,
5032     $child_is_transparent, $element_state) = @_;
5033 wakaba 1.130 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
5034     $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
5035 wakaba 1.40 $self->{onerror}->(node => $child_el,
5036     type => 'element not allowed:minus',
5037 wakaba 1.104 level => $self->{level}->{must});
5038 wakaba 1.40 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
5039     #
5040     } elsif ($element_state->{phase} eq 'in tbodys') {
5041     if ($child_nsuri eq $HTML_NS and $child_ln eq 'tbody') {
5042     #$element_state->{phase} = 'in tbodys';
5043     } elsif (not $element_state->{has_tfoot} and
5044     $child_nsuri eq $HTML_NS and $child_ln eq 'tfoot') {
5045     $element_state->{phase} = 'after tfoot';
5046     $element_state->{has_tfoot} = 1;
5047     } else {
5048 wakaba 1.104 $self->{onerror}->(node => $child_el, type => 'element not allowed',
5049     level => $self->{level}->{must});
5050 wakaba 1.40 }
5051     } elsif ($element_state->{phase} eq 'in trs') {
5052     if ($child_nsuri eq $HTML_NS and $child_ln eq 'tr') {
5053     #$element_state->{phase} = 'in trs';
5054     } elsif (not $element_state->{has_tfoot} and
5055     $child_nsuri eq $HTML_NS and $child_ln eq 'tfoot') {
5056     $element_state->{phase} = 'after tfoot';
5057     $element_state->{has_tfoot} = 1;
5058     } else {
5059 wakaba 1.104 $self->{onerror}->(node => $child_el, type => 'element not allowed',
5060     level => $self->{level}->{must});
5061 wakaba 1.40 }
5062     } elsif ($element_state->{phase} eq 'after thead') {
5063     if ($child_nsuri eq $HTML_NS and $child_ln eq 'tbody') {
5064     $element_state->{phase} = 'in tbodys';
5065     } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'tr') {
5066     $element_state->{phase} = 'in trs';
5067     } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'tfoot') {
5068     $element_state->{phase} = 'in tbodys';
5069     $element_state->{has_tfoot} = 1;
5070     } else {
5071 wakaba 1.104 $self->{onerror}->(node => $child_el, type => 'element not allowed',
5072     level => $self->{level}->{must});
5073 wakaba 1.40 }
5074     } elsif ($element_state->{phase} eq 'in colgroup') {
5075     if ($child_nsuri eq $HTML_NS and $child_ln eq 'colgroup') {
5076     $element_state->{phase} = 'in colgroup';
5077     } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'thead') {
5078     $element_state->{phase} = 'after thead';
5079     } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'tbody') {
5080     $element_state->{phase} = 'in tbodys';
5081     } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'tr') {
5082     $element_state->{phase} = 'in trs';
5083     } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'tfoot') {
5084     $element_state->{phase} = 'in tbodys';
5085     $element_state->{has_tfoot} = 1;
5086     } else {
5087 wakaba 1.104 $self->{onerror}->(node => $child_el, type => 'element not allowed',
5088     level => $self->{level}->{must});
5089 wakaba 1.40 }
5090     } elsif ($element_state->{phase} eq 'before caption') {
5091     if ($child_nsuri eq $HTML_NS and $child_ln eq 'caption') {
5092     $element_state->{phase} = 'in colgroup';
5093     } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'colgroup') {
5094     $element_state->{phase} = 'in colgroup';
5095     } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'thead') {
5096     $element_state->{phase} = 'after thead';
5097     } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'tbody') {
5098     $element_state->{phase} = 'in tbodys';
5099     } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'tr') {
5100     $element_state->{phase} = 'in trs';
5101     } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'tfoot') {
5102     $element_state->{phase} = 'in tbodys';
5103     $element_state->{has_tfoot} = 1;
5104     } else {
5105 wakaba 1.104 $self->{onerror}->(node => $child_el, type => 'element not allowed',
5106     level => $self->{level}->{must});
5107 wakaba 1.40 }
5108     } elsif ($element_state->{phase} eq 'after tfoot') {
5109 wakaba 1.104 $self->{onerror}->(node => $child_el, type => 'element not allowed',
5110     level => $self->{level}->{must});
5111 wakaba 1.40 } else {
5112     die "check_child_element: Bad |table| phase: $element_state->{phase}";
5113     }
5114     },
5115     check_child_text => sub {
5116     my ($self, $item, $child_node, $has_significant, $element_state) = @_;
5117     if ($has_significant) {
5118 wakaba 1.104 $self->{onerror}->(node => $child_node, type => 'character not allowed',
5119     level => $self->{level}->{must});
5120 wakaba 1.1 }
5121 wakaba 1.40 },
5122     check_end => sub {
5123     my ($self, $item, $element_state) = @_;
5124 wakaba 1.1
5125     ## Table model errors
5126     require Whatpm::HTMLTable;
5127 wakaba 1.87 my $table = Whatpm::HTMLTable->form_table ($item->{node}, sub {
5128 wakaba 1.104 $self->{onerror}->(@_);
5129     }, $self->{level});
5130 wakaba 1.87 Whatpm::HTMLTable->assign_header
5131 wakaba 1.104 ($table, $self->{onerror}, $self->{level});
5132 wakaba 1.87 push @{$self->{return}->{table}}, $table;
5133 wakaba 1.1
5134 wakaba 1.40 $HTMLChecker{check_end}->(@_);
5135 wakaba 1.1 },
5136     };
5137    
5138     $Element->{$HTML_NS}->{caption} = {
5139 wakaba 1.40 %HTMLPhrasingContentChecker,
5140 wakaba 1.153 status => FEATURE_HTML5_LC | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5141 wakaba 1.69 check_attrs => $GetHTMLAttrsChecker->({
5142     align => $GetHTMLEnumeratedAttrChecker->({
5143     top => 1, bottom => 1, left => 1, right => 1,
5144     }),
5145     }, {
5146 wakaba 1.49 %HTMLAttrStatus,
5147 wakaba 1.82 %HTMLM12NXHTML2CommonAttrStatus,
5148 wakaba 1.49 align => FEATURE_M12N10_REC_DEPRECATED,
5149 wakaba 1.153 lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
5150 wakaba 1.49 }),
5151 wakaba 1.1 };
5152    
5153 wakaba 1.69 my %cellalign = (
5154     ## HTML4 %cellhalign;
5155 wakaba 1.70 align => $GetHTMLEnumeratedAttrChecker->({
5156     left => 1, center => 1, right => 1, justify => 1, char => 1,
5157     }),
5158     char => sub {
5159     my ($self, $attr) = @_;
5160 wakaba 1.69
5161 wakaba 1.70 ## NOTE: "character" or |%Character;| in HTML4.
5162    
5163     my $value = $attr->value;
5164     if (length $value != 1) {
5165     $self->{onerror}->(node => $attr, type => 'char:syntax error',
5166 wakaba 1.105 level => $self->{level}->{html4_fact});
5167 wakaba 1.70 }
5168     },
5169 wakaba 1.86 charoff => $HTMLLengthAttrChecker,
5170    
5171 wakaba 1.69 ## HTML4 %cellvalign;
5172 wakaba 1.70 valign => $GetHTMLEnumeratedAttrChecker->({
5173     top => 1, middle => 1, bottom => 1, baseline => 1,
5174     }),
5175 wakaba 1.69 );
5176    
5177 wakaba 1.1 $Element->{$HTML_NS}->{colgroup} = {
5178 wakaba 1.40 %HTMLEmptyChecker,
5179 wakaba 1.153 status => FEATURE_HTML5_LC | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5180 wakaba 1.40 check_attrs => $GetHTMLAttrsChecker->({
5181 wakaba 1.69 %cellalign,
5182 wakaba 1.1 span => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
5183     ## NOTE: Defined only if "the |colgroup| element contains no |col| elements"
5184     ## TODO: "attribute not supported" if |col|.
5185     ## ISSUE: MUST NOT if any |col|?
5186     ## ISSUE: MUST NOT for |<colgroup span="1"><any><col/></any></colgroup>| (though non-conforming)?
5187 wakaba 1.49 }, {
5188     %HTMLAttrStatus,
5189 wakaba 1.82 %HTMLM12NXHTML2CommonAttrStatus,
5190 wakaba 1.49 align => FEATURE_M12N10_REC,
5191     char => FEATURE_M12N10_REC,
5192     charoff => FEATURE_M12N10_REC,
5193 wakaba 1.153 lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
5194     span => FEATURE_HTML5_LC | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5195 wakaba 1.49 valign => FEATURE_M12N10_REC,
5196     width => FEATURE_M12N10_REC,
5197 wakaba 1.1 }),
5198 wakaba 1.40 check_child_element => sub {
5199     my ($self, $item, $child_el, $child_nsuri, $child_ln,
5200     $child_is_transparent, $element_state) = @_;
5201 wakaba 1.130 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
5202     $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
5203 wakaba 1.40 $self->{onerror}->(node => $child_el,
5204     type => 'element not allowed:minus',
5205 wakaba 1.104 level => $self->{level}->{must});
5206 wakaba 1.40 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
5207     #
5208     } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'col') {
5209     #
5210     } else {
5211 wakaba 1.104 $self->{onerror}->(node => $child_el, type => 'element not allowed',
5212     level => $self->{level}->{must});
5213 wakaba 1.40 }
5214     },
5215     check_child_text => sub {
5216     my ($self, $item, $child_node, $has_significant, $element_state) = @_;
5217     if ($has_significant) {
5218 wakaba 1.104 $self->{onerror}->(node => $child_node, type => 'character not allowed',
5219     level => $self->{level}->{must});
5220 wakaba 1.1 }
5221     },
5222     };
5223    
5224     $Element->{$HTML_NS}->{col} = {
5225 wakaba 1.40 %HTMLEmptyChecker,
5226 wakaba 1.153 status => FEATURE_HTML5_LC | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5227 wakaba 1.40 check_attrs => $GetHTMLAttrsChecker->({
5228 wakaba 1.69 %cellalign,
5229 wakaba 1.1 span => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
5230 wakaba 1.49 }, {
5231     %HTMLAttrStatus,
5232 wakaba 1.82 %HTMLM12NXHTML2CommonAttrStatus,
5233 wakaba 1.49 align => FEATURE_M12N10_REC,
5234     char => FEATURE_M12N10_REC,
5235     charoff => FEATURE_M12N10_REC,
5236 wakaba 1.153 lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
5237     span => FEATURE_HTML5_LC | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5238 wakaba 1.49 valign => FEATURE_M12N10_REC,
5239     width => FEATURE_M12N10_REC,
5240 wakaba 1.1 }),
5241     };
5242    
5243     $Element->{$HTML_NS}->{tbody} = {
5244 wakaba 1.40 %HTMLChecker,
5245 wakaba 1.153 status => FEATURE_HTML5_LC | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5246 wakaba 1.69 check_attrs => $GetHTMLAttrsChecker->({
5247     %cellalign,
5248     }, {
5249 wakaba 1.49 %HTMLAttrStatus,
5250 wakaba 1.82 %HTMLM12NXHTML2CommonAttrStatus,
5251 wakaba 1.49 align => FEATURE_M12N10_REC,
5252     char => FEATURE_M12N10_REC,
5253     charoff => FEATURE_M12N10_REC,
5254 wakaba 1.153 lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
5255 wakaba 1.49 valign => FEATURE_M12N10_REC,
5256     }),
5257 wakaba 1.40 check_child_element => sub {
5258     my ($self, $item, $child_el, $child_nsuri, $child_ln,
5259     $child_is_transparent, $element_state) = @_;
5260 wakaba 1.130 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
5261     $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
5262 wakaba 1.40 $self->{onerror}->(node => $child_el,
5263     type => 'element not allowed:minus',
5264 wakaba 1.104 level => $self->{level}->{must});
5265 wakaba 1.40 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
5266     #
5267     } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'tr') {
5268 wakaba 1.84 #
5269 wakaba 1.40 } else {
5270 wakaba 1.104 $self->{onerror}->(node => $child_el, type => 'element not allowed',
5271     level => $self->{level}->{must});
5272 wakaba 1.40 }
5273     },
5274     check_child_text => sub {
5275     my ($self, $item, $child_node, $has_significant, $element_state) = @_;
5276     if ($has_significant) {
5277 wakaba 1.104 $self->{onerror}->(node => $child_node, type => 'character not allowed',
5278     level => $self->{level}->{must});
5279 wakaba 1.1 }
5280 wakaba 1.40 },
5281 wakaba 1.1 };
5282    
5283     $Element->{$HTML_NS}->{thead} = {
5284 wakaba 1.40 %{$Element->{$HTML_NS}->{tbody}},
5285 wakaba 1.1 };
5286    
5287     $Element->{$HTML_NS}->{tfoot} = {
5288 wakaba 1.40 %{$Element->{$HTML_NS}->{tbody}},
5289 wakaba 1.1 };
5290    
5291     $Element->{$HTML_NS}->{tr} = {
5292 wakaba 1.40 %HTMLChecker,
5293 wakaba 1.153 status => FEATURE_HTML5_LC | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5294 wakaba 1.69 check_attrs => $GetHTMLAttrsChecker->({
5295     %cellalign,
5296     bgcolor => $HTMLColorAttrChecker,
5297     }, {
5298 wakaba 1.49 %HTMLAttrStatus,
5299 wakaba 1.82 %HTMLM12NXHTML2CommonAttrStatus,
5300 wakaba 1.49 align => FEATURE_M12N10_REC,
5301     bgcolor => FEATURE_M12N10_REC_DEPRECATED,
5302     char => FEATURE_M12N10_REC,
5303     charoff => FEATURE_M12N10_REC,
5304 wakaba 1.153 lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
5305 wakaba 1.49 valign => FEATURE_M12N10_REC,
5306     }),
5307 wakaba 1.40 check_child_element => sub {
5308     my ($self, $item, $child_el, $child_nsuri, $child_ln,
5309     $child_is_transparent, $element_state) = @_;
5310 wakaba 1.130 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
5311     $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
5312 wakaba 1.40 $self->{onerror}->(node => $child_el,
5313     type => 'element not allowed:minus',
5314 wakaba 1.104 level => $self->{level}->{must});
5315 wakaba 1.40 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
5316     #
5317     } elsif ($child_nsuri eq $HTML_NS and
5318     ($child_ln eq 'td' or $child_ln eq 'th')) {
5319 wakaba 1.84 #
5320 wakaba 1.40 } else {
5321 wakaba 1.104 $self->{onerror}->(node => $child_el, type => 'element not allowed',
5322     level => $self->{level}->{must});
5323 wakaba 1.40 }
5324     },
5325     check_child_text => sub {
5326     my ($self, $item, $child_node, $has_significant, $element_state) = @_;
5327     if ($has_significant) {
5328 wakaba 1.104 $self->{onerror}->(node => $child_node, type => 'character not allowed',
5329     level => $self->{level}->{must});
5330 wakaba 1.1 }
5331     },
5332     };
5333    
5334     $Element->{$HTML_NS}->{td} = {
5335 wakaba 1.72 %HTMLFlowContentChecker,
5336 wakaba 1.153 status => FEATURE_HTML5_WD | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5337 wakaba 1.40 check_attrs => $GetHTMLAttrsChecker->({
5338 wakaba 1.69 %cellalign,
5339     abbr => sub {}, ## NOTE: HTML4 %Text; and SHOULD be short.
5340     axis => sub {}, ## NOTE: HTML4 "cdata", comma-separated
5341     bgcolor => $HTMLColorAttrChecker,
5342 wakaba 1.1 colspan => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
5343 wakaba 1.87 headers => sub {
5344     ## NOTE: Will be checked by Whatpm::HTMLTable->assign_header.
5345     ## Though that method does not check the |headers| attribute of a
5346     ## |td| element if the element does not form a table, in that case
5347     ## the |td| element is non-conforming anyway.
5348     },
5349 wakaba 1.69 nowrap => $GetHTMLBooleanAttrChecker->('nowrap'),
5350 wakaba 1.1 rowspan => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
5351 wakaba 1.69 scope => $GetHTMLEnumeratedAttrChecker
5352     ->({row => 1, col => 1, rowgroup => 1, colgroup => 1}),
5353 wakaba 1.49 }, {
5354     %HTMLAttrStatus,
5355 wakaba 1.82 %HTMLM12NXHTML2CommonAttrStatus,
5356     abbr => FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5357 wakaba 1.49 align => FEATURE_M12N10_REC,
5358 wakaba 1.82 axis => FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5359 wakaba 1.49 bgcolor => FEATURE_M12N10_REC_DEPRECATED,
5360     char => FEATURE_M12N10_REC,
5361     charoff => FEATURE_M12N10_REC,
5362 wakaba 1.153 colspan => FEATURE_HTML5_LC | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5363     headers => FEATURE_HTML5_WD | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5364 wakaba 1.49 height => FEATURE_M12N10_REC_DEPRECATED,
5365 wakaba 1.153 lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
5366 wakaba 1.49 nowrap => FEATURE_M12N10_REC_DEPRECATED,
5367 wakaba 1.153 rowspan => FEATURE_HTML5_LC | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5368 wakaba 1.82 scope => FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5369 wakaba 1.49 valign => FEATURE_M12N10_REC,
5370     width => FEATURE_M12N10_REC_DEPRECATED,
5371 wakaba 1.1 }),
5372     };
5373    
5374     $Element->{$HTML_NS}->{th} = {
5375 wakaba 1.40 %HTMLPhrasingContentChecker,
5376 wakaba 1.153 status => FEATURE_HTML5_LC | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5377 wakaba 1.40 check_attrs => $GetHTMLAttrsChecker->({
5378 wakaba 1.69 %cellalign,
5379     abbr => sub {}, ## NOTE: HTML4 %Text; and SHOULD be short.
5380     axis => sub {}, ## NOTE: HTML4 "cdata", comma-separated
5381     bgcolor => $HTMLColorAttrChecker,
5382 wakaba 1.1 colspan => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
5383 wakaba 1.87 ## TODO: HTML4(?) |headers|
5384 wakaba 1.69 nowrap => $GetHTMLBooleanAttrChecker->('nowrap'),
5385 wakaba 1.1 rowspan => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
5386     scope => $GetHTMLEnumeratedAttrChecker
5387     ->({row => 1, col => 1, rowgroup => 1, colgroup => 1}),
5388 wakaba 1.49 }, {
5389     %HTMLAttrStatus,
5390 wakaba 1.82 %HTMLM12NXHTML2CommonAttrStatus,
5391     abbr => FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5392 wakaba 1.49 align => FEATURE_M12N10_REC,
5393 wakaba 1.82 axis => FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5394 wakaba 1.49 bgcolor => FEATURE_M12N10_REC_DEPRECATED,
5395     char => FEATURE_M12N10_REC,
5396     charoff => FEATURE_M12N10_REC,
5397 wakaba 1.153 colspan => FEATURE_HTML5_LC | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5398 wakaba 1.82 headers => FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5399 wakaba 1.49 height => FEATURE_M12N10_REC_DEPRECATED,
5400 wakaba 1.153 lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
5401 wakaba 1.49 nowrap => FEATURE_M12N10_REC_DEPRECATED,
5402 wakaba 1.153 rowspan => FEATURE_HTML5_LC | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5403     scope => FEATURE_HTML5_LC | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
5404 wakaba 1.49 valign => FEATURE_M12N10_REC,
5405     width => FEATURE_M12N10_REC_DEPRECATED,
5406 wakaba 1.1 }),
5407     };
5408    
5409 wakaba 1.52 $Element->{$HTML_NS}->{form} = {
5410 wakaba 1.121 %HTMLFlowContentChecker,
5411 wakaba 1.119 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
5412 wakaba 1.52 check_attrs => $GetHTMLAttrsChecker->({
5413 wakaba 1.161 accept => $AcceptAttrChecker,
5414 wakaba 1.129 'accept-charset' => $HTMLCharsetsAttrChecker,
5415 wakaba 1.52 action => $HTMLURIAttrChecker, ## TODO: "User agent behavior for a value other than HTTP URI is undefined" [HTML4]
5416 wakaba 1.56 data => $HTMLURIAttrChecker, ## TODO: MUST point ... [WF2]
5417     enctype => $HTMLIMTAttrChecker, ## TODO: "multipart/form-data" should be used when type=file is used [HTML4] ## TODO: MUST NOT parameter [WF2]
5418     method => $GetHTMLEnumeratedAttrChecker->({
5419     get => 1, post => 1, put => 1, delete => 1,
5420     }),
5421 wakaba 1.52 ## NOTE: "get" SHOULD be used for idempotent submittion,
5422     ## "post" SHOULD be used otherwise [HTML4]. This cannot be tested.
5423 wakaba 1.133 name => sub {
5424     my ($self, $attr) = @_;
5425    
5426     my $value = $attr->value;
5427     if ($value eq '') {
5428     $self->{onerror}->(type => 'empty form name',
5429     node => $attr,
5430     level => $self->{level}->{must});
5431     } else {
5432     if ($self->{form}->{$value}) {
5433     $self->{onerror}->(type => 'duplicate form name',
5434     node => $attr,
5435     value => $value,
5436     level => $self->{level}->{must});
5437     } else {
5438     $self->{form}->{$value} = 1;
5439     }
5440     }
5441     },
5442 wakaba 1.126 onformchange => $HTMLEventHandlerAttrChecker,
5443     onforminput => $HTMLEventHandlerAttrChecker,
5444 wakaba 1.56 onreceived => $HTMLEventHandlerAttrChecker,
5445     replace => $GetHTMLEnumeratedAttrChecker->({document => 1, values => 1}),
5446 wakaba 1.52 target => $HTMLTargetAttrChecker,
5447 wakaba 1.121 ## TODO: Warn for combination whose behavior is not defined in HTML4/WF2.
5448 wakaba 1.52 }, {
5449     %HTMLAttrStatus,
5450     %HTMLM12NCommonAttrStatus,
5451 wakaba 1.161 accept => FEATURE_HTML5_DROPPED | FEATURE_WF2X | FEATURE_M12N10_REC,
5452 wakaba 1.119 'accept-charset' => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5453     action => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
5454 wakaba 1.56 data => FEATURE_WF2,
5455 wakaba 1.119 enctype => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
5456 wakaba 1.153 lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
5457 wakaba 1.119 method => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
5458     #name => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC_DEPRECATED,
5459     name => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5460 wakaba 1.126 onformchange => FEATURE_WF2_INFORMATIVE,
5461     onforminput => FEATURE_WF2_INFORMATIVE,
5462 wakaba 1.56 onreceived => FEATURE_WF2,
5463 wakaba 1.52 onreset => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5464     onsubmit => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5465 wakaba 1.56 replace => FEATURE_WF2,
5466 wakaba 1.61 sdapref => FEATURE_HTML20_RFC,
5467     sdasuff => FEATURE_HTML20_RFC,
5468 wakaba 1.119 target => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5469 wakaba 1.52 }),
5470 wakaba 1.66 check_start => sub {
5471     my ($self, $item, $element_state) = @_;
5472 wakaba 1.121 $self->_add_minus_elements ($element_state, {$HTML_NS => {form => 1}});
5473 wakaba 1.66
5474     $element_state->{uri_info}->{action}->{type}->{action} = 1;
5475     $element_state->{uri_info}->{data}->{type}->{resource} = 1;
5476 wakaba 1.79 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
5477     $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
5478 wakaba 1.136 $element_state->{id_type} = 'form';
5479 wakaba 1.66 },
5480 wakaba 1.121 check_end => sub {
5481     my ($self, $item, $element_state) = @_;
5482     $self->_remove_minus_elements ($element_state);
5483    
5484     $HTMLFlowContentChecker{check_end}->(@_);
5485     },
5486 wakaba 1.52 };
5487    
5488     $Element->{$HTML_NS}->{fieldset} = {
5489 wakaba 1.134 %HTMLFlowContentChecker,
5490 wakaba 1.119 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
5491 wakaba 1.56 check_attrs => $GetHTMLAttrsChecker->({
5492     disabled => $GetHTMLBooleanAttrChecker->('disabled'),
5493 wakaba 1.136 form => $HTMLFormAttrChecker,
5494 wakaba 1.165 name => $FormControlNameAttrChecker,
5495 wakaba 1.56 }, {
5496 wakaba 1.52 %HTMLAttrStatus,
5497     %HTMLM12NCommonAttrStatus,
5498 wakaba 1.119 disabled => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5499     form => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5500 wakaba 1.153 lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
5501 wakaba 1.125 name => FEATURE_HTML5_DEFAULT,
5502 wakaba 1.52 }),
5503 wakaba 1.134 ## NOTE: legend, Flow
5504     check_child_element => sub {
5505     my ($self, $item, $child_el, $child_nsuri, $child_ln,
5506     $child_is_transparent, $element_state) = @_;
5507     if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
5508     $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
5509     $self->{onerror}->(node => $child_el,
5510     type => 'element not allowed:minus',
5511     level => $self->{level}->{must});
5512     $element_state->{has_non_legend} = 1;
5513     } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
5514     #
5515     } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'legend') {
5516     if ($element_state->{has_non_legend}) {
5517     $self->{onerror}->(node => $child_el,
5518     type => 'element not allowed:details legend',
5519     level => $self->{level}->{must});
5520     }
5521     $element_state->{has_legend} = 1;
5522     $element_state->{has_non_legend} = 1;
5523     } else {
5524     $HTMLFlowContentChecker{check_child_element}->(@_);
5525     $element_state->{has_non_legend} = 1 unless $child_is_transparent;
5526     ## TODO:
5527     ## |<details><object><legend>xx</legend></object>..</details>|
5528     ## should be an error, since |object| is allowed as flow,
5529     ## therefore |details| part of the content model does not match.
5530     }
5531     },
5532     check_child_text => sub {
5533     my ($self, $item, $child_node, $has_significant, $element_state) = @_;
5534     if ($has_significant) {
5535     $element_state->{has_non_legend} = 1;
5536     }
5537     },
5538     check_end => sub {
5539     my ($self, $item, $element_state) = @_;
5540    
5541     unless ($element_state->{has_legend}) {
5542     $self->{onerror}->(node => $item->{node},
5543     type => 'child element missing',
5544     text => 'legend',
5545     level => $self->{level}->{must});
5546     }
5547    
5548     $HTMLFlowContentChecker{check_end}->(@_);
5549     ## ISSUE: |<details><legend>aa</legend></details>| error?
5550     },
5551     ## NOTE: This definition is partially reused by |details| element's
5552     ## checker.
5553 wakaba 1.52 ## TODO: Tests for <nest/> in <fieldset>
5554     };
5555    
5556     $Element->{$HTML_NS}->{input} = {
5557 wakaba 1.119 %HTMLEmptyChecker,
5558     status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
5559 wakaba 1.140 check_attrs => sub {
5560     my ($self, $item, $element_state) = @_;
5561 wakaba 1.142
5562 wakaba 1.145 my $state = $item->{node}->get_attribute_ns (undef, 'type');
5563 wakaba 1.142 $state = 'text' unless defined $state;
5564     $state =~ tr/A-Z/a-z/; ## ASCII case-insensitive
5565    
5566 wakaba 1.140 for my $attr (@{$item->{node}->attributes}) {
5567     my $attr_ns = $attr->namespace_uri;
5568     $attr_ns = '' unless defined $attr_ns;
5569     my $attr_ln = $attr->manakai_local_name;
5570     my $checker;
5571     my $status;
5572     if ($attr_ns eq '') {
5573     $status =
5574     {
5575     %HTMLAttrStatus,
5576     %HTMLM12NCommonAttrStatus,
5577     accept => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
5578     'accept-charset' => FEATURE_HTML2X_RFC,
5579     accesskey => FEATURE_M12N10_REC,
5580     action => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5581     align => FEATURE_M12N10_REC_DEPRECATED,
5582     alt => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5583     autocomplete => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5584     autofocus => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5585     checked => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5586     datafld => FEATURE_HTML4_REC_RESERVED,
5587     dataformatas => FEATURE_HTML4_REC_RESERVED,
5588     datasrc => FEATURE_HTML4_REC_RESERVED,
5589     disabled => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
5590     enctype => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5591     form => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5592 wakaba 1.150 inputmode => FEATURE_HTML5_DROPPED | FEATURE_WF2X |
5593     FEATURE_XHTMLBASIC11_CR,
5594 wakaba 1.140 ismap => FEATURE_M12N10_REC,
5595 wakaba 1.153 lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
5596 wakaba 1.140 list => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5597     max => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5598 wakaba 1.150 maxlength => FEATURE_HTML5_DEFAULT | FEATURE_WF2X |
5599     FEATURE_M12N10_REC,
5600 wakaba 1.140 method => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5601     min => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5602 wakaba 1.156 multiple => FEATURE_HTML5_DEFAULT,
5603 wakaba 1.140 name => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5604 wakaba 1.161 novalidate => FEATURE_HTML5_DEFAULT,
5605 wakaba 1.140 onblur => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5606     onchange => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5607     onfocus => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5608     onformchange => FEATURE_WF2_INFORMATIVE,
5609     onforminput => FEATURE_WF2_INFORMATIVE,
5610     oninput => FEATURE_WF2,
5611     oninvalid => FEATURE_WF2,
5612     onselect => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5613     pattern => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5614 wakaba 1.156 placeholder => FEATURE_HTML5_DEFAULT,
5615 wakaba 1.140 readonly => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
5616     replace => FEATURE_WF2,
5617     required => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5618     sdapref => FEATURE_HTML20_RFC,
5619 wakaba 1.154 size => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
5620 wakaba 1.140 src => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5621     step => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5622     tabindex => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5623     target => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
5624 wakaba 1.161 template => FEATURE_HTML5_AT_RISK | FEATURE_WF2, ## TODO:dropped
5625 wakaba 1.140 type => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5626     usemap => FEATURE_HTML5_DROPPED | FEATURE_M12N10_REC,
5627     value => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
5628     }->{$attr_ln};
5629    
5630     $checker =
5631     {
5632 wakaba 1.141 ## NOTE: Value of an empty string means that the attribute is only
5633     ## applicable for a specific set of states.
5634 wakaba 1.142 accept => '',
5635 wakaba 1.149 'accept-charset' => $HTMLCharsetsAttrChecker,
5636     ## NOTE: To which states it applies is not defined in RFC 2070.
5637 wakaba 1.150 accesskey => '', ## NOTE: Not applied to |hidden| [WF2].
5638 wakaba 1.142 action => '',
5639 wakaba 1.150 align => '',
5640 wakaba 1.141 alt => '',
5641 wakaba 1.142 autocomplete => '',
5642 wakaba 1.165 autofocus => $AutofocusAttrChecker,
5643     ## NOTE: <input type=hidden disabled> is not disallowed.
5644 wakaba 1.142 checked => '',
5645     disabled => $GetHTMLBooleanAttrChecker->('disabled'),
5646 wakaba 1.165 ## NOTE: <input type=hidden disabled> is not disallowed.
5647 wakaba 1.142 enctype => '',
5648     form => $HTMLFormAttrChecker,
5649 wakaba 1.150 inputmode => '',
5650     ismap => '', ## NOTE: "MUST" be type=image [HTML4]
5651 wakaba 1.142 list => '',
5652     max => '',
5653     maxlength => '',
5654     method => '',
5655     min => '',
5656 wakaba 1.156 multiple => '',
5657 wakaba 1.165 name => $FormControlNameAttrChecker,
5658 wakaba 1.149 onformchange => $HTMLEventHandlerAttrChecker, # [WF2]
5659     onforminput => $HTMLEventHandlerAttrChecker, # [WF2]
5660     oninput => $HTMLEventHandlerAttrChecker, # [WF2]
5661     oninvalid => $HTMLEventHandlerAttrChecker, # [WF2]
5662     ## TODO: tests for four attributes above
5663 wakaba 1.142 pattern => '',
5664 wakaba 1.156 placeholder => '',
5665 wakaba 1.142 readonly => '',
5666 wakaba 1.150 replace => '',
5667 wakaba 1.142 required => '',
5668     size => '',
5669     src => '',
5670     step => '',
5671     target => '',
5672 wakaba 1.140 type => $GetHTMLEnumeratedAttrChecker->({
5673 wakaba 1.156 hidden => 1, text => 1, search => 1, url => 1,
5674     email => 1, password => 1,
5675 wakaba 1.141 datetime => 1, date => 1, month => 1, week => 1, time => 1,
5676 wakaba 1.157 'datetime-local' => 1, number => 1, range => 1, color => 1,
5677     checkbox => 1,
5678 wakaba 1.141 radio => 1, file => 1, submit => 1, image => 1, reset => 1,
5679     button => 1,
5680 wakaba 1.140 }),
5681 wakaba 1.151 usemap => '',
5682 wakaba 1.142 value => '',
5683 wakaba 1.140 }->{$attr_ln};
5684 wakaba 1.141
5685     ## State-dependent checkers
5686     unless ($checker) {
5687     if ($state eq 'hidden') {
5688     $checker =
5689     {
5690 wakaba 1.142 value => sub {
5691     my ($self, $attr, $item, $element_state) = @_;
5692 wakaba 1.145 my $name = $item->{node}->get_attribute_ns (undef, 'name');
5693 wakaba 1.142 if (defined $name and $name eq '_charset_') { ## case-sensitive
5694     $self->{onerror}->(node => $attr,
5695     type => '_charset_ value',
5696     level => $self->{level}->{must});
5697     }
5698     },
5699 wakaba 1.141 }->{$attr_ln} || $checker;
5700 wakaba 1.142 ## TODO: Warn if no name attribute?
5701     ## TODO: Warn if name!=_charset_ and no value attribute?
5702 wakaba 1.143 } elsif ($state eq 'datetime') {
5703     $checker =
5704     {
5705 wakaba 1.150 accesskey => $HTMLAccesskeyAttrChecker,
5706 wakaba 1.145 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5707     on => 1, off => 1,
5708     }),
5709 wakaba 1.158 list => $ListAttrChecker,
5710 wakaba 1.143 ## TODO: max
5711     ## TODO: min
5712 wakaba 1.150 ## TODO: min <= max
5713 wakaba 1.145 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
5714 wakaba 1.146 required => $GetHTMLBooleanAttrChecker->('required'),
5715 wakaba 1.148 step => $StepAttrChecker,
5716 wakaba 1.143 value => sub {
5717 wakaba 1.156 ## TODO: syntax
5718 wakaba 1.143 ## TODO: Warn if not a valid UTC date and time string.
5719 wakaba 1.150 ## TODO: Warn unless min <= value <= max
5720 wakaba 1.143 },
5721     }->{$attr_ln} || $checker;
5722 wakaba 1.144 } elsif ($state eq 'date') {
5723     $checker =
5724     {
5725 wakaba 1.150 accesskey => $HTMLAccesskeyAttrChecker,
5726 wakaba 1.145 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5727     on => 1, off => 1,
5728     }),
5729 wakaba 1.158 list => $ListAttrChecker,
5730 wakaba 1.144 ## TODO: max
5731     ## TODO: min
5732 wakaba 1.150 ## TODO: min <= max
5733 wakaba 1.145 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
5734 wakaba 1.146 required => $GetHTMLBooleanAttrChecker->('required'),
5735 wakaba 1.148 step => $StepAttrChecker,
5736 wakaba 1.144 value => sub {
5737 wakaba 1.156 ## TODO: syntax
5738 wakaba 1.144 ## TODO: Warn if not a valid date string.
5739 wakaba 1.150 ## TODO: Warn unless min <= value <= max
5740 wakaba 1.144 },
5741     }->{$attr_ln} || $checker;
5742     } elsif ($state eq 'month') {
5743     $checker =
5744     {
5745 wakaba 1.150 accesskey => $HTMLAccesskeyAttrChecker,
5746 wakaba 1.145 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5747     on => 1, off => 1,
5748     }),
5749 wakaba 1.158 list => $ListAttrChecker,
5750 wakaba 1.144 ## TODO: max
5751     ## TODO: min
5752 wakaba 1.150 ## TODO: min <= max
5753 wakaba 1.145 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
5754 wakaba 1.146 required => $GetHTMLBooleanAttrChecker->('required'),
5755 wakaba 1.148 step => $StepAttrChecker,
5756 wakaba 1.144 value => sub {
5757 wakaba 1.156 ## TODO: syntax
5758 wakaba 1.144 ## TODO: Warn if not a valid month string.
5759 wakaba 1.150 ## TODO: Warn unless min <= value <= max
5760 wakaba 1.144 },
5761     }->{$attr_ln} || $checker;
5762     } elsif ($state eq 'week') {
5763     $checker =
5764     {
5765 wakaba 1.150 accesskey => $HTMLAccesskeyAttrChecker,
5766 wakaba 1.145 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5767     on => 1, off => 1,
5768     }),
5769 wakaba 1.158 list => $ListAttrChecker,
5770 wakaba 1.144 ## TODO: max
5771     ## TODO: min
5772 wakaba 1.150 ## TODO: min <= max
5773 wakaba 1.145 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
5774 wakaba 1.146 required => $GetHTMLBooleanAttrChecker->('required'),
5775 wakaba 1.148 step => $StepAttrChecker,
5776 wakaba 1.144 value => sub {
5777 wakaba 1.156 ## TODO: syntax
5778 wakaba 1.144 ## TODO: Warn if not a valid week string.
5779 wakaba 1.150 ## TODO: Warn unless min <= value <= max
5780 wakaba 1.144 },
5781     }->{$attr_ln} || $checker;
5782     } elsif ($state eq 'time') {
5783     $checker =
5784     {
5785 wakaba 1.150 accesskey => $HTMLAccesskeyAttrChecker,
5786 wakaba 1.145 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5787     on => 1, off => 1,
5788     }),
5789 wakaba 1.158 list => $ListAttrChecker,
5790 wakaba 1.144 ## TODO: max
5791     ## TODO: min
5792 wakaba 1.150 ## TODO: min <= max
5793 wakaba 1.145 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
5794 wakaba 1.146 required => $GetHTMLBooleanAttrChecker->('required'),
5795 wakaba 1.148 step => $StepAttrChecker,
5796 wakaba 1.144 value => sub {
5797 wakaba 1.156 ## TODO: syntax
5798 wakaba 1.144 ## TODO: Warn if not a valid time string.
5799 wakaba 1.150 ## TODO: Warn unless min <= value <= max
5800 wakaba 1.144 },
5801     }->{$attr_ln} || $checker;
5802     } elsif ($state eq 'datetime-local') {
5803     $checker =
5804     {
5805 wakaba 1.150 accesskey => $HTMLAccesskeyAttrChecker,
5806 wakaba 1.145 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5807     on => 1, off => 1,
5808     }),
5809 wakaba 1.158 list => $ListAttrChecker,
5810 wakaba 1.144 ## TODO: max
5811     ## TODO: min
5812 wakaba 1.150 ## TODO: min <= max
5813 wakaba 1.145 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
5814 wakaba 1.146 required => $GetHTMLBooleanAttrChecker->('required'),
5815 wakaba 1.148 step => $StepAttrChecker,
5816 wakaba 1.144 value => sub {
5817 wakaba 1.156 ## TODO: syntax
5818 wakaba 1.144 ## TODO: Warn if not a valid local date and time string.
5819 wakaba 1.150 ## TODO: Warn unless min <= value <= max
5820 wakaba 1.144 },
5821     }->{$attr_ln} || $checker;
5822     } elsif ($state eq 'number') {
5823     $checker =
5824     {
5825 wakaba 1.150 accesskey => $HTMLAccesskeyAttrChecker,
5826 wakaba 1.145 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5827     on => 1, off => 1,
5828     }),
5829 wakaba 1.158 list => $ListAttrChecker,
5830 wakaba 1.144 max => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
5831     min => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
5832     ## TODO: min & max tests
5833 wakaba 1.150 ## TODO: min <= max
5834 wakaba 1.145 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
5835 wakaba 1.146 required => $GetHTMLBooleanAttrChecker->('required'),
5836 wakaba 1.148 step => $StepAttrChecker,
5837 wakaba 1.144 value => sub {
5838 wakaba 1.156 ## TODO: syntax
5839 wakaba 1.144 ## TODO: Warn if not a valid floating point number.
5840 wakaba 1.150 ## TODO: Warn unless min <= value <= max
5841 wakaba 1.144 },
5842     }->{$attr_ln} || $checker;
5843     } elsif ($state eq 'range') {
5844     $checker =
5845     {
5846 wakaba 1.150 accesskey => $HTMLAccesskeyAttrChecker,
5847 wakaba 1.145 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5848     on => 1, off => 1,
5849     }),
5850 wakaba 1.158 list => $ListAttrChecker,
5851 wakaba 1.144 max => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
5852     min => $GetHTMLFloatingPointNumberAttrChecker->(sub { 1 }),
5853     ## TODO: min & max tests
5854 wakaba 1.150 ## TODO: min <= max
5855 wakaba 1.148 step => $StepAttrChecker,
5856 wakaba 1.144 value => sub {
5857 wakaba 1.156 ## TODO: syntax
5858 wakaba 1.144 ## TODO: Warn if not a valid floating point number.
5859 wakaba 1.150 ## TODO: Warn unless min <= value <= max
5860 wakaba 1.144 },
5861     }->{$attr_ln} || $checker;
5862 wakaba 1.157 } elsif ($state eq 'color') {
5863     $checker =
5864     {
5865     accesskey => $HTMLAccesskeyAttrChecker,
5866     autocomplete => $GetHTMLEnumeratedAttrChecker->({
5867     on => 1, off => 1,
5868     }),
5869 wakaba 1.158 list => $ListAttrChecker,
5870 wakaba 1.157 value => sub {
5871     my ($self, $attr) = @_;
5872     unless ($attr->value =~ /\A#[0-9A-Fa-f]{6}\z/) {
5873     $self->{onerror}->(node => $attr,
5874     type => 'scolor:syntax error', ## TODOC: type
5875     level => $self->{level}->{must});
5876     }
5877     },
5878     }->{$attr_ln} || $checker;
5879 wakaba 1.144 } elsif ($state eq 'checkbox' or $state eq 'radio') {
5880     $checker =
5881     {
5882 wakaba 1.150 accesskey => $HTMLAccesskeyAttrChecker,
5883 wakaba 1.149 checked => $GetHTMLBooleanAttrChecker->('checked'),
5884     ## ISSUE: checked value not (yet?) defined.
5885     ## TODO: tests
5886 wakaba 1.146 required => $GetHTMLBooleanAttrChecker->('required'),
5887 wakaba 1.144 value => sub { }, ## NOTE: No restriction.
5888     }->{$attr_ln} || $checker;
5889     ## TODO: There MUST be another input type=radio with same
5890     ## name (Radio state).
5891     ## ISSUE: There should be exactly one type=radio with checked?
5892     } elsif ($state eq 'file') {
5893     $checker =
5894     {
5895 wakaba 1.161 accept => $AcceptAttrChecker,
5896 wakaba 1.150 accesskey => $HTMLAccesskeyAttrChecker,
5897 wakaba 1.159 multiple => $GetHTMLBooleanAttrChecker->('multiple'),
5898 wakaba 1.146 required => $GetHTMLBooleanAttrChecker->('required'),
5899 wakaba 1.144 }->{$attr_ln} || $checker;
5900     } elsif ($state eq 'submit') {
5901     $checker =
5902     {
5903 wakaba 1.150 accesskey => $HTMLAccesskeyAttrChecker,
5904 wakaba 1.149 action => $HTMLURIAttrChecker,
5905     ## ISSUE: Not yet defined.
5906     ## TODO: tests
5907     enctype => $HTMLIMTAttrChecker,
5908     ## ISSUE: Not yet defined.
5909     ## TODO: tests
5910     method => $GetHTMLEnumeratedAttrChecker->({
5911     get => 1, post => 1, put => 1, delete => 1,
5912     }),
5913     ## ISSUE: Not yet defined.
5914     ## TODO: tests
5915     replace => $GetHTMLEnumeratedAttrChecker->({
5916     document => 1, values => 1,
5917     }),
5918     target => $HTMLTargetAttrChecker,
5919     ## ISSUE: Not yet defined.
5920     ## TODO: tests
5921 wakaba 1.144 value => sub { }, ## NOTE: No restriction.
5922     }->{$attr_ln} || $checker;
5923     } elsif ($state eq 'image') {
5924     $checker =
5925     {
5926 wakaba 1.150 accesskey => $HTMLAccesskeyAttrChecker,
5927 wakaba 1.149 action => $HTMLURIAttrChecker,
5928     ## ISSUE: Not yet defined.
5929     ## TODO: tests
5930     align => $GetHTMLEnumeratedAttrChecker->({
5931     top => 1, middle => 1, bottom => 1, left => 1, right => 1,
5932     }),
5933 wakaba 1.144 alt => sub {
5934     my ($self, $attr) = @_;
5935     my $value = $attr->value;
5936     unless (length $value) {
5937     $self->{onerror}->(node => $attr,
5938     type => 'empty anchor image alt',
5939     level => $self->{level}->{must});
5940     }
5941     },
5942 wakaba 1.149 enctype => $HTMLIMTAttrChecker,
5943     ## ISSUE: Not yet defined.
5944     ## TODO: tests
5945     ismap => $GetHTMLBooleanAttrChecker->('ismap'),
5946     method => $GetHTMLEnumeratedAttrChecker->({
5947     get => 1, post => 1, put => 1, delete => 1,
5948     }),
5949     ## ISSUE: Not yet defined.
5950     ## TODO: tests
5951     replace => $GetHTMLEnumeratedAttrChecker->({
5952     document => 1, values => 1,
5953     }),
5954 wakaba 1.144 src => $HTMLURIAttrChecker,
5955     ## TODO: There is requirements on the referenced resource.
5956 wakaba 1.149 target => $HTMLTargetAttrChecker,
5957     ## ISSUE: Not yet defined.
5958     ## TODO: tests
5959     usemap => $HTMLUsemapAttrChecker,
5960 wakaba 1.144 }->{$attr_ln} || $checker;
5961     ## TODO: alt & src are required.
5962     } elsif ({
5963     reset => 1, button => 1,
5964     ## NOTE: From Web Forms 2.0:
5965     remove => 1, 'move-up' => 1, 'move-down' => 1,
5966     add => 1,
5967     }->{$state}) {
5968     $checker =
5969     {
5970 wakaba 1.150 accesskey => $HTMLAccesskeyAttrChecker,
5971 wakaba 1.144 ## NOTE: According to Web Forms 2.0, |input| attribute
5972     ## has |template| attribute to support the |add| button
5973     ## type (as part of the repetition template feature). It
5974     ## conflicts with the |template| global attribute
5975     ## introduced as part of the data template feature.
5976     ## NOTE: |template| attribute as defined in Web Forms 2.0
5977     ## has no author requirement.
5978     value => sub { }, ## NOTE: No restriction.
5979     }->{$attr_ln} || $checker;
5980 wakaba 1.156 } else { # Text, Search, E-mail, URL, Password
5981 wakaba 1.141 $checker =
5982     {
5983 wakaba 1.150 accesskey => $HTMLAccesskeyAttrChecker,
5984 wakaba 1.145 autocomplete => $GetHTMLEnumeratedAttrChecker->({
5985     on => 1, off => 1,
5986     }),
5987 wakaba 1.149 ## TODO: inputmode [WF2]
5988 wakaba 1.158 list => $ListAttrChecker,
5989 wakaba 1.147 maxlength => sub {
5990     my ($self, $attr, $item, $element_state) = @_;
5991    
5992     $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 })->(@_);
5993    
5994 wakaba 1.165 if ($attr->value =~ /^[\x09\x0A\x0C\x0D\x20]*([0-9]+)/) {
5995 wakaba 1.147 ## NOTE: Applying the rules for parsing non-negative
5996     ## integers results in a number.
5997     my $max_allowed_value_length = 0+$1;
5998    
5999     my $value = $item->{node}->get_attribute_ns (undef, 'value');
6000     if (defined $value) {
6001     my $codepoint_length = length $value;
6002 wakaba 1.162
6003 wakaba 1.147 if ($codepoint_length > $max_allowed_value_length) {
6004     $self->{onerror}
6005     ->(node => $item->{node}
6006     ->get_attribute_node_ns (undef, 'value'),
6007     type => 'value too long',
6008     level => $self->{level}->{must});
6009     }
6010     }
6011     }
6012     },
6013 wakaba 1.160 pattern => $PatternAttrChecker,
6014 wakaba 1.159 placeholder => sub {
6015     my ($self, $attr) = @_;
6016     if ($attr->value =~ /[\x0D\x0A]/) {
6017     $self->{onerror}->(node => $attr,
6018     type => 'newline in value', ## TODOC: type
6019     level => $self->{level}->{must});
6020     }
6021     },
6022 wakaba 1.145 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
6023 wakaba 1.146 required => $GetHTMLBooleanAttrChecker->('required'),
6024 wakaba 1.147 size => $GetHTMLNonNegativeIntegerAttrChecker->(sub {shift > 0}),
6025 wakaba 1.143 value => sub {
6026 wakaba 1.156 my ($self, $attr, $item, $element_state) = @_;
6027     if ($state eq 'url') {
6028     $HTMLURIAttrChecker->(@_);
6029     } elsif ($state eq 'email') {
6030     if ($item->{node}->has_attribute_ns (undef, 'multiple')) {
6031     my @addr = split /,/, $attr->value, -1;
6032     @addr = ('') unless @addr;
6033     for (@addr) {
6034 wakaba 1.165 s/\A[\x09\x0A\x0C\x0D\x20]+//;
6035     s/[\x09\x0A\x0C\x0D\x20]\z//;
6036 wakaba 1.156 unless (/\A$ValidEmailAddress\z/) {
6037     $self->{onerror}->(node => $attr,
6038     type => 'email:syntax error', ## TODO: type
6039     value => $_,
6040     level => $self->{level}->{must});
6041     }
6042     }
6043     } else {
6044     unless ($attr->value =~ /\A$ValidEmailAddress\z/) {
6045     $self->{onerror}->(node => $attr,
6046     type => 'email:syntax error', ## TODO: type
6047     level => $self->{level}->{must});
6048     }
6049     }
6050     } else {
6051     if ($attr->value =~ /[\x0D\x0A]/) {
6052     $self->{onerror}->(node => $attr,
6053     type => 'newline in value', ## TODO: type
6054     level => $self->{level}->{must});
6055     }
6056     }
6057 wakaba 1.143 },
6058 wakaba 1.141 }->{$attr_ln} || $checker;
6059 wakaba 1.147 $checker = '' if $state eq 'password' and $attr_ln eq 'list';
6060 wakaba 1.156 $checker = $GetHTMLBooleanAttrChecker->('multiple')
6061     if $state eq 'email' and $attr_ln eq 'multiple';
6062 wakaba 1.161
6063     if ($item->{node}->has_attribute_ns (undef, 'pattern') and
6064     not $item->{node}->has_attribute_ns (undef, 'title')) {
6065     $self->{onerror}->(node => $item->{node},
6066     type => 'attribute missing',
6067     text => 'title',
6068     level => $self->{level}->{should});
6069     }
6070 wakaba 1.141 }
6071     }
6072    
6073     if (defined $checker) {
6074     if ($checker eq '') {
6075     $checker = sub {
6076     my ($self, $attr) = @_;
6077     $self->{onerror}->(node => $attr,
6078     type => 'input attr not applicable',
6079     text => $state,
6080     level => $self->{level}->{must});
6081     };
6082     }
6083 wakaba 1.140 } elsif ($attr_ln =~ /^data-\p{InXMLNCNameChar10}+\z/ and
6084     $attr_ln !~ /[A-Z]/) {
6085     $checker = $HTMLDatasetAttrChecker;
6086     $status = $HTMLDatasetAttrStatus;
6087     } else {
6088     $checker = $HTMLAttrChecker->{$attr_ln};
6089     }
6090     }
6091     $checker ||= $AttrChecker->{$attr_ns}->{$attr_ln}
6092     || $AttrChecker->{$attr_ns}->{''};
6093     $status ||= $AttrStatus->{$attr_ns}->{$attr_ln}
6094     || $AttrStatus->{$attr_ns}->{''};
6095     $status = FEATURE_ALLOWED if not defined $status and length $attr_ns;
6096 wakaba 1.157
6097     ## TODOC: accesskey="" is also applied to type=search and type=color
6098 wakaba 1.140
6099     if ($checker) {
6100     $checker->($self, $attr, $item, $element_state) if ref $checker;
6101     } elsif ($attr_ns eq '' and not $status) {
6102     #
6103     } else {
6104     $self->{onerror}->(node => $attr,
6105     type => 'unknown attribute',
6106     level => $self->{level}->{uncertain});
6107     ## ISSUE: No comformance createria for unknown attributes in the spec
6108     }
6109    
6110     $self->_attr_status_info ($attr, $status);
6111     }
6112 wakaba 1.150
6113     $element_state->{uri_info}->{action}->{type}->{action} = 1;
6114     $element_state->{uri_info}->{datasrc}->{type}->{resource} = 1;
6115     $element_state->{uri_info}->{src}->{type}->{embedded} = 1;
6116     $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6117     $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6118 wakaba 1.140 },
6119 wakaba 1.66 check_start => sub {
6120     my ($self, $item, $element_state) = @_;
6121 wakaba 1.139 if ($self->{flag}->{has_label} and $self->{flag}->{has_labelable}) {
6122     $self->{onerror}->(node => $item->{node},
6123     type => 'multiple labelable fae',
6124     level => $self->{level}->{must});
6125     } else {
6126     $self->{flag}->{has_labelable} = 2;
6127     }
6128 wakaba 1.138
6129     $element_state->{id_type} = 'labelable';
6130 wakaba 1.66 },
6131 wakaba 1.52 };
6132    
6133 wakaba 1.56 ## TODO: Form |name| attributes: MUST NOT conflict with RFC 3106 [WF2]
6134    
6135 wakaba 1.80 ## NOTE: "authors who are nesting repetition blocks should position such
6136     ## [repetition-block-related] buttons carefully to make clear which block a
6137 wakaba 1.150 ## button applies to." [WF2]: I have no idea how this can be tested.
6138 wakaba 1.80
6139 wakaba 1.52 $Element->{$HTML_NS}->{button} = {
6140 wakaba 1.119 %HTMLPhrasingContentChecker, ## ISSUE: -interactive?
6141     status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6142 wakaba 1.52 check_attrs => $GetHTMLAttrsChecker->({
6143 wakaba 1.66 accesskey => $HTMLAccesskeyAttrChecker,
6144 wakaba 1.165 ## ISSUE: In HTML5, no "MUST NOT" for using |action|, |method|,
6145     ## |enctype|, |target|, and |novalidate| with non-|submit|-|type|
6146     ## |button| elements.
6147 wakaba 1.56 action => $HTMLURIAttrChecker,
6148 wakaba 1.165 autofocus => $AutofocusAttrChecker,
6149 wakaba 1.52 disabled => $GetHTMLBooleanAttrChecker->('disabled'),
6150 wakaba 1.136 form => $HTMLFormAttrChecker,
6151 wakaba 1.56 method => $GetHTMLEnumeratedAttrChecker->({
6152     get => 1, post => 1, put => 1, delete => 1,
6153     }),
6154 wakaba 1.165 name => $FormControlNameAttrChecker,
6155 wakaba 1.162 novalidate => $GetHTMLBooleanAttrChecker->('novalidate'), ## TODO: tests
6156     onformchange => $HTMLEventHandlerAttrChecker, ## TODO: tests
6157     onforminput => $HTMLEventHandlerAttrChecker, ## TODO: tests
6158 wakaba 1.56 replace => $GetHTMLEnumeratedAttrChecker->({document => 1, values => 1}),
6159     target => $HTMLTargetAttrChecker,
6160 wakaba 1.80 ## NOTE: According to Web Forms 2.0, |button| attribute has |template|
6161     ## attribute to support the |add| button type (as part of repetition
6162     ## template feature). It conflicts with the |template| global attribute
6163     ## introduced as part of the data template feature.
6164     ## NOTE: |template| attribute as defined in Web Forms 2.0 has no
6165     ## author requirement.
6166 wakaba 1.52 type => $GetHTMLEnumeratedAttrChecker->({
6167     button => 1, submit => 1, reset => 1,
6168     }),
6169 wakaba 1.162 value => sub {}, ## NOTE: No restriction.
6170 wakaba 1.52 }, {
6171     %HTMLAttrStatus,
6172     %HTMLM12NCommonAttrStatus,
6173     accesskey => FEATURE_M12N10_REC,
6174 wakaba 1.119 action => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6175     autofocus => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6176 wakaba 1.52 datafld => FEATURE_HTML4_REC_RESERVED,
6177     dataformatas => FEATURE_HTML4_REC_RESERVED,
6178     datasrc => FEATURE_HTML4_REC_RESERVED,
6179 wakaba 1.119 disabled => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6180     enctype => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6181     form => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6182 wakaba 1.153 lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
6183 wakaba 1.119 method => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6184     name => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6185 wakaba 1.162 novalidate => FEATURE_HTML5_DEFAULT,
6186 wakaba 1.52 onblur => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6187     onfocus => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6188 wakaba 1.126 onformchange => FEATURE_WF2_INFORMATIVE,
6189     onforminput => FEATURE_WF2_INFORMATIVE,
6190 wakaba 1.56 replace => FEATURE_WF2,
6191 wakaba 1.52 tabindex => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6192 wakaba 1.119 target => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6193 wakaba 1.162 template => FEATURE_HTML5_AT_RISK | FEATURE_WF2, ## TODO: dropped
6194 wakaba 1.119 type => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6195     value => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6196 wakaba 1.52 }),
6197 wakaba 1.66 check_start => sub {
6198     my ($self, $item, $element_state) = @_;
6199 wakaba 1.139 if ($self->{flag}->{has_label} and $self->{flag}->{has_labelable}) {
6200     $self->{onerror}->(node => $item->{node},
6201     type => 'multiple labelable fae',
6202     level => $self->{level}->{must});
6203     } else {
6204     $self->{flag}->{has_labelable} = 2;
6205     }
6206 wakaba 1.162
6207     ## ISSUE: "The value attribute must not be present unless the form
6208     ## [content] attribute is present.": Wrong?
6209 wakaba 1.139
6210 wakaba 1.66 $element_state->{uri_info}->{action}->{type}->{action} = 1;
6211     $element_state->{uri_info}->{datasrc}->{type}->{resource} = 1;
6212 wakaba 1.79 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6213     $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6214 wakaba 1.138
6215     $element_state->{id_type} = 'labelable';
6216 wakaba 1.66 },
6217 wakaba 1.52 };
6218    
6219     $Element->{$HTML_NS}->{label} = {
6220 wakaba 1.139 %HTMLPhrasingContentChecker,
6221 wakaba 1.119 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC
6222     | FEATURE_XHTML2_ED,
6223 wakaba 1.52 check_attrs => $GetHTMLAttrsChecker->({
6224 wakaba 1.66 accesskey => $HTMLAccesskeyAttrChecker,
6225 wakaba 1.138 for => sub {
6226     my ($self, $attr) = @_;
6227    
6228     ## NOTE: MUST be an ID of a labelable element.
6229    
6230     push @{$self->{idref}}, ['labelable', $attr->value, $attr];
6231     },
6232 wakaba 1.136 form => $HTMLFormAttrChecker,
6233 wakaba 1.52 }, {
6234     %HTMLAttrStatus,
6235 wakaba 1.82 %HTMLM12NXHTML2CommonAttrStatus,
6236 wakaba 1.56 accesskey => FEATURE_WF2 | FEATURE_M12N10_REC,
6237 wakaba 1.119 for => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6238     form => FEATURE_HTML5_DEFAULT,
6239 wakaba 1.153 lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
6240 wakaba 1.52 onblur => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6241     onfocus => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6242     }),
6243 wakaba 1.139 check_start => sub {
6244     my ($self, $item, $element_state) = @_;
6245     $self->_add_minus_elements ($element_state, {$HTML_NS => {label => 1}});
6246    
6247     $element_state->{has_label_original} = $self->{flag}->{has_label};
6248     $self->{flag}->{has_label} = 1;
6249     $element_state->{has_labelable_original} = $self->{flag}->{has_labelable};
6250 wakaba 1.155 $self->{flag}->{has_labelable}
6251     = $item->{node}->has_attribute_ns (undef, 'for') ? 1 : 0;
6252 wakaba 1.139
6253     $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6254     $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6255     },
6256     check_end => sub {
6257     my ($self, $item, $element_state) = @_;
6258     $self->_remove_minus_elements ($element_state);
6259    
6260     if ($self->{flag}->{has_labelable} == 1) { # has for="" but no labelable
6261     $self->{flag}->{has_labelable}
6262     = $element_state->{has_labelable_original};
6263     }
6264     delete $self->{flag}->{has_label}
6265     unless $element_state->{has_label_original};
6266     ## TODO: Warn if no labelable descendant? <input type=hidden>?
6267    
6268     ## NOTE: |<label for=a><input id=a></label>| is non-conforming.
6269    
6270     $HTMLPhrasingContentChecker{check_end}->(@_);
6271     },
6272 wakaba 1.52 ## TODO: Tests for <nest/> in <label>
6273     };
6274    
6275     $Element->{$HTML_NS}->{select} = {
6276 wakaba 1.121 %HTMLChecker,
6277 wakaba 1.163 ## ISSUE: HTML5 has no requirement like these:
6278 wakaba 1.52 ## TODO: author should SELECTED at least one OPTION in non-MULTIPLE case [HTML4].
6279     ## TODO: more than one OPTION with SELECTED in non-MULTIPLE case is "error" [HTML4]
6280 wakaba 1.119 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6281 wakaba 1.56 is_root => 1, ## TODO: SHOULD NOT in application/xhtml+xml [WF2]
6282 wakaba 1.52 check_attrs => $GetHTMLAttrsChecker->({
6283 wakaba 1.66 accesskey => $HTMLAccesskeyAttrChecker,
6284 wakaba 1.165 autofocus => $AutofocusAttrChecker,
6285 wakaba 1.52 disabled => $GetHTMLBooleanAttrChecker->('disabled'),
6286 wakaba 1.56 data => $HTMLURIAttrChecker, ## TODO: MUST point ... [WF2]
6287 wakaba 1.136 form => $HTMLFormAttrChecker,
6288 wakaba 1.52 multiple => $GetHTMLBooleanAttrChecker->('multiple'),
6289 wakaba 1.165 name => $FormControlNameAttrChecker,
6290 wakaba 1.163 ## TODO: tests for on*
6291 wakaba 1.126 onformchange => $HTMLEventHandlerAttrChecker,
6292     onforminput => $HTMLEventHandlerAttrChecker,
6293     oninput => $HTMLEventHandlerAttrChecker,
6294 wakaba 1.56 oninvalid => $HTMLEventHandlerAttrChecker,
6295 wakaba 1.163 size => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
6296 wakaba 1.52 }, {
6297     %HTMLAttrStatus,
6298     %HTMLM12NCommonAttrStatus,
6299 wakaba 1.56 accesskey => FEATURE_WF2,
6300 wakaba 1.119 autofocus => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6301 wakaba 1.56 data => FEATURE_WF2,
6302 wakaba 1.52 datafld => FEATURE_HTML4_REC_RESERVED,
6303     dataformatas => FEATURE_HTML4_REC_RESERVED,
6304     datasrc => FEATURE_HTML4_REC_RESERVED,
6305 wakaba 1.119 disabled => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6306     form => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6307 wakaba 1.153 lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
6308 wakaba 1.119 multiple => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6309     name => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6310 wakaba 1.52 onblur => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6311     onchange => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6312 wakaba 1.126 onformchange => FEATURE_WF2_INFORMATIVE,
6313     onforminput => FEATURE_WF2_INFORMATIVE,
6314 wakaba 1.52 onfocus => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6315 wakaba 1.126 oninput => FEATURE_WF2,
6316 wakaba 1.56 oninvalid => FEATURE_WF2,
6317 wakaba 1.61 sdaform => FEATURE_HTML20_RFC,
6318     sdapref => FEATURE_HTML20_RFC,
6319 wakaba 1.119 size => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6320 wakaba 1.52 tabindex => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6321     }),
6322 wakaba 1.66 check_start => sub {
6323     my ($self, $item, $element_state) = @_;
6324 wakaba 1.139 if ($self->{flag}->{has_label} and $self->{flag}->{has_labelable}) {
6325     $self->{onerror}->(node => $item->{node},
6326     type => 'multiple labelable fae',
6327     level => $self->{level}->{must});
6328     } else {
6329     $self->{flag}->{has_labelable} = 2;
6330     }
6331 wakaba 1.66
6332     $element_state->{uri_info}->{data}->{type}->{resource} = 1;
6333     $element_state->{uri_info}->{datasrc}->{type}->{resource} = 1;
6334 wakaba 1.79 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6335     $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6336 wakaba 1.138
6337     $element_state->{id_type} = 'labelable';
6338 wakaba 1.66 },
6339 wakaba 1.121 check_child_element => sub {
6340 wakaba 1.163 ## NOTE: (option | optgroup)*
6341    
6342 wakaba 1.121 my ($self, $item, $child_el, $child_nsuri, $child_ln,
6343     $child_is_transparent, $element_state) = @_;
6344 wakaba 1.130 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
6345     $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
6346 wakaba 1.121 $self->{onerror}->(node => $child_el,
6347     type => 'element not allowed:minus',
6348     level => $self->{level}->{must});
6349     } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
6350     #
6351     } elsif ($child_nsuri eq $HTML_NS and
6352     {
6353     option => 1, optgroup => 1,
6354     }->{$child_ln}) {
6355     #
6356     } else {
6357     $self->{onerror}->(node => $child_el, type => 'element not allowed',
6358     level => $self->{level}->{must});
6359     }
6360     },
6361     check_child_text => sub {
6362     my ($self, $item, $child_node, $has_significant, $element_state) = @_;
6363     if ($has_significant) {
6364     $self->{onerror}->(node => $child_node, type => 'character not allowed',
6365     level => $self->{level}->{must});
6366     }
6367     },
6368 wakaba 1.52 };
6369 wakaba 1.1
6370 wakaba 1.52 $Element->{$HTML_NS}->{datalist} = {
6371 wakaba 1.121 %HTMLPhrasingContentChecker,
6372 wakaba 1.119 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6373 wakaba 1.56 check_attrs => $GetHTMLAttrsChecker->({
6374     data => $HTMLURIAttrChecker, ## TODO: MUST point ... [WF2]
6375     }, {
6376 wakaba 1.52 %HTMLAttrStatus,
6377 wakaba 1.56 data => FEATURE_WF2,
6378 wakaba 1.52 }),
6379 wakaba 1.66 check_start => sub {
6380     my ($self, $item, $element_state) = @_;
6381    
6382 wakaba 1.121 $element_state->{phase} = 'any'; # any | phrasing | option
6383    
6384 wakaba 1.66 $element_state->{uri_info}->{data}->{type}->{resource} = 1;
6385 wakaba 1.79 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6386     $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6387 wakaba 1.158
6388     $element_state->{id_type} = 'datalist';
6389 wakaba 1.66 },
6390 wakaba 1.121 ## NOTE: phrasing | option*
6391     check_child_element => sub {
6392     my ($self, $item, $child_el, $child_nsuri, $child_ln,
6393     $child_is_transparent, $element_state) = @_;
6394 wakaba 1.130 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
6395     $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
6396 wakaba 1.121 $self->{onerror}->(node => $child_el,
6397     type => 'element not allowed:minus',
6398     level => $self->{level}->{must});
6399     } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
6400     #
6401     } elsif ($element_state->{phase} eq 'phrasing') {
6402     if ($HTMLPhrasingContent->{$child_nsuri}->{$child_ln}) {
6403     #
6404     } else {
6405     $self->{onerror}->(node => $child_el,
6406     type => 'element not allowed:phrasing',
6407     level => $self->{level}->{must});
6408     }
6409     } elsif ($element_state->{phase} eq 'option') {
6410     if ($child_nsuri eq $HTML_NS and $child_ln eq 'option') {
6411     #
6412     } else {
6413     $self->{onerror}->(node => $child_el,
6414     type => 'element not allowed',
6415     level => $self->{level}->{must});
6416     }
6417     } elsif ($element_state->{phase} eq 'any') {
6418     if ($HTMLPhrasingContent->{$child_nsuri}->{$child_ln}) {
6419     $element_state->{phase} = 'phrasing';
6420     } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'option') {
6421     $element_state->{phase} = 'option';
6422     } else {
6423     $self->{onerror}->(node => $child_el,
6424     type => 'element not allowed',
6425     level => $self->{level}->{must});
6426     }
6427     } else {
6428     die "check_child_element: Bad |datalist| phase: $element_state->{phase}";
6429     }
6430     },
6431     check_child_text => sub {
6432     my ($self, $item, $child_node, $has_significant, $element_state) = @_;
6433     if ($has_significant) {
6434     if ($element_state->{phase} eq 'phrasing') {
6435     #
6436     } elsif ($element_state->{phase} eq 'any') {
6437     $element_state->{phase} = 'phrasing';
6438     } else {
6439     $self->{onerror}->(node => $child_node,
6440     type => 'character not allowed',
6441     level => $self->{level}->{must});
6442     }
6443     }
6444     },
6445     check_end => sub {
6446     my ($self, $item, $element_state) = @_;
6447     if ($element_state->{phase} eq 'phrasing') {
6448     if ($element_state->{has_significant}) {
6449     $item->{real_parent_state}->{has_significant} = 1;
6450     } elsif ($item->{transparent}) {
6451     #
6452     } else {
6453     $self->{onerror}->(node => $item->{node},
6454     type => 'no significant content',
6455     level => $self->{level}->{should});
6456     }
6457     } else {
6458     ## NOTE: Since the content model explicitly allows a |datalist| element
6459     ## being empty, we don't raise "no significant content" error for this
6460     ## element when there is no element. (We should raise an error for
6461     ## |<datalist><br></datalist>|, however.)
6462     ## NOTE: As a side-effect, when the |datalist| element only contains
6463     ## non-conforming content, then the |phase| flag has not changed from
6464     ## |any|, no "no significant content" error is raised neither.
6465     $HTMLChecker{check_end}->(@_);
6466     }
6467     },
6468 wakaba 1.52 };
6469 wakaba 1.49
6470 wakaba 1.52 $Element->{$HTML_NS}->{optgroup} = {
6471 wakaba 1.121 %HTMLChecker,
6472 wakaba 1.119 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6473 wakaba 1.52 check_attrs => $GetHTMLAttrsChecker->({
6474     disabled => $GetHTMLBooleanAttrChecker->('disabled'),
6475 wakaba 1.164 label => sub {},
6476 wakaba 1.52 }, {
6477     %HTMLAttrStatus,
6478     %HTMLM12NCommonAttrStatus,
6479 wakaba 1.119 disabled => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6480     label => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6481 wakaba 1.153 lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
6482 wakaba 1.52 }),
6483 wakaba 1.164 check_attrs2 => sub {
6484     my ($self, $item, $element_state) = @_;
6485    
6486     unless ($item->{node}->has_attribute_ns (undef, 'label')) {
6487     $self->{onerror}->(node => $item->{node},
6488     type => 'attribute missing',
6489     text => 'label',
6490     level => $self->{level}->{must});
6491     }
6492     },
6493 wakaba 1.121 check_child_element => sub {
6494     my ($self, $item, $child_el, $child_nsuri, $child_ln,
6495     $child_is_transparent, $element_state) = @_;
6496 wakaba 1.130 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
6497     $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
6498 wakaba 1.121 $self->{onerror}->(node => $child_el,
6499     type => 'element not allowed:minus',
6500     level => $self->{level}->{must});
6501     } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
6502     #
6503     } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'option') {
6504     #
6505     } else {
6506     $self->{onerror}->(node => $child_el, type => 'element not allowed',
6507     level => $self->{level}->{must});
6508     }
6509     },
6510     check_child_text => sub {
6511     my ($self, $item, $child_node, $has_significant, $element_state) = @_;
6512     if ($has_significant) {
6513     $self->{onerror}->(node => $child_node, type => 'character not allowed',
6514     level => $self->{level}->{must});
6515     }
6516     },
6517 wakaba 1.52 };
6518    
6519     $Element->{$HTML_NS}->{option} = {
6520     %HTMLTextChecker,
6521 wakaba 1.119 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6522 wakaba 1.52 check_attrs => $GetHTMLAttrsChecker->({
6523     disabled => $GetHTMLBooleanAttrChecker->('disabled'),
6524 wakaba 1.164 label => sub {}, ## NOTE: No restriction.
6525     selected => $GetHTMLBooleanAttrChecker->('selected'), ## ISSUE: Not a "boolean attribute"
6526     value => sub {}, ## NOTE: No restriction.
6527 wakaba 1.52 }, {
6528     %HTMLAttrStatus,
6529     %HTMLM12NCommonAttrStatus,
6530 wakaba 1.119 disabled => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6531     label => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6532 wakaba 1.153 lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
6533 wakaba 1.61 sdaform => FEATURE_HTML20_RFC,
6534     sdapref => FEATURE_HTML20_RFC,
6535 wakaba 1.119 selected => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6536     value => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6537 wakaba 1.52 }),
6538     };
6539 wakaba 1.49
6540 wakaba 1.52 $Element->{$HTML_NS}->{textarea} = {
6541     %HTMLTextChecker,
6542 wakaba 1.121 status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6543 wakaba 1.52 check_attrs => $GetHTMLAttrsChecker->({
6544 wakaba 1.164 accept => $HTMLIMTAttrChecker, ## TODO: MUST be a text-based type [WF2]
6545 wakaba 1.66 accesskey => $HTMLAccesskeyAttrChecker,
6546 wakaba 1.165 autofocus => $AutofocusAttrChecker,
6547 wakaba 1.164 cols => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
6548 wakaba 1.52 disabled => $GetHTMLBooleanAttrChecker->('disabled'),
6549 wakaba 1.136 form => $HTMLFormAttrChecker,
6550 wakaba 1.56 ## TODO: inputmode [WF2]
6551 wakaba 1.164 maxlength => sub {
6552     my ($self, $attr, $item, $element_state) = @_;
6553    
6554     $GetHTMLNonNegativeIntegerAttrChecker->(sub { 1 })->(@_);
6555    
6556 wakaba 1.165 if ($attr->value =~ /^[\x09\x0A\x0C\x0D\x20]*([0-9]+)/) {
6557 wakaba 1.164 ## NOTE: Applying the rules for parsing non-negative integers
6558     ## results in a number.
6559     my $max_allowed_value_length = 0+$1;
6560    
6561     ## ISSUE: "The the purposes of this requirement," (typo)
6562    
6563     ## ISSUE: This constraint is applied w/o CRLF normalization to
6564     ## |value| attribute, but w/ CRLF normalization to
6565     ## concept-value.
6566     my $value = $item->{node}->text_content;
6567     if (defined $value) {
6568     my $codepoint_length = length $value;
6569    
6570     if ($codepoint_length > $max_allowed_value_length) {
6571     $self->{onerror}->(node => $item->{node},
6572     type => 'value too long',
6573     level => $self->{level}->{must});
6574     }
6575     }
6576     }
6577     },
6578 wakaba 1.165 name => $FormControlNameAttrChecker,
6579 wakaba 1.164 onformchange => $HTMLEventHandlerAttrChecker, ## TODO: tests
6580     onforminput => $HTMLEventHandlerAttrChecker, ## TODO: tests
6581     oninput => $HTMLEventHandlerAttrChecker, ## TODO: tests
6582 wakaba 1.161 pattern => $PatternAttrChecker,
6583 wakaba 1.52 readonly => $GetHTMLBooleanAttrChecker->('readonly'),
6584 wakaba 1.56 required => $GetHTMLBooleanAttrChecker->('required'),
6585 wakaba 1.164 rows => $GetHTMLNonNegativeIntegerAttrChecker->(sub { shift > 0 }),
6586     oninput => $HTMLEventHandlerAttrChecker, ## TODO: tests
6587     oninvalid => $HTMLEventHandlerAttrChecker, ## TODO: tests
6588 wakaba 1.161 ## NOTE: |title| had special semantics if |pattern| was specified [WF2].
6589 wakaba 1.56 wrap => $GetHTMLEnumeratedAttrChecker->({soft => 1, hard => 1}),
6590 wakaba 1.52 }, {
6591     %HTMLAttrStatus,
6592     %HTMLM12NCommonAttrStatus,
6593 wakaba 1.164 accept => FEATURE_HTML5_DROPPED | FEATURE_WF2X,
6594 wakaba 1.61 'accept-charset' => FEATURE_HTML2X_RFC,
6595 wakaba 1.52 accesskey => FEATURE_M12N10_REC,
6596 wakaba 1.121 autofocus => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6597     cols => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6598 wakaba 1.52 datafld => FEATURE_HTML4_REC_RESERVED,
6599 wakaba 1.49 dataformatas => FEATURE_HTML4_REC_RESERVED,
6600     datasrc => FEATURE_HTML4_REC_RESERVED,
6601 wakaba 1.121 disabled => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6602     form => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6603 wakaba 1.164 inputmode => FEATURE_HTML5_DROPPED | FEATURE_WF2X | FEATURE_XHTMLBASIC11_CR,
6604 wakaba 1.153 lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
6605 wakaba 1.121 maxlength => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6606     name => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6607 wakaba 1.52 onblur => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6608     onchange => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6609     onfocus => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6610 wakaba 1.164 onformchange => FEATURE_WF2_INFORMATIVE, ## TODO: tests
6611     onforminput => FEATURE_WF2_INFORMATIVE, ## TODO: tests
6612     oninput => FEATURE_WF2, ## TODO: tests
6613     oninvalid => FEATURE_WF2, ## TODO: tests
6614 wakaba 1.52 onselect => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6615 wakaba 1.161 pattern => FEATURE_HTML5_DROPPED | FEATURE_WF2X,
6616 wakaba 1.121 readonly => FEATURE_HTML5_DEFAULT | FEATURE_WF2X | FEATURE_M12N10_REC,
6617     required => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6618     rows => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6619 wakaba 1.61 sdaform => FEATURE_HTML20_RFC,
6620     sdapref => FEATURE_HTML20_RFC,
6621 wakaba 1.52 tabindex => FEATURE_HTML5_DEFAULT | FEATURE_M12N10_REC,
6622 wakaba 1.121 wrap => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6623 wakaba 1.52 }),
6624 wakaba 1.66 check_start => sub {
6625     my ($self, $item, $element_state) = @_;
6626 wakaba 1.139 if ($self->{flag}->{has_label} and $self->{flag}->{has_labelable}) {
6627     $self->{onerror}->(node => $item->{node},
6628     type => 'multiple labelable fae',
6629     level => $self->{level}->{must});
6630     } else {
6631     $self->{flag}->{has_labelable} = 2;
6632     }
6633 wakaba 1.164
6634     $element_state->{uri_info}->{data}->{type}->{resource} = 1;
6635     $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6636     $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6637    
6638     $element_state->{id_type} = 'labelable';
6639     },
6640     check_attrs2 => sub {
6641     my ($self, $item, $element_state) = @_;
6642 wakaba 1.66
6643 wakaba 1.161 if ($item->{node}->has_attribute_ns (undef, 'pattern') and
6644     not $item->{node}->has_attribute_ns (undef, 'title')) {
6645     ## NOTE: WF2 (dropped by HTML5)
6646     $self->{onerror}->(node => $item->{node},
6647     type => 'attribute missing',
6648     text => 'title',
6649     level => $self->{level}->{should});
6650     }
6651    
6652 wakaba 1.164 unless ($item->{node}->has_attribute_ns (undef, 'cols')) {
6653     my $wrap = $item->{node}->get_attribute_ns (undef, 'wrap');
6654     if (defined $wrap) {
6655     $wrap =~ tr/A-Z/a-z/; ## ASCII case-insensitive
6656     if ($wrap eq 'hard') {
6657     $self->{onerror}->(node => $item->{node},
6658     type => 'attribute missing',
6659     text => 'cols',
6660     level => $self->{level}->{must});
6661     }
6662     }
6663     }
6664 wakaba 1.66 },
6665 wakaba 1.52 };
6666 wakaba 1.49
6667 wakaba 1.52 $Element->{$HTML_NS}->{output} = {
6668 wakaba 1.121 %HTMLPhrasingContentChecker,
6669     status => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6670 wakaba 1.56 check_attrs => $GetHTMLAttrsChecker->({
6671 wakaba 1.165 for => sub {
6672     my ($self, $attr) = @_;
6673    
6674     ## NOTE: "Unordered set of unique space-separated tokens".
6675    
6676     my %word;
6677     for my $word (grep {length $_}
6678     split /[\x09\x0A\x0C\x0D\x20]+/, $attr->value) {
6679     unless ($word{$word}) {
6680     $word{$word} = 1;
6681     push @{$self->{idref}}, ['any', $word, $attr];
6682     } else {
6683     $self->{onerror}->(node => $attr, type => 'duplicate token',
6684     value => $word,
6685     level => $self->{level}->{must});
6686     }
6687     }
6688     },
6689 wakaba 1.136 form => $HTMLFormAttrChecker,
6690 wakaba 1.165 name => $FormControlNameAttrChecker,
6691     onformchange => $HTMLEventHandlerAttrChecker, ## TODO: tests
6692     onforminput => $HTMLEventHandlerAttrChecker, ## TODO: tests
6693 wakaba 1.56 }, {
6694 wakaba 1.52 %HTMLAttrStatus,
6695 wakaba 1.121 for => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6696     form => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6697     name => FEATURE_HTML5_DEFAULT | FEATURE_WF2X,
6698 wakaba 1.56 onchange => FEATURE_HTML5_DEFAULT | FEATURE_WF2,
6699     onformchange => FEATURE_WF2,
6700     onforminput => FEATURE_WF2,
6701 wakaba 1.52 }),
6702     };
6703    
6704     $Element->{$HTML_NS}->{isindex} = {
6705     %HTMLEmptyChecker,
6706 wakaba 1.54 status => FEATURE_M12N10_REC_DEPRECATED |
6707     Whatpm::ContentChecker::FEATURE_DEPRECATED_SHOULD, ## [HTML4]
6708 wakaba 1.52 check_attrs => $GetHTMLAttrsChecker->({
6709     prompt => sub {}, ## NOTE: Text [M12N]
6710     }, {
6711     %HTMLAttrStatus,
6712 wakaba 1.153 class => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
6713     dir => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
6714     id => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
6715     lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
6716 wakaba 1.52 prompt => FEATURE_M12N10_REC_DEPRECATED,
6717 wakaba 1.61 sdapref => FEATURE_HTML20_RFC,
6718 wakaba 1.153 style => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
6719     title => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
6720 wakaba 1.52 }),
6721     ## TODO: Tests
6722     ## TODO: Tests for <nest/> in <isindex>
6723 wakaba 1.66 check_start => sub {
6724     my ($self, $item, $element_state) = @_;
6725    
6726     $element_state->{uri_info}->{action}->{type}->{action} = 1;
6727 wakaba 1.79 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6728     $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6729 wakaba 1.66 },
6730 wakaba 1.52 };
6731 wakaba 1.49
6732 wakaba 1.1 $Element->{$HTML_NS}->{script} = {
6733 wakaba 1.40 %HTMLChecker,
6734 wakaba 1.153 status => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
6735 wakaba 1.40 check_attrs => $GetHTMLAttrsChecker->({
6736 wakaba 1.91 charset => sub {
6737     my ($self, $attr) = @_;
6738    
6739     unless ($attr->owner_element->has_attribute_ns (undef, 'src')) {
6740     $self->{onerror}->(type => 'attribute not allowed',
6741     node => $attr,
6742 wakaba 1.104 level => $self->{level}->{must});
6743 wakaba 1.91 }
6744    
6745     $HTMLCharsetChecker->($attr->value, @_);
6746     },
6747 wakaba 1.86 language => sub {}, ## NOTE: No syntax constraint according to HTML4.
6748 wakaba 1.91 src => $HTMLURIAttrChecker, ## TODO: pointed resource MUST be in type of type="" (resource error)
6749 wakaba 1.1 defer => $GetHTMLBooleanAttrChecker->('defer'),
6750     async => $GetHTMLBooleanAttrChecker->('async'),
6751 wakaba 1.91 type => $HTMLIMTAttrChecker, ## TODO: MUST NOT: |charset=""| parameter
6752 wakaba 1.49 }, {
6753     %HTMLAttrStatus,
6754 wakaba 1.153 async => FEATURE_HTML5_WD,
6755     charset => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
6756     defer => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
6757 wakaba 1.49 event => FEATURE_HTML4_REC_RESERVED,
6758     for => FEATURE_HTML4_REC_RESERVED,
6759 wakaba 1.154 href => FEATURE_RDFA_REC,
6760 wakaba 1.153 id => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
6761 wakaba 1.49 language => FEATURE_M12N10_REC_DEPRECATED,
6762 wakaba 1.153 src => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
6763     type => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
6764 wakaba 1.9 }),
6765 wakaba 1.40 check_start => sub {
6766     my ($self, $item, $element_state) = @_;
6767 wakaba 1.1
6768 wakaba 1.40 if ($item->{node}->has_attribute_ns (undef, 'src')) {
6769     $element_state->{must_be_empty} = 1;
6770 wakaba 1.1 } else {
6771     ## NOTE: No content model conformance in HTML5 spec.
6772 wakaba 1.40 my $type = $item->{node}->get_attribute_ns (undef, 'type');
6773     my $language = $item->{node}->get_attribute_ns (undef, 'language');
6774 wakaba 1.1 if ((defined $type and $type eq '') or
6775     (defined $language and $language eq '')) {
6776     $type = 'text/javascript';
6777     } elsif (defined $type) {
6778     #
6779     } elsif (defined $language) {
6780     $type = 'text/' . $language;
6781     } else {
6782     $type = 'text/javascript';
6783     }
6784 wakaba 1.93
6785     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)]) {
6786     $type = "$1/$2";
6787     $type =~ tr/A-Z/a-z/; ## NOTE: ASCII case-insensitive
6788     ## TODO: Though we strip prameter here, it should not be ignored for the purpose of conformance checking...
6789     }
6790     $element_state->{script_type} = $type;
6791 wakaba 1.40 }
6792 wakaba 1.66
6793     $element_state->{uri_info}->{src}->{type}->{resource} = 1;
6794 wakaba 1.79 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6795     $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6796 wakaba 1.107
6797     $element_state->{text} = '';
6798 wakaba 1.40 },
6799     check_child_element => sub {
6800     my ($self, $item, $child_el, $child_nsuri, $child_ln,
6801     $child_is_transparent, $element_state) = @_;
6802 wakaba 1.130 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
6803     $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
6804 wakaba 1.40 $self->{onerror}->(node => $child_el,
6805     type => 'element not allowed:minus',
6806 wakaba 1.104 level => $self->{level}->{must});
6807 wakaba 1.40 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
6808     #
6809     } else {
6810     if ($element_state->{must_be_empty}) {
6811     $self->{onerror}->(node => $child_el,
6812 wakaba 1.104 type => 'element not allowed:empty',
6813     level => $self->{level}->{must});
6814 wakaba 1.40 }
6815     }
6816     },
6817     check_child_text => sub {
6818     my ($self, $item, $child_node, $has_significant, $element_state) = @_;
6819     if ($has_significant and
6820     $element_state->{must_be_empty}) {
6821     $self->{onerror}->(node => $child_node,
6822 wakaba 1.104 type => 'character not allowed:empty',
6823     level => $self->{level}->{must});
6824 wakaba 1.40 }
6825 wakaba 1.115 $element_state->{text} .= $child_node->data;
6826 wakaba 1.40 },
6827     check_end => sub {
6828     my ($self, $item, $element_state) = @_;
6829     unless ($element_state->{must_be_empty}) {
6830 wakaba 1.93 if ($element_state->{script_type} =~ m![+/][Xx][Mm][Ll]\z!) {
6831     ## NOTE: XML content should be checked by THIS instance of checker
6832     ## as part of normal tree validation.
6833 wakaba 1.104 $self->{onerror}->(node => $item->{node},
6834     type => 'XML script lang',
6835     text => $element_state->{script_type},
6836     level => $self->{level}->{uncertain});
6837     ## ISSUE: Should we raise some kind of error for
6838     ## <script type="text/xml">aaaaa</script>?
6839     ## NOTE: ^^^ This is why we throw an "uncertain" error.
6840 wakaba 1.93 } else {
6841     $self->{onsubdoc}->({s => $element_state->{text},
6842     container_node => $item->{node},
6843     media_type => $element_state->{script_type},
6844     is_char_string => 1});
6845     }
6846 wakaba 1.40
6847     $HTMLChecker{check_end}->(@_);
6848 wakaba 1.1 }
6849     },
6850 wakaba 1.91 ## TODO: There MUST be |type| unless the script type is JavaScript. (resource error)
6851     ## NOTE: "When used to include script data, the script data must be embedded
6852     ## inline, the format of the data must be given using the type attribute,
6853     ## and the src attribute must not be specified." - not testable.
6854     ## TODO: It would be possible to err <script type=text/plain src=...>
6855 wakaba 1.1 };
6856 wakaba 1.25 ## ISSUE: Significant check and text child node
6857 wakaba 1.1
6858     ## NOTE: When script is disabled.
6859     $Element->{$HTML_NS}->{noscript} = {
6860 wakaba 1.40 %HTMLTransparentChecker,
6861 wakaba 1.153 status => FEATURE_HTML5_LC | FEATURE_M12N10_REC,
6862 wakaba 1.49 check_attrs => $GetHTMLAttrsChecker->({}, {
6863     %HTMLAttrStatus,
6864     %HTMLM12NCommonAttrStatus,
6865 wakaba 1.153 lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
6866 wakaba 1.49 }),
6867 wakaba 1.40 check_start => sub {
6868     my ($self, $item, $element_state) = @_;
6869 wakaba 1.3
6870 wakaba 1.40 unless ($item->{node}->owner_document->manakai_is_html) {
6871 wakaba 1.104 $self->{onerror}->(node => $item->{node}, type => 'in XML:noscript',
6872     level => $self->{level}->{must});
6873 wakaba 1.3 }
6874    
6875 wakaba 1.40 unless ($self->{flag}->{in_head}) {
6876     $self->_add_minus_elements ($element_state,
6877     {$HTML_NS => {noscript => 1}});
6878     }
6879 wakaba 1.79
6880     $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6881     $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6882 wakaba 1.3 },
6883 wakaba 1.40 check_child_element => sub {
6884     my ($self, $item, $child_el, $child_nsuri, $child_ln,
6885     $child_is_transparent, $element_state) = @_;
6886     if ($self->{flag}->{in_head}) {
6887 wakaba 1.130 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
6888     $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
6889 wakaba 1.40 $self->{onerror}->(node => $child_el,
6890     type => 'element not allowed:minus',
6891 wakaba 1.104 level => $self->{level}->{must});
6892 wakaba 1.40 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
6893     #
6894     } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'link') {
6895     #
6896     } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'style') {
6897     if ($child_el->has_attribute_ns (undef, 'scoped')) {
6898     $self->{onerror}->(node => $child_el,
6899     type => 'element not allowed:head noscript',
6900 wakaba 1.104 level => $self->{level}->{must});
6901 wakaba 1.40 }
6902     } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'meta') {
6903 wakaba 1.47 my $http_equiv_attr
6904     = $child_el->get_attribute_node_ns (undef, 'http-equiv');
6905     if ($http_equiv_attr) {
6906     ## TODO: case
6907     if (lc $http_equiv_attr->value eq 'content-type') {
6908 wakaba 1.40 $self->{onerror}->(node => $child_el,
6909 wakaba 1.34 type => 'element not allowed:head noscript',
6910 wakaba 1.104 level => $self->{level}->{must});
6911 wakaba 1.47 } else {
6912     #
6913 wakaba 1.3 }
6914 wakaba 1.47 } else {
6915     $self->{onerror}->(node => $child_el,
6916     type => 'element not allowed:head noscript',
6917 wakaba 1.104 level => $self->{level}->{must});
6918 wakaba 1.3 }
6919 wakaba 1.40 } else {
6920     $self->{onerror}->(node => $child_el,
6921     type => 'element not allowed:head noscript',
6922 wakaba 1.104 level => $self->{level}->{must});
6923 wakaba 1.40 }
6924     } else {
6925     $HTMLTransparentChecker{check_child_element}->(@_);
6926     }
6927     },
6928     check_child_text => sub {
6929     my ($self, $item, $child_node, $has_significant, $element_state) = @_;
6930     if ($self->{flag}->{in_head}) {
6931     if ($has_significant) {
6932     $self->{onerror}->(node => $child_node,
6933 wakaba 1.104 type => 'character not allowed',
6934     level => $self->{level}->{must});
6935 wakaba 1.3 }
6936     } else {
6937 wakaba 1.40 $HTMLTransparentChecker{check_child_text}->(@_);
6938     }
6939     },
6940     check_end => sub {
6941     my ($self, $item, $element_state) = @_;
6942     $self->_remove_minus_elements ($element_state);
6943     if ($self->{flag}->{in_head}) {
6944     $HTMLChecker{check_end}->(@_);
6945     } else {
6946     $HTMLPhrasingContentChecker{check_end}->(@_);
6947 wakaba 1.3 }
6948 wakaba 1.1 },
6949     };
6950 wakaba 1.3 ## ISSUE: Scripting is disabled: <head><noscript><html a></noscript></head>
6951 wakaba 1.1
6952     $Element->{$HTML_NS}->{'event-source'} = {
6953 wakaba 1.40 %HTMLEmptyChecker,
6954 wakaba 1.118 status => FEATURE_HTML5_LC_DROPPED,
6955     check_attrs => $GetHTMLAttrsChecker->({
6956     src => $HTMLURIAttrChecker,
6957     }, {
6958     %HTMLAttrStatus,
6959     src => FEATURE_HTML5_LC_DROPPED,
6960     }),
6961     check_start => sub {
6962     my ($self, $item, $element_state) = @_;
6963    
6964     $element_state->{uri_info}->{src}->{type}->{resource} = 1;
6965     $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6966     $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6967     },
6968     };
6969    
6970     $Element->{$HTML_NS}->{eventsource} = {
6971     %HTMLEmptyChecker,
6972 wakaba 1.153 status => FEATURE_HTML5_WD,
6973 wakaba 1.40 check_attrs => $GetHTMLAttrsChecker->({
6974 wakaba 1.1 src => $HTMLURIAttrChecker,
6975 wakaba 1.50 }, {
6976     %HTMLAttrStatus,
6977 wakaba 1.153 src => FEATURE_HTML5_WD,
6978 wakaba 1.1 }),
6979 wakaba 1.66 check_start => sub {
6980     my ($self, $item, $element_state) = @_;
6981    
6982     $element_state->{uri_info}->{src}->{type}->{resource} = 1;
6983 wakaba 1.79 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
6984     $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
6985 wakaba 1.66 },
6986 wakaba 1.1 };
6987    
6988     $Element->{$HTML_NS}->{details} = {
6989 wakaba 1.134 %{$Element->{$HTML_NS}->{fieldset}},
6990 wakaba 1.153 status => FEATURE_HTML5_LC,
6991 wakaba 1.40 check_attrs => $GetHTMLAttrsChecker->({
6992 wakaba 1.1 open => $GetHTMLBooleanAttrChecker->('open'),
6993 wakaba 1.50 }, {
6994     %HTMLAttrStatus,
6995 wakaba 1.153 open => FEATURE_HTML5_LC,
6996 wakaba 1.1 }),
6997     };
6998    
6999     $Element->{$HTML_NS}->{datagrid} = {
7000 wakaba 1.72 %HTMLFlowContentChecker,
7001 wakaba 1.48 status => FEATURE_HTML5_WD,
7002 wakaba 1.40 check_attrs => $GetHTMLAttrsChecker->({
7003 wakaba 1.1 disabled => $GetHTMLBooleanAttrChecker->('disabled'),
7004     multiple => $GetHTMLBooleanAttrChecker->('multiple'),
7005 wakaba 1.50 }, {
7006     %HTMLAttrStatus,
7007     disabled => FEATURE_HTML5_WD,
7008     multiple => FEATURE_HTML5_WD,
7009 wakaba 1.1 }),
7010 wakaba 1.40 check_start => sub {
7011     my ($self, $item, $element_state) = @_;
7012 wakaba 1.1
7013 wakaba 1.40 $self->_add_minus_elements ($element_state,
7014     {$HTML_NS => {a => 1, datagrid => 1}});
7015     $element_state->{phase} = 'any';
7016 wakaba 1.79 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
7017     $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
7018 wakaba 1.40 },
7019 wakaba 1.95 ## NOTE: Flow -(text* (table|select|datalist) Flow*) | table | select |
7020     ## datalist | Empty
7021 wakaba 1.40 check_child_element => sub {
7022     my ($self, $item, $child_el, $child_nsuri, $child_ln,
7023     $child_is_transparent, $element_state) = @_;
7024 wakaba 1.130 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
7025     $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
7026 wakaba 1.40 $self->{onerror}->(node => $child_el,
7027     type => 'element not allowed:minus',
7028 wakaba 1.104 level => $self->{level}->{must});
7029 wakaba 1.40 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
7030     #
7031 wakaba 1.72 } elsif ($element_state->{phase} eq 'flow') {
7032     if ($HTMLFlowContent->{$child_nsuri}->{$child_ln}) {
7033 wakaba 1.44 if (not $element_state->{has_element} and
7034 wakaba 1.40 $child_nsuri eq $HTML_NS and
7035 wakaba 1.95 {
7036     table => 1, select => 1, datalist => 1,
7037     }->{$child_ln}) {
7038 wakaba 1.40 $self->{onerror}->(node => $child_el,
7039 wakaba 1.104 type => 'element not allowed',
7040     level => $self->{level}->{must});
7041 wakaba 1.40 } else {
7042 wakaba 1.8 #
7043 wakaba 1.1 }
7044 wakaba 1.40 } else {
7045     $self->{onerror}->(node => $child_el,
7046 wakaba 1.121 type => 'element not allowed', ## TODO: :flow
7047 wakaba 1.104 level => $self->{level}->{must});
7048 wakaba 1.40 }
7049 wakaba 1.43 $element_state->{has_element} = 1;
7050 wakaba 1.40 } elsif ($element_state->{phase} eq 'any') {
7051     if ($child_nsuri eq $HTML_NS and
7052     {table => 1, select => 1, datalist => 1}->{$child_ln}) {
7053     $element_state->{phase} = 'none';
7054 wakaba 1.72 } elsif ($HTMLFlowContent->{$child_nsuri}->{$child_ln}) {
7055 wakaba 1.40 $element_state->{has_element} = 1;
7056 wakaba 1.72 $element_state->{phase} = 'flow';
7057 wakaba 1.40 } else {
7058     $self->{onerror}->(node => $child_el,
7059 wakaba 1.104 type => 'element not allowed',
7060     level => $self->{level}->{must});
7061 wakaba 1.40 }
7062     } elsif ($element_state->{phase} eq 'none') {
7063     $self->{onerror}->(node => $child_el,
7064 wakaba 1.104 type => 'element not allowed',
7065     level => $self->{level}->{must});
7066 wakaba 1.40 } else {
7067     die "check_child_element: Bad |datagrid| phase: $element_state->{phase}";
7068     }
7069     },
7070     check_child_text => sub {
7071     my ($self, $item, $child_node, $has_significant, $element_state) = @_;
7072     if ($has_significant) {
7073 wakaba 1.72 if ($element_state->{phase} eq 'flow') {
7074 wakaba 1.40 #
7075     } elsif ($element_state->{phase} eq 'any') {
7076 wakaba 1.72 $element_state->{phase} = 'flow';
7077 wakaba 1.40 } else {
7078     $self->{onerror}->(node => $child_node,
7079 wakaba 1.104 type => 'character not allowed',
7080     level => $self->{level}->{must});
7081 wakaba 1.1 }
7082     }
7083 wakaba 1.40 },
7084     check_end => sub {
7085     my ($self, $item, $element_state) = @_;
7086     $self->_remove_minus_elements ($element_state);
7087 wakaba 1.1
7088 wakaba 1.95 if ($element_state->{phase} eq 'flow') {
7089     if ($element_state->{has_significant}) {
7090     $item->{real_parent_state}->{has_significant} = 1;
7091     } elsif ($item->{transparent}) {
7092     #
7093     } else {
7094     $self->{onerror}->(node => $item->{node},
7095 wakaba 1.104 type => 'no significant content',
7096 wakaba 1.110 level => $self->{level}->{should});
7097 wakaba 1.95 }
7098     } else {
7099     ## NOTE: Since the content model explicitly allows a |datagird| element
7100     ## being empty, we don't raise "no significant content" error for this
7101     ## element when there is no element. (We should raise an error for
7102     ## |<datagrid><br></datagrid>|, however.)
7103     ## NOTE: As a side-effect, when the |datagrid| element only contains
7104     ## non-conforming content, then the |phase| flag has not changed from
7105     ## |any|, no "no significant content" error is raised neither.
7106     ## NOTE: Another side-effect of the current implementation:
7107     ## |<daragrid><datagrid/></datagrid>| has no "no significant content"
7108     ## error at all.
7109 wakaba 1.40 $HTMLChecker{check_end}->(@_);
7110     }
7111     },
7112 wakaba 1.1 };
7113    
7114     $Element->{$HTML_NS}->{command} = {
7115 wakaba 1.40 %HTMLEmptyChecker,
7116 wakaba 1.48 status => FEATURE_HTML5_WD,
7117 wakaba 1.40 check_attrs => $GetHTMLAttrsChecker->({
7118 wakaba 1.1 checked => $GetHTMLBooleanAttrChecker->('checked'),
7119     default => $GetHTMLBooleanAttrChecker->('default'),
7120     disabled => $GetHTMLBooleanAttrChecker->('disabled'),
7121     icon => $HTMLURIAttrChecker,
7122     label => sub { }, ## NOTE: No conformance creteria
7123     radiogroup => sub { }, ## NOTE: No conformance creteria
7124     type => sub {
7125     my ($self, $attr) = @_;
7126     my $value = $attr->value;
7127     unless ({command => 1, checkbox => 1, radio => 1}->{$value}) {
7128 wakaba 1.104 $self->{onerror}->(node => $attr, type => 'invalid attribute value',
7129     level => $self->{level}->{must});
7130 wakaba 1.1 }
7131     },
7132 wakaba 1.50 }, {
7133     %HTMLAttrStatus,
7134     checked => FEATURE_HTML5_WD,
7135     default => FEATURE_HTML5_WD,
7136     disabled => FEATURE_HTML5_WD,
7137     icon => FEATURE_HTML5_WD,
7138     label => FEATURE_HTML5_WD,
7139     radiogroup => FEATURE_HTML5_WD,
7140     type => FEATURE_HTML5_WD,
7141 wakaba 1.1 }),
7142 wakaba 1.66 check_start => sub {
7143     my ($self, $item, $element_state) = @_;
7144    
7145     $element_state->{uri_info}->{icon}->{type}->{embedded} = 1;
7146 wakaba 1.79 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
7147     $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
7148 wakaba 1.66 },
7149 wakaba 1.115 };
7150    
7151     $Element->{$HTML_NS}->{bb} = {
7152     %HTMLPhrasingContentChecker,
7153 wakaba 1.153 status => FEATURE_HTML5_WD,
7154 wakaba 1.115 check_attrs => $GetHTMLAttrsChecker->({
7155     type => $GetHTMLEnumeratedAttrChecker->({makeapp => 1}),
7156     }, {
7157     %HTMLAttrStatus,
7158 wakaba 1.153 type => FEATURE_HTML5_WD,
7159 wakaba 1.115 }),
7160 wakaba 1.130 check_start => sub {
7161     my ($self, $item, $element_state) = @_;
7162     $self->_add_minus_elements ($element_state, $HTMLInteractiveContent);
7163    
7164     $element_state->{uri_info}->{template}->{type}->{resource} = 1;
7165     $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
7166     },
7167     check_end => sub {
7168     my ($self, $item, $element_state) = @_;
7169     $self->_remove_minus_elements ($element_state);
7170    
7171     $HTMLTransparentChecker{check_end}->(@_);
7172     },
7173 wakaba 1.1 };
7174    
7175     $Element->{$HTML_NS}->{menu} = {
7176 wakaba 1.40 %HTMLPhrasingContentChecker,
7177 wakaba 1.54 #status => FEATURE_M12N10_REC_DEPRECATED | FEATURE_HTML5_WD,
7178     status => FEATURE_M12N10_REC | FEATURE_HTML5_WD,
7179     ## NOTE: We don't want any |menu| element warned as deprecated.
7180 wakaba 1.40 check_attrs => $GetHTMLAttrsChecker->({
7181 wakaba 1.1 autosubmit => $GetHTMLBooleanAttrChecker->('autosubmit'),
7182 wakaba 1.68 compact => $GetHTMLBooleanAttrChecker->('compact'),
7183 wakaba 1.135 ## ISSUE: <menu id=""><p contextmenu=""> match? (In the current
7184     ## implementation, it does not match.)
7185 wakaba 1.1 label => sub { }, ## NOTE: No conformance creteria
7186     type => $GetHTMLEnumeratedAttrChecker->({context => 1, toolbar => 1}),
7187 wakaba 1.49 }, {
7188     %HTMLAttrStatus,
7189     %HTMLM12NCommonAttrStatus,
7190 wakaba 1.61 align => FEATURE_HTML2X_RFC,
7191 wakaba 1.113 autosubmit => FEATURE_HTML5_DROPPED,
7192 wakaba 1.49 compat => FEATURE_M12N10_REC_DEPRECATED,
7193 wakaba 1.50 label => FEATURE_HTML5_WD,
7194 wakaba 1.153 lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
7195 wakaba 1.61 sdaform => FEATURE_HTML20_RFC,
7196     sdapref => FEATURE_HTML20_RFC,
7197 wakaba 1.50 type => FEATURE_HTML5_WD,
7198 wakaba 1.1 }),
7199 wakaba 1.40 check_start => sub {
7200     my ($self, $item, $element_state) = @_;
7201     $element_state->{phase} = 'li or phrasing';
7202     $element_state->{in_menu_original} = $self->{flag}->{in_menu};
7203     $self->{flag}->{in_menu} = 1;
7204 wakaba 1.79
7205     $element_state->{uri_info}->{template}->{type}->{resource} = 1;
7206     $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
7207 wakaba 1.135 $element_state->{id_type} = 'menu';
7208 wakaba 1.40 },
7209     check_child_element => sub {
7210     my ($self, $item, $child_el, $child_nsuri, $child_ln,
7211     $child_is_transparent, $element_state) = @_;
7212 wakaba 1.130 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
7213     $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
7214 wakaba 1.40 $self->{onerror}->(node => $child_el,
7215     type => 'element not allowed:minus',
7216 wakaba 1.104 level => $self->{level}->{must});
7217 wakaba 1.40 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
7218     #
7219     } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'li') {
7220     if ($element_state->{phase} eq 'li') {
7221     #
7222     } elsif ($element_state->{phase} eq 'li or phrasing') {
7223     $element_state->{phase} = 'li';
7224     } else {
7225 wakaba 1.104 $self->{onerror}->(node => $child_el, type => 'element not allowed',
7226     level => $self->{level}->{must});
7227 wakaba 1.40 }
7228     } elsif ($HTMLPhrasingContent->{$child_nsuri}->{$child_ln}) {
7229     if ($element_state->{phase} eq 'phrasing') {
7230     #
7231     } elsif ($element_state->{phase} eq 'li or phrasing') {
7232     $element_state->{phase} = 'phrasing';
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     } else {
7238 wakaba 1.104 $self->{onerror}->(node => $child_el, type => 'element not allowed',
7239     level => $self->{level}->{must});
7240 wakaba 1.40 }
7241     },
7242     check_child_text => sub {
7243     my ($self, $item, $child_node, $has_significant, $element_state) = @_;
7244     if ($has_significant) {
7245     if ($element_state->{phase} eq 'phrasing') {
7246     #
7247     } elsif ($element_state->{phase} eq 'li or phrasing') {
7248     $element_state->{phase} = 'phrasing';
7249     } else {
7250     $self->{onerror}->(node => $child_node,
7251 wakaba 1.104 type => 'character not allowed',
7252     level => $self->{level}->{must});
7253 wakaba 1.1 }
7254     }
7255 wakaba 1.40 },
7256     check_end => sub {
7257     my ($self, $item, $element_state) = @_;
7258     delete $self->{flag}->{in_menu} unless $element_state->{in_menu_original};
7259    
7260     if ($element_state->{phase} eq 'li') {
7261     $HTMLChecker{check_end}->(@_);
7262     } else { # 'phrasing' or 'li or phrasing'
7263     $HTMLPhrasingContentChecker{check_end}->(@_);
7264 wakaba 1.1 }
7265     },
7266 wakaba 1.8 };
7267    
7268     $Element->{$HTML_NS}->{datatemplate} = {
7269 wakaba 1.40 %HTMLChecker,
7270 wakaba 1.48 status => FEATURE_HTML5_AT_RISK,
7271 wakaba 1.40 check_child_element => sub {
7272     my ($self, $item, $child_el, $child_nsuri, $child_ln,
7273     $child_is_transparent, $element_state) = @_;
7274 wakaba 1.130 if ($self->{minus_elements}->{$child_nsuri}->{$child_ln} and
7275     $IsInHTMLInteractiveContent->($child_el, $child_nsuri, $child_ln)) {
7276 wakaba 1.40 $self->{onerror}->(node => $child_el,
7277     type => 'element not allowed:minus',
7278 wakaba 1.104 level => $self->{level}->{must});
7279 wakaba 1.40 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
7280     #
7281     } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'rule') {
7282     #
7283     } else {
7284     $self->{onerror}->(node => $child_el,
7285 wakaba 1.104 type => 'element not allowed:datatemplate',
7286     level => $self->{level}->{must});
7287 wakaba 1.40 }
7288     },
7289     check_child_text => sub {
7290     my ($self, $item, $child_node, $has_significant, $element_state) = @_;
7291     if ($has_significant) {
7292 wakaba 1.104 $self->{onerror}->(node => $child_node, type => 'character not allowed',
7293     level => $self->{level}->{must});
7294 wakaba 1.8 }
7295     },
7296     is_xml_root => 1,
7297     };
7298    
7299     $Element->{$HTML_NS}->{rule} = {
7300 wakaba 1.40 %HTMLChecker,
7301 wakaba 1.48 status => FEATURE_HTML5_AT_RISK,
7302 wakaba 1.40 check_attrs => $GetHTMLAttrsChecker->({
7303 wakaba 1.23 condition => $HTMLSelectorsAttrChecker,
7304 wakaba 1.92 mode => $GetHTMLUnorderedUniqueSetOfSpaceSeparatedTokensAttrChecker->(),
7305 wakaba 1.50 }, {
7306     %HTMLAttrStatus,
7307     condition => FEATURE_HTML5_AT_RISK,
7308     mode => FEATURE_HTML5_AT_RISK,
7309 wakaba 1.8 }),
7310 wakaba 1.40 check_start => sub {
7311     my ($self, $item, $element_state) = @_;
7312 wakaba 1.79
7313 wakaba 1.40 $self->_add_plus_elements ($element_state, {$HTML_NS => {nest => 1}});
7314 wakaba 1.79 $element_state->{in_rule_original} = $self->{flag}->{in_rule};
7315     $self->{flag}->{in_rule} = 1;
7316    
7317     $element_state->{uri_info}->{template}->{type}->{resource} = 1;
7318     $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
7319 wakaba 1.40 },
7320     check_child_element => sub { },
7321     check_child_text => sub { },
7322     check_end => sub {
7323     my ($self, $item, $element_state) = @_;
7324 wakaba 1.79
7325 wakaba 1.40 $self->_remove_plus_elements ($element_state);
7326 wakaba 1.79 delete $self->{flag}->{in_rule} unless $element_state->{in_rule_original};
7327    
7328 wakaba 1.40 $HTMLChecker{check_end}->(@_);
7329 wakaba 1.8 },
7330     ## NOTE: "MAY be anything that, when the parent |datatemplate|
7331     ## is applied to some conforming data, results in a conforming DOM tree.":
7332     ## We don't check against this.
7333     };
7334    
7335     $Element->{$HTML_NS}->{nest} = {
7336 wakaba 1.40 %HTMLEmptyChecker,
7337 wakaba 1.48 status => FEATURE_HTML5_AT_RISK,
7338 wakaba 1.40 check_attrs => $GetHTMLAttrsChecker->({
7339 wakaba 1.23 filter => $HTMLSelectorsAttrChecker,
7340     mode => sub {
7341     my ($self, $attr) = @_;
7342     my $value = $attr->value;
7343 wakaba 1.132 if ($value !~ /\A[^\x09\x0A\x0C\x0D\x20]+\z/) {
7344 wakaba 1.104 $self->{onerror}->(node => $attr, type => 'mode:syntax error',
7345     level => $self->{level}->{must});
7346 wakaba 1.23 }
7347     },
7348 wakaba 1.50 }, {
7349     %HTMLAttrStatus,
7350     filter => FEATURE_HTML5_AT_RISK,
7351     mode => FEATURE_HTML5_AT_RISK,
7352 wakaba 1.8 }),
7353 wakaba 1.1 };
7354    
7355     $Element->{$HTML_NS}->{legend} = {
7356 wakaba 1.40 %HTMLPhrasingContentChecker,
7357 wakaba 1.153 status => FEATURE_HTML5_LC | FEATURE_M12N10_REC,
7358 wakaba 1.52 check_attrs => $GetHTMLAttrsChecker->({
7359 wakaba 1.66 accesskey => $HTMLAccesskeyAttrChecker,
7360 wakaba 1.52 # align => $GetHTMLEnumeratedAttrChecker->({
7361     # top => 1, bottom => 1, left => 1, right => 1,
7362     # }),
7363     }, {
7364 wakaba 1.49 %HTMLAttrStatus,
7365     %HTMLM12NCommonAttrStatus,
7366     accesskey => FEATURE_M12N10_REC,
7367     align => FEATURE_M12N10_REC_DEPRECATED,
7368 wakaba 1.153 lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
7369 wakaba 1.49 }),
7370 wakaba 1.1 };
7371    
7372     $Element->{$HTML_NS}->{div} = {
7373 wakaba 1.72 %HTMLFlowContentChecker,
7374 wakaba 1.153 status => FEATURE_HTML5_LC | FEATURE_XHTML2_ED | FEATURE_M12N10_REC,
7375 wakaba 1.68 check_attrs => $GetHTMLAttrsChecker->({
7376     align => $GetHTMLEnumeratedAttrChecker->({
7377     left => 1, center => 1, right => 1, justify => 1,
7378     }),
7379     }, {
7380 wakaba 1.49 %HTMLAttrStatus,
7381 wakaba 1.82 %HTMLM12NXHTML2CommonAttrStatus,
7382 wakaba 1.49 align => FEATURE_M12N10_REC_DEPRECATED,
7383     datafld => FEATURE_HTML4_REC_RESERVED,
7384     dataformatas => FEATURE_HTML4_REC_RESERVED,
7385     datasrc => FEATURE_HTML4_REC_RESERVED,
7386 wakaba 1.153 lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
7387 wakaba 1.49 }),
7388 wakaba 1.66 check_start => sub {
7389     my ($self, $item, $element_state) = @_;
7390    
7391     $element_state->{uri_info}->{datasrc}->{type}->{resource} = 1;
7392 wakaba 1.79 $element_state->{uri_info}->{template}->{type}->{resource} = 1;
7393     $element_state->{uri_info}->{ref}->{type}->{resource} = 1;
7394 wakaba 1.66 },
7395 wakaba 1.1 };
7396    
7397 wakaba 1.64 $Element->{$HTML_NS}->{center} = {
7398 wakaba 1.72 %HTMLFlowContentChecker,
7399 wakaba 1.64 status => FEATURE_M12N10_REC_DEPRECATED,
7400     check_attrs => $GetHTMLAttrsChecker->({}, {
7401     %HTMLAttrStatus,
7402     %HTMLM12NCommonAttrStatus,
7403 wakaba 1.153 lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
7404 wakaba 1.64 }),
7405     };
7406    
7407 wakaba 1.1 $Element->{$HTML_NS}->{font} = {
7408 wakaba 1.40 %HTMLTransparentChecker,
7409 wakaba 1.78 status => FEATURE_HTML5_DROPPED | FEATURE_M12N10_REC_DEPRECATED,
7410 wakaba 1.70 check_attrs => $GetHTMLAttrsChecker->({
7411     ## TODO: HTML4 |size|, |color|, |face|
7412 wakaba 1.49 }, {
7413     %HTMLAttrStatus,
7414 wakaba 1.153 class => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
7415 wakaba 1.49 color => FEATURE_M12N10_REC_DEPRECATED,
7416 wakaba 1.153 dir => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
7417 wakaba 1.49 face => FEATURE_M12N10_REC_DEPRECATED,
7418 wakaba 1.153 id => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
7419     lang => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
7420 wakaba 1.49 size => FEATURE_M12N10_REC_DEPRECATED,
7421 wakaba 1.153 style => FEATURE_HTML5_WD | FEATURE_XHTML10_REC,
7422     title => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
7423 wakaba 1.49 }),
7424 wakaba 1.78 ## NOTE: When the |font| element was defined in the HTML5 specification,
7425     ## it is allowed only in a document with the WYSIWYG signature. The
7426     ## checker does not check whether there is the signature, since the
7427     ## signature is dropped, too, and has never been implemented. (In addition,
7428     ## for any |font| element an "element not defined" error is raised anyway,
7429     ## such that we don't have to raise an additional error.)
7430 wakaba 1.1 };
7431 wakaba 1.49
7432 wakaba 1.64 $Element->{$HTML_NS}->{basefont} = {
7433     %HTMLEmptyChecker,
7434     status => FEATURE_M12N10_REC_DEPRECATED,
7435     check_attrs => $GetHTMLAttrsChecker->({
7436     ## TODO: color, face, size
7437     }, {
7438     %HTMLAttrStatus,
7439     color => FEATURE_M12N10_REC_DEPRECATED,
7440     face => FEATURE_M12N10_REC_DEPRECATED,
7441 wakaba 1.153 #id => FEATURE_HTML5_WD | FEATURE_M12N10_REC_DEPRECATED,
7442     id => FEATURE_HTML5_WD | FEATURE_M12N10_REC,
7443 wakaba 1.64 size => FEATURE_M12N10_REC_DEPRECATED,
7444     }),
7445     };
7446    
7447 wakaba 1.49 ## TODO: frameset FEATURE_M12N10_REC
7448     ## class title id cols rows onload onunload style(x10)
7449     ## frame frameborder longdesc marginheight marginwidth noresize scrolling src name(deprecated) class,id,title,style(x10)
7450     ## noframes Common, lang(xhtml10)
7451    
7452 wakaba 1.100 ## TODO: CR: rbc rtc @rbspan (M12NXHTML2Common)
7453 wakaba 1.56
7454 wakaba 1.61 ## TODO: xmp, listing, plaintext FEATURE_HTML32_REC_OBSOLETE
7455     ## TODO: ^^^ lang, dir, id, class [HTML 2.x] sdaform [HTML 2.0]
7456     ## xmp, listing sdapref[HTML2,0]
7457    
7458 wakaba 1.56 =pod
7459    
7460 wakaba 1.61 HTML 2.0 nextid @n
7461    
7462     RFC 2659: CERTS CRYPTOPTS
7463    
7464     ISO-HTML: pre-html, divN
7465 wakaba 1.82
7466     XHTML2: blockcode (Common), h (Common), separator (Common), l (Common),
7467     di (Common), nl (Common), handler (Common, type), standby (Common),
7468     summary (Common)
7469    
7470 wakaba 1.97 Access & XHTML2: access (LC)
7471 wakaba 1.82
7472     XML Events & XForms (for XHTML2 support; very, very low priority)
7473 wakaba 1.61
7474 wakaba 1.56 =cut
7475 wakaba 1.61
7476     ## NOTE: Where RFC 2659 allows additional attributes is unclear.
7477     ## We added them only to |a|. |link| and |form| might also allow them
7478     ## in theory.
7479 wakaba 1.1
7480     $Whatpm::ContentChecker::Namespace->{$HTML_NS}->{loaded} = 1;
7481    
7482     1;

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24