/[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.88 - (hide annotations) (download)
Sat May 10 06:04:39 2008 UTC (18 years, 5 months ago) by wakaba
Branch: MAIN
Changes since 1.87: +3 -2 lines
++ whatpm/t/ChangeLog	10 May 2008 05:54:08 -0000
2008-05-10  Wakaba  <wakaba@suika.fam.cx>

	* content-model-2.dat: Boolean attributes now allow uppercase
	letters (HTML5 revision 1572).

++ whatpm/Whatpm/ChangeLog	10 May 2008 05:57:19 -0000
2008-05-10  Wakaba  <wakaba@suika.fam.cx>

	* CacheManifest.pm: Don't replace U+0000 NULL (HTML5 revision
	1553).

++ whatpm/Whatpm/ContentChecker/ChangeLog	10 May 2008 05:54:35 -0000
2008-05-10  Wakaba  <wakaba@suika.fam.cx>

	* HTML.pm ($GetHTMLBooleanAttrChecker): Allow uppercase
	letters (HTML5 revision 1572).

1 wakaba 1.1 package Whatpm::ContentChecker;
2     use strict;
3     require Whatpm::ContentChecker;
4    
5     my $HTML_NS = q<http://www.w3.org/1999/xhtml>;
6    
7 wakaba 1.58 sub FEATURE_HTML5_ROLE () {
8     Whatpm::ContentChecker::FEATURE_STATUS_WD
9     ## TODO: svg:*/@role
10     }
11    
12 wakaba 1.54 sub FEATURE_HTML5_LC () {
13     Whatpm::ContentChecker::FEATURE_STATUS_LC |
14     Whatpm::ContentChecker::FEATURE_ALLOWED
15     }
16     sub FEATURE_HTML5_AT_RISK () {
17     Whatpm::ContentChecker::FEATURE_STATUS_WD |
18     Whatpm::ContentChecker::FEATURE_ALLOWED
19     }
20     sub FEATURE_HTML5_WD () {
21     Whatpm::ContentChecker::FEATURE_STATUS_WD |
22     Whatpm::ContentChecker::FEATURE_ALLOWED
23     }
24     sub FEATURE_HTML5_FD () {
25     Whatpm::ContentChecker::FEATURE_STATUS_WD |
26     Whatpm::ContentChecker::FEATURE_ALLOWED
27     }
28     sub FEATURE_HTML5_DEFAULT () {
29     Whatpm::ContentChecker::FEATURE_STATUS_WD |
30     Whatpm::ContentChecker::FEATURE_ALLOWED
31 wakaba 1.49 }
32 wakaba 1.54 sub FEATURE_HTML5_DROPPED () {
33     ## NOTE: Was part of HTML5, but was dropped.
34 wakaba 1.49 Whatpm::ContentChecker::FEATURE_STATUS_WD
35     }
36 wakaba 1.54 sub FEATURE_WF2 () {
37     Whatpm::ContentChecker::FEATURE_STATUS_LC |
38     Whatpm::ContentChecker::FEATURE_ALLOWED
39     }
40 wakaba 1.56 sub FEATURE_WF2_DEPRECATED () {
41     Whatpm::ContentChecker::FEATURE_STATUS_LC
42     ## NOTE: MUST NOT be used.
43     }
44 wakaba 1.49
45 wakaba 1.61 ## NOTE: Metainformation Attributes Module by W3C XHTML2 WG.
46     sub FEATURE_RDFA_LC () {
47     Whatpm::ContentChecker::FEATURE_STATUS_LC
48     }
49 wakaba 1.58
50 wakaba 1.82 sub FEATURE_RDFA_ED () {
51     Whatpm::ContentChecker::FEATURE_STATUS_WD
52     }
53    
54 wakaba 1.58 ## NOTE: XHTML Role LCWD has almost no information on how the |role|
55     ## attribute can be used- the only requirements for that matter is:
56     ## "the attribute MUST be referenced using its namespace-qualified form" (and
57     ## this is a host language conformance!).
58 wakaba 1.82 sub FEATURE_ROLE_LC () {
59     Whatpm::ContentChecker::FEATURE_STATUS_LC
60     }
61    
62     sub FEATURE_XHTML2_ED () {
63     Whatpm::ContentChecker::FEATURE_STATUS_WD
64     }
65 wakaba 1.58
66 wakaba 1.55 sub FEATURE_XHTMLBASIC11_CR () {
67     ## NOTE: Only additions to M12N10_REC are marked.
68     Whatpm::ContentChecker::FEATURE_STATUS_CR
69     }
70     sub FEATURE_XHTMLBASIC11_CR_DEPRECATED () {
71     Whatpm::ContentChecker::FEATURE_STATUS_CR |
72     Whatpm::ContentChecker::FEATURE_DEPRECATED_INFO
73     }
74    
75 wakaba 1.82 sub FEATURE_M12N11_LC () {
76     Whatpm::ContentChecker::FEATURE_STATUS_LC
77     }
78    
79 wakaba 1.49 ## NOTE: M12N10 status is based on its abstract module definition.
80     ## It contains a number of problems. (However, again, it's a REC!)
81 wakaba 1.54 sub FEATURE_M12N10_REC () {
82     ## NOTE: Oh, XHTML m12n 1.0 passed the CR phase! W3C Process suck!
83     Whatpm::ContentChecker::FEATURE_STATUS_REC
84     }
85     sub FEATURE_M12N10_REC_DEPRECATED () {
86     Whatpm::ContentChecker::FEATURE_STATUS_REC |
87     Whatpm::ContentChecker::FEATURE_DEPRECATED_INFO
88     }
89 wakaba 1.58 ## NOTE: XHTML M12N 1.1 is a LC at the time of writing and no
90     ## addition from 1.0.
91 wakaba 1.49
92     ## NOTE: XHTML10 status is based on its transitional and frameset DTDs
93     ## (second edition). Only missing attributes from M12N10 abstract
94     ## definition are added.
95 wakaba 1.54 sub FEATURE_XHTML10_REC () {
96     Whatpm::ContentChecker::FEATURE_STATUS_CR
97     }
98    
99 wakaba 1.61 ## NOTE: Diff from HTML4.
100     sub FEATURE_ISOHTML_PREPARATION () { ## Informative documentation
101     Whatpm::ContentChecker::FEATURE_STATUS_CR
102     }
103 wakaba 1.58
104 wakaba 1.49 ## NOTE: HTML4 status is based on its transitional and frameset DTDs (HTML
105     ## 4.01). Only missing attributes from XHTML10 are added.
106 wakaba 1.54 sub FEATURE_HTML4_REC_RESERVED () {
107     Whatpm::ContentChecker::FEATURE_STATUS_WD
108     }
109    
110     ## TODO: According to HTML4 definition, authors SHOULD use style sheets
111     ## rather than presentational attributes (deprecated or not deprecated).
112 wakaba 1.48
113 wakaba 1.61 ## NOTE: Diff from HTML4.
114     sub FEATURE_HTML32_REC_OBSOLETE () {
115     Whatpm::ContentChecker::FEATURE_STATUS_CR |
116     Whatpm::ContentChecker::FEATURE_DEPRECATED_SHOULD
117     ## NOTE: Lowercase normative "should".
118     }
119    
120     sub FEATURE_RFC2659 () { ## Experimental RFC
121     Whatpm::ContentChecker::FEATURE_STATUS_CR
122     }
123    
124     ## NOTE: HTML 2.x - diff from HTML 2.0 and not in newer versions.
125     sub FEATURE_HTML2X_RFC () { ## Proposed Standard, obsolete
126     Whatpm::ContentChecker::FEATURE_STATUS_CR
127     }
128    
129     ## NOTE: Diff from HTML 2.0.
130     sub FEATURE_RFC1942 () { ## Experimental RFC, obsolete
131     Whatpm::ContentChecker::FEATURE_STATUS_CR
132     }
133    
134     ## NOTE: Diff from HTML 3.2.
135     sub FEATURE_HTML20_RFC () { ## Proposed Standard, obsolete
136     Whatpm::ContentChecker::FEATURE_STATUS_CR
137     }
138 wakaba 1.58
139 wakaba 1.29 ## December 2007 HTML5 Classification
140    
141     my $HTMLMetadataContent = {
142     $HTML_NS => {
143     title => 1, base => 1, link => 1, style => 1, script => 1, noscript => 1,
144     'event-source' => 1, command => 1, datatemplate => 1,
145     ## NOTE: A |meta| with no |name| element is not allowed as
146     ## a metadata content other than |head| element.
147     meta => 1,
148 wakaba 1.56 ## NOTE: Only when empty [WF2]
149     form => 1,
150 wakaba 1.29 },
151     ## NOTE: RDF is mentioned in the HTML5 spec.
152     ## TODO: Other RDF elements?
153     q<http://www.w3.org/1999/02/22-rdf-syntax-ns#> => {RDF => 1},
154     };
155    
156 wakaba 1.72 my $HTMLFlowContent = {
157 wakaba 1.29 $HTML_NS => {
158     section => 1, nav => 1, article => 1, blockquote => 1, aside => 1,
159     h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1, header => 1,
160     footer => 1, address => 1, p => 1, hr => 1, dialog => 1, pre => 1,
161     ol => 1, ul => 1, dl => 1, figure => 1, map => 1, table => 1,
162 wakaba 1.72 details => 1, ## ISSUE: "Flow element" in spec.
163     datagrid => 1, ## ISSUE: "Flow element" in spec.
164 wakaba 1.29 datatemplate => 1,
165     div => 1, ## ISSUE: No category in spec.
166     ## NOTE: |style| is only allowed if |scoped| attribute is specified.
167     ## Additionally, it must be before any other element or
168     ## non-inter-element-whitespace text node.
169     style => 1,
170    
171 wakaba 1.38 br => 1, q => 1, cite => 1, em => 1, strong => 1, small => 1, mark => 1,
172 wakaba 1.29 dfn => 1, abbr => 1, time => 1, progress => 1, meter => 1, code => 1,
173     var => 1, samp => 1, kbd => 1, sub => 1, sup => 1, span => 1, i => 1,
174     b => 1, bdo => 1, script => 1, noscript => 1, 'event-source' => 1,
175     command => 1, font => 1,
176     a => 1,
177     datagrid => 1, ## ISSUE: "Interactive element" in the spec.
178     ## NOTE: |area| is allowed only as a descendant of |map|.
179     area => 1,
180    
181     ins => 1, del => 1,
182    
183 wakaba 1.72 ## NOTE: If there is a |menu| ancestor, phrasing. Otherwise, flow.
184 wakaba 1.29 menu => 1,
185    
186     img => 1, iframe => 1, embed => 1, object => 1, video => 1, audio => 1,
187     canvas => 1,
188     },
189    
190     ## NOTE: Embedded
191     q<http://www.w3.org/1998/Math/MathML> => {math => 1},
192     q<http://www.w3.org/2000/svg> => {svg => 1},
193     };
194    
195 wakaba 1.58 my $HTMLSectioningContent = {
196 wakaba 1.57 $HTML_NS => {
197     section => 1, nav => 1, article => 1, aside => 1,
198     ## NOTE: |body| is only allowed in |html| element.
199     body => 1,
200     },
201     };
202    
203 wakaba 1.58 my $HTMLSectioningRoot = {
204 wakaba 1.29 $HTML_NS => {
205 wakaba 1.58 blockquote => 1, datagrid => 1, figure => 1, td => 1,
206 wakaba 1.29 },
207     };
208    
209     my $HTMLHeadingContent = {
210     $HTML_NS => {
211     h1 => 1, h2 => 1, h3 => 1, h4 => 1, h5 => 1, h6 => 1, header => 1,
212     },
213     };
214    
215     my $HTMLPhrasingContent = {
216 wakaba 1.72 ## NOTE: All phrasing content is also flow content.
217 wakaba 1.29 $HTML_NS => {
218 wakaba 1.38 br => 1, q => 1, cite => 1, em => 1, strong => 1, small => 1, mark => 1,
219 wakaba 1.29 dfn => 1, abbr => 1, time => 1, progress => 1, meter => 1, code => 1,
220     var => 1, samp => 1, kbd => 1, sub => 1, sup => 1, span => 1, i => 1,
221     b => 1, bdo => 1, script => 1, noscript => 1, 'event-source' => 1,
222     command => 1, font => 1,
223     a => 1,
224     datagrid => 1, ## ISSUE: "Interactive element" in the spec.
225     ## NOTE: |area| is allowed only as a descendant of |map|.
226     area => 1,
227    
228     ## NOTE: Transparent.
229     ins => 1, del => 1,
230    
231 wakaba 1.72 ## NOTE: If there is a |menu| ancestor, phrasing. Otherwise, flow.
232 wakaba 1.29 menu => 1,
233    
234     img => 1, iframe => 1, embed => 1, object => 1, video => 1, audio => 1,
235     canvas => 1,
236 wakaba 1.56
237     ## NOTE: WF2
238     input => 1, ## NOTE: type=hidden
239     datalist => 1, ## NOTE: block | where |select| allowed
240 wakaba 1.29 },
241    
242     ## NOTE: Embedded
243     q<http://www.w3.org/1998/Math/MathML> => {math => 1},
244     q<http://www.w3.org/2000/svg> => {svg => 1},
245    
246     ## NOTE: And non-inter-element-whitespace text nodes.
247     };
248    
249 wakaba 1.40 ## $HTMLEmbeddedContent: See Whatpm::ContentChecker.
250 wakaba 1.29
251     my $HTMLInteractiveContent = {
252     $HTML_NS => {
253     a => 1,
254 wakaba 1.36 datagrid => 1, ## ISSUE: Categorized as "Inetractive element"
255 wakaba 1.29 },
256     };
257    
258 wakaba 1.36 ## NOTE: $HTMLTransparentElements: See Whatpm::ContentChecker.
259     ## NOTE: Semi-transparent elements: See Whatpm::ContentChecker.
260    
261     ## -- Common attribute syntacx checkers
262    
263 wakaba 1.1 our $AttrChecker;
264 wakaba 1.82 our $AttrStatus;
265 wakaba 1.1
266     my $GetHTMLEnumeratedAttrChecker = sub {
267     my $states = shift; # {value => conforming ? 1 : -1}
268     return sub {
269     my ($self, $attr) = @_;
270     my $value = lc $attr->value; ## TODO: ASCII case insensitibility?
271     if ($states->{$value} > 0) {
272     #
273     } elsif ($states->{$value}) {
274     $self->{onerror}->(node => $attr, type => 'enumerated:non-conforming');
275     } else {
276     $self->{onerror}->(node => $attr, type => 'enumerated:invalid');
277     }
278     };
279     }; # $GetHTMLEnumeratedAttrChecker
280    
281     my $GetHTMLBooleanAttrChecker = sub {
282     my $local_name = shift;
283     return sub {
284     my ($self, $attr) = @_;
285 wakaba 1.88 my $value = lc $attr->value; ## TODO: case
286 wakaba 1.1 unless ($value eq $local_name or $value eq '') {
287 wakaba 1.88 $self->{onerror}->(node => $attr, type => 'boolean:invalid',
288     level => $self->{must_level});
289 wakaba 1.1 }
290     };
291     }; # $GetHTMLBooleanAttrChecker
292    
293 wakaba 1.8 ## Unordered set of space-separated tokens
294 wakaba 1.18 my $HTMLUnorderedUniqueSetOfSpaceSeparatedTokensAttrChecker = sub {
295 wakaba 1.8 my ($self, $attr) = @_;
296     my %word;
297     for my $word (grep {length $_} split /[\x09-\x0D\x20]/, $attr->value) {
298     unless ($word{$word}) {
299     $word{$word} = 1;
300     } else {
301     $self->{onerror}->(node => $attr, type => 'duplicate token:'.$word);
302     }
303     }
304 wakaba 1.18 }; # $HTMLUnorderedUniqueSetOfSpaceSeparatedTokensAttrChecker
305 wakaba 1.8
306 wakaba 1.1 ## |rel| attribute (unordered set of space separated tokens,
307     ## whose allowed values are defined by the section on link types)
308     my $HTMLLinkTypesAttrChecker = sub {
309 wakaba 1.66 my ($a_or_area, $todo, $self, $attr, $item, $element_state) = @_;
310 wakaba 1.1 my %word;
311     for my $word (grep {length $_} split /[\x09-\x0D\x20]/, $attr->value) {
312     unless ($word{$word}) {
313     $word{$word} = 1;
314 wakaba 1.18 } elsif ($word eq 'up') {
315     #
316 wakaba 1.1 } else {
317     $self->{onerror}->(node => $attr, type => 'duplicate token:'.$word);
318     }
319     }
320     ## NOTE: Case sensitive match (since HTML5 spec does not say link
321     ## types are case-insensitive and it says "The value should not
322     ## be confusingly similar to any other defined value (e.g.
323     ## differing only in case).").
324     ## NOTE: Though there is no explicit "MUST NOT" for undefined values,
325     ## "MAY"s and "only ... MAY" restrict non-standard non-registered
326     ## values to be used conformingly.
327 wakaba 1.66
328     my $is_hyperlink;
329     my $is_resource;
330 wakaba 1.1 require Whatpm::_LinkTypeList;
331     our $LinkType;
332     for my $word (keys %word) {
333     my $def = $LinkType->{$word};
334     if (defined $def) {
335     if ($def->{status} eq 'accepted') {
336     if (defined $def->{effect}->[$a_or_area]) {
337     #
338     } else {
339     $self->{onerror}->(node => $attr,
340     type => 'link type:bad context:'.$word);
341     }
342     } elsif ($def->{status} eq 'proposal') {
343     $self->{onerror}->(node => $attr, level => 's',
344     type => 'link type:proposed:'.$word);
345 wakaba 1.20 if (defined $def->{effect}->[$a_or_area]) {
346     #
347     } else {
348     $self->{onerror}->(node => $attr,
349     type => 'link type:bad context:'.$word);
350     }
351 wakaba 1.1 } else { # rejected or synonym
352     $self->{onerror}->(node => $attr,
353     type => 'link type:non-conforming:'.$word);
354     }
355 wakaba 1.4 if (defined $def->{effect}->[$a_or_area]) {
356     if ($word eq 'alternate') {
357     #
358     } elsif ($def->{effect}->[$a_or_area] eq 'hyperlink') {
359 wakaba 1.66 $is_hyperlink = 1;
360 wakaba 1.4 }
361     }
362 wakaba 1.1 if ($def->{unique}) {
363     unless ($self->{has_link_type}->{$word}) {
364     $self->{has_link_type}->{$word} = 1;
365     } else {
366     $self->{onerror}->(node => $attr,
367     type => 'link type:duplicate:'.$word);
368     }
369     }
370 wakaba 1.66
371     if (defined $def->{effect}->[$a_or_area] and $word ne 'alternate') {
372     $is_hyperlink = 1 if $def->{effect}->[$a_or_area] eq 'hyperlink';
373     $is_resource = 1 if $def->{effect}->[$a_or_area] eq 'external resource';
374     }
375 wakaba 1.1 } else {
376     $self->{onerror}->(node => $attr, level => 'unsupported',
377     type => 'link type:'.$word);
378     }
379     }
380 wakaba 1.66 $is_hyperlink = 1 if $word{alternate} and not $word{stylesheet};
381 wakaba 1.1 ## TODO: The Pingback 1.0 specification, which is referenced by HTML5,
382     ## says that using both X-Pingback: header field and HTML
383     ## <link rel=pingback> is deprecated and if both appears they
384     ## SHOULD contain exactly the same value.
385     ## ISSUE: Pingback 1.0 specification defines the exact representation
386     ## of its link element, which cannot be tested by the current arch.
387     ## ISSUE: Pingback 1.0 specification says that the document MUST NOT
388     ## include any string that matches to the pattern for the rel=pingback link,
389     ## which again inpossible to test.
390     ## ISSUE: rel=pingback href MUST NOT include entities other than predefined 4.
391 wakaba 1.12
392     ## NOTE: <link rel="up index"><link rel="up up index"> is not an error.
393 wakaba 1.17 ## NOTE: We can't check "If the page is part of multiple hierarchies,
394     ## then they SHOULD be described in different paragraphs.".
395 wakaba 1.66
396     $todo->{has_hyperlink_link_type} = 1 if $is_hyperlink;
397     if ($is_hyperlink or $a_or_area) {
398     $element_state->{uri_info}->{href}->{type}->{hyperlink} = 1;
399     }
400     if ($is_resource and not $a_or_area) {
401     $element_state->{uri_info}->{href}->{type}->{resource} = 1;
402     }
403 wakaba 1.1 }; # $HTMLLinkTypesAttrChecker
404 wakaba 1.20
405     ## 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."
406 wakaba 1.1
407     ## URI (or IRI)
408     my $HTMLURIAttrChecker = sub {
409 wakaba 1.66 my ($self, $attr, $item, $element_state) = @_;
410 wakaba 1.1 ## ISSUE: Relative references are allowed? (RFC 3987 "IRI" is an absolute reference with optional fragment identifier.)
411     my $value = $attr->value;
412     Whatpm::URIChecker->check_iri_reference ($value, sub {
413     my %opt = @_;
414     $self->{onerror}->(node => $attr, level => $opt{level},
415     type => 'URI::'.$opt{type}.
416     (defined $opt{position} ? ':'.$opt{position} : ''));
417     });
418 wakaba 1.17 $self->{has_uri_attr} = 1; ## TODO: <html manifest>
419 wakaba 1.66
420     my $attr_name = $attr->name;
421     $element_state->{uri_info}->{$attr_name}->{node} = $attr;
422     ## TODO: absolute
423     push @{$self->{return}->{uri}->{$value} ||= []},
424     $element_state->{uri_info}->{$attr_name};
425 wakaba 1.1 }; # $HTMLURIAttrChecker
426    
427     ## A space separated list of one or more URIs (or IRIs)
428     my $HTMLSpaceURIsAttrChecker = sub {
429     my ($self, $attr) = @_;
430 wakaba 1.66
431     my $type = {ping => 'action',
432     profile => 'namespace',
433     archive => 'resource'}->{$attr->name};
434    
435 wakaba 1.1 my $i = 0;
436     for my $value (split /[\x09-\x0D\x20]+/, $attr->value) {
437     Whatpm::URIChecker->check_iri_reference ($value, sub {
438     my %opt = @_;
439     $self->{onerror}->(node => $attr, level => $opt{level},
440 wakaba 1.2 type => 'URIs:'.':'.
441     $opt{type}.':'.$i.
442 wakaba 1.1 (defined $opt{position} ? ':'.$opt{position} : ''));
443     });
444 wakaba 1.66
445     ## TODO: absolute
446     push @{$self->{return}->{uri}->{$value} ||= []},
447 wakaba 1.67 {node => $attr, type => {$type => 1}};
448 wakaba 1.66
449 wakaba 1.1 $i++;
450     }
451 wakaba 1.67 ## ISSUE: Relative references? (especially, in profile="")
452 wakaba 1.1 ## ISSUE: Leading or trailing white spaces are conformant?
453     ## ISSUE: A sequence of white space characters are conformant?
454     ## ISSUE: A zero-length string is conformant? (It does contain a relative reference, i.e. same as base URI.)
455     ## NOTE: Duplication seems not an error.
456 wakaba 1.4 $self->{has_uri_attr} = 1;
457 wakaba 1.1 }; # $HTMLSpaceURIsAttrChecker
458    
459     my $HTMLDatetimeAttrChecker = sub {
460     my ($self, $attr) = @_;
461     my $value = $attr->value;
462     ## ISSUE: "space", not "space character" (in parsing algorihtm, "space character")
463     if ($value =~ /\A([0-9]{4})-([0-9]{2})-([0-9]{2})(?>[\x09-\x0D\x20]+(?>T[\x09-\x0D\x20]*)?|T[\x09-\x0D\x20]*)([0-9]{2}):([0-9]{2})(?>:([0-9]{2}))?(?>\.([0-9]+))?[\x09-\x0D\x20]*(?>Z|[+-]([0-9]{2}):([0-9]{2}))\z/) {
464     my ($y, $M, $d, $h, $m, $s, $f, $zh, $zm)
465     = ($1, $2, $3, $4, $5, $6, $7, $8, $9);
466     if (0 < $M and $M < 13) { ## ISSUE: This is not explicitly specified (though in parsing algorithm)
467     $self->{onerror}->(node => $attr, type => 'datetime:bad day')
468     if $d < 1 or
469     $d > [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]->[$M];
470     $self->{onerror}->(node => $attr, type => 'datetime:bad day')
471     if $M == 2 and $d == 29 and
472     not ($y % 400 == 0 or ($y % 4 == 0 and $y % 100 != 0));
473     } else {
474     $self->{onerror}->(node => $attr, type => 'datetime:bad month');
475     }
476     $self->{onerror}->(node => $attr, type => 'datetime:bad hour') if $h > 23;
477     $self->{onerror}->(node => $attr, type => 'datetime:bad minute') if $m > 59;
478     $self->{onerror}->(node => $attr, type => 'datetime:bad second')
479     if defined $s and $s > 59;
480     $self->{onerror}->(node => $attr, type => 'datetime:bad timezone hour')
481     if $zh > 23;
482     $self->{onerror}->(node => $attr, type => 'datetime:bad timezone minute')
483     if $zm > 59;
484     ## ISSUE: Maybe timezone -00:00 should have same semantics as in RFC 3339.
485     } else {
486     $self->{onerror}->(node => $attr, type => 'datetime:syntax error');
487     }
488     }; # $HTMLDatetimeAttrChecker
489    
490     my $HTMLIntegerAttrChecker = sub {
491     my ($self, $attr) = @_;
492     my $value = $attr->value;
493     unless ($value =~ /\A-?[0-9]+\z/) {
494     $self->{onerror}->(node => $attr, type => 'integer:syntax error');
495     }
496     }; # $HTMLIntegerAttrChecker
497    
498     my $GetHTMLNonNegativeIntegerAttrChecker = sub {
499     my $range_check = shift;
500     return sub {
501     my ($self, $attr) = @_;
502     my $value = $attr->value;
503     if ($value =~ /\A[0-9]+\z/) {
504