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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24