/[suikacvs]/markup/html/whatpm/Whatpm/ContentChecker.pm
Suika

Contents of /markup/html/whatpm/Whatpm/ContentChecker.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.90 - (hide annotations) (download)
Tue Sep 9 04:45:13 2008 UTC (16 years, 1 month ago) by wakaba
Branch: MAIN
Changes since 1.89: +3 -3 lines
++ whatpm/t/ChangeLog	9 Sep 2008 04:32:11 -0000
2008-09-09  Wakaba  <wakaba@suika.fam.cx>

	* content-model-1.dat, content-model-7.dat: |a| is now
	transparent (HTML5 revision 1963).

++ whatpm/Whatpm/ContentChecker/ChangeLog	9 Sep 2008 04:32:26 -0000
2008-09-09  Wakaba  <wakaba@suika.fam.cx>

	* HTML.pm: |a| is now transparent (HTML5 revision 1963).

1 wakaba 1.1 package Whatpm::ContentChecker;
2     use strict;
3 wakaba 1.90 our $VERSION=do{my @r=(q$Revision: 1.89 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};
4 wakaba 1.1
5 wakaba 1.18 require Whatpm::URIChecker;
6    
7 wakaba 1.13 ## ISSUE: How XML and XML Namespaces conformance can (or cannot)
8     ## be applied to an in-memory representation (i.e. DOM)?
9    
10 wakaba 1.50 ## TODO: Conformance of an HTML document with non-html root element.
11    
12 wakaba 1.70 ## Stability
13 wakaba 1.67 sub FEATURE_STATUS_REC () { 0b1 } ## Interoperable standard
14     sub FEATURE_STATUS_CR () { 0b10 } ## Call for implementation
15     sub FEATURE_STATUS_LC () { 0b100 } ## Last call for comments
16     sub FEATURE_STATUS_WD () { 0b1000 } ## Working or editor's draft
17    
18 wakaba 1.70 ## Deprecated
19     sub FEATURE_DEPRECATED_SHOULD () { 0b100000 } ## SHOULD-level
20     sub FEATURE_DEPRECATED_INFO () { 0b1000000 } ## Does not affect conformance
21    
22     ## Conformance
23     sub FEATURE_ALLOWED () { 0b10000 }
24    
25 wakaba 1.42 my $HTML_NS = q<http://www.w3.org/1999/xhtml>;
26 wakaba 1.9 my $XML_NS = q<http://www.w3.org/XML/1998/namespace>;
27     my $XMLNS_NS = q<http://www.w3.org/2000/xmlns/>;
28    
29 wakaba 1.42 my $Namespace = {
30 wakaba 1.79 '' => {loaded => 1},
31 wakaba 1.43 q<http://www.w3.org/2005/Atom> => {module => 'Whatpm::ContentChecker::Atom'},
32 wakaba 1.72 q<http://purl.org/syndication/history/1.0>
33     => {module => 'Whatpm::ContentChecker::Atom'},
34     q<http://purl.org/syndication/threading/1.0>
35     => {module => 'Whatpm::ContentChecker::Atom'},
36 wakaba 1.42 $HTML_NS => {module => 'Whatpm::ContentChecker::HTML'},
37     $XML_NS => {loaded => 1},
38     $XMLNS_NS => {loaded => 1},
39 wakaba 1.73 q<http://www.w3.org/1999/02/22-rdf-syntax-ns#> => {loaded => 1},
40 wakaba 1.42 };
41    
42 wakaba 1.79 sub load_ns_module ($) {
43     my $nsuri = shift; # namespace URI or ''
44     unless ($Namespace->{$nsuri}->{loaded}) {
45     if ($Namespace->{$nsuri}->{module}) {
46     eval qq{ require $Namespace->{$nsuri}->{module} } or die $@;
47     } else {
48     $Namespace->{$nsuri}->{loaded} = 1;
49     }
50     }
51     } # load_ns_module
52    
53 wakaba 1.42 our $AttrChecker = {
54 wakaba 1.9 $XML_NS => {
55 wakaba 1.13 space => sub {
56     my ($self, $attr) = @_;
57     my $value = $attr->value;
58     if ($value eq 'default' or $value eq 'preserve') {
59     #
60     } else {
61     ## NOTE: An XML "error"
62 wakaba 1.83 $self->{onerror}->(node => $attr, level => $self->{level}->{xml_error},
63 wakaba 1.33 type => 'invalid attribute value');
64 wakaba 1.13 }
65     },
66     lang => sub {
67 wakaba 1.35 my ($self, $attr) = @_;
68 wakaba 1.47 my $value = $attr->value;
69     if ($value eq '') {
70     #
71     } else {
72     require Whatpm::LangTag;
73     Whatpm::LangTag->check_rfc3066_language_tag ($value, sub {
74 wakaba 1.83 $self->{onerror}->(@_, node => $attr);
75 wakaba 1.85 }, $self->{level});
76 wakaba 1.47 }
77    
78 wakaba 1.13 ## NOTE: "The values of the attribute are language identifiers
79     ## as defined by [IETF RFC 3066], Tags for the Identification
80     ## of Languages, or its successor; in addition, the empty string
81     ## may be specified." ("may" in lower case)
82 wakaba 1.47 ## NOTE: Is an RFC 3066-valid (but RFC 4647-invalid) language tag
83     ## allowed today?
84    
85     ## TODO: test data
86    
87 wakaba 1.89 my $nsuri = $attr->owner_element->namespace_uri;
88     if (defined $nsuri and $nsuri eq $HTML_NS) {
89     my $lang_attr = $attr->owner_element->get_attribute_node_ns
90     (undef, 'lang');
91     if ($lang_attr) {
92     my $lang_attr_value = $lang_attr->value;
93     $lang_attr_value =~ tr/A-Z/a-z/; ## ASCII case-insensitive
94     my $value = $value;
95     $value =~ tr/A-Z/a-z/; ## ASCII case-insensitive
96     if ($lang_attr_value ne $value) {
97     ## NOTE: HTML5 Section "The |lang| and |xml:lang| attributes"
98     $self->{onerror}->(node => $attr,
99     type => 'xml:lang ne lang',
100     level => $self->{level}->{must});
101     }
102     }
103     }
104    
105 wakaba 1.35 if ($attr->owner_document->manakai_is_html) { # MUST NOT
106 wakaba 1.83 $self->{onerror}->(node => $attr, type => 'in HTML:xml:lang',
107     level => $self->{level}->{must});
108 wakaba 1.35 ## TODO: Test data...
109     }
110 wakaba 1.13 },
111     base => sub {
112     my ($self, $attr) = @_;
113     my $value = $attr->value;
114     if ($value =~ /[^\x{0000}-\x{10FFFF}]/) { ## ISSUE: Should we disallow noncharacters?
115     $self->{onerror}->(node => $attr,
116 wakaba 1.83 type => 'invalid attribute value',
117     level => $self->{level}->{fact}, ## TODO: correct?
118     );
119 wakaba 1.13 }
120 wakaba 1.18 ## NOTE: Conformance to URI standard is not checked since there is
121     ## no author requirement on conformance in the XML Base specification.
122 wakaba 1.13 },
123     id => sub {
124     my ($self, $attr) = @_;
125     my $value = $attr->value;
126     $value =~ s/[\x09\x0A\x0D\x20]+/ /g;
127     $value =~ s/^\x20//;
128     $value =~ s/\x20$//;
129     ## TODO: NCName in XML 1.0 or 1.1
130     ## TODO: declared type is ID?
131 wakaba 1.83 if ($self->{id}->{$value}) {
132     $self->{onerror}->(node => $attr,
133     type => 'duplicate ID',
134     level => $self->{level}->{xml_id_error});
135 wakaba 1.37 push @{$self->{id}->{$value}}, $attr;
136 wakaba 1.13 } else {
137 wakaba 1.37 $self->{id}->{$value} = [$attr];
138 wakaba 1.13 }
139     },
140 wakaba 1.9 },
141     $XMLNS_NS => {
142 wakaba 1.13 '' => sub {
143     my ($self, $attr) = @_;
144     my $ln = $attr->manakai_local_name;
145     my $value = $attr->value;
146     if ($value eq $XML_NS and $ln ne 'xml') {
147     $self->{onerror}
148 wakaba 1.83 ->(node => $attr,
149     type => 'Reserved Prefixes and Namespace Names:Name',
150     text => $value,
151     level => $self->{level}->{nc});
152 wakaba 1.13 } elsif ($value eq $XMLNS_NS) {
153     $self->{onerror}
154 wakaba 1.83 ->(node => $attr,
155     type => 'Reserved Prefixes and Namespace Names:Name',
156     text => $value,
157     level => $self->{level}->{nc});
158 wakaba 1.13 }
159     if ($ln eq 'xml' and $value ne $XML_NS) {
160     $self->{onerror}
161 wakaba 1.83 ->(node => $attr,
162     type => 'Reserved Prefixes and Namespace Names:Prefix',
163     text => $ln,
164     level => $self->{level}->{nc});
165 wakaba 1.13 } elsif ($ln eq 'xmlns') {
166     $self->{onerror}
167 wakaba 1.83 ->(node => $attr,
168     type => 'Reserved Prefixes and Namespace Names:Prefix',
169     text => $ln,
170     level => $self->{level}->{nc});
171 wakaba 1.13 }
172     ## TODO: If XML 1.0 and empty
173     },
174     xmlns => sub {
175     my ($self, $attr) = @_;
176     ## TODO: In XML 1.0, URI reference [RFC 3986] or an empty string
177     ## TODO: In XML 1.1, IRI reference [RFC 3987] or an empty string
178 wakaba 1.18 ## TODO: relative references are deprecated
179 wakaba 1.13 my $value = $attr->value;
180     if ($value eq $XML_NS) {
181     $self->{onerror}
182 wakaba 1.83 ->(node => $attr,
183     type => 'Reserved Prefixes and Namespace Names:Name',
184     text => $value,
185     level => $self->{level}->{nc});
186 wakaba 1.13 } elsif ($value eq $XMLNS_NS) {
187     $self->{onerror}
188 wakaba 1.83 ->(node => $attr,
189     type => 'Reserved Prefixes and Namespace Names:Name',
190     text => $value,
191     level => $self->{level}->{nc});
192 wakaba 1.13 }
193     },
194 wakaba 1.9 },
195     };
196    
197 wakaba 1.14 ## ISSUE: Should we really allow these attributes?
198 wakaba 1.13 $AttrChecker->{''}->{'xml:space'} = $AttrChecker->{$XML_NS}->{space};
199     $AttrChecker->{''}->{'xml:lang'} = $AttrChecker->{$XML_NS}->{lang};
200 wakaba 1.89 ## NOTE: Checker for (null, "xml:lang") attribute is shadowed for
201     ## HTML elements in Whatpm::ContentChecker::HTML.
202 wakaba 1.13 $AttrChecker->{''}->{'xml:base'} = $AttrChecker->{$XML_NS}->{base};
203     $AttrChecker->{''}->{'xml:id'} = $AttrChecker->{$XML_NS}->{id};
204    
205 wakaba 1.79 our $AttrStatus;
206    
207     for (qw/space lang base id/) {
208     $AttrStatus->{$XML_NS}->{$_} = FEATURE_STATUS_REC | FEATURE_ALLOWED;
209     $AttrStatus->{''}->{"xml:$_"} = FEATURE_STATUS_REC | FEATURE_ALLOWED;
210     ## XML 1.0: FEATURE_STATUS_CR
211     ## XML 1.1: FEATURE_STATUS_REC
212     ## XML Namespaces 1.0: FEATURE_STATUS_CR
213     ## XML Namespaces 1.1: FEATURE_STATUS_REC
214     ## XML Base: FEATURE_STATUS_REC
215     ## xml:id: FEATURE_STATUS_REC
216     }
217    
218     $AttrStatus->{$XMLNS_NS}->{''} = FEATURE_STATUS_REC | FEATURE_ALLOWED;
219    
220     ## TODO: xsi:schemaLocation for XHTML2 support (very, very low priority)
221    
222 wakaba 1.60 our %AnyChecker = (
223     check_start => sub { },
224     check_attrs => sub {
225     my ($self, $item, $element_state) = @_;
226     for my $attr (@{$item->{node}->attributes}) {
227 wakaba 1.9 my $attr_ns = $attr->namespace_uri;
228     $attr_ns = '' unless defined $attr_ns;
229     my $attr_ln = $attr->manakai_local_name;
230 wakaba 1.79
231     load_ns_module ($attr_ns);
232    
233 wakaba 1.9 my $checker = $AttrChecker->{$attr_ns}->{$attr_ln}
234 wakaba 1.60 || $AttrChecker->{$attr_ns}->{''};
235 wakaba 1.79 my $status = $AttrStatus->{$attr_ns}->{$attr_ln}
236     || $AttrStatus->{$attr_ns}->{''};
237     if (not defined $status) {
238     $status = FEATURE_ALLOWED;
239     ## NOTE: FEATURE_ALLOWED for all attributes, since the element
240     ## is not supported and therefore "attribute not defined" error
241     ## should not raised (too verbose) and global attributes should be
242     ## allowed anyway (if a global attribute has its specified creteria
243     ## for where it may be specified, then it should be checked in it's
244     ## checker function).
245     }
246 wakaba 1.9 if ($checker) {
247     $checker->($self, $attr);
248 wakaba 1.17 } else {
249 wakaba 1.83 $self->{onerror}->(node => $attr,
250     type => 'unknown attribute',
251     level => $self->{level}->{uncertain});
252 wakaba 1.9 }
253 wakaba 1.79 $self->_attr_status_info ($attr, $status);
254 wakaba 1.9 }
255     },
256 wakaba 1.60 check_child_element => sub {
257     my ($self, $item, $child_el, $child_nsuri, $child_ln,
258     $child_is_transparent, $element_state) = @_;
259     if ($self->{minus_elements}->{$child_nsuri}->{$child_ln}) {
260     $self->{onerror}->(node => $child_el,
261     type => 'element not allowed:minus',
262 wakaba 1.83 level => $self->{level}->{must});
263 wakaba 1.60 } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
264     #
265     } else {
266     #
267     }
268     },
269     check_child_text => sub { },
270     check_end => sub {
271     my ($self, $item, $element_state) = @_;
272 wakaba 1.82 ## NOTE: There is a modified copy of the code below for |html:ruby|.
273 wakaba 1.60 if ($element_state->{has_significant}) {
274 wakaba 1.66 $item->{real_parent_state}->{has_significant} = 1;
275 wakaba 1.60 }
276     },
277     );
278    
279     our $ElementDefault = {
280     %AnyChecker,
281 wakaba 1.70 status => FEATURE_ALLOWED,
282     ## NOTE: No "element not defined" error - it is not supported anyway.
283 wakaba 1.60 check_start => sub {
284     my ($self, $item, $element_state) = @_;
285 wakaba 1.83 $self->{onerror}->(node => $item->{node},
286     type => 'unknown element',
287     level => $self->{level}->{uncertain});
288 wakaba 1.60 },
289 wakaba 1.1 };
290    
291 wakaba 1.60 our $HTMLEmbeddedContent = {
292     ## NOTE: All embedded content is also phrasing content.
293     $HTML_NS => {
294     img => 1, iframe => 1, embed => 1, object => 1, video => 1, audio => 1,
295     canvas => 1,
296     },
297     q<http://www.w3.org/1998/Math/MathML> => {math => 1},
298     q<http://www.w3.org/2000/svg> => {svg => 1},
299     ## NOTE: Foreign elements with content (but no metadata) are
300     ## embedded content.
301     };
302    
303 wakaba 1.7 my $HTMLTransparentElements = {
304 wakaba 1.90 $HTML_NS => {qw/ins 1 del 1 font 1 noscript 1 canvas 1 a 1/},
305 wakaba 1.29 ## NOTE: |html:noscript| is transparent if scripting is disabled
306     ## and not in |head|.
307 wakaba 1.7 };
308    
309 wakaba 1.61 my $HTMLSemiTransparentElements = {
310     $HTML_NS => {object => 1, video => 1, audio => 1},
311     };
312 wakaba 1.57
313 wakaba 1.42 our $Element = {};
314 wakaba 1.7
315 wakaba 1.73 $Element->{q<http://www.w3.org/1999/02/22-rdf-syntax-ns#>}->{RDF} = {
316     %AnyChecker,
317     status => FEATURE_STATUS_REC | FEATURE_ALLOWED,
318     is_root => 1, ## ISSUE: Not explicitly allowed for non application/rdf+xml
319     check_start => sub {
320     my ($self, $item, $element_state) = @_;
321     my $triple = [];
322     push @{$self->{return}->{rdf}}, [$item->{node}, $triple];
323     require Whatpm::RDFXML;
324     my $rdf = Whatpm::RDFXML->new;
325 wakaba 1.75 ## TODO: Should we make bnodeid unique in a document?
326 wakaba 1.73 $rdf->{onerror} = $self->{onerror};
327 wakaba 1.84 $rdf->{level} = $self->{level};
328 wakaba 1.73 $rdf->{ontriple} = sub {
329     my %opt = @_;
330     push @$triple,
331     [$opt{node}, $opt{subject}, $opt{predicate}, $opt{object}];
332 wakaba 1.74 if (defined $opt{id}) {
333     push @$triple,
334     [$opt{node},
335     $opt{id},
336     {uri => q<http://www.w3.org/1999/02/22-rdf-syntax-ns#subject>},
337     $opt{subject}];
338     push @$triple,
339     [$opt{node},
340     $opt{id},
341     {uri => q<http://www.w3.org/1999/02/22-rdf-syntax-ns#predicate>},
342     $opt{predicate}];
343     push @$triple,
344     [$opt{node},
345     $opt{id},
346     {uri => q<http://www.w3.org/1999/02/22-rdf-syntax-ns#object>},
347     $opt{object}];
348     push @$triple,
349     [$opt{node},
350     $opt{id},
351     {uri => q<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>},
352     {uri => q<http://www.w3.org/1999/02/22-rdf-syntax-ns#Statement>}];
353     }
354 wakaba 1.73 };
355     $rdf->convert_rdf_element ($item->{node});
356     },
357     };
358    
359 wakaba 1.83 my $default_error_level = {
360     must => 'm',
361     should => 's',
362     warn => 'w',
363     good => 'w',
364     info => 'i',
365 wakaba 1.85
366 wakaba 1.83 uncertain => 'u',
367    
368 wakaba 1.84 html4_fact => 'm',
369 wakaba 1.89 html5_no_may => 'm',
370    
371 wakaba 1.83 xml_error => 'm', ## TODO: correct?
372 wakaba 1.88 xml_id_error => 'm', ## TODO: ?
373 wakaba 1.83 nc => 'm', ## XML Namespace Constraints ## TODO: correct?
374 wakaba 1.84
375 wakaba 1.85 uri_syntax => 'm',
376     uri_fact => 'm',
377     uri_lc_must => 'm',
378     uri_lc_should => 'w',
379    
380 wakaba 1.87 mime_must => 'm', # lowercase "must"
381     mime_fact => 'm',
382     mime_strongly_discouraged => 'w',
383     mime_discouraged => 'w',
384    
385 wakaba 1.85 langtag_fact => 'm',
386    
387 wakaba 1.84 rdf_fact => 'm',
388     rdf_grammer => 'm',
389     rdf_lc_must => 'm',
390 wakaba 1.83 };
391    
392 wakaba 1.56 sub check_document ($$$;$) {
393     my ($self, $doc, $onerror, $onsubdoc) = @_;
394 wakaba 1.42 $self = bless {}, $self unless ref $self;
395     $self->{onerror} = $onerror;
396 wakaba 1.56 $self->{onsubdoc} = $onsubdoc || sub {
397     warn "A subdocument is not conformance-checked";
398     };
399 wakaba 1.1
400 wakaba 1.83 $self->{level} ||= $default_error_level;
401 wakaba 1.48
402 wakaba 1.73 ## TODO: If application/rdf+xml, RDF/XML mode should be invoked.
403    
404 wakaba 1.42 my $docel = $doc->document_element;
405     unless (defined $docel) {
406     ## ISSUE: Should we check content of Document node?
407 wakaba 1.83 $onerror->(node => $doc, type => 'no document element',
408     level => $self->{level}->{must});
409 wakaba 1.42 ## ISSUE: Is this non-conforming (to what spec)? Or just a warning?
410     return {
411     class => {},
412     id => {}, table => [], term => {},
413     };
414 wakaba 1.1 }
415    
416 wakaba 1.42 ## ISSUE: Unexpanded entity references and HTML5 conformance
417 wakaba 1.1
418 wakaba 1.42 my $docel_nsuri = $docel->namespace_uri;
419     $docel_nsuri = '' unless defined $docel_nsuri;
420 wakaba 1.79 load_ns_module ($docel_nsuri);
421 wakaba 1.42 my $docel_def = $Element->{$docel_nsuri}->{$docel->manakai_local_name} ||
422     $Element->{$docel_nsuri}->{''} ||
423     $ElementDefault;
424     if ($docel_def->{is_root}) {
425     #
426 wakaba 1.50 } elsif ($docel_def->{is_xml_root}) {
427     unless ($doc->manakai_is_html) {
428     #
429     } else {
430 wakaba 1.83 $onerror->(node => $docel, type => 'element not allowed:root:xml',
431     level => $self->{level}->{must});
432 wakaba 1.50 }
433 wakaba 1.42 } else {
434 wakaba 1.83 $onerror->(node => $docel, type => 'element not allowed:root',
435     level => $self->{level}->{must});
436 wakaba 1.1 }
437    
438 wakaba 1.42 ## TODO: Check for other items other than document element
439     ## (second (errorous) element, text nodes, PI nodes, doctype nodes)
440 wakaba 1.2
441 wakaba 1.56 my $return = $self->check_element ($docel, $onerror, $onsubdoc);
442 wakaba 1.51
443 wakaba 1.52 ## TODO: Test for these checks are necessary.
444 wakaba 1.51 my $charset_name = $doc->input_encoding;
445     if (defined $charset_name) {
446     require Message::Charset::Info;
447     my $charset = $Message::Charset::Info::IANACharset->{$charset_name};
448    
449 wakaba 1.71 if ($doc->manakai_is_html) {
450     if (not $doc->manakai_has_bom and
451     not defined $doc->manakai_charset) {
452     unless ($charset->{is_html_ascii_superset}) {
453 wakaba 1.86 $onerror->(node => $doc,
454     level => $self->{level}->{must},
455 wakaba 1.83 type => 'non ascii superset',
456     text => $charset_name);
457 wakaba 1.71 }
458    
459     if (not $self->{has_charset} and ## TODO: This does not work now.
460     not $charset->{iana_names}->{'us-ascii'}) {
461 wakaba 1.86 $onerror->(node => $doc,
462     level => $self->{level}->{must},
463 wakaba 1.83 type => 'no character encoding declaration',
464     text => $charset_name);
465 wakaba 1.71 }
466 wakaba 1.51 }
467 wakaba 1.71
468     if ($charset->{iana_names}->{'utf-8'}) {
469     #
470     } elsif ($charset->{iana_names}->{'jis_x0212-1990'} or
471     $charset->{iana_names}->{'x-jis0208'} or
472     $charset->{iana_names}->{'utf-32'} or ## ISSUE: UTF-32BE? UTF-32LE?
473     $charset->{is_ebcdic_based}) {
474     $onerror->(node => $doc,
475 wakaba 1.83 type => 'bad character encoding',
476     text => $charset_name,
477     level => $self->{level}->{should},
478     layer => 'encode');
479 wakaba 1.71 } elsif ($charset->{iana_names}->{'cesu-8'} or
480     $charset->{iana_names}->{'utf-8'} or ## ISSUE: UNICODE-1-1-UTF-7?
481     $charset->{iana_names}->{'bocu-1'} or
482     $charset->{iana_names}->{'scsu'}) {
483     $onerror->(node => $doc,
484 wakaba 1.83 type => 'disallowed character encoding',
485     text => $charset_name,
486     level => $self->{level}->{must},
487     layer => 'encode');
488 wakaba 1.71 } else {
489     $onerror->(node => $doc,
490 wakaba 1.83 type => 'non-utf-8 character encoding',
491     text => $charset_name,
492     level => $self->{level}->{good},
493     layer => 'encode');
494 wakaba 1.51 }
495     }
496 wakaba 1.52 } elsif ($doc->manakai_is_html) {
497     ## NOTE: MUST and SHOULD requirements above cannot be tested,
498     ## since the document has no input charset encoding information.
499     $onerror->(node => $doc,
500 wakaba 1.83 type => 'character encoding unchecked',
501     level => $self->{level}->{info},
502     layer => 'encode');
503 wakaba 1.51 }
504    
505     return $return;
506 wakaba 1.42 } # check_document
507 wakaba 1.1
508 wakaba 1.81 ## Check an element. The element is checked as if it is an orphan node (i.e.
509     ## an element without a parent node).
510 wakaba 1.56 sub check_element ($$$;$) {
511     my ($self, $el, $onerror, $onsubdoc) = @_;
512 wakaba 1.42 $self = bless {}, $self unless ref $self;
513     $self->{onerror} = $onerror;
514 wakaba 1.56 $self->{onsubdoc} = $onsubdoc || sub {
515     warn "A subdocument is not conformance-checked";
516     };
517 wakaba 1.2
518 wakaba 1.83 $self->{level} ||= $default_error_level;
519 wakaba 1.48
520 wakaba 1.61 $self->{plus_elements} = {};
521     $self->{minus_elements} = {};
522 wakaba 1.42 $self->{id} = {};
523     $self->{term} = {};
524     $self->{usemap} = [];
525 wakaba 1.78 $self->{ref} = []; # datetemplate data references
526     $self->{template} = []; # datatemplate template references
527 wakaba 1.42 $self->{contextmenu} = [];
528     $self->{map} = {};
529     $self->{menu} = {};
530     $self->{has_link_type} = {};
531 wakaba 1.60 $self->{flag} = {};
532 wakaba 1.46 #$self->{has_uri_attr};
533     #$self->{has_hyperlink_element};
534 wakaba 1.51 #$self->{has_charset};
535 wakaba 1.57 #$self->{has_base};
536 wakaba 1.42 $self->{return} = {
537     class => {},
538 wakaba 1.80 id => $self->{id},
539     table => [], # table objects returned by Whatpm::HTMLTable
540     term => $self->{term},
541 wakaba 1.76 uri => {}, # URIs other than those in RDF triples
542     ## TODO: xmlns="", SYSTEM "", atom:* src="", xml:base=""
543 wakaba 1.73 rdf => [],
544 wakaba 1.42 };
545 wakaba 1.4
546 wakaba 1.60 my @item = ({type => 'element', node => $el, parent_state => {}});
547 wakaba 1.66 $item[-1]->{real_parent_state} = $item[-1]->{parent_state};
548 wakaba 1.60 while (@item) {
549     my $item = shift @item;
550     if (ref $item eq 'ARRAY') {
551     my $code = shift @$item;
552     next unless $code;## TODO: temp.
553     $code->(@$item);
554     } elsif ($item->{type} eq 'element') {
555     my $el_nsuri = $item->{node}->namespace_uri;
556     $el_nsuri = '' unless defined $el_nsuri;
557     my $el_ln = $item->{node}->manakai_local_name;
558 wakaba 1.79
559     load_ns_module ($el_nsuri);
560 wakaba 1.63
561     my $element_state = {};
562 wakaba 1.60 my $eldef = $Element->{$el_nsuri}->{$el_ln} ||
563     $Element->{$el_nsuri}->{''} ||
564 wakaba 1.42 $ElementDefault;
565 wakaba 1.61 my $content_def = $item->{transparent}
566     ? $item->{parent_def} || $eldef : $eldef;
567 wakaba 1.63 my $content_state = $item->{transparent}
568 wakaba 1.65 ? $item->{parent_def}
569     ? $item->{parent_state} || $element_state : $element_state
570     : $element_state;
571 wakaba 1.60
572 wakaba 1.67 unless ($eldef->{status} & FEATURE_STATUS_REC) {
573     my $status = $eldef->{status} & FEATURE_STATUS_CR ? 'cr' :
574     $eldef->{status} & FEATURE_STATUS_LC ? 'lc' :
575     $eldef->{status} & FEATURE_STATUS_WD ? 'wd' : 'non-standard';
576     $self->{onerror}->(node => $item->{node},
577     type => 'status:'.$status.':element',
578 wakaba 1.83 level => $self->{level}->{info});
579 wakaba 1.67 }
580 wakaba 1.70 if (not ($eldef->{status} & FEATURE_ALLOWED)) {
581     $self->{onerror}->(node => $item->{node},
582     type => 'element not defined',
583 wakaba 1.83 level => $self->{level}->{must});
584 wakaba 1.70 } elsif ($eldef->{status} & FEATURE_DEPRECATED_SHOULD) {
585     $self->{onerror}->(node => $item->{node},
586     type => 'deprecated:element',
587 wakaba 1.83 level => $self->{level}->{should});
588 wakaba 1.70 } elsif ($eldef->{status} & FEATURE_DEPRECATED_INFO) {
589     $self->{onerror}->(node => $item->{node},
590     type => 'deprecated:element',
591 wakaba 1.83 level => $self->{level}->{info});
592 wakaba 1.70 }
593 wakaba 1.67
594 wakaba 1.60 my @new_item;
595     push @new_item, [$eldef->{check_start}, $self, $item, $element_state];
596     push @new_item, [$eldef->{check_attrs}, $self, $item, $element_state];
597 wakaba 1.61
598 wakaba 1.60 my @child = @{$item->{node}->child_nodes};
599     while (@child) {
600     my $child = shift @child;
601     my $child_nt = $child->node_type;
602     if ($child_nt == 1) { # ELEMENT_NODE
603     my $child_nsuri = $child->namespace_uri;
604     $child_nsuri = '' unless defined $child_nsuri;
605     my $child_ln = $child->manakai_local_name;
606     if ($HTMLTransparentElements->{$child_nsuri}->{$child_ln} and
607     not (($self->{flag}->{in_head} or
608 wakaba 1.61 ($el_nsuri eq $HTML_NS and $el_ln eq 'head')) and
609     $child_nsuri eq $HTML_NS and $child_ln eq 'noscript')) {
610 wakaba 1.60 push @new_item, [$content_def->{check_child_element},
611     $self, $item, $child,
612 wakaba 1.66 $child_nsuri, $child_ln, 1,
613     $content_state, $element_state];
614 wakaba 1.60 push @new_item, {type => 'element', node => $child,
615 wakaba 1.65 parent_state => $content_state,
616 wakaba 1.61 parent_def => $content_def,
617 wakaba 1.66 real_parent_state => $element_state,
618 wakaba 1.60 transparent => 1};
619     } else {
620 wakaba 1.65 if ($item->{parent_def} and # has parent
621     $el_nsuri eq $HTML_NS) { ## $HTMLSemiTransparentElements
622 wakaba 1.61 if ($el_ln eq 'object') {
623     if ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
624     #
625     } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'param') {
626     #
627     } else {
628 wakaba 1.62 $content_def = $item->{parent_def} || $content_def;
629 wakaba 1.63 $content_state = $item->{parent_state} || $content_state;
630 wakaba 1.62 }
631     } elsif ($el_ln eq 'video' or $el_ln eq 'audio') {
632     if ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
633     #
634     } elsif ($child_nsuri eq $HTML_NS and $child_ln eq 'source') {
635     $element_state->{has_source} = 1;
636     } else {
637     $content_def = $item->{parent_def} || $content_def;
638 wakaba 1.63 $content_state = $item->{parent_state} || $content_state;
639 wakaba 1.61 }
640     }
641     }
642    
643 wakaba 1.60 push @new_item, [$content_def->{check_child_element},
644     $self, $item, $child,
645 wakaba 1.64 $child_nsuri, $child_ln,
646     $HTMLSemiTransparentElements
647     ->{$child_nsuri}->{$child_ln},
648 wakaba 1.66 $content_state, $element_state];
649 wakaba 1.60 push @new_item, {type => 'element', node => $child,
650 wakaba 1.65 parent_def => $content_def,
651 wakaba 1.66 real_parent_state => $element_state,
652 wakaba 1.65 parent_state => $content_state};
653 wakaba 1.60 }
654    
655     if ($HTMLEmbeddedContent->{$child_nsuri}->{$child_ln}) {
656     $element_state->{has_significant} = 1;
657     }
658     } elsif ($child_nt == 3 or # TEXT_NODE
659     $child_nt == 4) { # CDATA_SECTION_NODE
660     my $has_significant = ($child->data =~ /[^\x09-\x0D\x20]/);
661     push @new_item, [$content_def->{check_child_text},
662     $self, $item, $child, $has_significant,
663 wakaba 1.66 $content_state, $element_state];
664     $element_state->{has_significant} ||= $has_significant;
665 wakaba 1.61 if ($has_significant and
666     $HTMLSemiTransparentElements->{$el_nsuri}->{$el_ln}) {
667     $content_def = $item->{parent_def} || $content_def;
668     }
669 wakaba 1.60 } elsif ($child_nt == 5) { # ENTITY_REFERENCE_NODE
670     push @child, @{$child->child_nodes};
671 wakaba 1.1 }
672 wakaba 1.60 ## TODO: PI_NODE
673     ## TODO: Unknown node type
674 wakaba 1.1 }
675 wakaba 1.60
676     push @new_item, [$eldef->{check_end}, $self, $item, $element_state];
677    
678     unshift @item, @new_item;
679 wakaba 1.30 } else {
680 wakaba 1.60 die "$0: Internal error: Unsupported checking action type |$item->{type}|";
681 wakaba 1.4 }
682 wakaba 1.1 }
683 wakaba 1.17
684 wakaba 1.78 for (@{$self->{template}}) {
685     ## TODO: If the document is an XML document, ...
686     ## NOTE: If the document is an HTML document:
687     ## ISSUE: We need to percent-decode?
688     F: {
689     if ($self->{id}->{$_->[0]}) {
690     my $el = $self->{id}->{$_->[0]}->[0]->owner_element;
691     if ($el->node_type == 1 and # ELEMENT_NODE
692     $el->manakai_local_name eq 'datatemplate') {
693     my $nsuri = $el->namespace_uri;
694     if (defined $nsuri and $nsuri eq $HTML_NS) {
695     if ($el eq $_->[1]->owner_element) {
696     $self->{onerror}->(node => $_->[1],
697     type => 'fragment points itself',
698 wakaba 1.83 level => $self->{level}->{must});
699 wakaba 1.78 }
700    
701     last F;
702     }
703     }
704     }
705     ## TODO: Should we raise a "fragment points nothing" error instead
706     ## if the fragment identifier identifies no element?
707    
708     $self->{onerror}->(node => $_->[1], type => 'template:not template',
709 wakaba 1.83 level => $self->{level}->{must});
710 wakaba 1.78 } # F
711     }
712    
713     for (@{$self->{ref}}) {
714     ## TOOD: If XML
715     ## NOTE: If it is an HTML document:
716     if ($_->[0] eq '') {
717     ## NOTE: It points the top of the document.
718     } elsif ($self->{id}->{$_->[0]}) {
719     if ($self->{id}->{$_->[0]}->[0]->owner_element
720     eq $_->[1]->owner_element) {
721     $self->{onerror}->(node => $_->[1], type => 'fragment points itself',
722 wakaba 1.83 level => $self->{level}->{must});
723 wakaba 1.78 }
724     } else {
725     $self->{onerror}->(node => $_->[1], type => 'fragment points nothing',
726 wakaba 1.83 level => $self->{level}->{must});
727 wakaba 1.78 }
728     }
729    
730     ## TODO: Maybe we should have $document->manakai_get_by_fragment or something
731    
732 wakaba 1.17 for (@{$self->{usemap}}) {
733     unless ($self->{map}->{$_->[0]}) {
734 wakaba 1.83 $self->{onerror}->(node => $_->[1], type => 'no referenced map',
735     level => $self->{level}->{must});
736 wakaba 1.17 }
737     }
738    
739 wakaba 1.32 for (@{$self->{contextmenu}}) {
740     unless ($self->{menu}->{$_->[0]}) {
741 wakaba 1.83 $self->{onerror}->(node => $_->[1], type => 'no referenced menu',
742     level => $self->{level}->{must});
743 wakaba 1.32 }
744     }
745    
746 wakaba 1.61 delete $self->{plus_elements};
747     delete $self->{minus_elements};
748 wakaba 1.17 delete $self->{onerror};
749     delete $self->{id};
750     delete $self->{usemap};
751 wakaba 1.78 delete $self->{ref};
752     delete $self->{template};
753 wakaba 1.17 delete $self->{map};
754 wakaba 1.33 return $self->{return};
755 wakaba 1.1 } # check_element
756    
757 wakaba 1.60 sub _add_minus_elements ($$@) {
758     my $self = shift;
759     my $element_state = shift;
760     for my $elements (@_) {
761     for my $nsuri (keys %$elements) {
762     for my $ln (keys %{$elements->{$nsuri}}) {
763     unless ($self->{minus_elements}->{$nsuri}->{$ln}) {
764     $element_state->{minus_elements_original}->{$nsuri}->{$ln} = 0;
765     $self->{minus_elements}->{$nsuri}->{$ln} = 1;
766     }
767     }
768     }
769     }
770     } # _add_minus_elements
771    
772     sub _remove_minus_elements ($$) {
773     my $self = shift;
774     my $element_state = shift;
775     for my $nsuri (keys %{$element_state->{minus_elements_original}}) {
776     for my $ln (keys %{$element_state->{minus_elements_original}->{$nsuri}}) {
777     delete $self->{minus_elements}->{$nsuri}->{$ln};
778     }
779     }
780     } # _remove_minus_elements
781    
782     sub _add_plus_elements ($$@) {
783     my $self = shift;
784     my $element_state = shift;
785     for my $elements (@_) {
786     for my $nsuri (keys %$elements) {
787     for my $ln (keys %{$elements->{$nsuri}}) {
788     unless ($self->{plus_elements}->{$nsuri}->{$ln}) {
789     $element_state->{plus_elements_original}->{$nsuri}->{$ln} = 0;
790     $self->{plus_elements}->{$nsuri}->{$ln} = 1;
791     }
792     }
793     }
794     }
795     } # _add_plus_elements
796    
797     sub _remove_plus_elements ($$) {
798     my $self = shift;
799     my $element_state = shift;
800     for my $nsuri (keys %{$element_state->{plus_elements_original}}) {
801     for my $ln (keys %{$element_state->{plus_elements_original}->{$nsuri}}) {
802     delete $self->{plus_elements}->{$nsuri}->{$ln};
803     }
804     }
805     } # _remove_plus_elements
806    
807 wakaba 1.68 sub _attr_status_info ($$$) {
808     my ($self, $attr, $status_code) = @_;
809 wakaba 1.70
810     if (not ($status_code & FEATURE_ALLOWED)) {
811     $self->{onerror}->(node => $attr,
812     type => 'attribute not defined',
813 wakaba 1.83 level => $self->{level}->{must});
814 wakaba 1.70 } elsif ($status_code & FEATURE_DEPRECATED_SHOULD) {
815     $self->{onerror}->(node => $attr,
816     type => 'deprecated:attr',
817 wakaba 1.83 level => $self->{level}->{should});
818 wakaba 1.70 } elsif ($status_code & FEATURE_DEPRECATED_INFO) {
819     $self->{onerror}->(node => $attr,
820     type => 'deprecated:attr',
821 wakaba 1.83 level => $self->{level}->{info});
822 wakaba 1.70 }
823    
824 wakaba 1.68 my $status;
825     if ($status_code & FEATURE_STATUS_REC) {
826     return;
827     } elsif ($status_code & FEATURE_STATUS_CR) {
828     $status = 'cr';
829     } elsif ($status_code & FEATURE_STATUS_LC) {
830     $status = 'lc';
831     } elsif ($status_code & FEATURE_STATUS_WD) {
832     $status = 'wd';
833     } else {
834     $status = 'non-standard';
835     }
836     $self->{onerror}->(node => $attr,
837     type => 'status:'.$status.':attr',
838 wakaba 1.83 level => $self->{level}->{info});
839 wakaba 1.68 } # _attr_status_info
840    
841 wakaba 1.2 sub _add_minuses ($@) {
842     my $self = shift;
843     my $r = {};
844     for my $list (@_) {
845     for my $ns (keys %$list) {
846     for my $ln (keys %{$list->{$ns}}) {
847     unless ($self->{minuses}->{$ns}->{$ln}) {
848     $self->{minuses}->{$ns}->{$ln} = 1;
849     $r->{$ns}->{$ln} = 1;
850     }
851     }
852     }
853     }
854 wakaba 1.4 return {type => 'plus', list => $r};
855 wakaba 1.2 } # _add_minuses
856    
857 wakaba 1.50 sub _add_pluses ($@) {
858     my $self = shift;
859     my $r = {};
860     for my $list (@_) {
861     for my $ns (keys %$list) {
862     for my $ln (keys %{$list->{$ns}}) {
863     unless ($self->{pluses}->{$ns}->{$ln}) {
864     $self->{pluses}->{$ns}->{$ln} = 1;
865     $r->{$ns}->{$ln} = 1;
866     }
867     }
868     }
869     }
870     return {type => 'minus', list => $r};
871     } # _add_pluses
872    
873 wakaba 1.2 sub _remove_minuses ($$) {
874 wakaba 1.4 my ($self, $todo) = @_;
875 wakaba 1.50 if ($todo->{type} eq 'minus') {
876     for my $ns (keys %{$todo->{list}}) {
877     for my $ln (keys %{$todo->{list}->{$ns}}) {
878     delete $self->{pluses}->{$ns}->{$ln} if $todo->{list}->{$ns}->{$ln};
879     }
880 wakaba 1.2 }
881 wakaba 1.50 } elsif ($todo->{type} eq 'plus') {
882     for my $ns (keys %{$todo->{list}}) {
883     for my $ln (keys %{$todo->{list}->{$ns}}) {
884     delete $self->{minuses}->{$ns}->{$ln} if $todo->{list}->{$ns}->{$ln};
885     }
886     }
887     } else {
888     die "$0: Unknown +- type: $todo->{type}";
889 wakaba 1.2 }
890     1;
891     } # _remove_minuses
892    
893 wakaba 1.50 ## NOTE: Priority for "minuses" and "pluses" are currently left
894     ## undefined and implemented inconsistently; it is not a problem for
895     ## now, since no element belongs to both lists.
896    
897 wakaba 1.30 sub _check_get_children ($$$) {
898     my ($self, $node, $parent_todo) = @_;
899 wakaba 1.4 my $new_todos = [];
900 wakaba 1.2 my $sib = [];
901     TP: {
902     my $node_ns = $node->namespace_uri;
903     $node_ns = '' unless defined $node_ns;
904     my $node_ln = $node->manakai_local_name;
905 wakaba 1.45 if ($HTMLTransparentElements->{$node_ns}->{$node_ln}) {
906     if ($node_ns eq $HTML_NS and $node_ln eq 'noscript') {
907     if ($parent_todo->{flag}->{in_head}) {
908     #
909     } else {
910     my $end = $self->_add_minuses ({$HTML_NS, {noscript => 1}});
911     push @$sib, $end;
912    
913     unshift @$sib, @{$node->child_nodes};
914     push @$new_todos, {type => 'element-attributes', node => $node};
915     last TP;
916     }
917 wakaba 1.58 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'del') {
918     my $sig_flag = $parent_todo->{flag}->{has_descendant}->{significant};
919     unshift @$sib, @{$node->child_nodes};
920     push @$new_todos, {type => 'element-attributes', node => $node};
921     push @$new_todos,
922     {type => 'code',
923     code => sub {
924     $parent_todo->{flag}->{has_descendant}->{significant} = 0
925     if not $sig_flag;
926     }};
927     last TP;
928 wakaba 1.45 } else {
929     unshift @$sib, @{$node->child_nodes};
930     push @$new_todos, {type => 'element-attributes', node => $node};
931     last TP;
932 wakaba 1.2 }
933     }
934 wakaba 1.8 if ($node_ns eq $HTML_NS and ($node_ln eq 'video' or $node_ln eq 'audio')) {
935 wakaba 1.2 if ($node->has_attribute_ns (undef, 'src')) {
936     unshift @$sib, @{$node->child_nodes};
937 wakaba 1.9 push @$new_todos, {type => 'element-attributes', node => $node};
938 wakaba 1.2 last TP;
939     } else {
940     my @cn = @{$node->child_nodes};
941     CN: while (@cn) {
942     my $cn = shift @cn;
943     my $cnt = $cn->node_type;
944     if ($cnt == 1) {
945 wakaba 1.8 my $cn_nsuri = $cn->namespace_uri;
946     $cn_nsuri = '' unless defined $cn_nsuri;
947     if ($cn_nsuri eq $HTML_NS and $cn->manakai_local_name eq 'source') {
948 wakaba 1.2 #
949     } else {
950     last CN;
951     }
952     } elsif ($cnt == 3 or $cnt == 4) {
953     if ($cn->data =~ /[^\x09-\x0D\x20]/) {
954     last CN;
955     }
956     }
957     } # CN
958     unshift @$sib, @cn;
959     }
960 wakaba 1.57 } elsif ($node_ns eq $HTML_NS and $node_ln eq 'object') {
961     my @cn = @{$node->child_nodes};
962     CN: while (@cn) {
963     my $cn = shift @cn;
964     my $cnt = $cn->node_type;
965     if ($cnt == 1) {
966     my $cn_nsuri = $cn->namespace_uri;
967     $cn_nsuri = '' unless defined $cn_nsuri;
968     if ($cn_nsuri eq $HTML_NS and $cn->manakai_local_name eq 'param') {
969     #
970     } else {
971     last CN;
972     }
973     } elsif ($cnt == 3 or $cnt == 4) {
974     if ($cn->data =~ /[^\x09-\x0D\x20]/) {
975     last CN;
976     }
977     }
978     } # CN
979     unshift @$sib, @cn;
980 wakaba 1.2 }
981 wakaba 1.4 push @$new_todos, {type => 'element', node => $node};
982 wakaba 1.2 } # TP
983 wakaba 1.30
984     for my $new_todo (@$new_todos) {
985     $new_todo->{flag} = {%{$parent_todo->{flag} or {}}};
986     }
987    
988 wakaba 1.4 return ($sib, $new_todos);
989 wakaba 1.2 } # _check_get_children
990    
991 wakaba 1.44 =head1 LICENSE
992    
993 wakaba 1.56 Copyright 2007-2008 Wakaba <w@suika.fam.cx>
994 wakaba 1.44
995     This library is free software; you can redistribute it
996     and/or modify it under the same terms as Perl itself.
997    
998     =cut
999    
1000 wakaba 1.1 1;
1001 wakaba 1.90 # $Date: 2008/08/30 12:33:36 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24