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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.35 - (hide annotations) (download)
Sun Feb 10 04:08:04 2008 UTC (16 years, 3 months ago) by wakaba
Branch: MAIN
Changes since 1.34: +299 -34 lines
++ ChangeLog	10 Feb 2008 04:07:28 -0000
	* cc.cgi: |text/css| support.  |id_prefix| support was
	partially broken.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24