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

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

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1.1 by wakaba, Wed Jun 27 11:08:03 2007 UTC revision 1.35 by wakaba, Sun Feb 10 04:08:04 2008 UTC
# Line 1  Line 1 
1  #!/usr/bin/perl  #!/usr/bin/perl
2  use strict;  use strict;
3    use utf8;
4    
5  use lib qw[/home/httpd/html/www/markup/html/whatpm  use lib qw[/home/httpd/html/www/markup/html/whatpm
6             /home/wakaba/work/manakai/lib             /home/wakaba/work/manakai2/lib];
            /home/wakaba/public_html/-temp/wiki/lib];  
7  use CGI::Carp qw[fatalsToBrowser];  use CGI::Carp qw[fatalsToBrowser];
8    use Scalar::Util qw[refaddr];
9  use Time::HiRes qw/time/;  use Time::HiRes qw/time/;
10    
11  use SuikaWiki::Input::HTTP; ## TODO: Use some better CGI module  sub htescape ($) {
12      my $s = $_[0];
13      $s =~ s/&/&/g;
14      $s =~ s/</&lt;/g;
15      $s =~ s/>/&gt;/g;
16      $s =~ s/"/&quot;/g;
17      $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      return $s;
21    } # htescape
22    
23  my $http = SuikaWiki::Input::HTTP->new;    my @nav;
24      my %time;
25      require Message::DOM::DOMImplementation;
26      my $dom = Message::DOM::DOMImplementation->new;
27    {
28      use Message::CGI::HTTP;
29      my $http = Message::CGI::HTTP->new;
30    
31  ## TODO: _charset_    if ($http->get_meta_variable ('PATH_INFO') ne '/') {
32        print STDOUT "Status: 404 Not Found\nContent-Type: text/plain; charset=us-ascii\n\n400";
33        exit;
34      }
35    
36  my @mode = split m#/#, scalar $http->meta_variable ('PATH_INFO'), -1;    binmode STDOUT, ':utf8';
37  shift @mode if @mode and $mode[0] == '';    $| = 1;
 ## TODO: decode unreserved characters  
38    
39    my $s = $http->parameter ('s');    load_text_catalog ('en'); ## TODO: conneg
40    if (length $s > 1000_000) {  
41      print STDOUT "Status: 400 Document Too Long\nContent-Type: text/plain; charset=us-ascii\n\nToo long";    print STDOUT qq[Content-Type: text/html; charset=utf-8
42      exit;  
43    <!DOCTYPE html>
44    <html lang="en">
45    <head>
46    <title>Web Document Conformance Checker (BETA)</title>
47    <link rel="stylesheet" href="../cc-style.css" type="text/css">
48    </head>
49    <body>
50    <h1><a href="../cc-interface">Web Document Conformance Checker</a>
51    (<em>beta</em>)</h1>
52    ];
53    
54      $| = 0;
55      my $input = get_input_document ($http, $dom);
56      my $char_length = 0;
57    
58      print qq[
59    <div id="document-info" class="section">
60    <dl>
61    <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    <dt>Document URI</dt>
64        <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    ]; # no </dl> yet
71      push @nav, ['#document-info' => 'Information'];
72    
73    if (defined $input->{s}) {
74      $char_length = length $input->{s};
75    
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        @{[$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    <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    <dt>Length</dt>
86        <dd>$char_length byte@{[$char_length == 1 ? '' : 's']}</dd>
87    </dl>
88    </div>
89    ];
90    
91      $input->{id_prefix} = '';
92      #$input->{nested} = 0;
93      my $result = {conforming_min => 1, conforming_max => 1};
94      check_and_print ($input => $result);
95      print_result_section ($result);
96    } else {
97      print STDOUT qq[</dl></div>];
98      print_result_input_error_section ($input);
99    }
100    
101      print STDOUT qq[
102    <ul class="navigation" id="nav-items">
103    ];
104      for (@nav) {
105        print STDOUT qq[<li><a href="$_->[0]">$_->[1]</a></li>];
106    }    }
107    my $char_length = length $s;    print STDOUT qq[
108    my %time;  </ul>
109    my $time1;  </body>
110    my $time2;  </html>
111    ];
112    
113      for (qw/decode parse parse_html parse_xml parse_manifest
114              check check_manifest/) {
115        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    exit;
121    }
122    
123    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        } elsif ($err->{level} eq 'u' or $err->{level} eq 'unsupported') {
133          $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    sub check_and_print ($$) {
152      my ($input, $result) = @_;
153    
154      print_http_header_section ($input, $result);
155    
   require Message::DOM::DOMImplementation;  
   my $dom = Message::DOM::DOMImplementation->____new;  
 #  $| = 1;  
156    my $doc;    my $doc;
157    my $el;    my $el;
158      my $cssom;
159      my $manifest;
160      my @subdoc;
161    
162      if ($input->{media_type} eq 'text/html') {
163        ($doc, $el) = print_syntax_error_html_section ($input, $result);
164        print_source_string_section
165            ($input,
166             \($input->{s}),
167             $input->{charset} || $doc->input_encoding);
168      } 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        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      } 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        print_source_string_section ($input, \($input->{s}),
189                                     'utf-8');
190      } 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        $doc->document_uri ($input->{uri});
197        $doc->manakai_entity_base_uri ($input->{base_uri});
198        print_structure_dump_dom_section ($input, $doc, $el);
199        my $elements = print_structure_error_dom_section
200            ($input, $doc, $el, $result, sub {
201              push @subdoc, shift;
202            });
203        print_table_section ($input, $elements->{table}) if @{$elements->{table}};
204        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      } elsif (defined $cssom) {
214        print_structure_dump_cssom_section ($input, $cssom);
215        ## TODO: CSSOM validation
216      } elsif (defined $manifest) {
217        print_structure_dump_manifest_section ($input, $manifest);
218        print_structure_error_manifest_section ($input, $manifest, $result);
219      }
220    
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        $subinput->{id_prefix} .= '-';
242        check_and_print ($subinput => $result);
243    
244        print STDOUT qq[</div>];
245      }
246    } # check_and_print
247    
248    sub print_http_header_section ($$) {
249      my ($input, $result) = @_;
250      return unless defined $input->{header_status_code} or
251          defined $input->{header_status_text} or
252          @{$input->{header_field} or []};
253      
254      push @nav, ['#source-header' => 'HTTP Header'] unless $input->{nested};
255      print STDOUT qq[<div id="$input->{id_prefix}source-header" class="section">
256    <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  if (@mode == 3 and $mode[0] eq 'html' and    print STDOUT qq[</tbody></table></div>];
280      ($mode[2] eq 'html' or $mode[2] eq 'test')) {  } # print_http_header_section
   print STDOUT "Content-Type: text/plain; charset=utf-8\n\n";  
281    
282    sub print_syntax_error_html_section ($$) {
283      my ($input, $result) = @_;
284      
285    require Encode;    require Encode;
286    require Whatpm::HTML;    require Whatpm::HTML;
   
   $time1 = time;  
   $s = Encode::decode ('utf-8', $s);  
   $time2 = time;  
   $time{decode} = $time2 - $time1;  
287        
288      print STDOUT qq[
289    <div id="$input->{id_prefix}parse-errors" class="section">
290    <h2>Parse Errors</h2>
291    
292    print STDOUT "#errors\n";  <dl>];
293      push @nav, ['#parse-errors' => 'Parse Error'] unless $input->{nested};
294    
295    my $onerror = sub {    my $onerror = sub {
296      my (%opt) = @_;      my (%opt) = @_;
297      print STDOUT "$opt{line},$opt{column},$opt{type}\n";      my ($type, $cls, $msg) = get_text ($opt{type}, $opt{level});
298        if ($opt{column} > 0) {
299          print STDOUT qq[<dt class="$cls"><a href="#$input->{id_prefix}line-$opt{line}">Line $opt{line}</a> column $opt{column}</dt>\n];
300        } else {
301          $opt{line} = $opt{line} - 1 || 1;
302          print STDOUT qq[<dt class="$cls"><a href="#$input->{id_prefix}line-$opt{line}">Line $opt{line}</a></dt>\n];
303        }
304        $type =~ tr/ /-/;
305        $type =~ s/\|/%7C/g;
306        $msg .= qq[ [<a href="../error-description#@{[htescape ($type)]}">Description</a>]];
307        print STDOUT qq[<dd class="$cls">], get_error_level_label (\%opt);
308        print STDOUT qq[$msg</dd>\n];
309    
310        add_error ('syntax', \%opt => $result);
311    };    };
312    
313    $doc = $dom->create_document;    my $doc = $dom->create_document;
314    $time1 = time;    my $el;
315    if (length $mode[1]) {    my $inner_html_element = $input->{inner_html_element};
316      if (defined $inner_html_element and length $inner_html_element) {
317        $input->{charset} ||= 'windows-1252'; ## TODO: for now.
318        my $time1 = time;
319        my $t = Encode::decode ($input->{charset}, $input->{s});
320        $time{decode} = time - $time1;
321        
322      $el = $doc->create_element_ns      $el = $doc->create_element_ns
323          ('http://www.w3.org/1999/xhtml', [undef, $mode[1]]);          ('http://www.w3.org/1999/xhtml', [undef, $inner_html_element]);
324      Whatpm::HTML->set_inner_html ($el, $s, $onerror);      $time1 = time;
325        Whatpm::HTML->set_inner_html ($el, $t, $onerror);
326        $time{parse} = time - $time1;
327    } else {    } else {
328      Whatpm::HTML->parse_string ($s => $doc, $onerror);      my $time1 = time;
329        Whatpm::HTML->parse_byte_string
330            ($input->{charset}, $input->{s} => $doc, $onerror);
331        $time{parse_html} = time - $time1;
332    }    }
333    $time2 = time;    $doc->manakai_charset ($input->{official_charset})
334    $time{parse} = $time2 - $time1;        if defined $input->{official_charset};
335      
336    print "#document\n";    print STDOUT qq[</dl></div>];
337    
338    my $out;    return ($doc, $el);
339    if ($mode[2] eq 'html') {  } # print_syntax_error_html_section
     $time1 = time;  
     $out = Whatpm::HTML->get_inner_html ($el || $doc);  
     $time2 = time;  
     $time{serialize_html} = $time2 - $time1;  
   } else { # test  
     $time1 = time;  
     $out = test_serialize ($el || $doc);  
     $time2 = time;  
     $time{serialize_test} = $time2 - $time1;  
   }  
   print STDOUT Encode::encode ('utf-8', $$out);  
   print STDOUT "\n";  
 } elsif (@mode == 3 and $mode[0] eq 'xhtml' and  
          ($mode[2] eq 'html' or $mode[2] eq 'test')) {  
   print STDOUT "Content-Type: text/plain; charset=utf-8\n\n";  
340    
341    sub print_syntax_error_xml_section ($$) {
342      my ($input, $result) = @_;
343      
344    require Message::DOM::XMLParserTemp;    require Message::DOM::XMLParserTemp;
345    print STDOUT "#errors\n";    
346      print STDOUT qq[
347    <div id="$input->{id_prefix}parse-errors" class="section">
348    <h2>Parse Errors</h2>
349    
350    <dl>];
351      push @nav, ['#parse-errors' => 'Parse Error'] unless $input->{prefix};
352    
353    my $onerror = sub {    my $onerror = sub {
354      my $err = shift;      my $err = shift;
355      print STDOUT $err->location->line_number, ",";      my $line = $err->location->line_number;
356      print STDOUT $err->location->column_number, ",";      print STDOUT qq[<dt><a href="#$input->{id_prefix}line-$line">Line $line</a> column ];
357      print STDOUT $err->text, "\n";      print STDOUT $err->location->column_number, "</dt><dd>";
358        print STDOUT htescape $err->text, "</dd>\n";
359    
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      return 1;      return 1;
368    };    };
369    
   open my $fh, '<', \$s;  
370    my $time1 = time;    my $time1 = time;
371    $doc = Message::DOM::XMLParserTemp->parse_byte_stream    open my $fh, '<', \($input->{s});
372        ($fh => $dom, $onerror, charset => 'utf-8');    my $doc = Message::DOM::XMLParserTemp->parse_byte_stream
373    my $time2 = time;        ($fh => $dom, $onerror, charset => $input->{charset});
374    $time{parse_xml} = $time2 - $time1;    $time{parse_xml} = time - $time1;
375      $doc->manakai_charset ($input->{official_charset})
376    print "#document\n";        if defined $input->{official_charset};
377    
378    my $out;    print STDOUT qq[</dl></div>];
379    if ($mode[2] eq 'html') {  
380      ## TODO: Use XHTML serializer    return ($doc, undef);
381      #$out = Whatpm::HTML->get_inner_html ($doc);  } # print_syntax_error_xml_section
382    } else { # test  
383      $time1 = time;  sub get_css_parser () {
384      $out = test_serialize ($doc);    our $CSSParser;
385      $time2 = time;    return $CSSParser if $CSSParser;
386      $time{serialize_test} = $time2 - $time1;  
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    print STDOUT Encode::encode ('utf-8', $$out);    $p->{prop_value}->{visibility}->{$_} = 1 for qw/
451    print STDOUT "\n";      visible hidden collapse
452  } else {    /;
453    print STDOUT "Status: 404 Not Found\nContent-Type: text/plain; charset=us-ascii\n\n404";    $p->{prop_value}->{'list-style-type'}->{$_} = 1 for qw/
454    exit;      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    if ($http->parameter ('dom5')) {  <dl>];
551      require Whatpm::ContentChecker;    push @nav, ['#parse-errors' => 'Parse Error'] unless $input->{nested};
552      my $onerror = sub {  
553        my %opt = @_;    my $p = get_css_parser ();
554        print STDOUT get_node_path ($opt{node}) . ';' . $opt{type} . "\n";    $p->{onerror} = sub {
555      };      my (%opt) = @_;
556      print STDOUT "#domerrors\n";      my ($type, $cls, $msg) = get_text ($opt{type}, $opt{level});
557      $time1 = time;      if ($opt{token}) {
558      if ($el) {        print STDOUT qq[<dt class="$cls"><a href="#$input->{id_prefix}line-$opt{token}->{line}">Line $opt{token}->{line}</a> column $opt{token}->{column}];
       Whatpm::ContentChecker->check_element ($el, $onerror);  
559      } else {      } else {
560        Whatpm::ContentChecker->check_document ($doc, $onerror);        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      }      }
     $time2 = time;  
     $time{check} = $time2 - $time1;  
589    }    }
590      
591      my $cssom = $p->parse_char_string ($$s);
592      $cssom->manakai_input_encoding ($charset) if defined $charset;
593    
594    print STDOUT "#log\n";    print STDOUT qq[</dl></div>];
595    for (qw/decode parse parse_xml serialize_html serialize_xml serialize_test  
596            check/) {    return $cssom;
597      next unless defined $time{$_};  } # print_syntax_error_css_section
598      print STDOUT {  
599        decode => 'bytes->chars',  sub print_syntax_error_manifest_section ($$) {
600        parse => 'html5(chars)->dom5',    my ($input, $result) = @_;
601        parse_xml => 'xml1(chars)->dom5',  
602        serialize_html => 'dom5->html5(char)',    require Whatpm::CacheManifest;
603        serialize_xml => 'dom5->xml1(char)',  
604        serialize_test => 'dom5->test(char)',    print STDOUT qq[
605        check => 'dom5 check',  <div id="$input->{id_prefix}parse-errors" class="section">
606      }->{$_};  <h2>Parse Errors</h2>
607      print STDOUT "\t", $time{$_}, "s\n";  
608      open my $file, '>>', ".manakai-$_.txt" or die ".manakai-$_.txt: $!";  <dl>];
609      print $file $char_length, "\t", $time{$_}, "\n";    push @nav, ['#parse-errors' => 'Parse Error'] unless $input->{nested};
610    
611      my $onerror = sub {
612        my (%opt) = @_;
613        my ($type, $cls, $msg) = get_text ($opt{type}, $opt{level});
614        print STDOUT qq[<dt class="$cls">], get_error_label ($input, \%opt),
615            qq[</dt>];
616        $type =~ tr/ /-/;
617        $type =~ s/\|/%7C/g;
618        $msg .= qq[ [<a href="../error-description#@{[htescape ($type)]}">Description</a>]];
619        print STDOUT qq[<dd class="$cls">], get_error_level_label (\%opt);
620        print STDOUT qq[$msg</dd>\n];
621    
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    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    
648  exit;    my $i = 1;                            
649      push @nav, ['#source-string' => 'Source'] unless $input->{nested};
650      print STDOUT qq[<div id="$input->{id_prefix}source-string" class="section">
651    <h2>Document Source</h2>
652    <ol lang="">\n];
653      if (length $$s) {
654        while ($$s =~ /\G([^\x0A]*?)\x0D?\x0A/gc) {
655          print STDOUT qq[<li id="$input->{id_prefix}line-$i">], htescape $1,
656              "</li>\n";
657          $i++;
658        }
659        if ($$s =~ /\G([^\x0A]+)/gc) {
660          print STDOUT qq[<li id="$input->{id_prefix}line-$i">], htescape $1,
661              "</li>\n";
662        }
663      } else {
664        print STDOUT q[<li id="$input->{id_prefix}line-1"></li>];
665      }
666      print STDOUT "</ol></div>";
667    } # print_input_string_section
668    
669  sub test_serialize ($) {  sub print_document_tree ($$) {
670    my $node = shift;    my ($input, $node) = @_;
   my $r = '';  
671    
672    my @node = map { [$_, ''] } @{$node->child_nodes};    my $r = '<ol class="xoxo">';
673    
674      my @node = ($node);
675    while (@node) {    while (@node) {
676      my $child = shift @node;      my $child = shift @node;
677      my $nt = $child->[0]->node_type;      unless (ref $child) {
678      if ($nt == $child->[0]->ELEMENT_NODE) {        $r .= $child;
679        $r .= '| ' . $child->[1] . '<' . $child->[0]->tag_name . ">\x0A"; ## ISSUE: case?        next;
680        }
681        for my $attr (sort {$a->[0] cmp $b->[0]} map { [$_->name, $_->value] }  
682                      @{$child->[0]->attributes}) {      my $node_id = $input->{id_prefix} . 'node-'.refaddr $child;
683          $r .= '| ' . $child->[1] . '  ' . $attr->[0] . '="'; ## ISSUE: case?      my $nt = $child->node_type;
684          $r .= $attr->[1] . '"' . "\x0A";      if ($nt == $child->ELEMENT_NODE) {
685        }        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        unshift @node,            '</code>'; ## ISSUE: case
688          map { [$_, $child->[1] . '  '] } @{$child->[0]->child_nodes};  
689      } elsif ($nt == $child->[0]->TEXT_NODE) {        if ($child->has_attributes) {
690        $r .= '| ' . $child->[1] . '"' . $child->[0]->data . '"' . "\x0A";          $r .= '<ul class="attributes">';
691      } elsif ($nt == $child->[0]->CDATA_SECTION_NODE) {          for my $attr (sort {$a->[0] cmp $b->[0]} map { [$_->name, $_->value, $_->namespace_uri, 'node-'.refaddr $_] }
692        $r .= '| ' . $child->[1] . '<![CDATA[' . $child->[0]->data . "]]>\x0A";                        @{$child->attributes}) {
693      } elsif ($nt == $child->[0]->COMMENT_NODE) {            $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        $r .= '| ' . $child->[1] . '<!-- ' . $child->[0]->data . " -->\x0A";            $r .= '<q>' . htescape ($attr->[1]) . '</q></li>'; ## TODO: children
695      } elsif ($nt == $child->[0]->DOCUMENT_TYPE_NODE) {          }
696        $r .= '| ' . $child->[1] . '<!DOCTYPE ' . $child->[0]->name . ">\x0A";          $r .= '</ul>';
697      } elsif ($nt == $child->[0]->PROCESSING_INSTRUCTION_NODE) {        }
698        $r .= '| ' . $child->[1] . '<?' . $child->[0]->target . ' ' .  
699            $child->[0]->data . "?>\x0A";        if ($child->has_child_nodes) {
700            $r .= '<ol class="children">';
701            unshift @node, @{$child->child_nodes}, '</ol></li>';
702          } else {
703            $r .= '</li>';
704          }
705        } elsif ($nt == $child->TEXT_NODE) {
706          $r .= qq'<li id="$node_id" class="tree-text"><q lang="">' . htescape ($child->data) . '</q></li>';
707        } elsif ($nt == $child->CDATA_SECTION_NODE) {
708          $r .= qq'<li id="$node_id" class="tree-cdata"><code>&lt;[CDATA[</code><q lang="">' . htescape ($child->data) . '</q><code>]]&gt;</code></li>';
709        } elsif ($nt == $child->COMMENT_NODE) {
710          $r .= qq'<li id="$node_id" class="tree-comment"><code>&lt;!--</code><q lang="">' . htescape ($child->data) . '</q><code>--&gt;</code></li>';
711        } elsif ($nt == $child->DOCUMENT_NODE) {
712          $r .= qq'<li id="$node_id" class="tree-document">Document';
713          $r .= qq[<ul class="attributes">];
714          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          $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          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          $r .= qq[</ul>];
741          if ($child->has_child_nodes) {
742            $r .= '<ol class="children">';
743            unshift @node, @{$child->child_nodes}, '</ol></li>';
744          }
745        } elsif ($nt == $child->DOCUMENT_TYPE_NODE) {
746          $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          $r .= '</ul></li>';
751        } elsif ($nt == $child->PROCESSING_INSTRUCTION_NODE) {
752          $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      } else {      } else {
754        $r .= '| ' . $child->[1] . $child->[0]->node_type . "\x0A"; # error        $r .= qq'<li id="$node_id" class="tree-unknown">@{[$child->node_type]} @{[htescape ($child->node_name)]}</li>'; # error
755      }      }
756    }    }
757    
758      $r .= '</ol>';
759      print STDOUT $r;
760    } # print_document_tree
761    
762    sub print_structure_dump_dom_section ($$$) {
763      my ($input, $doc, $el) = @_;
764    
765      print STDOUT qq[
766    <div id="$input->{id_prefix}document-tree" class="section">
767    <h2>Document Tree</h2>
768    ];
769      push @nav, [qq[#$input->{id_prefix}document-tree] => 'Tree']
770          unless $input->{nested};
771    
772      print_document_tree ($input, $el || $doc);
773    
774      print STDOUT qq[</div>];
775    } # print_structure_dump_dom_section
776    
777    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    sub print_structure_dump_manifest_section ($$) {
794      my ($input, $manifest) = @_;
795    
796      print STDOUT qq[
797    <div id="$input->{id_prefix}dump-manifest" class="section">
798    <h2>Cache Manifest</h2>
799    ];
800      push @nav, [qq[#$input->{id_prefix}dump-manifest] => 'Cache Manifest']
801          unless $input->{nested};
802    
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    
825      print STDOUT qq[</dl></div>];
826    } # print_structure_dump_manifest_section
827    
828    sub print_structure_error_dom_section ($$$$$) {
829      my ($input, $doc, $el, $result, $onsubdoc) = @_;
830    
831      print STDOUT qq[<div id="$input->{id_prefix}document-errors" class="section">
832    <h2>Document Errors</h2>
833    
834    <dl>];
835      push @nav, [qq[#$input->{id_prefix}document-errors] => 'Document Error']
836          unless $input->{nested};
837    
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        print STDOUT qq[<dt class="$cls">] . get_error_label ($input, \%opt) .
846            qq[</dt>\n<dd class="$cls">], get_error_level_label (\%opt);
847        print STDOUT $msg, "</dd>\n";
848        add_error ('structure', \%opt => $result);
849      };
850    
851      my $elements;
852      my $time1 = time;
853      if ($el) {
854        $elements = Whatpm::ContentChecker->check_element
855            ($el, $onerror, $onsubdoc);
856      } else {
857        $elements = Whatpm::ContentChecker->check_document
858            ($doc, $onerror, $onsubdoc);
859      }
860      $time{check} = time - $time1;
861    
862      print STDOUT qq[</dl></div>];
863    
864      return $elements;
865    } # print_structure_error_dom_section
866    
867    sub print_structure_error_manifest_section ($$$) {
868      my ($input, $manifest, $result) = @_;
869    
870      print STDOUT qq[<div id="$input->{id_prefix}document-errors" class="section">
871    <h2>Document Errors</h2>
872    
873    <dl>];
874      push @nav, [qq[#$input->{id_prefix}document-errors] => 'Document Error']
875          unless $input->{nested};
876    
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        print STDOUT qq[<dt class="$cls">] . get_error_label ($input, \%opt) .
885            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    
892    sub print_table_section ($$) {
893      my ($input, $tables) = @_;
894      
895      push @nav, [qq[#$input->{id_prefix}tables] => 'Tables']
896          unless $input->{nested};
897      print STDOUT qq[
898    <div id="$input->{id_prefix}tables" class="section">
899    <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        print STDOUT qq[<div class="section" id="$input->{id_prefix}table-$i"><h3>] .
914            get_node_link ($input, $table_el) . q[</h3>];
915    
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        print STDOUT qq[, document.getElementById ('$input->{id_prefix}table-$i')];
947        print STDOUT qq[, '$input->{id_prefix}');</script>];
948      }
949        
950    return \$r;    print STDOUT qq[</div>];
951  } # test_serialize  } # print_table_section
952    
953    sub print_listing_section ($$$) {
954      my ($opt, $input, $ids) = @_;
955      
956      push @nav, ['#' . $input->{id_prefix} . $opt->{id} => $opt->{label}]
957          unless $input->{nested};
958      print STDOUT qq[
959    <div id="$input->{id_prefix}$opt->{id}" class="section">
960    <h2>$opt->{heading}</h2>
961    
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          print STDOUT qq[<dd>].get_node_link ($input, $_).qq[</dd>];
968        }
969      }
970      print STDOUT qq[</dl></div>];
971    } # print_listing_section
972    
973    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      if ($result->{unsupported} and $result->{conforming_max}) {  
981        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            features.  The document might or might not be conforming.</p>];
985      } 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    <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    
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      my $score_unit = $score_base / 100;
1015      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        $score_min += (($result->{$_->[1]}->{score_min} *= $score_unit) += $score_base);
1025        $score_max += (($result->{$_->[1]}->{score_max} *= $score_unit) += $score_base);
1026    
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          print qq[<td class="@{[$result->{$_->[1]}->{must} ? 'FAIL' : $result->{$_->[1]}->{should} ? 'SEE-RESULT' : '']}">&#x2212;&#x221E;..$result->{$_->[1]}->{score_max}</td>];
1039        } elsif ($result->{$_->[1]}->{score_min} != $result->{$_->[1]}->{score_max}) {
1040          print qq[<td class="@{[$result->{$_->[1]}->{must} ? 'FAIL' : 'SEE-RESULT']}">$result->{$_->[1]}->{score_min}..$result->{$_->[1]}->{score_max}</td></tr>];
1041        } else {
1042          print qq[<td class="@{[$result->{$_->[1]}->{must} ? 'FAIL' : '']}">$result->{$_->[1]}->{score_min}</td></tr>];
1043        }
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    <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    </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    sub print_result_unknown_type_section ($$) {
1065      my ($input, $result) = @_;
1066    
1067      my $euri = htescape ($input->{uri});
1068      print STDOUT qq[
1069    <div id="$input->{id_prefix}parse-errors" class="section">
1070    <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    </div>
1081    ];
1082      push @nav, [qq[#$input->{id_prefix}parse-errors] => 'Errors']
1083          unless $input->{nested};
1084      add_error (char => {level => 'u'} => $result);
1085      add_error (syntax => {level => 'u'} => $result);
1086      add_error (structure => {level => 'u'} => $result);
1087    } # 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    } # print_result_input_error_section
1096    
1097    sub get_error_label ($$) {
1098      my ($input, $err) = @_;
1099    
1100      my $r = '';
1101    
1102      if (defined $err->{line}) {
1103        if ($err->{column} > 0) {
1104          $r = qq[<a href="#$input->{id_prefix}line-$err->{line}">Line $err->{line}</a> column $err->{column}];
1105        } else {
1106          $err->{line} = $err->{line} - 1 || 1;
1107          $r = qq[<a href="#$input->{id_prefix}line-$err->{line}">Line $err->{line}</a>];
1108        }
1109      }
1110    
1111      if (defined $err->{node}) {
1112        $r .= ' ' if length $r;
1113        $r = get_node_link ($input, $err->{node});
1114      }
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    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      } elsif ($err->{level} eq 'u' or $err->{level} eq 'unsupported') {
1144        $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  sub get_node_path ($) {  sub get_node_path ($) {
1156    my $node = shift;    my $node = shift;
# Line 212  sub get_node_path ($) { Line 1167  sub get_node_path ($) {
1167        $rs = '"' . $node->data . '"';        $rs = '"' . $node->data . '"';
1168        $node = $node->parent_node;        $node = $node->parent_node;
1169      } elsif ($node->node_type == 9) {      } elsif ($node->node_type == 9) {
1170          @r = ('') unless @r;
1171        $rs = '';        $rs = '';
1172        $node = $node->parent_node;        $node = $node->parent_node;
1173      } else {      } else {
# Line 223  sub get_node_path ($) { Line 1179  sub get_node_path ($) {
1179    return join '/', @r;    return join '/', @r;
1180  } # get_node_path  } # get_node_path
1181    
1182    sub get_node_link ($$) {
1183      return qq[<a href="#$_[0]->{id_prefix}node-@{[refaddr $_[1]]}">] .
1184          htescape (get_node_path ($_[1])) . qq[</a>];
1185    } # get_node_link
1186    
1187    {
1188      my $Msg = {};
1189    
1190    sub load_text_catalog ($) {
1191      my $lang = shift; # MUST be a canonical lang name
1192      open my $file, '<:utf8', "cc-msg.$lang.txt"
1193          or die "$0: cc-msg.$lang.txt: $!";
1194      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      my ($type, $level, $node) = @_;
1205      $type = $level . ':' . $type if defined $level;
1206      $level = 'm' unless defined $level;
1207      my @arg;
1208      {
1209        if (defined $Msg->{$type}) {
1210          my $msg = $Msg->{$type}->[1];
1211          $msg =~ s{<var>\$([0-9]+)</var>}{
1212            defined $arg[$1] ? htescape ($arg[$1]) : '(undef)';
1213          }ge;
1214          $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          $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          return ($type, 'level-' . $level . ' ' . $Msg->{$type}->[0], $msg);
1232        } elsif ($type =~ s/:([^:]*)$//) {
1233          unshift @arg, $1;
1234          redo;
1235        }
1236      }
1237      return ($type, 'level-'.$level, htescape ($_[0]));
1238    } # get_text
1239    
1240    }
1241    
1242    sub get_input_document ($$) {
1243      my ($http, $dom) = @_;
1244    
1245      my $request_uri = $http->get_parameter ('uri');
1246      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        $req->header ('Accept-Encoding' => 'identity, *; q=0');
1294        my $res = $ua->request ($req);
1295        ## TODO: 401 sets |is_success| true.
1296        if ($res->is_success or $http->get_parameter ('error-page')) {
1297          $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          if (defined $ct and $ct =~ /;\s*charset\s*=\s*"?([^\s;"]+)"?/i) {
1304            $r->{charset} = lc $1;
1305            $r->{charset} =~ tr/\\//d;
1306            $r->{official_charset} = $r->{charset};
1307          }
1308    
1309          my $input_charset = $http->get_parameter ('charset');
1310          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          }
1315    
1316          ## TODO: Support for HTTP Content-Encoding
1317    
1318          $r->{s} = ''.$res->content;
1319    
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        } 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        $r->{s} = ''.$http->get_parameter ('s');
1344        $r->{uri} = q<thismessage:/>;
1345        $r->{request_uri} = q<thismessage:/>;
1346        $r->{base_uri} = q<thismessage:/>;
1347        $r->{charset} = ''.$http->get_parameter ('_charset_');
1348        $r->{charset} =~ s/\s+//g;
1349        $r->{charset} = 'utf-8' if $r->{charset} eq '';
1350        $r->{official_charset} = $r->{charset};
1351        $r->{header_field} = [];
1352    
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      }
1363    
1364      my $input_format = $http->get_parameter ('i');
1365      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          $r->{official_charset} = $r->{charset};
1379        } 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      $r->{inner_html_element} = $http->get_parameter ('e');
1391    
1392      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  =head1 AUTHOR  =head1 AUTHOR
1419    
1420  Wakaba <w@suika.fam.cx>.  Wakaba <w@suika.fam.cx>.
1421    
1422  =head1 LICENSE  =head1 LICENSE
1423    
1424  Copyright 2007 Wakaba <w@suika.fam.cx>  Copyright 2007-2008 Wakaba <w@suika.fam.cx>
1425    
1426  This library is free software; you can redistribute it  This library is free software; you can redistribute it
1427  and/or modify it under the same terms as Perl itself.  and/or modify it under the same terms as Perl itself.

Legend:
Removed from v.1.1  
changed lines
  Added in v.1.35

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24