/[suikacvs]/test/html-webhacc/cc.cgi
Suika

Contents of /test/html-webhacc/cc.cgi

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.36 - (hide annotations) (download)
Sun Feb 10 07:35:23 2008 UTC (16 years, 9 months ago) by wakaba
Branch: MAIN
Changes since 1.35: +4 -3 lines
++ ChangeLog	10 Feb 2008 07:35:19 -0000
	* cc.cgi: In CSS mode, add 'u' error for 'structure' category (until
	it is actually implemented).  Support for '-moz-pre-wrap'.
	Typo in 'collapse' value fixed.

2008-02-10  Wakaba  <wakaba@suika.fam.cx>

1 wakaba 1.1 #!/usr/bin/perl
2     use strict;
3 wakaba 1.23 use utf8;
4 wakaba 1.1
5     use lib qw[/home/httpd/html/www/markup/html/whatpm
6 wakaba 1.16 /home/wakaba/work/manakai2/lib];
7 wakaba 1.1 use CGI::Carp qw[fatalsToBrowser];
8 wakaba 1.2 use Scalar::Util qw[refaddr];
9 wakaba 1.16 use Time::HiRes qw/time/;
10 wakaba 1.1
11 wakaba 1.2 sub htescape ($) {
12     my $s = $_[0];
13     $s =~ s/&/&amp;/g;
14     $s =~ s/</&lt;/g;
15     $s =~ s/>/&gt;/g;
16     $s =~ s/"/&quot;/g;
17 wakaba 1.12 $s =~ s{([\x00-\x09\x0B-\x1F\x7F-\xA0\x{FEFF}\x{FFFC}-\x{FFFF}])}{
18     sprintf '<var>U+%04X</var>', ord $1;
19     }ge;
20 wakaba 1.2 return $s;
21     } # htescape
22    
23 wakaba 1.35 my @nav;
24     my %time;
25     require Message::DOM::DOMImplementation;
26     my $dom = Message::DOM::DOMImplementation->new;
27     {
28 wakaba 1.16 use Message::CGI::HTTP;
29     my $http = Message::CGI::HTTP->new;
30 wakaba 1.1
31 wakaba 1.16 if ($http->get_meta_variable ('PATH_INFO') ne '/') {
32 wakaba 1.8 print STDOUT "Status: 404 Not Found\nContent-Type: text/plain; charset=us-ascii\n\n400";
33     exit;
34     }
35    
36 wakaba 1.12 binmode STDOUT, ':utf8';
37 wakaba 1.14 $| = 1;
38 wakaba 1.12
39 wakaba 1.7 load_text_catalog ('en'); ## TODO: conneg
40    
41 wakaba 1.2 print STDOUT qq[Content-Type: text/html; charset=utf-8
42    
43     <!DOCTYPE html>
44     <html lang="en">
45     <head>
46     <title>Web Document Conformance Checker (BETA)</title>
47 wakaba 1.3 <link rel="stylesheet" href="../cc-style.css" type="text/css">
48 wakaba 1.2 </head>
49     <body>
50 wakaba 1.13 <h1><a href="../cc-interface">Web Document Conformance Checker</a>
51     (<em>beta</em>)</h1>
52 wakaba 1.14 ];
53 wakaba 1.2
54 wakaba 1.14 $| = 0;
55     my $input = get_input_document ($http, $dom);
56 wakaba 1.16 my $char_length = 0;
57 wakaba 1.14
58     print qq[
59 wakaba 1.4 <div id="document-info" class="section">
60 wakaba 1.2 <dl>
61 wakaba 1.9 <dt>Request URI</dt>
62     <dd><code class="URI" lang="">&lt;<a href="@{[htescape $input->{request_uri}]}">@{[htescape $input->{request_uri}]}</a>&gt;</code></dd>
63 wakaba 1.2 <dt>Document URI</dt>
64 wakaba 1.25 <dd><code class="URI" lang="">&lt;<a href="@{[htescape $input->{uri}]}" id=anchor-document-uri>@{[htescape $input->{uri}]}</a>&gt;</code>
65     <script>
66     document.title = '<'
67     + document.getElementById ('anchor-document-uri').href + '> \\u2014 '
68     + document.title;
69     </script></dd>
70 wakaba 1.2 ]; # no </dl> yet
71 wakaba 1.3 push @nav, ['#document-info' => 'Information'];
72 wakaba 1.1
73 wakaba 1.9 if (defined $input->{s}) {
74 wakaba 1.16 $char_length = length $input->{s};
75 wakaba 1.9
76     print STDOUT qq[
77     <dt>Base URI</dt>
78     <dd><code class="URI" lang="">&lt;<a href="@{[htescape $input->{base_uri}]}">@{[htescape $input->{base_uri}]}</a>&gt;</code></dd>
79     <dt>Internet Media Type</dt>
80     <dd><code class="MIME" lang="en">@{[htescape $input->{media_type}]}</code>
81 wakaba 1.25 @{[$input->{media_type_overridden} ? '<em>(overridden)</em>' : defined $input->{official_type} ? $input->{media_type} eq $input->{official_type} ? '' : '<em>(sniffed; official type is: <code class=MIME lang=en>'.htescape ($input->{official_type}).'</code>)' : '<em>(sniffed)</em>']}</dd>
82 wakaba 1.9 <dt>Character Encoding</dt>
83     <dd>@{[defined $input->{charset} ? '<code class="charset" lang="en">'.htescape ($input->{charset}).'</code>' : '(none)']}
84     @{[$input->{charset_overridden} ? '<em>(overridden)</em>' : '']}</dd>
85 wakaba 1.16 <dt>Length</dt>
86     <dd>$char_length byte@{[$char_length == 1 ? '' : 's']}</dd>
87 wakaba 1.9 </dl>
88     </div>
89     ];
90    
91 wakaba 1.35 $input->{id_prefix} = '';
92     #$input->{nested} = 0;
93 wakaba 1.20 my $result = {conforming_min => 1, conforming_max => 1};
94 wakaba 1.31 check_and_print ($input => $result);
95 wakaba 1.19 print_result_section ($result);
96 wakaba 1.9 } else {
97 wakaba 1.18 print STDOUT qq[</dl></div>];
98     print_result_input_error_section ($input);
99 wakaba 1.9 }
100 wakaba 1.3
101 wakaba 1.2 print STDOUT qq[
102 wakaba 1.3 <ul class="navigation" id="nav-items">
103     ];
104     for (@nav) {
105     print STDOUT qq[<li><a href="$_->[0]">$_->[1]</a></li>];
106     }
107     print STDOUT qq[
108     </ul>
109 wakaba 1.2 </body>
110     </html>
111     ];
112 wakaba 1.1
113 wakaba 1.24 for (qw/decode parse parse_html parse_xml parse_manifest
114     check check_manifest/) {
115 wakaba 1.16 next unless defined $time{$_};
116     open my $file, '>>', ".cc-$_.txt" or die ".cc-$_.txt: $!";
117     print $file $char_length, "\t", $time{$_}, "\n";
118     }
119    
120 wakaba 1.1 exit;
121 wakaba 1.35 }
122 wakaba 1.1
123 wakaba 1.19 sub add_error ($$$) {
124     my ($layer, $err, $result) = @_;
125     if (defined $err->{level}) {
126     if ($err->{level} eq 's') {
127     $result->{$layer}->{should}++;
128     $result->{$layer}->{score_min} -= 2;
129     $result->{conforming_min} = 0;
130     } elsif ($err->{level} eq 'w' or $err->{level} eq 'g') {
131     $result->{$layer}->{warning}++;
132 wakaba 1.30 } elsif ($err->{level} eq 'u' or $err->{level} eq 'unsupported') {
133 wakaba 1.19 $result->{$layer}->{unsupported}++;
134     $result->{unsupported} = 1;
135     } else {
136     $result->{$layer}->{must}++;
137     $result->{$layer}->{score_max} -= 2;
138     $result->{$layer}->{score_min} -= 2;
139     $result->{conforming_min} = 0;
140     $result->{conforming_max} = 0;
141     }
142     } else {
143     $result->{$layer}->{must}++;
144     $result->{$layer}->{score_max} -= 2;
145     $result->{$layer}->{score_min} -= 2;
146     $result->{conforming_min} = 0;
147     $result->{conforming_max} = 0;
148     }
149     } # add_error
150    
151 wakaba 1.31 sub check_and_print ($$) {
152     my ($input, $result) = @_;
153    
154     print_http_header_section ($input, $result);
155    
156     my $doc;
157     my $el;
158 wakaba 1.35 my $cssom;
159 wakaba 1.31 my $manifest;
160 wakaba 1.34 my @subdoc;
161 wakaba 1.31
162     if ($input->{media_type} eq 'text/html') {
163     ($doc, $el) = print_syntax_error_html_section ($input, $result);
164     print_source_string_section
165 wakaba 1.35 ($input,
166     \($input->{s}),
167     $input->{charset} || $doc->input_encoding);
168 wakaba 1.31 } elsif ({
169     'text/xml' => 1,
170     'application/atom+xml' => 1,
171     'application/rss+xml' => 1,
172     'application/svg+xml' => 1,
173     'application/xhtml+xml' => 1,
174     'application/xml' => 1,
175     }->{$input->{media_type}}) {
176     ($doc, $el) = print_syntax_error_xml_section ($input, $result);
177 wakaba 1.35 print_source_string_section ($input,
178     \($input->{s}),
179     $doc->input_encoding);
180     } elsif ($input->{media_type} eq 'text/css') {
181     $cssom = print_syntax_error_css_section ($input, $result);
182     print_source_string_section
183     ($input, \($input->{s}),
184     $cssom->manakai_input_encoding);
185 wakaba 1.31 } elsif ($input->{media_type} eq 'text/cache-manifest') {
186     ## TODO: MUST be text/cache-manifest
187     $manifest = print_syntax_error_manifest_section ($input, $result);
188 wakaba 1.35 print_source_string_section ($input, \($input->{s}),
189     'utf-8');
190 wakaba 1.31 } else {
191     ## TODO: Change HTTP status code??
192     print_result_unknown_type_section ($input, $result);
193     }
194    
195     if (defined $doc or defined $el) {
196 wakaba 1.34 $doc->document_uri ($input->{uri});
197     $doc->manakai_entity_base_uri ($input->{base_uri});
198 wakaba 1.32 print_structure_dump_dom_section ($input, $doc, $el);
199     my $elements = print_structure_error_dom_section
200 wakaba 1.34 ($input, $doc, $el, $result, sub {
201     push @subdoc, shift;
202     });
203 wakaba 1.32 print_table_section ($input, $elements->{table}) if @{$elements->{table}};
204 wakaba 1.33 print_listing_section ({
205     id => 'identifiers', label => 'IDs', heading => 'Identifiers',
206     }, $input, $elements->{id}) if keys %{$elements->{id}};
207     print_listing_section ({
208     id => 'terms', label => 'Terms', heading => 'Terms',
209     }, $input, $elements->{term}) if keys %{$elements->{term}};
210     print_listing_section ({
211     id => 'classes', label => 'Classes', heading => 'Classes',
212     }, $input, $elements->{class}) if keys %{$elements->{class}};
213 wakaba 1.35 } elsif (defined $cssom) {
214     print_structure_dump_cssom_section ($input, $cssom);
215     ## TODO: CSSOM validation
216 wakaba 1.36 add_error ('structure', {level => 'u'} => $result);
217 wakaba 1.31 } elsif (defined $manifest) {
218 wakaba 1.32 print_structure_dump_manifest_section ($input, $manifest);
219     print_structure_error_manifest_section ($input, $manifest, $result);
220 wakaba 1.31 }
221 wakaba 1.34
222     my $id_prefix = 0;
223     for my $subinput (@subdoc) {
224     $subinput->{id_prefix} = 'subdoc-' . ++$id_prefix;
225     $subinput->{nested} = 1;
226     $subinput->{base_uri} = $subinput->{container_node}->base_uri
227     unless defined $subinput->{base_uri};
228     my $ebaseuri = htescape ($subinput->{base_uri});
229     push @nav, ['#' . $subinput->{id_prefix} => 'Sub #' . $id_prefix];
230     print STDOUT qq[<div id="$subinput->{id_prefix}" class=section>
231     <h2>Subdocument #$id_prefix</h2>
232    
233     <dl>
234     <dt>Internet Media Type</dt>
235     <dd><code class="MIME" lang="en">@{[htescape $subinput->{media_type}]}</code>
236     <dt>Container Node</dt>
237     <dd>@{[get_node_link ($input, $subinput->{container_node})]}</dd>
238     <dt>Base <abbr title="Uniform Resource Identifiers">URI</abbr></dt>
239     <dd><code class=URI>&lt;<a href="$ebaseuri">$ebaseuri</a>></code></dd>
240     </dl>];
241    
242 wakaba 1.35 $subinput->{id_prefix} .= '-';
243 wakaba 1.34 check_and_print ($subinput => $result);
244    
245     print STDOUT qq[</div>];
246     }
247 wakaba 1.31 } # check_and_print
248    
249 wakaba 1.19 sub print_http_header_section ($$) {
250     my ($input, $result) = @_;
251 wakaba 1.9 return unless defined $input->{header_status_code} or
252     defined $input->{header_status_text} or
253 wakaba 1.34 @{$input->{header_field} or []};
254 wakaba 1.9
255 wakaba 1.32 push @nav, ['#source-header' => 'HTTP Header'] unless $input->{nested};
256     print STDOUT qq[<div id="$input->{id_prefix}source-header" class="section">
257 wakaba 1.9 <h2>HTTP Header</h2>
258    
259     <p><strong>Note</strong>: Due to the limitation of the
260     network library in use, the content of this section might
261     not be the real header.</p>
262    
263     <table><tbody>
264     ];
265    
266     if (defined $input->{header_status_code}) {
267     print STDOUT qq[<tr><th scope="row">Status code</th>];
268     print STDOUT qq[<td><code>@{[htescape ($input->{header_status_code})]}</code></td></tr>];
269     }
270     if (defined $input->{header_status_text}) {
271     print STDOUT qq[<tr><th scope="row">Status text</th>];
272     print STDOUT qq[<td><code>@{[htescape ($input->{header_status_text})]}</code></td></tr>];
273     }
274    
275     for (@{$input->{header_field}}) {
276     print STDOUT qq[<tr><th scope="row"><code>@{[htescape ($_->[0])]}</code></th>];
277     print STDOUT qq[<td><code>@{[htescape ($_->[1])]}</code></td></tr>];
278     }
279    
280     print STDOUT qq[</tbody></table></div>];
281     } # print_http_header_section
282    
283 wakaba 1.19 sub print_syntax_error_html_section ($$) {
284     my ($input, $result) = @_;
285 wakaba 1.18
286     require Encode;
287     require Whatpm::HTML;
288    
289     print STDOUT qq[
290 wakaba 1.32 <div id="$input->{id_prefix}parse-errors" class="section">
291 wakaba 1.18 <h2>Parse Errors</h2>
292    
293     <dl>];
294 wakaba 1.32 push @nav, ['#parse-errors' => 'Parse Error'] unless $input->{nested};
295 wakaba 1.18
296     my $onerror = sub {
297     my (%opt) = @_;
298     my ($type, $cls, $msg) = get_text ($opt{type}, $opt{level});
299     if ($opt{column} > 0) {
300 wakaba 1.35 print STDOUT qq[<dt class="$cls"><a href="#$input->{id_prefix}line-$opt{line}">Line $opt{line}</a> column $opt{column}</dt>\n];
301 wakaba 1.18 } else {
302     $opt{line} = $opt{line} - 1 || 1;
303 wakaba 1.35 print STDOUT qq[<dt class="$cls"><a href="#$input->{id_prefix}line-$opt{line}">Line $opt{line}</a></dt>\n];
304 wakaba 1.18 }
305     $type =~ tr/ /-/;
306     $type =~ s/\|/%7C/g;
307     $msg .= qq[ [<a href="../error-description#@{[htescape ($type)]}">Description</a>]];
308 wakaba 1.23 print STDOUT qq[<dd class="$cls">], get_error_level_label (\%opt);
309     print STDOUT qq[$msg</dd>\n];
310 wakaba 1.19
311     add_error ('syntax', \%opt => $result);
312 wakaba 1.18 };
313    
314     my $doc = $dom->create_document;
315     my $el;
316 wakaba 1.35 my $inner_html_element = $input->{inner_html_element};
317 wakaba 1.18 if (defined $inner_html_element and length $inner_html_element) {
318 wakaba 1.26 $input->{charset} ||= 'windows-1252'; ## TODO: for now.
319 wakaba 1.24 my $time1 = time;
320     my $t = Encode::decode ($input->{charset}, $input->{s});
321     $time{decode} = time - $time1;
322    
323 wakaba 1.18 $el = $doc->create_element_ns
324     ('http://www.w3.org/1999/xhtml', [undef, $inner_html_element]);
325 wakaba 1.24 $time1 = time;
326 wakaba 1.18 Whatpm::HTML->set_inner_html ($el, $t, $onerror);
327 wakaba 1.24 $time{parse} = time - $time1;
328 wakaba 1.18 } else {
329 wakaba 1.24 my $time1 = time;
330     Whatpm::HTML->parse_byte_string
331     ($input->{charset}, $input->{s} => $doc, $onerror);
332     $time{parse_html} = time - $time1;
333 wakaba 1.18 }
334 wakaba 1.26 $doc->manakai_charset ($input->{official_charset})
335     if defined $input->{official_charset};
336 wakaba 1.24
337 wakaba 1.18 print STDOUT qq[</dl></div>];
338    
339     return ($doc, $el);
340     } # print_syntax_error_html_section
341    
342 wakaba 1.19 sub print_syntax_error_xml_section ($$) {
343     my ($input, $result) = @_;
344 wakaba 1.18
345     require Message::DOM::XMLParserTemp;
346    
347     print STDOUT qq[
348 wakaba 1.32 <div id="$input->{id_prefix}parse-errors" class="section">
349 wakaba 1.18 <h2>Parse Errors</h2>
350    
351     <dl>];
352 wakaba 1.32 push @nav, ['#parse-errors' => 'Parse Error'] unless $input->{prefix};
353 wakaba 1.18
354     my $onerror = sub {
355     my $err = shift;
356     my $line = $err->location->line_number;
357 wakaba 1.35 print STDOUT qq[<dt><a href="#$input->{id_prefix}line-$line">Line $line</a> column ];
358 wakaba 1.18 print STDOUT $err->location->column_number, "</dt><dd>";
359     print STDOUT htescape $err->text, "</dd>\n";
360 wakaba 1.19
361     add_error ('syntax', {type => $err->text,
362     level => [
363     $err->SEVERITY_FATAL_ERROR => 'm',
364     $err->SEVERITY_ERROR => 'm',
365     $err->SEVERITY_WARNING => 's',
366     ]->[$err->severity]} => $result);
367    
368 wakaba 1.18 return 1;
369     };
370    
371     my $time1 = time;
372     open my $fh, '<', \($input->{s});
373     my $doc = Message::DOM::XMLParserTemp->parse_byte_stream
374     ($fh => $dom, $onerror, charset => $input->{charset});
375     $time{parse_xml} = time - $time1;
376 wakaba 1.26 $doc->manakai_charset ($input->{official_charset})
377     if defined $input->{official_charset};
378 wakaba 1.18
379     print STDOUT qq[</dl></div>];
380    
381     return ($doc, undef);
382     } # print_syntax_error_xml_section
383    
384 wakaba 1.35 sub get_css_parser () {
385     our $CSSParser;
386     return $CSSParser if $CSSParser;
387    
388     require Whatpm::CSS::Parser;
389     my $p = Whatpm::CSS::Parser->new;
390    
391     # if ($parse_mode eq 'q') {
392     # $p->{unitless_px} = 1;
393     # $p->{hashless_color} = 1;
394     # }
395    
396     $p->{prop}->{$_} = 1 for qw/
397     background background-attachment background-color background-image
398     background-position background-position-x background-position-y
399     background-repeat border border-bottom border-bottom-color
400     border-bottom-style border-bottom-width border-collapse border-color
401     border-left border-left-color
402     border-left-style border-left-width border-right border-right-color
403     border-right-style border-right-width
404     border-spacing -manakai-border-spacing-x -manakai-border-spacing-y
405     border-style border-top border-top-color border-top-style border-top-width
406     border-width bottom
407     caption-side clear clip color content counter-increment counter-reset
408     cursor direction display empty-cells float font
409     font-family font-size font-size-adjust font-stretch
410     font-style font-variant font-weight height left
411     letter-spacing line-height
412     list-style list-style-image list-style-position list-style-type
413     margin margin-bottom margin-left margin-right margin-top marker-offset
414     marks max-height max-width min-height min-width opacity -moz-opacity
415     orphans outline outline-color outline-style outline-width overflow
416     overflow-x overflow-y
417     padding padding-bottom padding-left padding-right padding-top
418     page page-break-after page-break-before page-break-inside
419     position quotes right size table-layout
420     text-align text-decoration text-indent text-transform
421     top unicode-bidi vertical-align visibility white-space width widows
422     word-spacing z-index
423     /;
424     $p->{prop_value}->{display}->{$_} = 1 for qw/
425     block clip inline inline-block inline-table list-item none
426     table table-caption table-cell table-column table-column-group
427     table-header-group table-footer-group table-row table-row-group
428     compact marker
429     /;
430     $p->{prop_value}->{position}->{$_} = 1 for qw/
431     absolute fixed relative static
432     /;
433     $p->{prop_value}->{float}->{$_} = 1 for qw/
434     left right none
435     /;
436     $p->{prop_value}->{clear}->{$_} = 1 for qw/
437     left right none both
438     /;
439     $p->{prop_value}->{direction}->{ltr} = 1;
440     $p->{prop_value}->{direction}->{rtl} = 1;
441     $p->{prop_value}->{marks}->{crop} = 1;
442     $p->{prop_value}->{marks}->{cross} = 1;
443     $p->{prop_value}->{'unicode-bidi'}->{$_} = 1 for qw/
444     normal bidi-override embed
445     /;
446     for my $prop_name (qw/overflow overflow-x overflow-y/) {
447     $p->{prop_value}->{$prop_name}->{$_} = 1 for qw/
448     visible hidden scroll auto -webkit-marquee -moz-hidden-unscrollable
449     /;
450     }
451     $p->{prop_value}->{visibility}->{$_} = 1 for qw/
452     visible hidden collapse
453     /;
454     $p->{prop_value}->{'list-style-type'}->{$_} = 1 for qw/
455     disc circle square decimal decimal-leading-zero
456     lower-roman upper-roman lower-greek lower-latin
457     upper-latin armenian georgian lower-alpha upper-alpha none
458     hebrew cjk-ideographic hiragana katakana hiragana-iroha
459     katakana-iroha
460     /;
461     $p->{prop_value}->{'list-style-position'}->{outside} = 1;
462     $p->{prop_value}->{'list-style-position'}->{inside} = 1;
463     $p->{prop_value}->{'page-break-before'}->{$_} = 1 for qw/
464     auto always avoid left right
465     /;
466     $p->{prop_value}->{'page-break-after'}->{$_} = 1 for qw/
467     auto always avoid left right
468     /;
469     $p->{prop_value}->{'page-break-inside'}->{auto} = 1;
470     $p->{prop_value}->{'page-break-inside'}->{avoid} = 1;
471     $p->{prop_value}->{'background-repeat'}->{$_} = 1 for qw/
472     repeat repeat-x repeat-y no-repeat
473     /;
474     $p->{prop_value}->{'background-attachment'}->{scroll} = 1;
475     $p->{prop_value}->{'background-attachment'}->{fixed} = 1;
476     $p->{prop_value}->{'font-size'}->{$_} = 1 for qw/
477     xx-small x-small small medium large x-large xx-large
478     -manakai-xxx-large -webkit-xxx-large
479     larger smaller
480     /;
481     $p->{prop_value}->{'font-style'}->{normal} = 1;
482     $p->{prop_value}->{'font-style'}->{italic} = 1;
483     $p->{prop_value}->{'font-style'}->{oblique} = 1;
484     $p->{prop_value}->{'font-variant'}->{normal} = 1;
485     $p->{prop_value}->{'font-variant'}->{'small-caps'} = 1;
486     $p->{prop_value}->{'font-stretch'}->{$_} = 1 for
487     qw/normal wider narrower ultra-condensed extra-condensed
488     condensed semi-condensed semi-expanded expanded
489     extra-expanded ultra-expanded/;
490     $p->{prop_value}->{'text-align'}->{$_} = 1 for qw/
491     left right center justify begin end
492     /;
493     $p->{prop_value}->{'text-transform'}->{$_} = 1 for qw/
494     capitalize uppercase lowercase none
495     /;
496     $p->{prop_value}->{'white-space'}->{$_} = 1 for qw/
497 wakaba 1.36 normal pre nowrap pre-line pre-wrap -moz-pre-wrap
498 wakaba 1.35 /;
499     $p->{prop_value}->{'text-decoration'}->{$_} = 1 for qw/
500     none blink underline overline line-through
501     /;
502     $p->{prop_value}->{'caption-side'}->{$_} = 1 for qw/
503     top bottom left right
504     /;
505     $p->{prop_value}->{'table-layout'}->{auto} = 1;
506     $p->{prop_value}->{'table-layout'}->{fixed} = 1;
507 wakaba 1.36 $p->{prop_value}->{'border-collapse'}->{collapse} = 1;
508 wakaba 1.35 $p->{prop_value}->{'border-collapse'}->{separate} = 1;
509     $p->{prop_value}->{'empty-cells'}->{show} = 1;
510     $p->{prop_value}->{'empty-cells'}->{hide} = 1;
511     $p->{prop_value}->{cursor}->{$_} = 1 for qw/
512     auto crosshair default pointer move e-resize ne-resize nw-resize n-resize
513     se-resize sw-resize s-resize w-resize text wait help progress
514     /;
515     for my $prop (qw/border-top-style border-left-style
516     border-bottom-style border-right-style outline-style/) {
517     $p->{prop_value}->{$prop}->{$_} = 1 for qw/
518     none hidden dotted dashed solid double groove ridge inset outset
519     /;
520     }
521     for my $prop (qw/color background-color
522     border-bottom-color border-left-color border-right-color
523     border-top-color border-color/) {
524     $p->{prop_value}->{$prop}->{transparent} = 1;
525     $p->{prop_value}->{$prop}->{flavor} = 1;
526     $p->{prop_value}->{$prop}->{'-manakai-default'} = 1;
527     }
528     $p->{prop_value}->{'outline-color'}->{invert} = 1;
529     $p->{prop_value}->{'outline-color'}->{'-manakai-invert-or-currentcolor'} = 1;
530     $p->{pseudo_class}->{$_} = 1 for qw/
531     active checked disabled empty enabled first-child first-of-type
532     focus hover indeterminate last-child last-of-type link only-child
533     only-of-type root target visited
534     lang nth-child nth-last-child nth-of-type nth-last-of-type not
535     -manakai-contains -manakai-current
536     /;
537     $p->{pseudo_element}->{$_} = 1 for qw/
538     after before first-letter first-line
539     /;
540    
541     return $CSSParser = $p;
542     } # get_css_parser
543    
544     sub print_syntax_error_css_section ($$) {
545     my ($input, $result) = @_;
546    
547     print STDOUT qq[
548     <div id="$input->{id_prefix}parse-errors" class="section">
549     <h2>Parse Errors</h2>
550    
551     <dl>];
552     push @nav, ['#parse-errors' => 'Parse Error'] unless $input->{nested};
553    
554     my $p = get_css_parser ();
555     $p->{onerror} = sub {
556     my (%opt) = @_;
557     my ($type, $cls, $msg) = get_text ($opt{type}, $opt{level});
558     if ($opt{token}) {
559     print STDOUT qq[<dt class="$cls"><a href="#$input->{id_prefix}line-$opt{token}->{line}">Line $opt{token}->{line}</a> column $opt{token}->{column}];
560     } else {
561     print STDOUT qq[<dt class="$cls">Unknown location];
562     }
563     if (defined $opt{value}) {
564     print STDOUT qq[ (<code>@{[htescape ($opt{value})]}</code>)];
565     } elsif (defined $opt{token}) {
566     print STDOUT qq[ (<code>@{[htescape (Whatpm::CSS::Tokenizer->serialize_token ($opt{token}))]}</code>)];
567     }
568     $type =~ tr/ /-/;
569     $type =~ s/\|/%7C/g;
570     $msg .= qq[ [<a href="../error-description#@{[htescape ($type)]}">Description</a>]];
571     print STDOUT qq[<dd class="$cls">], get_error_level_label (\%opt);
572     print STDOUT qq[$msg</dd>\n];
573    
574     add_error ('syntax', \%opt => $result);
575     };
576     $p->{href} = $input->{uri};
577     $p->{base_uri} = $input->{base_uri};
578    
579     my $s = \$input->{s};
580     my $charset;
581     unless ($input->{is_char_string}) {
582     require Encode;
583     if (defined $input->{charset}) {## TODO: IANA->Perl
584     $charset = $input->{charset};
585     $s = \(Encode::decode ($input->{charset}, $$s));
586     } else {
587     ## TODO: charset detection
588     $s = \(Encode::decode ($charset = 'utf-8', $$s));
589     }
590     }
591    
592     my $cssom = $p->parse_char_string ($$s);
593     $cssom->manakai_input_encoding ($charset) if defined $charset;
594    
595     print STDOUT qq[</dl></div>];
596    
597     return $cssom;
598     } # print_syntax_error_css_section
599    
600 wakaba 1.22 sub print_syntax_error_manifest_section ($$) {
601     my ($input, $result) = @_;
602    
603     require Whatpm::CacheManifest;
604    
605     print STDOUT qq[
606 wakaba 1.32 <div id="$input->{id_prefix}parse-errors" class="section">
607 wakaba 1.22 <h2>Parse Errors</h2>
608    
609     <dl>];
610 wakaba 1.32 push @nav, ['#parse-errors' => 'Parse Error'] unless $input->{nested};
611 wakaba 1.22
612     my $onerror = sub {
613     my (%opt) = @_;
614     my ($type, $cls, $msg) = get_text ($opt{type}, $opt{level});
615 wakaba 1.32 print STDOUT qq[<dt class="$cls">], get_error_label ($input, \%opt),
616     qq[</dt>];
617 wakaba 1.22 $type =~ tr/ /-/;
618     $type =~ s/\|/%7C/g;
619     $msg .= qq[ [<a href="../error-description#@{[htescape ($type)]}">Description</a>]];
620 wakaba 1.23 print STDOUT qq[<dd class="$cls">], get_error_level_label (\%opt);
621     print STDOUT qq[$msg</dd>\n];
622 wakaba 1.22
623     add_error ('syntax', \%opt => $result);
624     };
625    
626     my $time1 = time;
627     my $manifest = Whatpm::CacheManifest->parse_byte_string
628     ($input->{s}, $input->{uri}, $input->{base_uri}, $onerror);
629     $time{parse_manifest} = time - $time1;
630    
631     print STDOUT qq[</dl></div>];
632    
633     return $manifest;
634     } # print_syntax_error_manifest_section
635    
636 wakaba 1.35 sub print_source_string_section ($$$) {
637     my $input = shift;
638     my $s;
639     unless ($input->{is_char_string}) {
640     require Encode;
641     my $enc = Encode::find_encoding ($_[1]); ## TODO: charset name -> Perl name
642     return unless $enc;
643    
644     $s = \($enc->decode (${$_[0]}));
645     } else {
646     $s = $_[0];
647     }
648 wakaba 1.9
649     my $i = 1;
650 wakaba 1.32 push @nav, ['#source-string' => 'Source'] unless $input->{nested};
651     print STDOUT qq[<div id="$input->{id_prefix}source-string" class="section">
652 wakaba 1.9 <h2>Document Source</h2>
653     <ol lang="">\n];
654 wakaba 1.7 if (length $$s) {
655     while ($$s =~ /\G([^\x0A]*?)\x0D?\x0A/gc) {
656 wakaba 1.32 print STDOUT qq[<li id="$input->{id_prefix}line-$i">], htescape $1,
657     "</li>\n";
658 wakaba 1.7 $i++;
659     }
660     if ($$s =~ /\G([^\x0A]+)/gc) {
661 wakaba 1.32 print STDOUT qq[<li id="$input->{id_prefix}line-$i">], htescape $1,
662     "</li>\n";
663 wakaba 1.7 }
664     } else {
665 wakaba 1.32 print STDOUT q[<li id="$input->{id_prefix}line-1"></li>];
666 wakaba 1.2 }
667 wakaba 1.9 print STDOUT "</ol></div>";
668     } # print_input_string_section
669 wakaba 1.2
670 wakaba 1.35 sub print_document_tree ($$) {
671     my ($input, $node) = @_;
672    
673 wakaba 1.2 my $r = '<ol class="xoxo">';
674 wakaba 1.1
675 wakaba 1.2 my @node = ($node);
676 wakaba 1.1 while (@node) {
677     my $child = shift @node;
678 wakaba 1.2 unless (ref $child) {
679     $r .= $child;
680     next;
681     }
682    
683 wakaba 1.32 my $node_id = $input->{id_prefix} . 'node-'.refaddr $child;
684 wakaba 1.2 my $nt = $child->node_type;
685     if ($nt == $child->ELEMENT_NODE) {
686 wakaba 1.4 my $child_nsuri = $child->namespace_uri;
687     $r .= qq[<li id="$node_id" class="tree-element"><code title="@{[defined $child_nsuri ? $child_nsuri : '']}">] . htescape ($child->tag_name) .
688 wakaba 1.2 '</code>'; ## ISSUE: case
689    
690     if ($child->has_attributes) {
691     $r .= '<ul class="attributes">';
692 wakaba 1.4 for my $attr (sort {$a->[0] cmp $b->[0]} map { [$_->name, $_->value, $_->namespace_uri, 'node-'.refaddr $_] }
693 wakaba 1.2 @{$child->attributes}) {
694 wakaba 1.32 $r .= qq[<li id="$input->{id_prefix}$attr->[3]" class="tree-attribute"><code title="@{[defined $attr->[2] ? htescape ($attr->[2]) : '']}">] . htescape ($attr->[0]) . '</code> = '; ## ISSUE: case?
695 wakaba 1.2 $r .= '<q>' . htescape ($attr->[1]) . '</q></li>'; ## TODO: children
696     }
697     $r .= '</ul>';
698     }
699    
700 wakaba 1.7 if ($child->has_child_nodes) {
701 wakaba 1.2 $r .= '<ol class="children">';
702 wakaba 1.6 unshift @node, @{$child->child_nodes}, '</ol></li>';
703     } else {
704     $r .= '</li>';
705 wakaba 1.2 }
706     } elsif ($nt == $child->TEXT_NODE) {
707 wakaba 1.4 $r .= qq'<li id="$node_id" class="tree-text"><q lang="">' . htescape ($child->data) . '</q></li>';
708 wakaba 1.2 } elsif ($nt == $child->CDATA_SECTION_NODE) {
709 wakaba 1.4 $r .= qq'<li id="$node_id" class="tree-cdata"><code>&lt;[CDATA[</code><q lang="">' . htescape ($child->data) . '</q><code>]]&gt;</code></li>';
710 wakaba 1.2 } elsif ($nt == $child->COMMENT_NODE) {
711 wakaba 1.4 $r .= qq'<li id="$node_id" class="tree-comment"><code>&lt;!--</code><q lang="">' . htescape ($child->data) . '</q><code>--&gt;</code></li>';
712 wakaba 1.2 } elsif ($nt == $child->DOCUMENT_NODE) {
713 wakaba 1.6 $r .= qq'<li id="$node_id" class="tree-document">Document';
714 wakaba 1.7 $r .= qq[<ul class="attributes">];
715 wakaba 1.27 my $cp = $child->manakai_charset;
716     if (defined $cp) {
717     $r .= qq[<li><code>charset</code> parameter = <code>];
718     $r .= htescape ($cp) . qq[</code></li>];
719     }
720     $r .= qq[<li><code>inputEncoding</code> = ];
721     my $ie = $child->input_encoding;
722     if (defined $ie) {
723     $r .= qq[<code>@{[htescape ($ie)]}</code>];
724     if ($child->manakai_has_bom) {
725     $r .= qq[ (with <code class=charname><abbr>BOM</abbr></code>)];
726     }
727     } else {
728     $r .= qq[(<code>null</code>)];
729     }
730 wakaba 1.7 $r .= qq[<li>@{[scalar get_text ('manakaiIsHTML:'.($child->manakai_is_html?1:0))]}</li>];
731     $r .= qq[<li>@{[scalar get_text ('manakaiCompatMode:'.$child->manakai_compat_mode)]}</li>];
732 wakaba 1.9 unless ($child->manakai_is_html) {
733     $r .= qq[<li>XML version = <code>@{[htescape ($child->xml_version)]}</code></li>];
734     if (defined $child->xml_encoding) {
735     $r .= qq[<li>XML encoding = <code>@{[htescape ($child->xml_encoding)]}</code></li>];
736     } else {
737     $r .= qq[<li>XML encoding = (null)</li>];
738     }
739     $r .= qq[<li>XML standalone = @{[$child->xml_standalone ? 'true' : 'false']}</li>];
740     }
741 wakaba 1.7 $r .= qq[</ul>];
742 wakaba 1.2 if ($child->has_child_nodes) {
743 wakaba 1.7 $r .= '<ol class="children">';
744 wakaba 1.6 unshift @node, @{$child->child_nodes}, '</ol></li>';
745 wakaba 1.1 }
746 wakaba 1.2 } elsif ($nt == $child->DOCUMENT_TYPE_NODE) {
747 wakaba 1.5 $r .= qq'<li id="$node_id" class="tree-doctype"><code>&lt;!DOCTYPE&gt;</code><ul class="attributes">';
748     $r .= qq[<li class="tree-doctype-name">Name = <q>@{[htescape ($child->name)]}</q></li>];
749     $r .= qq[<li class="tree-doctype-publicid">Public identifier = <q>@{[htescape ($child->public_id)]}</q></li>];
750     $r .= qq[<li class="tree-doctype-systemid">System identifier = <q>@{[htescape ($child->system_id)]}</q></li>];
751 wakaba 1.2 $r .= '</ul></li>';
752     } elsif ($nt == $child->PROCESSING_INSTRUCTION_NODE) {
753 wakaba 1.4 $r .= qq'<li id="$node_id" class="tree-id"><code>&lt;?@{[htescape ($child->target)]}</code> <q>@{[htescape ($child->data)]}</q><code>?&gt;</code></li>';
754 wakaba 1.1 } else {
755 wakaba 1.4 $r .= qq'<li id="$node_id" class="tree-unknown">@{[$child->node_type]} @{[htescape ($child->node_name)]}</li>'; # error
756 wakaba 1.1 }
757     }
758 wakaba 1.2
759     $r .= '</ol>';
760     print STDOUT $r;
761     } # print_document_tree
762 wakaba 1.1
763 wakaba 1.32 sub print_structure_dump_dom_section ($$$) {
764     my ($input, $doc, $el) = @_;
765 wakaba 1.18
766     print STDOUT qq[
767 wakaba 1.32 <div id="$input->{id_prefix}document-tree" class="section">
768 wakaba 1.18 <h2>Document Tree</h2>
769     ];
770 wakaba 1.35 push @nav, [qq[#$input->{id_prefix}document-tree] => 'Tree']
771     unless $input->{nested};
772 wakaba 1.18
773 wakaba 1.35 print_document_tree ($input, $el || $doc);
774 wakaba 1.18
775     print STDOUT qq[</div>];
776 wakaba 1.22 } # print_structure_dump_dom_section
777    
778 wakaba 1.35 sub print_structure_dump_cssom_section ($$) {
779     my ($input, $cssom) = @_;
780    
781     print STDOUT qq[
782     <div id="$input->{id_prefix}document-tree" class="section">
783     <h2>Document Tree</h2>
784     ];
785     push @nav, [qq[#$input->{id_prefix}document-tree] => 'Tree']
786     unless $input->{nested};
787    
788     ## TODO:
789     print STDOUT "<pre>".htescape ($cssom->css_text)."</pre>";
790    
791     print STDOUT qq[</div>];
792     } # print_structure_dump_cssom_section
793    
794 wakaba 1.32 sub print_structure_dump_manifest_section ($$) {
795     my ($input, $manifest) = @_;
796 wakaba 1.22
797     print STDOUT qq[
798 wakaba 1.32 <div id="$input->{id_prefix}dump-manifest" class="section">
799 wakaba 1.22 <h2>Cache Manifest</h2>
800     ];
801 wakaba 1.35 push @nav, [qq[#$input->{id_prefix}dump-manifest] => 'Cache Manifest']
802     unless $input->{nested};
803 wakaba 1.22
804     print STDOUT qq[<dl><dt>Explicit entries</dt>];
805     for my $uri (@{$manifest->[0]}) {
806     my $euri = htescape ($uri);
807     print STDOUT qq[<dd><code class=uri>&lt;<a href="$euri">$euri</a>></code></dd>];
808     }
809    
810     print STDOUT qq[<dt>Fallback entries</dt><dd>
811     <table><thead><tr><th scope=row>Oppotunistic Caching Namespace</th>
812     <th scope=row>Fallback Entry</tr><tbody>];
813     for my $uri (sort {$a cmp $b} keys %{$manifest->[1]}) {
814     my $euri = htescape ($uri);
815     my $euri2 = htescape ($manifest->[1]->{$uri});
816     print STDOUT qq[<tr><td><code class=uri>&lt;<a href="$euri">$euri</a>></code></td>
817     <td><code class=uri>&lt;<a href="$euri2">$euri2</a>></code></td>];
818     }
819    
820     print STDOUT qq[</table><dt>Online whitelist</dt>];
821     for my $uri (@{$manifest->[2]}) {
822     my $euri = htescape ($uri);
823     print STDOUT qq[<dd><code class=uri>&lt;<a href="$euri">$euri</a>></code></dd>];
824     }
825 wakaba 1.18
826 wakaba 1.22 print STDOUT qq[</dl></div>];
827     } # print_structure_dump_manifest_section
828    
829 wakaba 1.34 sub print_structure_error_dom_section ($$$$$) {
830     my ($input, $doc, $el, $result, $onsubdoc) = @_;
831 wakaba 1.18
832 wakaba 1.32 print STDOUT qq[<div id="$input->{id_prefix}document-errors" class="section">
833 wakaba 1.18 <h2>Document Errors</h2>
834    
835     <dl>];
836 wakaba 1.35 push @nav, [qq[#$input->{id_prefix}document-errors] => 'Document Error']
837     unless $input->{nested};
838 wakaba 1.18
839     require Whatpm::ContentChecker;
840     my $onerror = sub {
841     my %opt = @_;
842     my ($type, $cls, $msg) = get_text ($opt{type}, $opt{level}, $opt{node});
843     $type =~ tr/ /-/;
844     $type =~ s/\|/%7C/g;
845     $msg .= qq[ [<a href="../error-description#@{[htescape ($type)]}">Description</a>]];
846 wakaba 1.32 print STDOUT qq[<dt class="$cls">] . get_error_label ($input, \%opt) .
847 wakaba 1.23 qq[</dt>\n<dd class="$cls">], get_error_level_label (\%opt);
848     print STDOUT $msg, "</dd>\n";
849 wakaba 1.19 add_error ('structure', \%opt => $result);
850 wakaba 1.18 };
851    
852     my $elements;
853     my $time1 = time;
854     if ($el) {
855 wakaba 1.34 $elements = Whatpm::ContentChecker->check_element
856     ($el, $onerror, $onsubdoc);
857 wakaba 1.18 } else {
858 wakaba 1.34 $elements = Whatpm::ContentChecker->check_document
859     ($doc, $onerror, $onsubdoc);
860 wakaba 1.18 }
861     $time{check} = time - $time1;
862    
863     print STDOUT qq[</dl></div>];
864    
865     return $elements;
866 wakaba 1.22 } # print_structure_error_dom_section
867    
868     sub print_structure_error_manifest_section ($$$) {
869 wakaba 1.32 my ($input, $manifest, $result) = @_;
870 wakaba 1.22
871 wakaba 1.32 print STDOUT qq[<div id="$input->{id_prefix}document-errors" class="section">
872 wakaba 1.22 <h2>Document Errors</h2>
873    
874     <dl>];
875 wakaba 1.35 push @nav, [qq[#$input->{id_prefix}document-errors] => 'Document Error']
876     unless $input->{nested};
877 wakaba 1.22
878     require Whatpm::CacheManifest;
879     Whatpm::CacheManifest->check_manifest ($manifest, sub {
880     my %opt = @_;
881     my ($type, $cls, $msg) = get_text ($opt{type}, $opt{level}, $opt{node});
882     $type =~ tr/ /-/;
883     $type =~ s/\|/%7C/g;
884     $msg .= qq[ [<a href="../error-description#@{[htescape ($type)]}">Description</a>]];
885 wakaba 1.32 print STDOUT qq[<dt class="$cls">] . get_error_label ($input, \%opt) .
886 wakaba 1.22 qq[</dt>\n<dd class="$cls">], $msg, "</dd>\n";
887     add_error ('structure', \%opt => $result);
888     });
889    
890     print STDOUT qq[</div>];
891     } # print_structure_error_manifest_section
892 wakaba 1.18
893 wakaba 1.32 sub print_table_section ($$) {
894     my ($input, $tables) = @_;
895 wakaba 1.18
896 wakaba 1.35 push @nav, [qq[#$input->{id_prefix}tables] => 'Tables']
897     unless $input->{nested};
898 wakaba 1.18 print STDOUT qq[
899 wakaba 1.32 <div id="$input->{id_prefix}tables" class="section">
900 wakaba 1.18 <h2>Tables</h2>
901    
902     <!--[if IE]><script type="text/javascript" src="../excanvas.js"></script><![endif]-->
903     <script src="../table-script.js" type="text/javascript"></script>
904     <noscript>
905     <p><em>Structure of tables are visualized here if scripting is enabled.</em></p>
906     </noscript>
907     ];
908    
909     require JSON;
910    
911     my $i = 0;
912     for my $table_el (@$tables) {
913     $i++;
914 wakaba 1.32 print STDOUT qq[<div class="section" id="$input->{id_prefix}table-$i"><h3>] .
915     get_node_link ($input, $table_el) . q[</h3>];
916 wakaba 1.18
917     ## TODO: Make |ContentChecker| return |form_table| result
918     ## so that this script don't have to run the algorithm twice.
919     my $table = Whatpm::HTMLTable->form_table ($table_el);
920    
921     for (@{$table->{column_group}}, @{$table->{column}}, $table->{caption}) {
922     next unless $_;
923     delete $_->{element};
924     }
925    
926     for (@{$table->{row_group}}) {
927     next unless $_;
928     next unless $_->{element};
929     $_->{type} = $_->{element}->manakai_local_name;
930     delete $_->{element};
931     }
932    
933     for (@{$table->{cell}}) {
934     next unless $_;
935     for (@{$_}) {
936     next unless $_;
937     for (@$_) {
938     $_->{id} = refaddr $_->{element} if defined $_->{element};
939     delete $_->{element};
940     $_->{is_header} = $_->{is_header} ? 1 : 0;
941     }
942     }
943     }
944    
945     print STDOUT '</div><script type="text/javascript">tableToCanvas (';
946     print STDOUT JSON::objToJson ($table);
947 wakaba 1.32 print STDOUT qq[, document.getElementById ('$input->{id_prefix}table-$i')];
948     print STDOUT qq[, '$input->{id_prefix}');</script>];
949 wakaba 1.18 }
950    
951     print STDOUT qq[</div>];
952     } # print_table_section
953    
954 wakaba 1.33 sub print_listing_section ($$$) {
955     my ($opt, $input, $ids) = @_;
956 wakaba 1.18
957 wakaba 1.35 push @nav, ['#' . $input->{id_prefix} . $opt->{id} => $opt->{label}]
958     unless $input->{nested};
959 wakaba 1.18 print STDOUT qq[
960 wakaba 1.33 <div id="$input->{id_prefix}$opt->{id}" class="section">
961     <h2>$opt->{heading}</h2>
962 wakaba 1.18
963     <dl>
964     ];
965     for my $id (sort {$a cmp $b} keys %$ids) {
966     print STDOUT qq[<dt><code>@{[htescape $id]}</code></dt>];
967     for (@{$ids->{$id}}) {
968 wakaba 1.32 print STDOUT qq[<dd>].get_node_link ($input, $_).qq[</dd>];
969 wakaba 1.18 }
970     }
971     print STDOUT qq[</dl></div>];
972 wakaba 1.33 } # print_listing_section
973 wakaba 1.18
974 wakaba 1.19 sub print_result_section ($) {
975     my $result = shift;
976    
977     print STDOUT qq[
978     <div id="result-summary" class="section">
979     <h2>Result</h2>];
980    
981 wakaba 1.21 if ($result->{unsupported} and $result->{conforming_max}) {
982 wakaba 1.19 print STDOUT qq[<p class=uncertain id=result-para>The conformance
983     checker cannot decide whether the document is conforming or
984     not, since the document contains one or more unsupported
985 wakaba 1.21 features. The document might or might not be conforming.</p>];
986 wakaba 1.19 } elsif ($result->{conforming_min}) {
987     print STDOUT qq[<p class=PASS id=result-para>No conformance-error is
988     found in this document.</p>];
989     } elsif ($result->{conforming_max}) {
990     print STDOUT qq[<p class=SEE-RESULT id=result-para>This document
991     is <strong>likely <em>non</em>-conforming</strong>, but in rare case
992     it might be conforming.</p>];
993     } else {
994     print STDOUT qq[<p class=FAIL id=result-para>This document is
995     <strong><em>non</em>-conforming</strong>.</p>];
996     }
997    
998     print STDOUT qq[<table>
999     <colgroup><col><colgroup><col><col><col><colgroup><col>
1000     <thead>
1001 wakaba 1.23 <tr><th scope=col></th>
1002     <th scope=col><a href="../error-description#level-m"><em class=rfc2119>MUST</em>‐level
1003     Errors</a></th>
1004     <th scope=col><a href="../error-description#level-s"><em class=rfc2119>SHOULD</em>‐level
1005     Errors</a></th>
1006     <th scope=col><a href="../error-description#level-w">Warnings</a></th>
1007     <th scope=col>Score</th></tr></thead><tbody>];
1008 wakaba 1.19
1009     my $must_error = 0;
1010     my $should_error = 0;
1011     my $warning = 0;
1012     my $score_min = 0;
1013     my $score_max = 0;
1014     my $score_base = 20;
1015 wakaba 1.21 my $score_unit = $score_base / 100;
1016 wakaba 1.19 for (
1017     [Transfer => 'transfer', ''],
1018     [Character => 'char', ''],
1019     [Syntax => 'syntax', '#parse-errors'],
1020     [Structure => 'structure', '#document-errors'],
1021     ) {
1022     $must_error += ($result->{$_->[1]}->{must} += 0);
1023     $should_error += ($result->{$_->[1]}->{should} += 0);
1024     $warning += ($result->{$_->[1]}->{warning} += 0);
1025 wakaba 1.21 $score_min += (($result->{$_->[1]}->{score_min} *= $score_unit) += $score_base);
1026     $score_max += (($result->{$_->[1]}->{score_max} *= $score_unit) += $score_base);
1027 wakaba 1.19
1028     my $uncertain = $result->{$_->[1]}->{unsupported} ? '?' : '';
1029     my $label = $_->[0];
1030     if ($result->{$_->[1]}->{must} or
1031     $result->{$_->[1]}->{should} or
1032     $result->{$_->[1]}->{warning} or
1033     $result->{$_->[1]}->{unsupported}) {
1034     $label = qq[<a href="$_->[2]">$label</a>];
1035     }
1036    
1037     print STDOUT qq[<tr class="@{[$uncertain ? 'uncertain' : '']}"><th scope=row>$label</th><td class="@{[$result->{$_->[1]}->{must} ? 'FAIL' : '']}">$result->{$_->[1]}->{must}$uncertain</td><td class="@{[$result->{$_->[1]}->{should} ? 'SEE-RESULT' : '']}">$result->{$_->[1]}->{should}$uncertain</td><td>$result->{$_->[1]}->{warning}$uncertain</td>];
1038     if ($uncertain) {
1039 wakaba 1.21 print qq[<td class="@{[$result->{$_->[1]}->{must} ? 'FAIL' : $result->{$_->[1]}->{should} ? 'SEE-RESULT' : '']}">&#x2212;&#x221E;..$result->{$_->[1]}->{score_max}</td>];
1040 wakaba 1.19 } elsif ($result->{$_->[1]}->{score_min} != $result->{$_->[1]}->{score_max}) {
1041 wakaba 1.21 print qq[<td class="@{[$result->{$_->[1]}->{must} ? 'FAIL' : 'SEE-RESULT']}">$result->{$_->[1]}->{score_min}..$result->{$_->[1]}->{score_max}</td></tr>];
1042 wakaba 1.19 } else {
1043 wakaba 1.21 print qq[<td class="@{[$result->{$_->[1]}->{must} ? 'FAIL' : '']}">$result->{$_->[1]}->{score_min}</td></tr>];
1044 wakaba 1.19 }
1045     }
1046    
1047     $score_max += $score_base;
1048    
1049     print STDOUT qq[
1050     <tr class=uncertain><th scope=row>Semantics</th><td>0?</td><td>0?</td><td>0?</td><td>&#x2212;&#x221E;..$score_base</td></tr>
1051     </tbody>
1052 wakaba 1.21 <tfoot><tr class=uncertain><th scope=row>Total</th>
1053     <td class="@{[$must_error ? 'FAIL' : '']}">$must_error?</td>
1054     <td class="@{[$should_error ? 'SEE-RESULT' : '']}">$should_error?</td>
1055     <td>$warning?</td>
1056     <td class="@{[$must_error ? 'FAIL' : $should_error ? 'SEE-RESULT' : '']}"><strong>&#x2212;&#x221E;..$score_max</strong></td></tr></tfoot>
1057 wakaba 1.19 </table>
1058    
1059     <p><strong>Important</strong>: This conformance checking service
1060     is <em>under development</em>. The result above might be <em>wrong</em>.</p>
1061     </div>];
1062     push @nav, ['#result-summary' => 'Result'];
1063     } # print_result_section
1064    
1065 wakaba 1.24 sub print_result_unknown_type_section ($$) {
1066     my ($input, $result) = @_;
1067 wakaba 1.18
1068 wakaba 1.24 my $euri = htescape ($input->{uri});
1069 wakaba 1.18 print STDOUT qq[
1070 wakaba 1.35 <div id="$input->{id_prefix}parse-errors" class="section">
1071 wakaba 1.24 <h2>Errors</h2>
1072    
1073     <dl>
1074     <dt class=unsupported><code>&lt;<a href="$euri">$euri</a>&gt;</code></dt>
1075     <dd class=unsupported><strong><a href="../error-description#level-u">Not
1076     supported</a></strong>:
1077     Media type
1078     <code class="MIME" lang="en">@{[htescape $input->{media_type}]}</code>
1079     is not supported.</dd>
1080     </dl>
1081 wakaba 1.18 </div>
1082     ];
1083 wakaba 1.35 push @nav, [qq[#$input->{id_prefix}parse-errors] => 'Errors']
1084     unless $input->{nested};
1085 wakaba 1.30 add_error (char => {level => 'u'} => $result);
1086     add_error (syntax => {level => 'u'} => $result);
1087     add_error (structure => {level => 'u'} => $result);
1088 wakaba 1.18 } # print_result_unknown_type_section
1089    
1090     sub print_result_input_error_section ($) {
1091     my $input = shift;
1092     print STDOUT qq[<div class="section" id="result-summary">
1093     <p><em><strong>Input Error</strong>: @{[htescape ($input->{error_status_text})]}</em></p>
1094     </div>];
1095     push @nav, ['#result-summary' => 'Result'];
1096 wakaba 1.32 } # print_result_input_error_section
1097 wakaba 1.18
1098 wakaba 1.32 sub get_error_label ($$) {
1099     my ($input, $err) = @_;
1100 wakaba 1.22
1101     my $r = '';
1102    
1103     if (defined $err->{line}) {
1104     if ($err->{column} > 0) {
1105 wakaba 1.35 $r = qq[<a href="#$input->{id_prefix}line-$err->{line}">Line $err->{line}</a> column $err->{column}];
1106 wakaba 1.22 } else {
1107     $err->{line} = $err->{line} - 1 || 1;
1108 wakaba 1.35 $r = qq[<a href="#$input->{id_prefix}line-$err->{line}">Line $err->{line}</a>];
1109 wakaba 1.22 }
1110     }
1111    
1112     if (defined $err->{node}) {
1113     $r .= ' ' if length $r;
1114 wakaba 1.32 $r = get_node_link ($input, $err->{node});
1115 wakaba 1.22 }
1116    
1117     if (defined $err->{index}) {
1118     $r .= ' ' if length $r;
1119     $r .= 'Index ' . (0+$err->{index});
1120     }
1121    
1122     if (defined $err->{value}) {
1123     $r .= ' ' if length $r;
1124     $r .= '<q><code>' . htescape ($err->{value}) . '</code></q>';
1125     }
1126    
1127     return $r;
1128     } # get_error_label
1129    
1130 wakaba 1.23 sub get_error_level_label ($) {
1131     my $err = shift;
1132    
1133     my $r = '';
1134    
1135     if (not defined $err->{level} or $err->{level} eq 'm') {
1136     $r = qq[<strong><a href="../error-description#level-m"><em class=rfc2119>MUST</em>‐level
1137     error</a></strong>: ];
1138     } elsif ($err->{level} eq 's') {
1139     $r = qq[<strong><a href="../error-description#level-s"><em class=rfc2119>SHOULD</em>‐level
1140     error</a></strong>: ];
1141     } elsif ($err->{level} eq 'w') {
1142     $r = qq[<strong><a href="../error-description#level-w">Warning</a></strong>:
1143     ];
1144 wakaba 1.30 } elsif ($err->{level} eq 'u' or $err->{level} eq 'unsupported') {
1145 wakaba 1.23 $r = qq[<strong><a href="../error-description#level-u">Not
1146     supported</a></strong>: ];
1147     } else {
1148     my $elevel = htescape ($err->{level});
1149     $r = qq[<strong><a href="../error-description#level-$elevel">$elevel</a></strong>:
1150     ];
1151     }
1152    
1153     return $r;
1154     } # get_error_level_label
1155    
1156 wakaba 1.1 sub get_node_path ($) {
1157     my $node = shift;
1158     my @r;
1159     while (defined $node) {
1160     my $rs;
1161     if ($node->node_type == 1) {
1162     $rs = $node->manakai_local_name;
1163     $node = $node->parent_node;
1164     } elsif ($node->node_type == 2) {
1165     $rs = '@' . $node->manakai_local_name;
1166     $node = $node->owner_element;
1167     } elsif ($node->node_type == 3) {
1168     $rs = '"' . $node->data . '"';
1169     $node = $node->parent_node;
1170     } elsif ($node->node_type == 9) {
1171 wakaba 1.9 @r = ('') unless @r;
1172 wakaba 1.1 $rs = '';
1173     $node = $node->parent_node;
1174     } else {
1175     $rs = '#' . $node->node_type;
1176     $node = $node->parent_node;
1177     }
1178     unshift @r, $rs;
1179     }
1180     return join '/', @r;
1181     } # get_node_path
1182    
1183 wakaba 1.32 sub get_node_link ($$) {
1184     return qq[<a href="#$_[0]->{id_prefix}node-@{[refaddr $_[1]]}">] .
1185     htescape (get_node_path ($_[1])) . qq[</a>];
1186 wakaba 1.6 } # get_node_link
1187    
1188 wakaba 1.7 {
1189     my $Msg = {};
1190    
1191     sub load_text_catalog ($) {
1192     my $lang = shift; # MUST be a canonical lang name
1193 wakaba 1.26 open my $file, '<:utf8', "cc-msg.$lang.txt"
1194     or die "$0: cc-msg.$lang.txt: $!";
1195 wakaba 1.7 while (<$file>) {
1196     if (s/^([^;]+);([^;]*);//) {
1197     my ($type, $cls, $msg) = ($1, $2, $_);
1198     $msg =~ tr/\x0D\x0A//d;
1199     $Msg->{$type} = [$cls, $msg];
1200     }
1201     }
1202     } # load_text_catalog
1203    
1204     sub get_text ($) {
1205 wakaba 1.15 my ($type, $level, $node) = @_;
1206 wakaba 1.7 $type = $level . ':' . $type if defined $level;
1207 wakaba 1.29 $level = 'm' unless defined $level;
1208 wakaba 1.7 my @arg;
1209     {
1210     if (defined $Msg->{$type}) {
1211     my $msg = $Msg->{$type}->[1];
1212 wakaba 1.10 $msg =~ s{<var>\$([0-9]+)</var>}{
1213     defined $arg[$1] ? htescape ($arg[$1]) : '(undef)';
1214     }ge;
1215 wakaba 1.15 $msg =~ s{<var>{\@([A-Za-z0-9:_.-]+)}</var>}{
1216     UNIVERSAL::can ($node, 'get_attribute_ns')
1217     ? htescape ($node->get_attribute_ns (undef, $1)) : ''
1218     }ge;
1219     $msg =~ s{<var>{\@}</var>}{
1220     UNIVERSAL::can ($node, 'value') ? htescape ($node->value) : ''
1221     }ge;
1222 wakaba 1.17 $msg =~ s{<var>{local-name}</var>}{
1223     UNIVERSAL::can ($node, 'manakai_local_name')
1224     ? htescape ($node->manakai_local_name) : ''
1225     }ge;
1226     $msg =~ s{<var>{element-local-name}</var>}{
1227     (UNIVERSAL::can ($node, 'owner_element') and
1228     $node->owner_element)
1229     ? htescape ($node->owner_element->manakai_local_name)
1230     : ''
1231     }ge;
1232 wakaba 1.29 return ($type, 'level-' . $level . ' ' . $Msg->{$type}->[0], $msg);
1233 wakaba 1.7 } elsif ($type =~ s/:([^:]*)$//) {
1234     unshift @arg, $1;
1235     redo;
1236     }
1237     }
1238 wakaba 1.29 return ($type, 'level-'.$level, htescape ($_[0]));
1239 wakaba 1.7 } # get_text
1240    
1241     }
1242    
1243 wakaba 1.9 sub get_input_document ($$) {
1244     my ($http, $dom) = @_;
1245    
1246 wakaba 1.16 my $request_uri = $http->get_parameter ('uri');
1247 wakaba 1.9 my $r = {};
1248     if (defined $request_uri and length $request_uri) {
1249     my $uri = $dom->create_uri_reference ($request_uri);
1250     unless ({
1251     http => 1,
1252     }->{lc $uri->uri_scheme}) {
1253     return {uri => $request_uri, request_uri => $request_uri,
1254     error_status_text => 'URI scheme not allowed'};
1255     }
1256    
1257     require Message::Util::HostPermit;
1258     my $host_permit = new Message::Util::HostPermit;
1259     $host_permit->add_rule (<<EOH);
1260     Allow host=suika port=80
1261     Deny host=suika
1262     Allow host=suika.fam.cx port=80
1263     Deny host=suika.fam.cx
1264     Deny host=localhost
1265     Deny host=*.localdomain
1266     Deny ipv4=0.0.0.0/8
1267     Deny ipv4=10.0.0.0/8
1268     Deny ipv4=127.0.0.0/8
1269     Deny ipv4=169.254.0.0/16
1270     Deny ipv4=172.0.0.0/11
1271     Deny ipv4=192.0.2.0/24
1272     Deny ipv4=192.88.99.0/24
1273     Deny ipv4=192.168.0.0/16
1274     Deny ipv4=198.18.0.0/15
1275     Deny ipv4=224.0.0.0/4
1276     Deny ipv4=255.255.255.255/32
1277     Deny ipv6=0::0/0
1278     Allow host=*
1279     EOH
1280     unless ($host_permit->check ($uri->uri_host, $uri->uri_port || 80)) {
1281     return {uri => $request_uri, request_uri => $request_uri,
1282     error_status_text => 'Connection to the host is forbidden'};
1283     }
1284    
1285     require LWP::UserAgent;
1286     my $ua = WDCC::LWPUA->new;
1287     $ua->{wdcc_dom} = $dom;
1288     $ua->{wdcc_host_permit} = $host_permit;
1289     $ua->agent ('Mozilla'); ## TODO: for now.
1290     $ua->parse_head (0);
1291     $ua->protocols_allowed ([qw/http/]);
1292     $ua->max_size (1000_000);
1293     my $req = HTTP::Request->new (GET => $request_uri);
1294 wakaba 1.28 $req->header ('Accept-Encoding' => 'identity, *; q=0');
1295 wakaba 1.9 my $res = $ua->request ($req);
1296 wakaba 1.16 ## TODO: 401 sets |is_success| true.
1297     if ($res->is_success or $http->get_parameter ('error-page')) {
1298 wakaba 1.9 $r->{base_uri} = $res->base; ## NOTE: It does check |Content-Base|, |Content-Location|, and <base>. ## TODO: Use our own code!
1299     $r->{uri} = $res->request->uri;
1300     $r->{request_uri} = $request_uri;
1301    
1302     ## TODO: More strict parsing...
1303     my $ct = $res->header ('Content-Type');
1304 wakaba 1.22 if (defined $ct and $ct =~ /;\s*charset\s*=\s*"?([^\s;"]+)"?/i) {
1305 wakaba 1.9 $r->{charset} = lc $1;
1306     $r->{charset} =~ tr/\\//d;
1307 wakaba 1.26 $r->{official_charset} = $r->{charset};
1308 wakaba 1.9 }
1309    
1310 wakaba 1.16 my $input_charset = $http->get_parameter ('charset');
1311 wakaba 1.9 if (defined $input_charset and length $input_charset) {
1312     $r->{charset_overridden}
1313     = (not defined $r->{charset} or $r->{charset} ne $input_charset);
1314     $r->{charset} = $input_charset;
1315 wakaba 1.25 }
1316    
1317     ## TODO: Support for HTTP Content-Encoding
1318 wakaba 1.9
1319     $r->{s} = ''.$res->content;
1320 wakaba 1.25
1321     require Whatpm::ContentType;
1322     ($r->{official_type}, $r->{media_type})
1323     = Whatpm::ContentType->get_sniffed_type
1324     (get_file_head => sub {
1325     return substr $r->{s}, 0, shift;
1326     },
1327     http_content_type_byte => $ct,
1328     has_http_content_encoding =>
1329     defined $res->header ('Content-Encoding'),
1330     supported_image_types => {});
1331 wakaba 1.9 } else {
1332     $r->{uri} = $res->request->uri;
1333     $r->{request_uri} = $request_uri;
1334     $r->{error_status_text} = $res->status_line;
1335     }
1336    
1337     $r->{header_field} = [];
1338     $res->scan (sub {
1339     push @{$r->{header_field}}, [$_[0], $_[1]];
1340     });
1341     $r->{header_status_code} = $res->code;
1342     $r->{header_status_text} = $res->message;
1343     } else {
1344 wakaba 1.16 $r->{s} = ''.$http->get_parameter ('s');
1345 wakaba 1.9 $r->{uri} = q<thismessage:/>;
1346     $r->{request_uri} = q<thismessage:/>;
1347     $r->{base_uri} = q<thismessage:/>;
1348 wakaba 1.16 $r->{charset} = ''.$http->get_parameter ('_charset_');
1349 wakaba 1.9 $r->{charset} =~ s/\s+//g;
1350     $r->{charset} = 'utf-8' if $r->{charset} eq '';
1351 wakaba 1.26 $r->{official_charset} = $r->{charset};
1352 wakaba 1.9 $r->{header_field} = [];
1353 wakaba 1.25
1354     require Whatpm::ContentType;
1355     ($r->{official_type}, $r->{media_type})
1356     = Whatpm::ContentType->get_sniffed_type
1357     (get_file_head => sub {
1358     return substr $r->{s}, 0, shift;
1359     },
1360     http_content_type_byte => undef,
1361     has_http_content_encoding => 0,
1362     supported_image_types => {});
1363 wakaba 1.9 }
1364    
1365 wakaba 1.16 my $input_format = $http->get_parameter ('i');
1366 wakaba 1.9 if (defined $input_format and length $input_format) {
1367     $r->{media_type_overridden}
1368     = (not defined $r->{media_type} or $input_format ne $r->{media_type});
1369     $r->{media_type} = $input_format;
1370     }
1371     if (defined $r->{s} and not defined $r->{media_type}) {
1372     $r->{media_type} = 'text/html';
1373     $r->{media_type_overridden} = 1;
1374     }
1375    
1376     if ($r->{media_type} eq 'text/xml') {
1377     unless (defined $r->{charset}) {
1378     $r->{charset} = 'us-ascii';
1379 wakaba 1.26 $r->{official_charset} = $r->{charset};
1380 wakaba 1.9 } elsif ($r->{charset_overridden} and $r->{charset} eq 'us-ascii') {
1381     $r->{charset_overridden} = 0;
1382     }
1383     }
1384    
1385     if (length $r->{s} > 1000_000) {
1386     $r->{error_status_text} = 'Entity-body too large';
1387     delete $r->{s};
1388     return $r;
1389     }
1390    
1391 wakaba 1.35 $r->{inner_html_element} = $http->get_parameter ('e');
1392    
1393 wakaba 1.9 return $r;
1394     } # get_input_document
1395    
1396     package WDCC::LWPUA;
1397     BEGIN { push our @ISA, 'LWP::UserAgent'; }
1398    
1399     sub redirect_ok {
1400     my $ua = shift;
1401     unless ($ua->SUPER::redirect_ok (@_)) {
1402     return 0;
1403     }
1404    
1405     my $uris = $_[1]->header ('Location');
1406     return 0 unless $uris;
1407     my $uri = $ua->{wdcc_dom}->create_uri_reference ($uris);
1408     unless ({
1409     http => 1,
1410     }->{lc $uri->uri_scheme}) {
1411     return 0;
1412     }
1413     unless ($ua->{wdcc_host_permit}->check ($uri->uri_host, $uri->uri_port || 80)) {
1414     return 0;
1415     }
1416     return 1;
1417     } # redirect_ok
1418    
1419 wakaba 1.1 =head1 AUTHOR
1420    
1421     Wakaba <w@suika.fam.cx>.
1422    
1423     =head1 LICENSE
1424    
1425 wakaba 1.35 Copyright 2007-2008 Wakaba <w@suika.fam.cx>
1426 wakaba 1.1
1427     This library is free software; you can redistribute it
1428     and/or modify it under the same terms as Perl itself.
1429    
1430     =cut
1431    
1432 wakaba 1.36 ## $Date: 2008/02/10 04:08:04 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24