/[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 - (hide 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 wakaba 1.1 #!/usr/bin/perl
2     use strict;
3 wakaba 1.23 use utf8;
4 wakaba 1.1
5     use lib qw[/home/httpd/html/www/markup/html/whatpm
6 wakaba 1.16 /home/wakaba/work/manakai2/lib];
7 wakaba 1.1 use CGI::Carp qw[fatalsToBrowser];
8 wakaba 1.2 use Scalar::Util qw[refaddr];
9 wakaba 1.16 use Time::HiRes qw/time/;
10 wakaba 1.1
11 wakaba 1.2 sub htescape ($) {
12     my $s = $_[0];
13     $s =~ s/&/&amp;/g;
14     $s =~ s/</&lt;/g;
15     $s =~ s/>/&gt;/g;
16     $s =~ s/"/&quot;/g;
17 wakaba 1.12 $s =~ s{([\x00-\x09\x0B-\x1F\x7F-\xA0\x{FEFF}\x{FFFC}-\x{FFFF}])}{
18     sprintf '<var>U+%04X</var>', ord $1;
19     }ge;
20 wakaba 1.2 return $s;
21     } # htescape
22    
23 wakaba 1.35 my @nav;
24     my %time;
25     require Message::DOM::DOMImplementation;
26     my $dom = Message::DOM::DOMImplementation->new;
27     {
28 wakaba 1.16 use Message::CGI::HTTP;
29     my $http = Message::CGI::HTTP->new;
30 wakaba 1.1
31 wakaba 1.16 if ($http->get_meta_variable ('PATH_INFO') ne '/') {
32 wakaba 1.8 print STDOUT "Status: 404 Not Found\nContent-Type: text/plain; charset=us-ascii\n\n400";
33     exit;
34     }
35    
36 wakaba 1.12 binmode STDOUT, ':utf8';
37 wakaba 1.14 $| = 1;
38 wakaba 1.12
39 wakaba 1.7 load_text_catalog ('en'); ## TODO: conneg
40    
41 wakaba 1.2 print STDOUT qq[Content-Type: text/html; charset=utf-8
42    
43     <!DOCTYPE html>
44     <html lang="en">
45     <head>
46     <title>Web Document Conformance Checker (BETA)</title>
47 wakaba 1.3 <link rel="stylesheet" href="../cc-style.css" type="text/css">
48 wakaba 1.2 </head>
49     <body>
50 wakaba 1.13 <h1><a href="../cc-interface">Web Document Conformance Checker</a>
51     (<em>beta</em>)</h1>
52 wakaba 1.14 ];
53 wakaba 1.2
54 wakaba 1.14 $| = 0;
55     my $input = get_input_document ($http, $dom);
56 wakaba 1.16 my $char_length = 0;
57 wakaba 1.14
58     print qq[
59 wakaba 1.4 <div id="document-info" class="section">
60 wakaba 1.2 <dl>
61 wakaba 1.9 <dt>Request URI</dt>
62     <dd><code class="URI" lang="">&lt;<a href="@{[htescape $input->{request_uri}]}">@{[htescape $input->{request_uri}]}</a>&gt;</code></dd>
63 wakaba 1.2 <dt>Document URI</dt>
64 wakaba 1.25 <dd><code class="URI" lang="">&lt;<a href="@{[htescape $input->{uri}]}" id=anchor-document-uri>@{[htescape $input->{uri}]}</a>&gt;</code>
65     <script>
66     document.title = '<'
67     + document.getElementById ('anchor-document-uri').href + '> \\u2014 '
68     + document.title;
69     </script></dd>
70 wakaba 1.2 ]; # no </dl> yet
71 wakaba 1.3 push @nav, ['#document-info' => 'Information'];
72 wakaba 1.1
73 wakaba 1.9 if (defined $input->{s}) {
74 wakaba 1.16 $char_length = length $input->{s};
75 wakaba 1.9
76     print STDOUT qq[
77     <dt>Base URI</dt>
78     <dd><code class="URI" lang="">&lt;<a href="@{[htescape $input->{base_uri}]}">@{[htescape $input->{base_uri}]}</a>&gt;</code></dd>
79     <dt>Internet Media Type</dt>
80     <dd><code class="MIME" lang="en">@{[htescape $input->{media_type}]}</code>
81 wakaba 1.25 @{[$input->{media_type_overridden} ? '<em>(overridden)</em>' : defined $input->{official_type} ? $input->{media_type} eq $input->{official_type} ? '' : '<em>(sniffed; official type is: <code class=MIME lang=en>'.htescape ($input->{official_type}).'</code>)' : '<em>(sniffed)</em>']}</dd>
82 wakaba 1.9 <dt>Character Encoding</dt>
83     <dd>@{[defined $input->{charset} ? '<code class="charset" lang="en">'.htescape ($input->{charset}).'</code>' : '(none)']}
84     @{[$input->{charset_overridden} ? '<em>(overridden)</em>' : '']}</dd>
85 wakaba 1.16 <dt>Length</dt>
86     <dd>$char_length byte@{[$char_length == 1 ? '' : 's']}</dd>
87 wakaba 1.9 </dl>
88     </div>
89 wakaba 1.39
90     <script src="../cc-script.js"></script>
91 wakaba 1.9 ];
92    
93 wakaba 1.35 $input->{id_prefix} = '';
94     #$input->{nested} = 0;
95 wakaba 1.20 my $result = {conforming_min => 1, conforming_max => 1};
96 wakaba 1.31 check_and_print ($input => $result);
97 wakaba 1.19 print_result_section ($result);
98 wakaba 1.9 } else {
99 wakaba 1.18 print STDOUT qq[</dl></div>];
100     print_result_input_error_section ($input);
101 wakaba 1.9 }
102 wakaba 1.3
103 wakaba 1.2 print STDOUT qq[
104 wakaba 1.3 <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 wakaba 1.2 </body>
112     </html>
113     ];
114 wakaba 1.1
115 wakaba 1.24 for (qw/decode parse parse_html parse_xml parse_manifest
116     check check_manifest/) {
117 wakaba 1.16 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 wakaba 1.1 exit;
123 wakaba 1.35 }
124 wakaba 1.1
125 wakaba 1.19 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 wakaba 1.30 } elsif ($err->{level} eq 'u' or $err->{level} eq 'unsupported') {
135 wakaba 1.19 $result->{$layer}->{unsupported}++;
136     $result->{unsupported} = 1;
137 wakaba 1.37 } elsif ($err->{level} eq 'i') {
138     #
139 wakaba 1.19 } 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 wakaba 1.31 sub check_and_print ($$) {
156     my ($input, $result) = @_;
157    
158     print_http_header_section ($input, $result);
159    
160     my $doc;
161     my $el;
162 wakaba 1.35 my $cssom;
163 wakaba 1.31 my $manifest;
164 wakaba 1.52 my $idl;
165 wakaba 1.34 my @subdoc;
166 wakaba 1.31
167     if ($input->{media_type} eq 'text/html') {
168     ($doc, $el) = print_syntax_error_html_section ($input, $result);
169     print_source_string_section
170 wakaba 1.35 ($input,
171     \($input->{s}),
172     $input->{charset} || $doc->input_encoding);
173 wakaba 1.31 } elsif ({
174     'text/xml' => 1,
175     'application/atom+xml' => 1,
176     'application/rss+xml' => 1,
177 wakaba 1.45 'image/svg+xml' => 1,
178 wakaba 1.31 'application/xhtml+xml' => 1,
179     'application/xml' => 1,
180 wakaba 1.45 ## 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 wakaba 1.31 }->{$input->{media_type}}) {
185     ($doc, $el) = print_syntax_error_xml_section ($input, $result);
186 wakaba 1.35 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 wakaba 1.31 } 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 wakaba 1.35 print_source_string_section ($input, \($input->{s}),
198     'utf-8');
199 wakaba 1.52 } 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 wakaba 1.31 } 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 wakaba 1.34 $doc->document_uri ($input->{uri});
210     $doc->manakai_entity_base_uri ($input->{base_uri});
211 wakaba 1.32 print_structure_dump_dom_section ($input, $doc, $el);
212     my $elements = print_structure_error_dom_section
213 wakaba 1.34 ($input, $doc, $el, $result, sub {
214     push @subdoc, shift;
215     });
216 wakaba 1.32 print_table_section ($input, $elements->{table}) if @{$elements->{table}};
217 wakaba 1.33 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 wakaba 1.48 print_uri_section ($input, $elements->{uri}) if keys %{$elements->{uri}};
227 wakaba 1.45 print_rdf_section ($input, $elements->{rdf}) if @{$elements->{rdf}};
228 wakaba 1.35 } elsif (defined $cssom) {
229     print_structure_dump_cssom_section ($input, $cssom);
230     ## TODO: CSSOM validation
231 wakaba 1.36 add_error ('structure', {level => 'u'} => $result);
232 wakaba 1.31 } elsif (defined $manifest) {
233 wakaba 1.32 print_structure_dump_manifest_section ($input, $manifest);
234     print_structure_error_manifest_section ($input, $manifest, $result);
235 wakaba 1.52 } elsif (defined $idl) {
236     print_structure_dump_webidl_section ($input, $idl);
237     print_structure_error_webidl_section ($input, $idl, $result);
238 wakaba 1.31 }
239 wakaba 1.34
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 wakaba 1.35 $subinput->{id_prefix} .= '-';
261 wakaba 1.34 check_and_print ($subinput => $result);
262    
263     print STDOUT qq[</div>];
264     }
265 wakaba 1.31 } # check_and_print
266    
267 wakaba 1.19 sub print_http_header_section ($$) {
268     my ($input, $result) = @_;
269 wakaba 1.9 return unless defined $input->{header_status_code} or
270     defined $input->{header_status_text} or
271 wakaba 1.34 @{$input->{header_field} or []};
272 wakaba 1.9
273 wakaba 1.32 push @nav, ['#source-header' => 'HTTP Header'] unless $input->{nested};
274     print STDOUT qq[<div id="$input->{id_prefix}source-header" class="section">
275 wakaba 1.9 <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 wakaba 1.19 sub print_syntax_error_html_section ($$) {
302     my ($input, $result) = @_;
303 wakaba 1.18
304     require Encode;
305     require Whatpm::HTML;
306    
307     print STDOUT qq[
308 wakaba 1.32 <div id="$input->{id_prefix}parse-errors" class="section">
309 wakaba 1.18 <h2>Parse Errors</h2>
310    
311 wakaba 1.39 <dl id="$input->{id_prefix}parse-errors-list">];
312 wakaba 1.32 push @nav, ['#parse-errors' => 'Parse Error'] unless $input->{nested};
313 wakaba 1.18
314     my $onerror = sub {
315     my (%opt) = @_;
316     my ($type, $cls, $msg) = get_text ($opt{type}, $opt{level});
317 wakaba 1.38 print STDOUT qq[<dt class="$cls">], get_error_label ($input, \%opt),
318     qq[</dt>];
319 wakaba 1.18 $type =~ tr/ /-/;
320     $type =~ s/\|/%7C/g;
321     $msg .= qq[ [<a href="../error-description#@{[htescape ($type)]}">Description</a>]];
322 wakaba 1.23 print STDOUT qq[<dd class="$cls">], get_error_level_label (\%opt);
323     print STDOUT qq[$msg</dd>\n];
324 wakaba 1.19
325     add_error ('syntax', \%opt => $result);
326 wakaba 1.18 };
327    
328     my $doc = $dom->create_document;
329     my $el;
330 wakaba 1.35 my $inner_html_element = $input->{inner_html_element};
331 wakaba 1.18 if (defined $inner_html_element and length $inner_html_element) {
332 wakaba 1.26 $input->{charset} ||= 'windows-1252'; ## TODO: for now.
333 wakaba 1.24 my $time1 = time;
334 wakaba 1.48 my $t = \($input->{s});
335     unless ($input->{is_char_string}) {
336     $t = \(Encode::decode ($input->{charset}, $$t));
337     }
338 wakaba 1.24 $time{decode} = time - $time1;
339    
340 wakaba 1.18 $el = $doc->create_element_ns
341     ('http://www.w3.org/1999/xhtml', [undef, $inner_html_element]);
342 wakaba 1.24 $time1 = time;
343 wakaba 1.48 Whatpm::HTML->set_inner_html ($el, $$t, $onerror);
344 wakaba 1.24 $time{parse} = time - $time1;
345 wakaba 1.18 } else {
346 wakaba 1.24 my $time1 = time;
347 wakaba 1.48 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 wakaba 1.24 $time{parse_html} = time - $time1;
354 wakaba 1.18 }
355 wakaba 1.26 $doc->manakai_charset ($input->{official_charset})
356     if defined $input->{official_charset};
357 wakaba 1.24
358 wakaba 1.18 print STDOUT qq[</dl></div>];
359    
360     return ($doc, $el);
361     } # print_syntax_error_html_section
362    
363 wakaba 1.19 sub print_syntax_error_xml_section ($$) {
364     my ($input, $result) = @_;
365 wakaba 1.18
366     require Message::DOM::XMLParserTemp;
367    
368     print STDOUT qq[
369 wakaba 1.32 <div id="$input->{id_prefix}parse-errors" class="section">
370 wakaba 1.18 <h2>Parse Errors</h2>
371    
372 wakaba 1.39 <dl id="$input->{id_prefix}parse-errors-list">];
373 wakaba 1.32 push @nav, ['#parse-errors' => 'Parse Error'] unless $input->{prefix};
374 wakaba 1.18
375     my $onerror = sub {
376     my $err = shift;
377     my $line = $err->location->line_number;
378 wakaba 1.35 print STDOUT qq[<dt><a href="#$input->{id_prefix}line-$line">Line $line</a> column ];
379 wakaba 1.18 print STDOUT $err->location->column_number, "</dt><dd>";
380     print STDOUT htescape $err->text, "</dd>\n";
381 wakaba 1.19
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 wakaba 1.18 return 1;
390     };
391    
392 wakaba 1.48 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 wakaba 1.18 my $time1 = time;
400 wakaba 1.48 open my $fh, '<', $t;
401 wakaba 1.18 my $doc = Message::DOM::XMLParserTemp->parse_byte_stream
402     ($fh => $dom, $onerror, charset => $input->{charset});
403     $time{parse_xml} = time - $time1;
404 wakaba 1.26 $doc->manakai_charset ($input->{official_charset})
405     if defined $input->{official_charset};
406 wakaba 1.18
407     print STDOUT qq[</dl></div>];
408    
409     return ($doc, undef);
410     } # print_syntax_error_xml_section
411    
412 wakaba 1.35 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 wakaba 1.37 alignment-baseline
421 wakaba 1.35 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 wakaba 1.37 cursor direction display dominant-baseline empty-cells float font
433 wakaba 1.35 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 wakaba 1.37 text-align text-anchor text-decoration text-indent text-transform
445 wakaba 1.35 top unicode-bidi vertical-align visibility white-space width widows
446 wakaba 1.37 word-spacing writing-mode z-index
447 wakaba 1.35 /;
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 wakaba 1.36 normal pre nowrap pre-line pre-wrap -moz-pre-wrap
522 wakaba 1.35 /;
523 wakaba 1.37 $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 wakaba 1.35 $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 wakaba 1.36 $p->{prop_value}->{'border-collapse'}->{collapse} = 1;
547 wakaba 1.35 $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 wakaba 1.39 <dl id="$input->{id_prefix}parse-errors-list">];
591 wakaba 1.35 push @nav, ['#parse-errors' => 'Parse Error'] unless $input->{nested};
592    
593     my $p = get_css_parser ();
594 wakaba 1.37 $p->init;
595 wakaba 1.35 $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 wakaba 1.37 # 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 wakaba 1.35 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 wakaba 1.22 sub print_syntax_error_manifest_section ($$) {
648     my ($input, $result) = @_;
649    
650     require Whatpm::CacheManifest;
651    
652     print STDOUT qq[
653 wakaba 1.32 <div id="$input->{id_prefix}parse-errors" class="section">
654 wakaba 1.22 <h2>Parse Errors</h2>
655    
656 wakaba 1.39 <dl id="$input->{id_prefix}parse-errors-list">];
657 wakaba 1.32 push @nav, ['#parse-errors' => 'Parse Error'] unless $input->{nested};
658 wakaba 1.22
659     my $onerror = sub {
660     my (%opt) = @_;
661     my ($type, $cls, $msg) = get_text ($opt{type}, $opt{level});
662 wakaba 1.32 print STDOUT qq[<dt class="$cls">], get_error_label ($input, \%opt),
663     qq[</dt>];
664 wakaba 1.22 $type =~ tr/ /-/;
665     $type =~ s/\|/%7C/g;
666     $msg .= qq[ [<a href="../error-description#@{[htescape ($type)]}">Description</a>]];
667 wakaba 1.23 print STDOUT qq[<dd class="$cls">], get_error_level_label (\%opt);
668     print STDOUT qq[$msg</dd>\n];
669 wakaba 1.22
670     add_error ('syntax', \%opt => $result);
671     };
672    
673 wakaba 1.48 my $m = $input->{is_char_string} ? 'parse_char_string' : 'parse_byte_string';
674 wakaba 1.22 my $time1 = time;
675 wakaba 1.48 my $manifest = Whatpm::CacheManifest->$m
676 wakaba 1.22 ($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 wakaba 1.52 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 wakaba 1.35 sub print_source_string_section ($$$) {
721     my $input = shift;
722     my $s;
723     unless ($input->{is_char_string}) {
724 wakaba 1.51 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 wakaba 1.35
738 wakaba 1.51 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 wakaba 1.35 } else {
747     $s = $_[0];
748     }
749 wakaba 1.9
750     my $i = 1;
751 wakaba 1.32 push @nav, ['#source-string' => 'Source'] unless $input->{nested};
752     print STDOUT qq[<div id="$input->{id_prefix}source-string" class="section">
753 wakaba 1.9 <h2>Document Source</h2>
754     <ol lang="">\n];
755 wakaba 1.7 if (length $$s) {
756 wakaba 1.41 while ($$s =~ /\G([^\x0D\x0A]*?)(?>\x0D\x0A?|\x0A)/gc) {
757 wakaba 1.32 print STDOUT qq[<li id="$input->{id_prefix}line-$i">], htescape $1,
758     "</li>\n";
759 wakaba 1.7 $i++;
760     }
761 wakaba 1.41 if ($$s =~ /\G([^\x0D\x0A]+)/gc) {
762 wakaba 1.32 print STDOUT qq[<li id="$input->{id_prefix}line-$i">], htescape $1,
763     "</li>\n";
764 wakaba 1.7 }
765     } else {
766 wakaba 1.32 print STDOUT q[<li id="$input->{id_prefix}line-1"></li>];
767 wakaba 1.2 }
768 wakaba 1.39 print STDOUT "</ol></div>
769 wakaba 1.42 <script>
770     addSourceToParseErrorList ('$input->{id_prefix}', 'parse-errors-list');
771     </script>";
772 wakaba 1.9 } # print_input_string_section
773 wakaba 1.2
774 wakaba 1.35 sub print_document_tree ($$) {
775     my ($input, $node) = @_;
776    
777 wakaba 1.2 my $r = '<ol class="xoxo">';
778 wakaba 1.1
779 wakaba 1.2 my @node = ($node);
780 wakaba 1.1 while (@node) {
781     my $child = shift @node;
782 wakaba 1.2 unless (ref $child) {
783     $r .= $child;
784     next;
785     }
786    
787 wakaba 1.32 my $node_id = $input->{id_prefix} . 'node-'.refaddr $child;
788 wakaba 1.2 my $nt = $child->node_type;
789     if ($nt == $child->ELEMENT_NODE) {
790 wakaba 1.4 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 wakaba 1.2 '</code>'; ## ISSUE: case
793    
794     if ($child->has_attributes) {
795     $r .= '<ul class="attributes">';
796 wakaba 1.4 for my $attr (sort {$a->[0] cmp $b->[0]} map { [$_->name, $_->value, $_->namespace_uri, 'node-'.refaddr $_] }
797 wakaba 1.2 @{$child->attributes}) {
798 wakaba 1.32 $r .= qq[<li id="$input->{id_prefix}$attr->[3]" class="tree-attribute"><code title="@{[defined $attr->[2] ? htescape ($attr->[2]) : '']}">] . htescape ($attr->[0]) . '</code> = '; ## ISSUE: case?
799 wakaba 1.2 $r .= '<q>' . htescape ($attr->[1]) . '</q></li>'; ## TODO: children
800     }
801     $r .= '</ul>';
802     }
803    
804 wakaba 1.7 if ($child->has_child_nodes) {
805 wakaba 1.2 $r .= '<ol class="children">';
806 wakaba 1.6 unshift @node, @{$child->child_nodes}, '</ol></li>';
807     } else {
808     $r .= '</li>';
809 wakaba 1.2 }
810     } elsif ($nt == $child->TEXT_NODE) {
811 wakaba 1.4 $r .= qq'<li id="$node_id" class="tree-text"><q lang="">' . htescape ($child->data) . '</q></li>';
812 wakaba 1.2 } elsif ($nt == $child->CDATA_SECTION_NODE) {
813 wakaba 1.4 $r .= qq'<li id="$node_id" class="tree-cdata"><code>&lt;[CDATA[</code><q lang="">' . htescape ($child->data) . '</q><code>]]&gt;</code></li>';
814 wakaba 1.2 } elsif ($nt == $child->COMMENT_NODE) {
815 wakaba 1.4 $r .= qq'<li id="$node_id" class="tree-comment"><code>&lt;!--</code><q lang="">' . htescape ($child->data) . '</q><code>--&gt;</code></li>';
816 wakaba 1.2 } elsif ($nt == $child->DOCUMENT_NODE) {
817 wakaba 1.6 $r .= qq'<li id="$node_id" class="tree-document">Document';
818 wakaba 1.7 $r .= qq[<ul class="attributes">];
819 wakaba 1.27 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 wakaba 1.7 $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 wakaba 1.9 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 wakaba 1.7 $r .= qq[</ul>];
846 wakaba 1.2 if ($child->has_child_nodes) {
847 wakaba 1.7 $r .= '<ol class="children">';
848 wakaba 1.6 unshift @node, @{$child->child_nodes}, '</ol></li>';
849 wakaba 1.1 }
850 wakaba 1.2 } elsif ($nt == $child->DOCUMENT_TYPE_NODE) {
851 wakaba 1.5 $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 wakaba 1.2 $r .= '</ul></li>';
856     } elsif ($nt == $child->PROCESSING_INSTRUCTION_NODE) {
857 wakaba 1.4 $r .= qq'<li id="$node_id" class="tree-id"><code>&lt;?@{[htescape ($child->target)]}</code> <q>@{[htescape ($child->data)]}</q><code>?&gt;</code></li>';
858 wakaba 1.1 } else {
859 wakaba 1.4 $r .= qq'<li id="$node_id" class="tree-unknown">@{[$child->node_type]} @{[htescape ($child->node_name)]}</li>'; # error
860 wakaba 1.1 }
861     }
862 wakaba 1.2
863     $r .= '</ol>';
864     print STDOUT $r;
865     } # print_document_tree
866 wakaba 1.1
867 wakaba 1.32 sub print_structure_dump_dom_section ($$$) {
868     my ($input, $doc, $el) = @_;
869 wakaba 1.18
870     print STDOUT qq[
871 wakaba 1.32 <div id="$input->{id_prefix}document-tree" class="section">
872 wakaba 1.18 <h2>Document Tree</h2>
873     ];
874 wakaba 1.35 push @nav, [qq[#$input->{id_prefix}document-tree] => 'Tree']
875     unless $input->{nested};
876 wakaba 1.18
877 wakaba 1.35 print_document_tree ($input, $el || $doc);
878 wakaba 1.18
879     print STDOUT qq[</div>];
880 wakaba 1.22 } # print_structure_dump_dom_section
881    
882 wakaba 1.35 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 wakaba 1.32 sub print_structure_dump_manifest_section ($$) {
899     my ($input, $manifest) = @_;
900 wakaba 1.22
901     print STDOUT qq[
902 wakaba 1.32 <div id="$input->{id_prefix}dump-manifest" class="section">
903 wakaba 1.22 <h2>Cache Manifest</h2>
904     ];
905 wakaba 1.35 push @nav, [qq[#$input->{id_prefix}dump-manifest] => 'Cache Manifest']
906     unless $input->{nested};
907 wakaba 1.22
908     print STDOUT qq[<dl><dt>Explicit entries</dt>];
909 wakaba 1.37 my $i = 0;
910 wakaba 1.22 for my $uri (@{$manifest->[0]}) {
911     my $euri = htescape ($uri);
912 wakaba 1.37 print STDOUT qq[<dd id="$input->{id_prefix}index-@{[$i++]}"><code class=uri>&lt;<a href="$euri">$euri</a>></code></dd>];
913 wakaba 1.22 }
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 wakaba 1.37 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 wakaba 1.22 }
924    
925     print STDOUT qq[</table><dt>Online whitelist</dt>];
926     for my $uri (@{$manifest->[2]}) {
927     my $euri = htescape ($uri);
928 wakaba 1.37 print STDOUT qq[<dd id="$input->{id_prefix}index-@{[$i++]}"><code class=uri>&lt;<a href="$euri">$euri</a>></code></dd>];
929 wakaba 1.22 }
930 wakaba 1.18
931 wakaba 1.22 print STDOUT qq[</dl></div>];
932     } # print_structure_dump_manifest_section
933    
934 wakaba 1.52 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 wakaba 1.34 sub print_structure_error_dom_section ($$$$$) {
952     my ($input, $doc, $el, $result, $onsubdoc) = @_;
953 wakaba 1.18
954 wakaba 1.32 print STDOUT qq[<div id="$input->{id_prefix}document-errors" class="section">
955 wakaba 1.18 <h2>Document Errors</h2>
956    
957 wakaba 1.42 <dl id=document-errors-list>];
958 wakaba 1.35 push @nav, [qq[#$input->{id_prefix}document-errors] => 'Document Error']
959     unless $input->{nested};
960 wakaba 1.18
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 wakaba 1.32 print STDOUT qq[<dt class="$cls">] . get_error_label ($input, \%opt) .
969 wakaba 1.23 qq[</dt>\n<dd class="$cls">], get_error_level_label (\%opt);
970     print STDOUT $msg, "</dd>\n";
971 wakaba 1.19 add_error ('structure', \%opt => $result);
972 wakaba 1.18 };
973    
974     my $elements;
975     my $time1 = time;
976     if ($el) {
977 wakaba 1.34 $elements = Whatpm::ContentChecker->check_element
978     ($el, $onerror, $onsubdoc);
979 wakaba 1.18 } else {
980 wakaba 1.34 $elements = Whatpm::ContentChecker->check_document
981     ($doc, $onerror, $onsubdoc);
982 wakaba 1.18 }
983     $time{check} = time - $time1;
984    
985 wakaba 1.42 print STDOUT qq[</dl>
986     <script>
987     addSourceToParseErrorList ('$input->{id_prefix}', 'document-errors-list');
988     </script></div>];
989 wakaba 1.18
990     return $elements;
991 wakaba 1.22 } # print_structure_error_dom_section
992    
993     sub print_structure_error_manifest_section ($$$) {
994 wakaba 1.32 my ($input, $manifest, $result) = @_;
995 wakaba 1.22
996 wakaba 1.32 print STDOUT qq[<div id="$input->{id_prefix}document-errors" class="section">
997 wakaba 1.22 <h2>Document Errors</h2>
998    
999     <dl>];
1000 wakaba 1.35 push @nav, [qq[#$input->{id_prefix}document-errors] => 'Document Error']
1001     unless $input->{nested};
1002 wakaba 1.22
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 wakaba 1.32 print STDOUT qq[<dt class="$cls">] . get_error_label ($input, \%opt) .
1011 wakaba 1.22 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 wakaba 1.18
1018 wakaba 1.52 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 wakaba 1.32 sub print_table_section ($$) {
1034     my ($input, $tables) = @_;
1035 wakaba 1.18
1036 wakaba 1.35 push @nav, [qq[#$input->{id_prefix}tables] => 'Tables']
1037     unless $input->{nested};
1038 wakaba 1.18 print STDOUT qq[
1039 wakaba 1.32 <div id="$input->{id_prefix}tables" class="section">
1040 wakaba 1.18 <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 wakaba 1.50 for my $table (@$tables) {
1053 wakaba 1.18 $i++;
1054 wakaba 1.32 print STDOUT qq[<div class="section" id="$input->{id_prefix}table-$i"><h3>] .
1055 wakaba 1.50 get_node_link ($input, $table->{element}) . q[</h3>];
1056    
1057     delete $table->{element};
1058 wakaba 1.18
1059 wakaba 1.49 for (@{$table->{column_group}}, @{$table->{column}}, $table->{caption},
1060     @{$table->{row}}) {
1061 wakaba 1.18 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 wakaba 1.32 print STDOUT qq[, document.getElementById ('$input->{id_prefix}table-$i')];
1087     print STDOUT qq[, '$input->{id_prefix}');</script>];
1088 wakaba 1.18 }
1089    
1090     print STDOUT qq[</div>];
1091     } # print_table_section
1092    
1093 wakaba 1.33 sub print_listing_section ($$$) {
1094     my ($opt, $input, $ids) = @_;
1095 wakaba 1.18
1096 wakaba 1.35 push @nav, ['#' . $input->{id_prefix} . $opt->{id} => $opt->{label}]
1097     unless $input->{nested};
1098 wakaba 1.18 print STDOUT qq[
1099 wakaba 1.33 <div id="$input->{id_prefix}$opt->{id}" class="section">
1100     <h2>$opt->{heading}</h2>
1101 wakaba 1.18
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 wakaba 1.32 print STDOUT qq[<dd>].get_node_link ($input, $_).qq[</dd>];
1108 wakaba 1.18 }
1109     }
1110     print STDOUT qq[</dl></div>];
1111 wakaba 1.33 } # print_listing_section
1112 wakaba 1.18
1113 wakaba 1.48 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 wakaba 1.45 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 wakaba 1.46 if (defined $resource->{uri}) {
1189 wakaba 1.45 my $euri = htescape ($resource->{uri});
1190     return '<code class=uri>&lt;<a href="' . $euri . '">' . $euri .
1191     '</a>></code>';
1192 wakaba 1.46 } elsif (defined $resource->{bnodeid}) {
1193 wakaba 1.45 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 wakaba 1.19 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 wakaba 1.21 if ($result->{unsupported} and $result->{conforming_max}) {
1221 wakaba 1.19 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 wakaba 1.21 features. The document might or might not be conforming.</p>];
1225 wakaba 1.19 } 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 wakaba 1.23 <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 wakaba 1.19
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 wakaba 1.21 my $score_unit = $score_base / 100;
1255 wakaba 1.19 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 wakaba 1.21 $score_min += (($result->{$_->[1]}->{score_min} *= $score_unit) += $score_base);
1265     $score_max += (($result->{$_->[1]}->{score_max} *= $score_unit) += $score_base);
1266 wakaba 1.19
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 wakaba 1.51 print qq[<td class="@{[$result->{$_->[1]}->{must} ? 'FAIL' : $result->{$_->[1]}->{should} ? 'SEE-RESULT' : '']}">&#x2212;&#x221E;..$result->{$_->[1]}->{score_max}];
1279 wakaba 1.19 } elsif ($result->{$_->[1]}->{score_min} != $result->{$_->[1]}->{score_max}) {
1280 wakaba 1.51 print qq[<td class="@{[$result->{$_->[1]}->{must} ? 'FAIL' : 'SEE-RESULT']}">$result->{$_->[1]}->{score_min}..$result->{$_->[1]}->{score_max}];
1281 wakaba 1.19 } else {
1282 wakaba 1.51 print qq[<td class="@{[$result->{$_->[1]}->{must} ? 'FAIL' : '']}">$result->{$_->[1]}->{score_min}];
1283 wakaba 1.19 }
1284 wakaba 1.51 print qq[ / 20];
1285 wakaba 1.19 }
1286    
1287     $score_max += $score_base;
1288    
1289     print STDOUT qq[
1290 wakaba 1.51 <tr class=uncertain><th scope=row>Semantics</th><td>0?</td><td>0?</td><td>0?</td><td>&#x2212;&#x221E;..$score_base / 20
1291 wakaba 1.19 </tbody>
1292 wakaba 1.21 <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 wakaba 1.51 <td class="@{[$must_error ? 'FAIL' : $should_error ? 'SEE-RESULT' : '']}"><strong>&#x2212;&#x221E;..$score_max</strong> / 100
1297 wakaba 1.19 </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 wakaba 1.24 sub print_result_unknown_type_section ($$) {
1306     my ($input, $result) = @_;
1307 wakaba 1.18
1308 wakaba 1.24 my $euri = htescape ($input->{uri});
1309 wakaba 1.18 print STDOUT qq[
1310 wakaba 1.35 <div id="$input->{id_prefix}parse-errors" class="section">
1311 wakaba 1.24 <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 wakaba 1.18 </div>
1322     ];
1323 wakaba 1.35 push @nav, [qq[#$input->{id_prefix}parse-errors] => 'Errors']
1324     unless $input->{nested};
1325 wakaba 1.30 add_error (char => {level => 'u'} => $result);
1326     add_error (syntax => {level => 'u'} => $result);
1327     add_error (structure => {level => 'u'} => $result);
1328 wakaba 1.18 } # 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 wakaba 1.32 } # print_result_input_error_section
1337 wakaba 1.18
1338 wakaba 1.32 sub get_error_label ($$) {
1339     my ($input, $err) = @_;
1340 wakaba 1.22
1341     my $r = '';
1342    
1343 wakaba 1.42 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 wakaba 1.40 } else {
1351 wakaba 1.42 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 wakaba 1.43 } else {
1356     my $parent = $err->{node}->parent_node;
1357 wakaba 1.44 if ($parent) {
1358     $line = $parent->get_user_data ('manakai_source_line');
1359     $column = $parent->get_user_data ('manakai_source_column');
1360     }
1361 wakaba 1.42 }
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 wakaba 1.40 }
1372 wakaba 1.42 }
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 wakaba 1.22 } else {
1378 wakaba 1.42 $line = $line - 1 || 1;
1379     $r = qq[<a href="#$input->{id_prefix}line-$line">Line $line</a>];
1380 wakaba 1.22 }
1381     }
1382    
1383     if (defined $err->{node}) {
1384     $r .= ' ' if length $r;
1385 wakaba 1.42 $r .= get_node_link ($input, $err->{node});
1386 wakaba 1.22 }
1387    
1388     if (defined $err->{index}) {
1389 wakaba 1.37 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 wakaba 1.22 }
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 wakaba 1.23 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 wakaba 1.30 } elsif ($err->{level} eq 'u' or $err->{level} eq 'unsupported') {
1420 wakaba 1.23 $r = qq[<strong><a href="../error-description#level-u">Not
1421     supported</a></strong>: ];
1422 wakaba 1.37 } elsif ($err->{level} eq 'i') {
1423     $r = qq[<strong><a href="../error-description#level-i">Information</a></strong>: ];
1424 wakaba 1.23 } 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 wakaba 1.1 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 wakaba 1.47 $rs = $node->node_name;
1440 wakaba 1.1 $node = $node->parent_node;
1441     } elsif ($node->node_type == 2) {
1442 wakaba 1.47 $rs = '@' . $node->node_name;
1443 wakaba 1.1 $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 wakaba 1.9 @r = ('') unless @r;
1449 wakaba 1.1 $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 wakaba 1.32 sub get_node_link ($$) {
1461     return qq[<a href="#$_[0]->{id_prefix}node-@{[refaddr $_[1]]}">] .
1462     htescape (get_node_path ($_[1])) . qq[</a>];
1463 wakaba 1.6 } # get_node_link
1464    
1465 wakaba 1.7 {
1466     my $Msg = {};
1467    
1468     sub load_text_catalog ($) {
1469     my $lang = shift; # MUST be a canonical lang name
1470 wakaba 1.26 open my $file, '<:utf8', "cc-msg.$lang.txt"
1471     or die "$0: cc-msg.$lang.txt: $!";
1472 wakaba 1.7 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 wakaba 1.15 my ($type, $level, $node) = @_;
1483 wakaba 1.7 $type = $level . ':' . $type if defined $level;
1484 wakaba 1.29 $level = 'm' unless defined $level;
1485 wakaba 1.7 my @arg;
1486     {
1487     if (defined $Msg->{$type}) {
1488     my $msg = $Msg->{$type}->[1];
1489 wakaba 1.10 $msg =~ s{<var>\$([0-9]+)</var>}{
1490     defined $arg[$1] ? htescape ($arg[$1]) : '(undef)';
1491     }ge;
1492 wakaba 1.15 $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 wakaba 1.17 $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 wakaba 1.29 return ($type, 'level-' . $level . ' ' . $Msg->{$type}->[0], $msg);
1510 wakaba 1.7 } elsif ($type =~ s/:([^:]*)$//) {
1511     unshift @arg, $1;
1512     redo;
1513     }
1514     }
1515 wakaba 1.29 return ($type, 'level-'.$level, htescape ($_[0]));
1516 wakaba 1.7 } # get_text
1517    
1518     }
1519    
1520 wakaba 1.48 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 wakaba 1.9 sub get_input_document ($$) {
1532     my ($http, $dom) = @_;
1533    
1534 wakaba 1.16 my $request_uri = $http->get_parameter ('uri');
1535 wakaba 1.9 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 wakaba 1.28 $req->header ('Accept-Encoding' => 'identity, *; q=0');
1583 wakaba 1.9 my $res = $ua->request ($req);
1584 wakaba 1.16 ## TODO: 401 sets |is_success| true.
1585     if ($res->is_success or $http->get_parameter ('error-page')) {
1586 wakaba 1.9 $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 wakaba 1.22 if (defined $ct and $ct =~ /;\s*charset\s*=\s*"?([^\s;"]+)"?/i) {
1593 wakaba 1.9 $r->{charset} = lc $1;
1594     $r->{charset} =~ tr/\\//d;
1595 wakaba 1.26 $r->{official_charset} = $r->{charset};
1596 wakaba 1.9 }
1597    
1598 wakaba 1.16 my $input_charset = $http->get_parameter ('charset');
1599 wakaba 1.9 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 wakaba 1.25 }
1604    
1605     ## TODO: Support for HTTP Content-Encoding
1606 wakaba 1.9
1607     $r->{s} = ''.$res->content;
1608 wakaba 1.25
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 wakaba 1.9 } 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 wakaba 1.16 $r->{s} = ''.$http->get_parameter ('s');
1633 wakaba 1.9 $r->{uri} = q<thismessage:/>;
1634     $r->{request_uri} = q<thismessage:/>;
1635     $r->{base_uri} = q<thismessage:/>;
1636 wakaba 1.16 $r->{charset} = ''.$http->get_parameter ('_charset_');
1637 wakaba 1.9 $r->{charset} =~ s/\s+//g;
1638     $r->{charset} = 'utf-8' if $r->{charset} eq '';
1639 wakaba 1.26 $r->{official_charset} = $r->{charset};
1640 wakaba 1.9 $r->{header_field} = [];
1641 wakaba 1.25
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 wakaba 1.9 }
1652    
1653 wakaba 1.16 my $input_format = $http->get_parameter ('i');
1654 wakaba 1.9 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 wakaba 1.26 $r->{official_charset} = $r->{charset};
1668 wakaba 1.9 } 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 wakaba 1.35 $r->{inner_html_element} = $http->get_parameter ('e');
1680    
1681 wakaba 1.9 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 wakaba 1.1 =head1 AUTHOR
1708    
1709     Wakaba <w@suika.fam.cx>.
1710    
1711     =head1 LICENSE
1712    
1713 wakaba 1.35 Copyright 2007-2008 Wakaba <w@suika.fam.cx>
1714 wakaba 1.1
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 wakaba 1.52 ## $Date: 2008/05/18 03:47:56 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24