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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.52 - (show annotations) (download)
Fri Jul 18 14:44:16 2008 UTC (15 years, 10 months ago) by wakaba
Branch: MAIN
Changes since 1.51: +77 -1 lines
++ ChangeLog	18 Jul 2008 14:44:11 -0000
2008-07-18  Wakaba  <wakaba@suika.fam.cx>

	* cc.cgi (print_structure_dump_webidl_section): Use ->idl_text
	for dummping (Data::Dumper::Dumper no longer used).

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24