/[suikacvs]/test/html-webhacc/WebHACC/Output.pm
Suika

Contents of /test/html-webhacc/WebHACC/Output.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.15 - (show annotations) (download)
Thu Aug 14 15:50:42 2008 UTC (16 years, 3 months ago) by wakaba
Branch: MAIN
Changes since 1.14: +11 -0 lines
++ ChangeLog	14 Aug 2008 15:42:17 -0000
	* cc.cgi: Generate result summary sections for
	each subdocument.

	* error-description-source.xml: New entries to
	support localization of result sections.

2008-08-14  Wakaba  <wakaba@suika.fam.cx>

	* cc-style.css: Support for revised version of result summary
	section styling.

2008-08-14  Wakaba  <wakaba@suika.fam.cx>

++ html/WebHACC/Language/ChangeLog	14 Aug 2008 15:50:38 -0000
	* Base.pm, CSS.pm, CacheManifest.pm, DOM.pm, Default.pm,
	HTML.pm, WebIDL.pm, XML.pm: Set |layer_applicable|
	or |layer_uncertain| flag appropriately.

2008-08-14  Wakaba  <wakaba@suika.fam.cx>

++ html/WebHACC/ChangeLog	14 Aug 2008 15:48:38 -0000
	* Input.pm: Methods |generate_transfer_sections|
	and |generate_http_header_section| are moved to HTTP
	subclass, since they are irrelevant to non-HTTP inputs.
	(_get_document): Forbidden host error was not represented
	by WebHACC::Input::Error subclass.
	(WebHACC::Input::Error generate_transfer_sections): Use
	role name for the section.
	(WebHACC::Input::HTTPError generate_transfer_sections): New method
	added, since the main superclass, i.e. WebHACC::Input::Error,
	no longer dumps HTTP headers due to the change mentioned above.

	* Output.pm (start_section): New roles "transfer-errors" and "result".

	* Result.pm (parent_result): New attribute.
	(layer_applicable, layer_uncertain): New methods to set flags.
	(add_error): Natural language strings are now handled
	by the catalog mechanism.  Use new scoring mechanism.
	(generate_result_section): Use catalog for all natural
	language strings.  Table generation is now much more sophiscated
	that it was.  Support for subdoc result summary.  Support
	for the column of the number of informational message.  Support
	for "N/A" status.

2008-08-14  Wakaba  <wakaba@suika.fam.cx>

1 package WebHACC::Output;
2 use strict;
3
4 require IO::Handle;
5 use Scalar::Util qw/refaddr/;
6
7 my $htescape = sub ($) {
8 my $s = $_[0];
9 $s =~ s/&/&amp;/g;
10 $s =~ s/</&lt;/g;
11 $s =~ s/>/&gt;/g;
12 $s =~ s/"/&quot;/g;
13 $s =~ s{([\x00-\x09\x0B-\x1F\x7F-\xA0\x{FEFF}\x{FFFC}-\x{FFFF}])}{
14 sprintf '<var>U+%04X</var>', ord $1;
15 }ge;
16 return $s;
17 };
18
19 my $htescape_value = sub ($) {
20 my $s = $_[0];
21 $s =~ s/&/&amp;/g;
22 $s =~ s/</&lt;/g;
23 $s =~ s/>/&gt;/g;
24 $s =~ s/"/&quot;/g;
25 return $s;
26 };
27
28 sub new ($) {
29 require WebHACC::Input;
30 return bless {nav => [], section_rank => 1,
31 input => WebHACC::Input->new}, shift;
32 } # new
33
34 sub input ($;$) {
35 if (@_ > 1) {
36 if (defined $_[1]) {
37 $_[0]->{input} = $_[1];
38 } else {
39 $_[0]->{input} = WebHACC::Input->new;
40 }
41 }
42
43 return $_[0]->{input};
44 } # input
45
46 sub handle ($;$) {
47 if (@_ > 1) {
48 if (defined $_[1]) {
49 $_[0]->{handle} = $_[1];
50 } else {
51 delete $_[0]->{handle};
52 }
53 }
54
55 return $_[0]->{handle};
56 } # handle
57
58 sub set_utf8 ($) {
59 binmode shift->{handle}, ':utf8';
60 } # set_utf8
61
62 sub set_flush ($) {
63 shift->{handle}->autoflush (1);
64 } # set_flush
65
66 sub unset_flush ($) {
67 shift->{handle}->autoflush (0);
68 } # unset_flush
69
70 sub html ($$) {
71 shift->{handle}->print (shift);
72 } # html
73
74 sub text ($$) {
75 shift->html ($htescape->(shift));
76 } # text
77
78 sub url ($$%) {
79 my ($self, $url, %opt) = @_;
80 $self->html (q[<code class=uri>&lt;]);
81 $self->link ($url, %opt, url => $url);
82 $self->html (q[></code>]);
83 } # url
84
85 sub start_tag ($$%) {
86 my ($self, $tag_name, %opt) = @_;
87 $self->html ('<' . $htescape_value->($tag_name)); # escape for safety
88 if (exists $opt{id}) {
89 my $id = $self->input->id_prefix . $opt{id};
90 $self->html (' id="' . $htescape_value->($id) . '"');
91 delete $opt{id};
92 }
93 for (keys %opt) { # for safety
94 $self->html (' ' . $htescape_value->($_) . '="' .
95 $htescape_value->($opt{$_}) . '"');
96 }
97 $self->html ('>');
98 } # start_tag
99
100 sub end_tag ($$) {
101 shift->html ('</' . $htescape_value->(shift) . '>');
102 } # end_tag
103
104 sub start_section ($%) {
105 my ($self, %opt) = @_;
106
107 my $class = 'section';
108 if (defined $opt{role}) {
109 if ($opt{role} eq 'parse-errors') {
110 $opt{id} ||= 'parse-errors';
111 $opt{title} ||= 'Parse Errors Section';
112 $opt{short_title} ||= 'Parse Errors';
113 $class .= ' errors';
114 delete $opt{role};
115 } elsif ($opt{role} eq 'structure-errors') {
116 $opt{id} ||= 'document-errors';
117 $opt{title} ||= 'Structural Errors';
118 $opt{short_title} ||= 'Struct. Errors';
119 $class .= ' errors';
120 delete $opt{role};
121 } elsif ($opt{role} eq 'transfer-errors') {
122 $opt{id} ||= 'transfer-errors';
123 $opt{title} ||= 'Transfer Errors';
124 $opt{short_title} ||= 'Trans. Errors';
125 $class .= ' errors';
126 delete $opt{role};
127 } elsif ($opt{role} eq 'reformatted') {
128 $opt{id} ||= 'document-tree';
129 $opt{title} ||= 'Reformatted Document Source';
130 $opt{short_title} ||= 'Reformatted';
131 $class .= ' dump';
132 delete $opt{role}
133 } elsif ($opt{role} eq 'tree') {
134 $opt{id} ||= 'document-tree';
135 $opt{title} ||= 'Document Tree';
136 $opt{short_title} ||= 'Tree';
137 $class .= ' dump';
138 delete $opt{role};
139 } elsif ($opt{role} eq 'structure') {
140 $opt{id} ||= 'document-structure';
141 $opt{title} ||= 'Document Structure';
142 $opt{short_title} ||= 'Structure';
143 $class .= ' dump';
144 delete $opt{role};
145 } elsif ($opt{role} eq 'subdoc') {
146 $class .= ' subdoc';
147 delete $opt{role};
148 } elsif ($opt{role} eq 'source') {
149 $opt{id} ||= 'source-string';
150 $opt{title} ||= 'Document Source';
151 $opt{short_title} ||= 'Source';
152 $class .= ' source';
153 delete $opt{role};
154 } elsif ($opt{role} eq 'result') {
155 $opt{id} ||= 'result-summary';
156 $opt{title} ||= 'Result';
157 $class .= ' result';
158 delete $opt{role};
159 }
160 }
161
162 $self->{section_rank}++;
163 $self->html (qq[<div class="$class"]);
164 if (defined $opt{id}) {
165 my $prefix = $self->input->id_prefix;
166 $opt{parent_id} ||= $prefix;
167 my $id = $prefix . $opt{id};
168 $self->html (' id="' . $htescape->($id) . '">');
169 if ($self->{section_rank} == 2 or length $opt{parent_id}) {
170 my $st = $opt{short_title} || $opt{title};
171 push @{$self->{nav}},
172 [$id => $st => $opt{text}];
173
174 $self->start_tag ('script');
175 $self->html (qq[ addSectionLink ('$id', ']);
176 $self->nl_text ($st, text => $opt{text});
177 if (defined $opt{parent_id}) {
178 $self->html (q[', '] . $opt{parent_id});
179 }
180 $self->html (q[') ]);
181 $self->end_tag ('script');
182 }
183 } else {
184 $self->html ('>');
185 }
186 my $section_rank = $self->{section_rank};
187 $section_rank = 6 if $section_rank > 6;
188 $self->html ('<h' . $section_rank . '>');
189 $self->nl_text ($opt{title}, text => $opt{text});
190 $self->html ('</h' . $section_rank . '>');
191 } # start_section
192
193 sub end_section ($) {
194 my $self = shift;
195 $self->html ('</div>');
196 $self->{handle}->flush;
197 $self->{section_rank}--;
198 } # end_section
199
200 sub start_error_list ($%) {
201 my ($self, %opt) = @_;
202
203 if (defined $opt{role}) {
204 if ($opt{role} eq 'parse-errors') {
205 $opt{id} ||= 'parse-errors-list';
206 delete $opt{role};
207 } elsif ($opt{role} eq 'structure-errors') {
208 $opt{id} ||= 'document-errors-list';
209 delete $opt{role};
210 }
211 }
212
213 $self->start_tag ('dl', %opt);
214 } # start_error_list
215
216 sub end_error_list ($%) {
217 my ($self, %opt) = @_;
218
219 if (defined $opt{role}) {
220 if ($opt{role} eq 'parse-errors') {
221 delete $opt{role};
222 $self->end_tag ('dl');
223 ## NOTE: For parse error list, the |add_source_to_parse_error_list|
224 ## method is invoked at the end of |generate_source_string_section|,
225 ## since that generation method is invoked after the error list
226 ## is generated.
227 } elsif ($opt{role} eq 'structure-errors') {
228 delete $opt{role};
229 $self->end_tag ('dl');
230 $self->add_source_to_parse_error_list ('document-errors-list');
231 } else {
232 $self->end_tag ('dl');
233 }
234 } else {
235 $self->end_tag ('dl');
236 }
237 } # end_error_list
238
239 sub add_source_to_parse_error_list ($$) {
240 my $self = shift;
241
242 $self->script (q[addSourceToParseErrorList ('] . $self->input->id_prefix .
243 q[', '] . shift () . q[')]);
244 } # add_source_to_parse_error_list
245
246 sub start_code_block ($) {
247 shift->html ('<pre><code>');
248 } # start_code_block
249
250 sub end_code_block ($) {
251 shift->html ('</code></pre>');
252 } # end_code_block
253
254 sub code ($$;%) {
255 my ($self, $content, %opt) = @_;
256 $self->start_tag ('code', %opt);
257 $self->text ($content);
258 $self->html ('</code>');
259 } # code
260
261 sub script ($$;%) {
262 my ($self, $content, %opt) = @_;
263 $self->start_tag ('script', %opt);
264 $self->html ($content);
265 $self->html ('</script>');
266 } # script
267
268 sub dt ($$;%) {
269 my ($self, $content, %opt) = @_;
270 $self->start_tag ('dt', %opt);
271 $self->nl_text ($content, text => $opt{text});
272 } # dt
273
274 sub select ($$%) {
275 my ($self, $options, %opt) = @_;
276
277 my $selected = $opt{selected};
278 delete $opt{selected};
279
280 $self->start_tag ('select', %opt);
281
282 my @options = @$options;
283 while (@options) {
284 my $opt = shift @options;
285 if ($opt->{options}) {
286 $self->html ('<optgroup label="');
287 $self->nl_text ($opt->{label});
288 $self->html ('">');
289 unshift @options, @{$opt->{options}}, {end_options => 1};
290 } elsif ($opt->{end_options}) {
291 $self->end_tag ('optgroup');
292 } else {
293 $self->start_tag ('option', value => $opt->{value},
294 ((defined $selected and $opt->{value} eq $selected)
295 ? (selected => '') : ()));
296 $self->nl_text (defined $opt->{label} ? $opt->{label} : $opt->{value});
297 }
298 }
299
300 $self->end_tag ('select');
301 } # select
302
303 sub link ($$%) {
304 my ($self, $content, %opt) = @_;
305 $self->start_tag ('a', %opt, href => $opt{url});
306 $self->text ($content);
307 $self->html ('</a>');
308 } # link
309
310 sub xref ($$%) {
311 my ($self, $content, %opt) = @_;
312 $self->html ('<a href="#' . $htescape->($self->input->id_prefix . $opt{target}) . '">');
313 $self->nl_text ($content, text => $opt{text});
314 $self->html ('</a>');
315 } # xref
316
317 sub link_to_webhacc ($$%) {
318 my ($self, $content, %opt) = @_;
319 $opt{url} = './?uri=' . $self->encode_url_component ($opt{url});
320 $self->link ($content, %opt);
321 } # link_to_webhacc
322
323 my $get_node_path = sub ($) {
324 my $node = shift;
325 my @r;
326 while (defined $node) {
327 my $rs;
328 if ($node->node_type == 1) {
329 $rs = $node->node_name;
330 $node = $node->parent_node;
331 } elsif ($node->node_type == 2) {
332 $rs = '@' . $node->node_name;
333 $node = $node->owner_element;
334 } elsif ($node->node_type == 3) {
335 $rs = '"' . $node->data . '"';
336 $node = $node->parent_node;
337 } elsif ($node->node_type == 9) {
338 @r = ('') unless @r;
339 $rs = '';
340 $node = $node->parent_node;
341 } else {
342 $rs = '#' . $node->node_type;
343 $node = $node->parent_node;
344 }
345 unshift @r, $rs;
346 }
347 return join '/', @r;
348 }; # $get_node_path
349
350 my $get_object_path = sub ($) {
351 my $node = shift;
352 my @r;
353 while (defined $node) {
354 my $ref = ref $node;
355 $ref =~ /([^:]+)$/;
356 my $rs = $1;
357 my $node_name = $node->node_name;
358 if (defined $node_name) {
359 $rs .= ' <code>' . $htescape->($node_name) . '</code>';
360 }
361 $node = undef;
362 unshift @r, $rs;
363 }
364 return join '/', @r;
365 }; # $get_object_path
366
367 sub node_link ($$) {
368 my ($self, $node) = @_;
369 if ($node->isa ('Message::IF::Node')) {
370 $self->xref ($get_node_path->($node), target => 'node-' . refaddr $node);
371 } else {
372 $self->html ($get_object_path->($node));
373 }
374 } # node_link
375
376 {
377 my $Msg = {};
378
379 sub load_text_catalog ($$) {
380 my $self = shift;
381
382 my $lang = shift; # MUST be a canonical lang name
383 my $file_name = qq[cc-msg.$lang.txt];
384 $lang = 'en' unless -f $file_name;
385 $self->{primary_language} = $lang;
386
387 open my $file, '<:utf8', $file_name or die "$0: $file_name: $!";
388 while (<$file>) {
389 if (s/^([^;]+);([^;]*);//) {
390 my ($type, $cls, $msg) = ($1, $2, $_);
391 $msg =~ tr/\x0D\x0A//d;
392 $Msg->{$type} = [$cls, $msg];
393 }
394 }
395 } # load_text_catalog
396
397 sub nl_text ($$;%) {
398 my ($self, $type, %opt) = @_;
399 my $node = $opt{node};
400
401 my @arg;
402 {
403 if (defined $Msg->{$type}) {
404 my $msg = $Msg->{$type}->[1];
405 if ($msg =~ /<var>/) {
406 $msg =~ s{<var>\$([0-9]+)</var>}{
407 defined $arg[$1] ? $htescape->($arg[$1]) : '(undef)';
408 }ge;
409 $msg =~ s{<var>{\@([A-Za-z0-9:_.-]+)}</var>}{
410 UNIVERSAL::can ($node, 'get_attribute_ns')
411 ? $htescape->($node->get_attribute_ns (undef, $1)) : ''
412 }ge;
413 $msg =~ s{<var>{\@}</var>}{
414 UNIVERSAL::can ($node, 'value') ? $htescape->($node->value) : ''
415 }ge;
416 $msg =~ s{<var>{text}</var>}{
417 defined $opt{text} ? $htescape->($opt{text}) : ''
418 }ge;
419 $msg =~ s{<var>{local-name}</var>}{
420 UNIVERSAL::can ($node, 'manakai_local_name')
421 ? $htescape->($node->manakai_local_name) : ''
422 }ge;
423 $msg =~ s{<var>{element-local-name}</var>}{
424 (UNIVERSAL::can ($node, 'owner_element') and
425 $node->owner_element)
426 ? $htescape->($node->owner_element->manakai_local_name) : ''
427 }ge;
428 }
429 $self->html ($msg);
430 return;
431 } elsif ($type =~ s/:([^:]*)$//) {
432 unshift @arg, $1;
433 redo;
434 }
435 }
436 $self->text ($type);
437 } # nl_text
438
439 }
440
441 sub nav_list ($) {
442 my $self = shift;
443 $self->html (q[<ul class="navigation" id="nav-items">]);
444 for (@{$self->{nav}}) {
445 $self->html (qq[<li><a href="#@{[$htescape->($_->[0])]}">]);
446 $self->nl_text ($_->[1], text => $_->[2]);
447 $self->html ('</a>');
448 }
449 $self->html ('</ul>');
450 } # nav_list
451
452 sub http_header ($) {
453 shift->html (qq[Content-Type: text/html; charset=utf-8\n\n]);
454 } # http_header
455
456 sub http_error ($$) {
457 my $self = shift;
458 my $code = 0+shift;
459 my $text = {
460 404 => 'Not Found',
461 }->{$code};
462 $self->html (qq[Status: $code $text\nContent-Type: text/html ; charset=us-ascii\n\n$code $text]);
463 } # http_error
464
465 sub html_header ($) {
466 my $self = shift;
467 $self->html (q[<!DOCTYPE html>]);
468 $self->start_tag ('html', lang => $self->{primary_language});
469 $self->html (q[<head><title>]);
470 $self->nl_text (q[WebHACC:Title]);
471 $self->html (q[</title>
472 <link rel="stylesheet" href="../cc-style.css" type="text/css">
473 <script src="../cc-script.js"></script>
474 </head>
475 <body onclick=" return onbodyclick (event) " onload=" onbodyload () ">
476 <h1>]);
477 $self->nl_text (q[WebHACC:Heading]);
478 $self->html (q[</h1><script> insertNavSections () </script>]);
479 } # html_header
480
481 sub generate_input_section ($$) {
482 my ($out, $cgi) = @_;
483
484 my $options = sub ($) {
485 my $context = shift;
486
487 $out->html (q[<div class="details default"><p class=legend onclick="nextSibling.style.display = nextSibling.style.display == 'block' ? 'none' : 'block'; parentNode.className = nextSibling.style.display == 'none' ? 'details' : 'details open'">]);
488 $out->nl_text (q[Options]);
489 $out->start_tag ('div');
490
491 if ($context eq 'url') {
492 $out->start_tag ('p');
493 $out->start_tag ('label');
494 $out->start_tag ('input', type => 'checkbox', name => 'error-page',
495 value => 1,
496 ($cgi->get_parameter ('error-page')
497 ? (checked => '') : ()));
498 $out->nl_text ('Check error page');
499 $out->end_tag ('label');
500 }
501
502 $out->start_tag ('p');
503 $out->start_tag ('label');
504 $out->nl_text (q[Content type]);
505 $out->text (': ');
506 $out->select ([
507 {value => '', label => 'As specified'},
508 {value => 'application/atom+xml'},
509 {value => 'application/xhtml+xml'},
510 {value => 'application/xml'},
511 {value => 'text/html'},
512 {value => 'text/xml'},
513 {value => 'text/css'},
514 {value => 'text/cache-manifest'},
515 {value => 'text/x-webidl'},
516 ], name => 'i', selected => scalar $cgi->get_parameter ('i'));
517 $out->end_tag ('label');
518
519 if ($context ne 'text') {
520 $out->start_tag ('p');
521 $out->start_tag ('label');
522 $out->nl_text (q[Charset]);
523 $out->text (q[: ]);
524 $out->select ([
525 {value => '', label => 'As specified'},
526 {label => 'Japanese charsets', options => [
527 {value => 'Windows-31J'},
528 {value => 'Shift_JIS'},
529 {value => 'EUC-JP'},
530 {value => 'ISO-2022-JP'},
531 ]},
532 {label => 'European charsets', options => [
533 {value => 'Windows-1252'},
534 {value => 'ISO-8859-1'},
535 {value => 'US-ASCII'},
536 ]},
537 {label => 'Asian charsets', options => [
538 {value => 'Windows-874'},
539 {value => 'ISO-8859-11'},
540 {value => 'TIS-620'},
541 ]},
542 {label => 'Unicode charsets', options => [
543 {value => 'UTF-8'},
544 {value => 'UTF-8n'},
545 ]},
546 ], name => 'charset',
547 selected => scalar $cgi->get_parameter ('charset'));
548 $out->end_tag ('label');
549 }
550
551 if ($context eq 'text') {
552 $out->start_tag ('p');
553 $out->start_tag ('label');
554 $out->nl_text ('Setting innerHTML');
555 $out->text (': ');
556 $out->start_tag ('input', name => 'e',
557 value => scalar $cgi->get_parameter ('e'));
558 $out->end_tag ('label');
559 }
560
561 $out->html (q[</div></div>]);
562 }; # $options
563
564 $out->start_section (id => 'input', title => 'Input');
565 $out->html (q[<script> insertNavSections ('input') </script>]);
566
567 $out->start_section (id => 'input-url', title => 'By URL',
568 parent_id => 'input');
569 $out->start_tag ('form', action => './#result-summary',
570 'accept-charset' => 'utf-8',
571 method => 'get');
572 $out->start_tag ('input', type => 'hidden', name => '_charset_');
573
574 $out->start_tag ('p');
575 $out->start_tag ('label');
576 $out->nl_text ('URL');
577 $out->text (': ');
578 $out->start_tag ('input',
579 name => 'uri',
580 type => 'url',
581 value => $cgi->get_parameter ('uri'));
582 $out->end_tag ('label');
583
584 $out->start_tag ('p');
585 $out->start_tag ('button', type => 'submit');
586 $out->nl_text ('Check');
587 $out->end_tag ('button');
588
589 $options->('url');
590
591 $out->end_tag ('form');
592 $out->end_section;
593
594 $out->end_tag ('fieldset');
595
596 ## TODO: File upload
597
598 $out->start_section (id => 'input-text', title => 'By direct input',
599 parent_id => 'input');
600 $out->start_tag ('form', action => './#result-summary',
601 'accept-charset' => 'utf-8',
602 method => 'post');
603 $out->start_tag ('input', type => 'hidden', name => '_charset_');
604
605 $out->start_tag ('p');
606 $out->start_tag ('label');
607 $out->nl_text ('Document source to check');
608 $out->text (': ');
609 $out->start_tag ('br');
610 $out->start_tag ('textarea',
611 name => 's');
612 my $s = $cgi->get_parameter ('s');
613 $out->html ($htescape_value->($s)) if defined $s;
614 $out->end_tag ('textarea');
615 $out->end_tag ('label');
616
617 $out->start_tag ('p');
618 $out->start_tag ('button', type => 'submit',
619 onclick => 'form.method = form.s.value.length > 512 ? "post" : "get"');
620 $out->nl_text ('Check');
621 $out->end_tag ('button');
622
623 $options->('text');
624
625 $out->end_tag ('form');
626 $out->end_section;
627
628 $out->script (q[
629 if (!document.webhaccNavigated &&
630 document.getElementsByTagName ('textarea')[0].value.length > 0) {
631 showTab ('input-text');
632 document.webhaccNavigated = false;
633 }
634 ]);
635
636 $out->end_section;
637 } # generate_input_section
638
639 sub encode_url_component ($$) {
640 shift;
641 require Encode;
642 my $s = Encode::encode ('utf8', shift);
643 $s =~ s/([^0-9A-Za-z_.~-])/sprintf '%%%02X', ord $1/ge;
644 return $s;
645 } # encode_url_component
646
647 1;

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24