/[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.92 - (show annotations) (download)
Mon Sep 15 02:54:12 2008 UTC (17 years, 11 months ago) by wakaba
Branch: MAIN
Changes since 1.91: +18 -11 lines
++ whatpm/Whatpm/ChangeLog	15 Sep 2008 02:54:04 -0000
2008-09-15  Wakaba  <wakaba@suika.fam.cx>

	* ContentChecker.pm: Don't call |loda_ns_module|
	for null-namespace elements/attributes.

	* HTML.pm.src: Fact out $disallowed_control_chars
	as a hash.

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24