File Coverage

blib/lib/Perl/Tidy/HtmlWriter.pm
Criterion Covered Total %
statement 385 737 52.2
branch 64 292 21.9
condition 10 57 17.5
subroutine 33 44 75.0
pod 0 21 0.0
total 492 1151 42.7


line stmt bran cond sub pod time code
1             #####################################################################
2             #
3             # The Perl::Tidy::HtmlWriter class writes a copy of the input stream in html
4             #
5             #####################################################################
6              
7             package Perl::Tidy::HtmlWriter;
8 39     39   336 use strict;
  39         95  
  39         1735  
9 39     39   273 use warnings;
  39         115  
  39         2285  
10             our $VERSION = '20230909';
11              
12 39     39   273 use English qw( -no_match_vars );
  39         109  
  39         463  
13 39     39   17579 use File::Basename;
  39         153  
  39         5268  
14              
15 39     39   321 use constant EMPTY_STRING => q{};
  39         107  
  39         3174  
16 39     39   268 use constant SPACE => q{ };
  39         118  
  39         7731  
17              
18             # class variables
19             my (
20              
21             # INITIALIZER: BEGIN block
22             $missing_html_entities,
23             $missing_pod_html,
24              
25             # INITIALIZER: BEGIN block
26             %short_to_long_names,
27             %token_short_names,
28              
29             # INITIALIZER: sub check_options
30             $rOpts,
31             $rOpts_html_entities,
32             $css_linkname,
33             %html_bold,
34             %html_color,
35             %html_italic,
36              
37             );
38              
39             # replace unsafe characters with HTML entity representation if HTML::Entities
40             # is available
41             #{ eval "use HTML::Entities"; $missing_html_entities = $@; }
42              
43             BEGIN {
44              
45 39     39   215 $missing_html_entities = EMPTY_STRING;
46 39 50       154 if ( !eval { require HTML::Entities; 1 } ) {
  39         23055  
  39         249408  
47 0 0       0 $missing_html_entities = $EVAL_ERROR ? $EVAL_ERROR : 1;
48             }
49              
50 39         115 $missing_pod_html = EMPTY_STRING;
51 39 50       85 if ( !eval { require Pod::Html; 1 } ) {
  39         23024  
  39         2655411  
52 0 0       0 $missing_pod_html = $EVAL_ERROR ? $EVAL_ERROR : 1;
53             }
54             } ## end BEGIN
55              
56             sub AUTOLOAD {
57              
58             # Catch any undefined sub calls so that we are sure to get
59             # some diagnostic information. This sub should never be called
60             # except for a programming error.
61 0     0   0 our $AUTOLOAD;
62 0 0       0 return if ( $AUTOLOAD =~ /\bDESTROY$/ );
63 0         0 my ( $pkg, $fname, $lno ) = caller();
64 0         0 my $my_package = __PACKAGE__;
65 0         0 print {*STDERR} <<EOM;
  0         0  
66             ======================================================================
67             Error detected in package '$my_package', version $VERSION
68             Received unexpected AUTOLOAD call for sub '$AUTOLOAD'
69             Called from package: '$pkg'
70             Called from File '$fname' at line '$lno'
71             This error is probably due to a recent programming change
72             ======================================================================
73             EOM
74 0         0 exit 1;
75             } ## end sub AUTOLOAD
76              
77       0     sub DESTROY {
78              
79             # required to avoid call to AUTOLOAD in some versions of perl
80             }
81              
82             sub new {
83              
84 1     1 0 6 my ( $class, @args ) = @_;
85              
86 1         13 my %defaults = (
87             input_file => undef,
88             html_file => undef,
89             extension => undef,
90             html_toc_extension => undef,
91             html_src_extension => undef,
92             );
93 1         7 my %args = ( %defaults, @args );
94              
95 1         4 my $input_file = $args{input_file};
96 1         7 my $html_file = $args{html_file};
97 1         3 my $extension = $args{extension};
98 1         6 my $html_toc_extension = $args{html_toc_extension};
99 1         4 my $html_src_extension = $args{html_src_extension};
100              
101 1         4 my $html_file_opened = 0;
102 1         2 my $html_fh;
103 1         5 ( $html_fh, my $html_filename ) =
104             Perl::Tidy::streamhandle( $html_file, 'w' );
105 1 50       4 if ( !$html_fh ) {
106 0         0 Perl::Tidy::Warn("can't open $html_file: $OS_ERROR\n");
107 0         0 return;
108             }
109 1         3 $html_file_opened = 1;
110              
111 1 50 33     10 if ( !$input_file || $input_file eq '-' || ref($input_file) ) {
      33        
112 0         0 $input_file = "NONAME";
113             }
114              
115             # write the table of contents to a string
116 1         2 my $toc_string;
117 1         5 my $html_toc_fh = Perl::Tidy::IOScalar->new( \$toc_string, 'w' );
118              
119 1         4 my $html_pre_fh;
120             my @pre_string_stack;
121 1 50       4 if ( $rOpts->{'html-pre-only'} ) {
122              
123             # pre section goes directly to the output stream
124 0         0 $html_pre_fh = $html_fh;
125 0         0 $html_pre_fh->print( <<"PRE_END");
126             <pre>
127             PRE_END
128             }
129             else {
130              
131             # pre section go out to a temporary string
132 1         2 my $pre_string;
133 1         5 $html_pre_fh = Perl::Tidy::IOScalar->new( \$pre_string, 'w' );
134 1         4 push @pre_string_stack, \$pre_string;
135             }
136              
137             # pod text gets diverted if the 'pod2html' is used
138 1         3 my $html_pod_fh;
139             my $pod_string;
140 1 50       4 if ( $rOpts->{'pod2html'} ) {
141 1 50       5 if ( $rOpts->{'html-pre-only'} ) {
142 0         0 undef $rOpts->{'pod2html'};
143             }
144             else {
145             ##eval "use Pod::Html";
146             #if ($@) {
147 1 50       4 if ($missing_pod_html) {
148 0         0 Perl::Tidy::Warn(
149             "unable to find Pod::Html; cannot use pod2html\n-npod disables this message\n"
150             );
151 0         0 undef $rOpts->{'pod2html'};
152             }
153             else {
154 1         4 $html_pod_fh = Perl::Tidy::IOScalar->new( \$pod_string, 'w' );
155             }
156             }
157             }
158              
159 1         3 my $toc_filename;
160             my $src_filename;
161 1 50       4 if ( $rOpts->{'frames'} ) {
162 0 0       0 if ( !$extension ) {
163 0         0 Perl::Tidy::Warn(
164             "cannot use frames without a specified output extension; ignoring -frm\n"
165             );
166 0         0 undef $rOpts->{'frames'};
167             }
168             else {
169 0         0 $toc_filename = $input_file . $html_toc_extension . $extension;
170 0         0 $src_filename = $input_file . $html_src_extension . $extension;
171             }
172             }
173              
174             # ----------------------------------------------------------
175             # Output is now directed as follows:
176             # html_toc_fh <-- table of contents items
177             # html_pre_fh <-- the <pre> section of formatted code, except:
178             # html_pod_fh <-- pod goes here with the pod2html option
179             # ----------------------------------------------------------
180              
181 1         3 my $title = $rOpts->{'title'};
182 1 50       4 if ( !$title ) {
183 1         38 ( $title, my $path ) = fileparse($input_file);
184             }
185 1         3 my $toc_item_count = 0;
186 1         3 my $in_toc_package = EMPTY_STRING;
187 1         2 my $last_level = 0;
188 1         26 return bless {
189             _input_file => $input_file, # name of input file
190             _title => $title, # title, unescaped
191             _html_file => $html_file, # name of .html output file
192             _toc_filename => $toc_filename, # for frames option
193             _src_filename => $src_filename, # for frames option
194             _html_file_opened => $html_file_opened, # a flag
195             _html_fh => $html_fh, # the output stream
196             _html_pre_fh => $html_pre_fh, # pre section goes here
197             _rpre_string_stack => \@pre_string_stack, # stack of pre sections
198             _html_pod_fh => $html_pod_fh, # pod goes here if pod2html
199             _rpod_string => \$pod_string, # string holding pod
200             _pod_cut_count => 0, # how many =cut's?
201             _html_toc_fh => $html_toc_fh, # fh for table of contents
202             _rtoc_string => \$toc_string, # string holding toc
203             _rtoc_item_count => \$toc_item_count, # how many toc items
204             _rin_toc_package => \$in_toc_package, # package name
205             _rtoc_name_count => {}, # hash to track unique names
206             _rpackage_stack => [], # stack to check for package
207             # name changes
208             _rlast_level => \$last_level, # brace indentation level
209             }, $class;
210             } ## end sub new
211              
212             sub add_toc_item {
213              
214             # Add an item to the html table of contents.
215             # This is called even if no table of contents is written,
216             # because we still want to put the anchors in the <pre> text.
217             # We are given an anchor name and its type; types are:
218             # 'package', 'sub', '__END__', '__DATA__', 'EOF'
219             # There must be an 'EOF' call at the end to wrap things up.
220 1     1 0 3 my ( $self, $name, $type ) = @_;
221 1         3 my $html_toc_fh = $self->{_html_toc_fh};
222 1         2 my $html_pre_fh = $self->{_html_pre_fh};
223 1         2 my $rtoc_name_count = $self->{_rtoc_name_count};
224 1         2 my $rtoc_item_count = $self->{_rtoc_item_count};
225 1         3 my $rlast_level = $self->{_rlast_level};
226 1         2 my $rin_toc_package = $self->{_rin_toc_package};
227 1         2 my $rpackage_stack = $self->{_rpackage_stack};
228              
229             # packages contain sublists of subs, so to avoid errors all package
230             # items are written and finished with the following routines
231             my $end_package_list = sub {
232 0 0   0   0 if ( ${$rin_toc_package} ) {
  0         0  
233 0         0 $html_toc_fh->print("</ul>\n</li>\n");
234 0         0 ${$rin_toc_package} = EMPTY_STRING;
  0         0  
235             }
236 0         0 return;
237 1         5 };
238              
239             my $start_package_list = sub {
240 0     0   0 my ( $unique_name, $package ) = @_;
241 0 0       0 if ( ${$rin_toc_package} ) { $end_package_list->() }
  0         0  
  0         0  
242 0         0 $html_toc_fh->print(<<EOM);
243             <li><a href=\"#$unique_name\">package $package</a>
244             <ul>
245             EOM
246 0         0 ${$rin_toc_package} = $package;
  0         0  
247 0         0 return;
248 1         5 };
249              
250             # start the table of contents on the first item
251 1 50       2 if ( !${$rtoc_item_count} ) {
  1         4  
252              
253             # but just quit if we hit EOF without any other entries
254             # in this case, there will be no toc
255 1 50       11 return if ( $type eq 'EOF' );
256 0         0 $html_toc_fh->print( <<"TOC_END");
257             <!-- BEGIN CODE INDEX --><a name="code-index"></a>
258             <ul>
259             TOC_END
260             }
261 0         0 ${$rtoc_item_count}++;
  0         0  
262              
263             # make a unique anchor name for this location:
264             # - packages get a 'package-' prefix
265             # - subs use their names
266 0         0 my $unique_name = $name;
267 0 0       0 if ( $type eq 'package' ) { $unique_name = "package-$name" }
  0         0  
268              
269             # append '-1', '-2', etc if necessary to make unique; this will
270             # be unique because subs and packages cannot have a '-'
271 0 0       0 if ( my $count = $rtoc_name_count->{ lc $unique_name }++ ) {
272 0         0 $unique_name .= "-$count";
273             }
274              
275             # - all names get terminal '-' if pod2html is used, to avoid
276             # conflicts with anchor names created by pod2html
277 0 0       0 if ( $rOpts->{'pod2html'} ) { $unique_name .= '-' }
  0         0  
278              
279             # start/stop lists of subs
280 0 0       0 if ( $type eq 'sub' ) {
281 0         0 my $package = $rpackage_stack->[ ${$rlast_level} ];
  0         0  
282 0 0       0 if ( !$package ) { $package = 'main' }
  0         0  
283              
284             # if we're already in a package/sub list, be sure its the right
285             # package or else close it
286 0 0 0     0 if ( ${$rin_toc_package} && ${$rin_toc_package} ne $package ) {
  0         0  
  0         0  
287 0         0 $end_package_list->();
288             }
289              
290             # start a package/sub list if necessary
291 0 0       0 if ( !${$rin_toc_package} ) {
  0         0  
292 0         0 $start_package_list->( $unique_name, $package );
293             }
294             }
295              
296             # now write an entry in the toc for this item
297 0 0       0 if ( $type eq 'package' ) {
    0          
298 0         0 $start_package_list->( $unique_name, $name );
299             }
300             elsif ( $type eq 'sub' ) {
301 0         0 $html_toc_fh->print("<li><a href=\"#$unique_name\">$name</a></li>\n");
302             }
303             else {
304 0         0 $end_package_list->();
305 0         0 $html_toc_fh->print("<li><a href=\"#$unique_name\">$name</a></li>\n");
306             }
307              
308             # write the anchor in the <pre> section
309 0         0 $html_pre_fh->print("<a name=\"$unique_name\"></a>");
310              
311             # end the table of contents, if any, on the end of file
312 0 0       0 if ( $type eq 'EOF' ) {
313 0         0 $html_toc_fh->print( <<"TOC_END");
314             </ul>
315             <!-- END CODE INDEX -->
316             TOC_END
317             }
318 0         0 return;
319             } ## end sub add_toc_item
320              
321             BEGIN {
322              
323             # This is the official list of tokens which may be identified by the
324             # user. Long names are used as getopt keys. Short names are
325             # convenient short abbreviations for specifying input. Short names
326             # somewhat resemble token type characters, but are often different
327             # because they may only be alphanumeric, to allow command line
328             # input. Also, note that because of case insensitivity of html,
329             # this table must be in a single case only (I've chosen to use all
330             # lower case).
331             # When adding NEW_TOKENS: update this hash table
332             # short names => long names
333 39     39   632 %short_to_long_names = (
334             'n' => 'numeric',
335             'p' => 'paren',
336             'q' => 'quote',
337             's' => 'structure',
338             'c' => 'comment',
339             'v' => 'v-string',
340             'cm' => 'comma',
341             'w' => 'bareword',
342             'co' => 'colon',
343             'pu' => 'punctuation',
344             'i' => 'identifier',
345             'j' => 'label',
346             'h' => 'here-doc-target',
347             'hh' => 'here-doc-text',
348             'k' => 'keyword',
349             'sc' => 'semicolon',
350             'm' => 'subroutine',
351             'pd' => 'pod-text',
352             );
353              
354             # Now we have to map actual token types into one of the above short
355             # names; any token types not mapped will get 'punctuation'
356             # properties.
357              
358             # The values of this hash table correspond to the keys of the
359             # previous hash table.
360             # The keys of this hash table are token types and can be seen
361             # by running with --dump-token-types (-dtt).
362              
363             # When adding NEW_TOKENS: update this hash table
364             # $type => $short_name
365             # c250: changed 'M' to 'S'
366 39         481 %token_short_names = (
367             '#' => 'c',
368             'n' => 'n',
369             'v' => 'v',
370             'k' => 'k',
371             'F' => 'k',
372             'Q' => 'q',
373             'q' => 'q',
374             'J' => 'j',
375             'j' => 'j',
376             'h' => 'h',
377             'H' => 'hh',
378             'w' => 'w',
379             ',' => 'cm',
380             '=>' => 'cm',
381             ';' => 'sc',
382             ':' => 'co',
383             'f' => 'sc',
384             '(' => 'p',
385             ')' => 'p',
386             'S' => 'm',
387             'pd' => 'pd',
388             'A' => 'co',
389             );
390              
391             # These token types will all be called identifiers for now
392             # Fix for c250: added new type 'P', formerly 'i'
393             # ( but package statements will eventually be split into 'k' and 'i')
394 39         177 my @identifier = qw< i t U C Y Z G P :: CORE::>;
395 39         494 @token_short_names{@identifier} = ('i') x scalar(@identifier);
396              
397             # These token types will be called 'structure'
398 39         137 my @structure = qw< { } >;
399 39         27334 @token_short_names{@structure} = ('s') x scalar(@structure);
400              
401             # OLD NOTES: save for reference
402             # Any of these could be added later if it would be useful.
403             # For now, they will by default become punctuation
404             # my @list = qw< L R [ ] >;
405             # @token_long_names{@list} = ('non-structure') x scalar(@list);
406             #
407             # my @list = qw"
408             # / /= * *= ** **= + += - -= % %= = ++ -- << <<= >> >>= pp p m mm
409             # ";
410             # @token_long_names{@list} = ('math') x scalar(@list);
411             #
412             # my @list = qw" & &= ~ ~= ^ ^= | |= ";
413             # @token_long_names{@list} = ('bit') x scalar(@list);
414             #
415             # my @list = qw" == != < > <= <=> ";
416             # @token_long_names{@list} = ('numerical-comparison') x scalar(@list);
417             #
418             # my @list = qw" && || ! &&= ||= //= ";
419             # @token_long_names{@list} = ('logical') x scalar(@list);
420             #
421             # my @list = qw" . .= =~ !~ x x= ";
422             # @token_long_names{@list} = ('string-operators') x scalar(@list);
423             #
424             # # Incomplete..
425             # my @list = qw" .. -> <> ... \ ? ";
426             # @token_long_names{@list} = ('misc-operators') x scalar(@list);
427              
428             } ## end BEGIN
429              
430             sub make_getopt_long_names {
431 557     557 0 2211 my ( $class, $rgetopt_names ) = @_;
432 557         3840 while ( my ( $short_name, $name ) = each %short_to_long_names ) {
433 10026         13564 push @{$rgetopt_names}, "html-color-$name=s";
  10026         19959  
434 10026         13566 push @{$rgetopt_names}, "html-italic-$name!";
  10026         19444  
435 10026         13293 push @{$rgetopt_names}, "html-bold-$name!";
  10026         39048  
436             }
437 557         2099 push @{$rgetopt_names}, "html-color-background=s";
  557         2573  
438 557         1427 push @{$rgetopt_names}, "html-linked-style-sheet=s";
  557         1444  
439 557         1115 push @{$rgetopt_names}, "nohtml-style-sheets";
  557         1469  
440 557         1110 push @{$rgetopt_names}, "html-pre-only";
  557         1431  
441 557         1247 push @{$rgetopt_names}, "html-line-numbers";
  557         1334  
442 557         1006 push @{$rgetopt_names}, "html-entities!";
  557         1211  
443 557         1039 push @{$rgetopt_names}, "stylesheet";
  557         1167  
444 557         994 push @{$rgetopt_names}, "html-table-of-contents!";
  557         1227  
445 557         1051 push @{$rgetopt_names}, "pod2html!";
  557         1241  
446 557         1003 push @{$rgetopt_names}, "frames!";
  557         1155  
447 557         1048 push @{$rgetopt_names}, "html-toc-extension=s";
  557         1119  
448 557         1056 push @{$rgetopt_names}, "html-src-extension=s";
  557         1173  
449              
450             # Pod::Html parameters:
451 557         1059 push @{$rgetopt_names}, "backlink=s";
  557         1299  
452 557         1052 push @{$rgetopt_names}, "cachedir=s";
  557         1233  
453 557         1024 push @{$rgetopt_names}, "htmlroot=s";
  557         1173  
454 557         954 push @{$rgetopt_names}, "libpods=s";
  557         1239  
455 557         1151 push @{$rgetopt_names}, "podpath=s";
  557         1366  
456 557         1104 push @{$rgetopt_names}, "podroot=s";
  557         1142  
457 557         1027 push @{$rgetopt_names}, "title=s";
  557         1168  
458              
459             # Pod::Html parameters with leading 'pod' which will be removed
460             # before the call to Pod::Html
461 557         1054 push @{$rgetopt_names}, "podquiet!";
  557         1152  
462 557         988 push @{$rgetopt_names}, "podverbose!";
  557         1173  
463 557         1004 push @{$rgetopt_names}, "podrecurse!";
  557         1143  
464 557         987 push @{$rgetopt_names}, "podflush";
  557         1120  
465 557         1010 push @{$rgetopt_names}, "podheader!";
  557         1222  
466 557         1043 push @{$rgetopt_names}, "podindex!";
  557         1189  
467 557         1606 return;
468             } ## end sub make_getopt_long_names
469              
470             sub make_abbreviated_names {
471              
472             # We're appending things like this to the expansion list:
473             # 'hcc' => [qw(html-color-comment)],
474             # 'hck' => [qw(html-color-keyword)],
475             # etc
476 557     557 0 2914 my ( $class, $rexpansion ) = @_;
477              
478             # abbreviations for color/bold/italic properties
479 557         3558 while ( my ( $short_name, $long_name ) = each %short_to_long_names ) {
480 10026         21847 ${$rexpansion}{"hc$short_name"} = ["html-color-$long_name"];
  10026         41092  
481 10026         21271 ${$rexpansion}{"hb$short_name"} = ["html-bold-$long_name"];
  10026         21178  
482 10026         20220 ${$rexpansion}{"hi$short_name"} = ["html-italic-$long_name"];
  10026         20964  
483 10026         19610 ${$rexpansion}{"nhb$short_name"} = ["nohtml-bold-$long_name"];
  10026         20674  
484 10026         19760 ${$rexpansion}{"nhi$short_name"} = ["nohtml-italic-$long_name"];
  10026         34700  
485             }
486              
487             # abbreviations for all other html options
488 557         2409 ${$rexpansion}{"hcbg"} = ["html-color-background"];
  557         2324  
489 557         1507 ${$rexpansion}{"pre"} = ["html-pre-only"];
  557         1559  
490 557         1460 ${$rexpansion}{"toc"} = ["html-table-of-contents"];
  557         1513  
491 557         1414 ${$rexpansion}{"ntoc"} = ["nohtml-table-of-contents"];
  557         1410  
492 557         1424 ${$rexpansion}{"nnn"} = ["html-line-numbers"];
  557         1333  
493 557         1467 ${$rexpansion}{"hent"} = ["html-entities"];
  557         1404  
494 557         1331 ${$rexpansion}{"nhent"} = ["nohtml-entities"];
  557         1590  
495 557         1432 ${$rexpansion}{"css"} = ["html-linked-style-sheet"];
  557         1529  
496 557         1448 ${$rexpansion}{"nss"} = ["nohtml-style-sheets"];
  557         1503  
497 557         1432 ${$rexpansion}{"ss"} = ["stylesheet"];
  557         1372  
498 557         1366 ${$rexpansion}{"pod"} = ["pod2html"];
  557         1464  
499 557         1408 ${$rexpansion}{"npod"} = ["nopod2html"];
  557         1453  
500 557         1431 ${$rexpansion}{"frm"} = ["frames"];
  557         1405  
501 557         1451 ${$rexpansion}{"nfrm"} = ["noframes"];
  557         1422  
502 557         1487 ${$rexpansion}{"text"} = ["html-toc-extension"];
  557         1512  
503 557         1407 ${$rexpansion}{"sext"} = ["html-src-extension"];
  557         1351  
504 557         2007 return;
505             } ## end sub make_abbreviated_names
506              
507             sub check_options {
508              
509             # This will be called once after options have been parsed
510             # Note that we are defining the package variable $rOpts here:
511 1     1 0 4 ( my $class, $rOpts ) = @_;
512              
513             # X11 color names for default settings that seemed to look ok
514             # (these color names are only used for programming clarity; the hex
515             # numbers are actually written)
516 39     39   498 use constant ForestGreen => "#228B22";
  39         126  
  39         3819  
517 39     39   371 use constant SaddleBrown => "#8B4513";
  39         125  
  39         2505  
518 39     39   320 use constant magenta4 => "#8B008B";
  39         143  
  39         2690  
519 39     39   304 use constant IndianRed3 => "#CD5555";
  39         148  
  39         2385  
520 39     39   308 use constant DeepSkyBlue4 => "#00688B";
  39         107  
  39         2768  
521 39     39   390 use constant MediumOrchid3 => "#B452CD";
  39         113  
  39         2327  
522 39     39   277 use constant black => "#000000";
  39         144  
  39         2100  
523 39     39   278 use constant white => "#FFFFFF";
  39         114  
  39         2392  
524 39     39   306 use constant red => "#FF0000";
  39         149  
  39         225668  
525              
526             # set default color, bold, italic properties
527             # anything not listed here will be given the default (punctuation) color --
528             # these types currently not listed and get default: ws pu s sc cm co p
529             # When adding NEW_TOKENS: add an entry here if you don't want defaults
530              
531             # set_default_properties( $short_name, default_color, bold?, italic? );
532 1         6 set_default_properties( 'c', ForestGreen, 0, 0 );
533 1         5 set_default_properties( 'pd', ForestGreen, 0, 1 );
534 1         10 set_default_properties( 'k', magenta4, 1, 0 ); # was SaddleBrown
535 1         6 set_default_properties( 'q', IndianRed3, 0, 0 );
536 1         4 set_default_properties( 'hh', IndianRed3, 0, 1 );
537 1         3 set_default_properties( 'h', IndianRed3, 1, 0 );
538 1         3 set_default_properties( 'i', DeepSkyBlue4, 0, 0 );
539 1         3 set_default_properties( 'w', black, 0, 0 );
540 1         6 set_default_properties( 'n', MediumOrchid3, 0, 0 );
541 1         4 set_default_properties( 'v', MediumOrchid3, 0, 0 );
542 1         7 set_default_properties( 'j', IndianRed3, 1, 0 );
543 1         6 set_default_properties( 'm', red, 1, 0 );
544              
545 1         4 set_default_color( 'html-color-background', white );
546 1         5 set_default_color( 'html-color-punctuation', black );
547              
548             # setup property lookup tables for tokens based on their short names
549             # every token type has a short name, and will use these tables
550             # to do the html markup
551 1         8 while ( my ( $short_name, $long_name ) = each %short_to_long_names ) {
552 18         55 $html_color{$short_name} = $rOpts->{"html-color-$long_name"};
553 18         35 $html_bold{$short_name} = $rOpts->{"html-bold-$long_name"};
554 18         51 $html_italic{$short_name} = $rOpts->{"html-italic-$long_name"};
555             }
556              
557             # write style sheet to STDOUT and die if requested
558 1 50       6 if ( defined( $rOpts->{'stylesheet'} ) ) {
559 0         0 write_style_sheet_file('-');
560 0         0 Perl::Tidy::Exit(0);
561             }
562              
563             # make sure user gives a file name after -css
564 1         8 $css_linkname = EMPTY_STRING;
565 1 50       4 if ( defined( $rOpts->{'html-linked-style-sheet'} ) ) {
566 0         0 $css_linkname = $rOpts->{'html-linked-style-sheet'};
567 0 0       0 if ( $css_linkname =~ /^-/ ) {
568 0         0 Perl::Tidy::Die("You must specify a valid filename after -css\n");
569             }
570             }
571              
572             # check for conflict
573 1 0 33     5 if ( $css_linkname && $rOpts->{'nohtml-style-sheets'} ) {
574 0         0 $rOpts->{'nohtml-style-sheets'} = 0;
575 0         0 Perl::Tidy::Warn(
576             "You can't specify both -css and -nss; -nss ignored\n");
577             }
578              
579             # write a style sheet file if necessary
580 1 50       5 if ($css_linkname) {
581              
582             # if the selected filename exists, don't write, because user may
583             # have done some work by hand to create it; use backup name instead
584             # Also, this will avoid a potential disaster in which the user
585             # forgets to specify the style sheet, like this:
586             # perltidy -html -css myfile1.pl myfile2.pl
587             # This would cause myfile1.pl to parsed as the style sheet by GetOpts
588 0 0       0 if ( !-e $css_linkname ) {
589 0         0 write_style_sheet_file($css_linkname);
590             }
591             }
592 1         3 $rOpts_html_entities = $rOpts->{'html-entities'};
593 1         3 return;
594             } ## end sub check_options
595              
596             sub write_style_sheet_file {
597              
598 0     0 0 0 my $filename = shift;
599 0         0 my $fh = IO::File->new("> $filename");
600 0 0       0 if ( !$fh ) {
601 0         0 Perl::Tidy::Die("can't open $filename: $OS_ERROR\n");
602             }
603 0         0 write_style_sheet_data($fh);
604 0 0 0     0 if ( $fh->can('close') && $filename ne '-' && !ref($filename) ) {
      0        
605 0 0       0 $fh->close()
606             or
607             Perl::Tidy::Warn("can't close style sheet '$filename' : $OS_ERROR\n");
608             }
609 0         0 return;
610             } ## end sub write_style_sheet_file
611              
612             sub write_style_sheet_data {
613              
614             # write the style sheet data to an open file handle
615 1     1 0 3 my $fh = shift;
616              
617 1         3 my $bg_color = $rOpts->{'html-color-background'};
618 1         3 my $text_color = $rOpts->{'html-color-punctuation'};
619              
620             # pre-bgcolor is new, and may not be defined
621 1         3 my $pre_bg_color = $rOpts->{'html-pre-color-background'};
622 1 50       4 $pre_bg_color = $bg_color unless $pre_bg_color;
623              
624 1         9 $fh->print(<<"EOM");
625             /* default style sheet generated by perltidy */
626             body {background: $bg_color; color: $text_color}
627             pre { color: $text_color;
628             background: $pre_bg_color;
629             font-family: courier;
630             }
631              
632             EOM
633              
634 1         11 foreach my $short_name ( sort keys %short_to_long_names ) {
635 18         25 my $long_name = $short_to_long_names{$short_name};
636              
637 18         29 my $abbrev = '.' . $short_name;
638 18 100       30 if ( length($short_name) == 1 ) { $abbrev .= SPACE } # for alignment
  12         20  
639 18         28 my $color = $html_color{$short_name};
640 18 100       29 if ( !defined($color) ) { $color = $text_color }
  5         8  
641 18         51 $fh->print("$abbrev \{ color: $color;");
642              
643 18 100       36 if ( $html_bold{$short_name} ) {
644 4         8 $fh->print(" font-weight:bold;");
645             }
646              
647 18 100       36 if ( $html_italic{$short_name} ) {
648 2         5 $fh->print(" font-style:italic;");
649             }
650 18         44 $fh->print("} /* $long_name */\n");
651             }
652 1         3 return;
653             } ## end sub write_style_sheet_data
654              
655             sub set_default_color {
656              
657             # make sure that options hash $rOpts->{$key} contains a valid color
658 14     14 0 28 my ( $key, $color ) = @_;
659 14 50       28 if ( $rOpts->{$key} ) { $color = $rOpts->{$key} }
  0         0  
660 14         23 $rOpts->{$key} = check_RGB($color);
661 14         20 return;
662             } ## end sub set_default_color
663              
664             sub check_RGB {
665              
666             # if color is a 6 digit hex RGB value, prepend a #, otherwise
667             # assume that it is a valid ascii color name
668 14     14 0 22 my ($color) = @_;
669 14 50       33 if ( $color =~ /^[0-9a-fA-F]{6,6}$/ ) { $color = "#$color" }
  0         0  
670 14         30 return $color;
671             } ## end sub check_RGB
672              
673             sub set_default_properties {
674 12     12 0 24 my ( $short_name, $color, $bold, $italic ) = @_;
675              
676 12         35 set_default_color( "html-color-$short_to_long_names{$short_name}", $color );
677 12         14 my $key;
678 12         23 $key = "html-bold-$short_to_long_names{$short_name}";
679 12 50       31 $rOpts->{$key} = ( defined $rOpts->{$key} ) ? $rOpts->{$key} : $bold;
680 12         16 $key = "html-italic-$short_to_long_names{$short_name}";
681 12 50       27 $rOpts->{$key} = ( defined $rOpts->{$key} ) ? $rOpts->{$key} : $italic;
682 12         16 return;
683             } ## end sub set_default_properties
684              
685             sub pod_to_html {
686              
687             # Use Pod::Html to process the pod and make the page
688             # then merge the perltidy code sections into it.
689             # return 1 if success, 0 otherwise
690 1     1 0 4 my ( $self, $pod_string, $css_string, $toc_string, $rpre_string_stack ) =
691             @_;
692 1         3 my $input_file = $self->{_input_file};
693 1         2 my $title = $self->{_title};
694 1         5 my $success_flag = 0;
695              
696             # don't try to use pod2html if no pod
697 1 50       3 if ( !$pod_string ) {
698 1         5 return $success_flag;
699             }
700              
701             # Pod::Html requires a real temporary filename
702 0         0 my ( $fh_tmp, $tmpfile ) = File::Temp::tempfile();
703 0 0       0 if ( !$fh_tmp ) {
704 0         0 Perl::Tidy::Warn(
705             "unable to open temporary file $tmpfile; cannot use pod2html\n");
706 0         0 return $success_flag;
707             }
708              
709             #------------------------------------------------------------------
710             # Warning: a temporary file is open; we have to clean up if
711             # things go bad. From here on all returns should be by going to
712             # RETURN so that the temporary file gets unlinked.
713             #------------------------------------------------------------------
714              
715             # write the pod text to the temporary file
716 0         0 $fh_tmp->print($pod_string);
717              
718 0 0       0 if ( !$fh_tmp->close() ) {
719 0         0 Perl::Tidy::Warn(
720             "unable to close temporary file $tmpfile; cannot use pod2html\n");
721 0         0 return $success_flag;
722             }
723              
724             # Hand off the pod to pod2html.
725             # Note that we can use the same temporary filename for input and output
726             # because of the way pod2html works.
727             {
728              
729 0         0 my @args;
  0         0  
730 0         0 push @args, "--infile=$tmpfile", "--outfile=$tmpfile", "--title=$title";
731              
732             # Flags with string args:
733             # "backlink=s", "cachedir=s", "htmlroot=s", "libpods=s",
734             # "podpath=s", "podroot=s"
735             # Note: -css=s is handled by perltidy itself
736 0         0 foreach my $kw (qw(backlink cachedir htmlroot libpods podpath podroot))
737             {
738 0 0       0 if ( $rOpts->{$kw} ) { push @args, "--$kw=$rOpts->{$kw}" }
  0         0  
739             }
740              
741             # Toggle switches; these have extra leading 'pod'
742             # "header!", "index!", "recurse!", "quiet!", "verbose!"
743 0         0 foreach my $kw (qw(podheader podindex podrecurse podquiet podverbose)) {
744 0         0 my $kwd = $kw; # allows us to strip 'pod'
745 0 0       0 if ( $rOpts->{$kw} ) { $kwd =~ s/^pod//; push @args, "--$kwd" }
  0 0       0  
  0         0  
746             elsif ( defined( $rOpts->{$kw} ) ) {
747 0         0 $kwd =~ s/^pod//;
748 0         0 push @args, "--no$kwd";
749             }
750             else {
751             ## ok - not defined
752             }
753             }
754              
755             # "flush",
756 0         0 my $kw = 'podflush';
757 0 0       0 if ( $rOpts->{$kw} ) { $kw =~ s/^pod//; push @args, "--$kw" }
  0         0  
  0         0  
758              
759             # Must clean up if pod2html dies (it can);
760             # Be careful not to overwrite callers __DIE__ routine
761             local $SIG{__DIE__} = sub {
762 0 0   0   0 unlink $tmpfile if -e $tmpfile;
763 0         0 Perl::Tidy::Die( $_[0] );
764 0         0 };
765              
766 0         0 Pod::Html::pod2html(@args);
767             }
768 0         0 $fh_tmp = IO::File->new( $tmpfile, 'r' );
769 0 0       0 if ( !$fh_tmp ) {
770              
771             # this error shouldn't happen ... we just used this filename
772 0         0 Perl::Tidy::Warn(
773             "unable to open temporary file $tmpfile; cannot use pod2html\n");
774 0         0 return $success_flag;
775             }
776              
777 0         0 my $html_fh = $self->{_html_fh};
778 0         0 my @toc;
779             my $in_toc;
780 0         0 my $ul_level = 0;
781 0         0 my $no_print;
782              
783             # This routine will write the html selectively and store the toc
784             my $html_print = sub {
785 0     0   0 foreach my $line (@_) {
786 0 0       0 $html_fh->print($line) unless ($no_print);
787 0 0       0 if ($in_toc) { push @toc, $line }
  0         0  
788             }
789 0         0 return;
790 0         0 };
791              
792             # loop over lines of html output from pod2html and merge in
793             # the necessary perltidy html sections
794 0         0 my ( $saw_body, $saw_index, $saw_body_end );
795              
796 0         0 my $timestamp = EMPTY_STRING;
797 0 0       0 if ( $rOpts->{'timestamp'} ) {
798 0         0 my $date = localtime;
799 0         0 $timestamp = "on $date";
800             }
801 0         0 while ( defined( my $line = $fh_tmp->getline() ) ) {
802              
803 0 0 0     0 if ( $line =~ /^\s*<html>\s*$/i ) {
    0 0        
    0          
    0          
    0          
    0          
    0          
    0          
    0          
804             ##my $date = localtime;
805             ##$html_print->("<!-- Generated by perltidy on $date -->\n");
806 0         0 $html_print->("<!-- Generated by perltidy $timestamp -->\n");
807 0         0 $html_print->($line);
808             }
809              
810             # Copy the perltidy css, if any, after <body> tag
811             elsif ( $line =~ /^\s*<body.*>\s*$/i ) {
812 0         0 $saw_body = 1;
813 0 0       0 $html_print->($css_string) if $css_string;
814 0         0 $html_print->($line);
815              
816             # add a top anchor and heading
817 0         0 $html_print->("<a name=\"-top-\"></a>\n");
818 0         0 $title = escape_html($title);
819 0         0 $html_print->("<h1>$title</h1>\n");
820             }
821              
822             # check for start of index, old pod2html
823             # before Pod::Html VERSION 1.15_02 it is delimited by comments as:
824             # <!-- INDEX BEGIN -->
825             # <ul>
826             # ...
827             # </ul>
828             # <!-- INDEX END -->
829             #
830             elsif ( $line =~ /^\s*<!-- INDEX BEGIN -->\s*$/i ) {
831 0         0 $in_toc = 'INDEX';
832              
833             # when frames are used, an extra table of contents in the
834             # contents panel is confusing, so don't print it
835             $no_print = $rOpts->{'frames'}
836 0   0     0 || !$rOpts->{'html-table-of-contents'};
837 0 0       0 $html_print->("<h2>Doc Index:</h2>\n") if $rOpts->{'frames'};
838 0         0 $html_print->($line);
839             }
840              
841             # check for start of index, new pod2html
842             # After Pod::Html VERSION 1.15_02 it is delimited as:
843             # <ul id="index">
844             # ...
845             # </ul>
846             elsif ( $line =~ /^\s*<ul\s+id="index">/i ) {
847 0         0 $in_toc = 'UL';
848 0         0 $ul_level = 1;
849              
850             # when frames are used, an extra table of contents in the
851             # contents panel is confusing, so don't print it
852             $no_print = $rOpts->{'frames'}
853 0   0     0 || !$rOpts->{'html-table-of-contents'};
854 0 0       0 $html_print->("<h2>Doc Index:</h2>\n") if $rOpts->{'frames'};
855 0         0 $html_print->($line);
856             }
857              
858             # Check for end of index, old pod2html
859             elsif ( $line =~ /^\s*<!-- INDEX END -->\s*$/i ) {
860 0         0 $saw_index = 1;
861 0         0 $html_print->($line);
862              
863             # Copy the perltidy toc, if any, after the Pod::Html toc
864 0 0       0 if ($toc_string) {
865 0 0       0 $html_print->("<hr />\n") if $rOpts->{'frames'};
866 0         0 $html_print->("<h2>Code Index:</h2>\n");
867             ##my @toc = map { $_ .= "\n" } split /\n/, $toc_string;
868 0         0 my @toc_st = map { $_ . "\n" } split /\n/, $toc_string;
  0         0  
869 0         0 $html_print->(@toc_st);
870             }
871 0         0 $in_toc = EMPTY_STRING;
872 0         0 $no_print = 0;
873             }
874              
875             # must track <ul> depth level for new pod2html
876             elsif ( $line =~ /\s*<ul>\s*$/i && $in_toc eq 'UL' ) {
877 0         0 $ul_level++;
878 0         0 $html_print->($line);
879             }
880              
881             # Check for end of index, for new pod2html
882             elsif ( $line =~ /\s*<\/ul>/i && $in_toc eq 'UL' ) {
883 0         0 $ul_level--;
884 0         0 $html_print->($line);
885              
886             # Copy the perltidy toc, if any, after the Pod::Html toc
887 0 0       0 if ( $ul_level <= 0 ) {
888 0         0 $saw_index = 1;
889 0 0       0 if ($toc_string) {
890 0 0       0 $html_print->("<hr />\n") if $rOpts->{'frames'};
891 0         0 $html_print->("<h2>Code Index:</h2>\n");
892             ##my @toc = map { $_ .= "\n" } split /\n/, $toc_string;
893 0         0 my @toc_st = map { $_ . "\n" } split /\n/, $toc_string;
  0         0  
894 0         0 $html_print->(@toc_st);
895             }
896 0         0 $in_toc = EMPTY_STRING;
897 0         0 $ul_level = 0;
898 0         0 $no_print = 0;
899             }
900             }
901              
902             # Copy one perltidy section after each marker
903             elsif ( $line =~ /^(.*)<!-- pERLTIDY sECTION -->(.*)$/ ) {
904 0         0 $line = $2;
905 0 0       0 $html_print->($1) if $1;
906              
907             # Intermingle code and pod sections if we saw multiple =cut's.
908 0 0       0 if ( $self->{_pod_cut_count} > 1 ) {
909 0         0 my $rpre_string = shift( @{$rpre_string_stack} );
  0         0  
910 0 0       0 if ( ${$rpre_string} ) {
  0         0  
911 0         0 $html_print->('<pre>');
912 0         0 $html_print->( ${$rpre_string} );
  0         0  
913 0         0 $html_print->('</pre>');
914             }
915             else {
916              
917             # shouldn't happen: we stored a string before writing
918             # each marker.
919 0         0 Perl::Tidy::Warn(
920             "Problem merging html stream with pod2html; order may be wrong\n"
921             );
922             }
923 0         0 $html_print->($line);
924             }
925              
926             # If didn't see multiple =cut lines, we'll put the pod out first
927             # and then the code, because it's less confusing.
928             else {
929              
930             # since we are not intermixing code and pod, we don't need
931             # or want any <hr> lines which separated pod and code
932 0 0       0 $html_print->($line) unless ( $line =~ /^\s*<hr>\s*$/i );
933             }
934             }
935              
936             # Copy any remaining code section before the </body> tag
937             elsif ( $line =~ /^\s*<\/body>\s*$/i ) {
938 0         0 $saw_body_end = 1;
939 0 0       0 if ( @{$rpre_string_stack} ) {
  0         0  
940 0 0       0 if ( $self->{_pod_cut_count} <= 1 ) {
941 0         0 $html_print->('<hr />');
942             }
943 0         0 while ( my $rpre_string = shift( @{$rpre_string_stack} ) ) {
  0         0  
944 0         0 $html_print->('<pre>');
945 0         0 $html_print->( ${$rpre_string} );
  0         0  
946 0         0 $html_print->('</pre>');
947             }
948             }
949 0         0 $html_print->($line);
950             }
951             else {
952 0         0 $html_print->($line);
953             }
954             }
955              
956 0         0 $success_flag = 1;
957 0 0       0 if ( !$saw_body ) {
958 0         0 Perl::Tidy::Warn("Did not see <body> in pod2html output\n");
959 0         0 $success_flag = 0;
960             }
961 0 0       0 if ( !$saw_body_end ) {
962 0         0 Perl::Tidy::Warn("Did not see </body> in pod2html output\n");
963 0         0 $success_flag = 0;
964             }
965 0 0       0 if ( !$saw_index ) {
966 0         0 Perl::Tidy::Warn("Did not find INDEX END in pod2html output\n");
967 0         0 $success_flag = 0;
968             }
969              
970 0 0       0 if ( $html_fh->can('close') ) {
971 0         0 $html_fh->close();
972             }
973              
974             # note that we have to unlink tmpfile before making frames
975             # because the tmpfile may be one of the names used for frames
976 0 0       0 if ( -e $tmpfile ) {
977 0 0       0 if ( !unlink($tmpfile) ) {
978 0         0 Perl::Tidy::Warn(
979             "couldn't unlink temporary file $tmpfile: $OS_ERROR\n");
980 0         0 $success_flag = 0;
981             }
982             }
983              
984 0 0 0     0 if ( $success_flag && $rOpts->{'frames'} ) {
985 0         0 $self->make_frame( \@toc );
986             }
987 0         0 return $success_flag;
988             } ## end sub pod_to_html
989              
990             sub make_frame {
991              
992             # Make a frame with table of contents in the left panel
993             # and the text in the right panel.
994             # On entry:
995             # $html_filename contains the no-frames html output
996             # $rtoc is a reference to an array with the table of contents
997 0     0 0 0 my ( $self, $rtoc ) = @_;
998 0         0 my $input_file = $self->{_input_file};
999 0         0 my $html_filename = $self->{_html_file};
1000 0         0 my $toc_filename = $self->{_toc_filename};
1001 0         0 my $src_filename = $self->{_src_filename};
1002 0         0 my $title = $self->{_title};
1003 0         0 $title = escape_html($title);
1004              
1005             # FUTURE input parameter:
1006 0         0 my $top_basename = EMPTY_STRING;
1007              
1008             # We need to produce 3 html files:
1009             # 1. - the table of contents
1010             # 2. - the contents (source code) itself
1011             # 3. - the frame which contains them
1012              
1013             # get basenames for relative links
1014 0         0 my ( $toc_basename, $toc_path ) = fileparse($toc_filename);
1015 0         0 my ( $src_basename, $src_path ) = fileparse($src_filename);
1016              
1017             # 1. Make the table of contents panel, with appropriate changes
1018             # to the anchor names
1019 0         0 my $src_frame_name = 'SRC';
1020 0         0 my $first_anchor =
1021             write_toc_html( $title, $toc_filename, $src_basename, $rtoc,
1022             $src_frame_name );
1023              
1024             # 2. The current .html filename is renamed to be the contents panel
1025 0 0       0 rename( $html_filename, $src_filename )
1026             or Perl::Tidy::Die(
1027             "Cannot rename $html_filename to $src_filename: $OS_ERROR\n");
1028              
1029             # 3. Then use the original html filename for the frame
1030 0         0 write_frame_html(
1031             $title, $html_filename, $top_basename,
1032             $toc_basename, $src_basename, $src_frame_name
1033             );
1034 0         0 return;
1035             } ## end sub make_frame
1036              
1037             sub write_toc_html {
1038              
1039             # write a separate html table of contents file for frames
1040 0     0 0 0 my ( $title, $toc_filename, $src_basename, $rtoc, $src_frame_name ) = @_;
1041 0 0       0 my $fh = IO::File->new( $toc_filename, 'w' )
1042             or Perl::Tidy::Die("Cannot open $toc_filename: $OS_ERROR\n");
1043 0         0 $fh->print(<<EOM);
1044             <html>
1045             <head>
1046             <title>$title</title>
1047             </head>
1048             <body>
1049             <h1><a href=\"$src_basename#-top-" target="$src_frame_name">$title</a></h1>
1050             EOM
1051              
1052 0         0 my $first_anchor =
1053             change_anchor_names( $rtoc, $src_basename, "$src_frame_name" );
1054 0         0 $fh->print( join EMPTY_STRING, @{$rtoc} );
  0         0  
1055              
1056 0         0 $fh->print(<<EOM);
1057             </body>
1058             </html>
1059             EOM
1060              
1061 0         0 return;
1062             } ## end sub write_toc_html
1063              
1064             sub write_frame_html {
1065              
1066             # write an html file to be the table of contents frame
1067             my (
1068 0     0 0 0 $title, $frame_filename, $top_basename,
1069             $toc_basename, $src_basename, $src_frame_name
1070             ) = @_;
1071              
1072 0 0       0 my $fh = IO::File->new( $frame_filename, 'w' )
1073             or Perl::Tidy::Die("Cannot open $toc_basename: $OS_ERROR\n");
1074              
1075 0         0 $fh->print(<<EOM);
1076             <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
1077             "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
1078             <?xml version="1.0" encoding="iso-8859-1" ?>
1079             <html xmlns="http://www.w3.org/1999/xhtml">
1080             <head>
1081             <title>$title</title>
1082             </head>
1083             EOM
1084              
1085             # two left panels, one right, if master index file
1086 0 0       0 if ($top_basename) {
1087 0         0 $fh->print(<<EOM);
1088             <frameset cols="20%,80%">
1089             <frameset rows="30%,70%">
1090             <frame src = "$top_basename" />
1091             <frame src = "$toc_basename" />
1092             </frameset>
1093             EOM
1094             }
1095              
1096             # one left panels, one right, if no master index file
1097             else {
1098 0         0 $fh->print(<<EOM);
1099             <frameset cols="20%,*">
1100             <frame src = "$toc_basename" />
1101             EOM
1102             }
1103 0         0 $fh->print(<<EOM);
1104             <frame src = "$src_basename" name = "$src_frame_name" />
1105             <noframes>
1106             <body>
1107             <p>If you see this message, you are using a non-frame-capable web client.</p>
1108             <p>This document contains:</p>
1109             <ul>
1110             <li><a href="$toc_basename">A table of contents</a></li>
1111             <li><a href="$src_basename">The source code</a></li>
1112             </ul>
1113             </body>
1114             </noframes>
1115             </frameset>
1116             </html>
1117             EOM
1118 0         0 return;
1119             } ## end sub write_frame_html
1120              
1121             sub change_anchor_names {
1122              
1123             # add a filename and target to anchors
1124             # also return the first anchor
1125 0     0 0 0 my ( $rlines, $filename, $target ) = @_;
1126 0         0 my $first_anchor;
1127 0         0 foreach my $line ( @{$rlines} ) {
  0         0  
1128              
1129             # We're looking for lines like this:
1130             # <LI><A HREF="#synopsis">SYNOPSIS</A></LI>
1131             # ---- - -------- -----------------
1132             # $1 $4 $5
1133 0 0       0 if ( $line =~ /^(.*)<a(.*)href\s*=\s*"([^#]*)#([^"]+)"[^>]*>(.*)$/i ) {
1134 0         0 my $pre = $1;
1135 0         0 my $name = $4;
1136 0         0 my $post = $5;
1137 0         0 my $href = "$filename#$name";
1138 0         0 $line = "$pre<a href=\"$href\" target=\"$target\">$post\n";
1139 0 0       0 if ( !$first_anchor ) { $first_anchor = $href }
  0         0  
1140             }
1141             }
1142 0         0 return $first_anchor;
1143             } ## end sub change_anchor_names
1144              
1145             sub close_html_file {
1146 1     1 0 3 my $self = shift;
1147 1 50       5 return unless $self->{_html_file_opened};
1148              
1149 1         2 my $html_fh = $self->{_html_fh};
1150 1         3 my $rtoc_string = $self->{_rtoc_string};
1151              
1152             # There are 3 basic paths to html output...
1153              
1154             # ---------------------------------
1155             # Path 1: finish up if in -pre mode
1156             # ---------------------------------
1157 1 50       4 if ( $rOpts->{'html-pre-only'} ) {
1158 0         0 $html_fh->print( <<"PRE_END");
1159             </pre>
1160             PRE_END
1161 0 0       0 $html_fh->close()
1162             if ( $html_fh->can('close') );
1163 0         0 return;
1164             }
1165              
1166             # Finish the index
1167 1         7 $self->add_toc_item( 'EOF', 'EOF' );
1168              
1169 1         3 my $rpre_string_stack = $self->{_rpre_string_stack};
1170              
1171             # Patch to darken the <pre> background color in case of pod2html and
1172             # interleaved code/documentation. Otherwise, the distinction
1173             # between code and documentation is blurred.
1174 1 50 33     9 if ( $rOpts->{pod2html}
      33        
1175             && $self->{_pod_cut_count} >= 1
1176             && $rOpts->{'html-color-background'} eq '#FFFFFF' )
1177             {
1178 0         0 $rOpts->{'html-pre-color-background'} = '#F0F0F0';
1179             }
1180              
1181             # put the css or its link into a string, if used
1182 1         3 my $css_string;
1183 1         5 my $fh_css = Perl::Tidy::IOScalar->new( \$css_string, 'w' );
1184              
1185             # use css linked to another file,
1186 1 50       6 if ( $rOpts->{'html-linked-style-sheet'} ) {
    50          
1187 0         0 $fh_css->print(
1188             qq(<link rel="stylesheet" href="$css_linkname" type="text/css" />));
1189             }
1190              
1191             # or no css,
1192             elsif ( $rOpts->{'nohtml-style-sheets'} ) {
1193              
1194             }
1195              
1196             # or use css embedded in this file
1197             else {
1198 1         4 $fh_css->print( <<'ENDCSS');
1199             <style type="text/css">
1200             <!--
1201             ENDCSS
1202 1         4 write_style_sheet_data($fh_css);
1203 1         3 $fh_css->print( <<"ENDCSS");
1204             -->
1205             </style>
1206             ENDCSS
1207             }
1208              
1209             # -----------------------------------------------------------
1210             # path 2: use pod2html if requested
1211             # If we fail for some reason, continue on to path 3
1212             # -----------------------------------------------------------
1213 1 50       8 if ( $rOpts->{'pod2html'} ) {
1214 1         3 my $rpod_string = $self->{_rpod_string};
1215             $self->pod_to_html(
1216 1         3 ${$rpod_string}, $css_string,
1217 1 50       4 ${$rtoc_string}, $rpre_string_stack
  1         7  
1218             ) && return;
1219             }
1220              
1221             # --------------------------------------------------
1222             # path 3: write code in html, with pod only in italics
1223             # --------------------------------------------------
1224 1         3 my $input_file = $self->{_input_file};
1225 1         4 my $title = escape_html($input_file);
1226 1         3 my $timestamp = EMPTY_STRING;
1227 1 50       4 if ( $rOpts->{'timestamp'} ) {
1228 0         0 my $date = localtime;
1229 0         0 $timestamp = "on $date";
1230             }
1231 1         8 $html_fh->print( <<"HTML_START");
1232             <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
1233             "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1234             <!-- Generated by perltidy $timestamp -->
1235             <html xmlns="http://www.w3.org/1999/xhtml">
1236             <head>
1237             <title>$title</title>
1238             HTML_START
1239              
1240             # output the css, if used
1241 1 50       4 if ($css_string) {
1242 1         5 $html_fh->print($css_string);
1243 1         4 $html_fh->print( <<"ENDCSS");
1244             </head>
1245             <body>
1246             ENDCSS
1247             }
1248             else {
1249              
1250 0         0 $html_fh->print( <<"HTML_START");
1251             </head>
1252             <body bgcolor=\"$rOpts->{'html-color-background'}\" text=\"$rOpts->{'html-color-punctuation'}\">
1253             HTML_START
1254             }
1255              
1256 1         4 $html_fh->print("<a name=\"-top-\"></a>\n");
1257 1         5 $html_fh->print( <<"EOM");
1258             <h1>$title</h1>
1259             EOM
1260              
1261             # copy the table of contents
1262 1 0 33     3 if ( ${$rtoc_string}
  1   33     5  
1263             && !$rOpts->{'frames'}
1264             && $rOpts->{'html-table-of-contents'} )
1265             {
1266 0         0 $html_fh->print( ${$rtoc_string} );
  0         0  
1267             }
1268              
1269             # copy the pre section(s)
1270 1         4 my $fname_comment = $input_file;
1271 1         2 $fname_comment =~ s/--+/-/g; # protect HTML comment tags
1272 1         7 $html_fh->print( <<"END_PRE");
1273             <hr />
1274             <!-- contents of filename: $fname_comment -->
1275             <pre>
1276             END_PRE
1277              
1278 1         2 foreach my $rpre_string ( @{$rpre_string_stack} ) {
  1         3  
1279 1         3 $html_fh->print( ${$rpre_string} );
  1         4  
1280             }
1281              
1282             # and finish the html page
1283 1         4 $html_fh->print( <<"HTML_END");
1284             </pre>
1285             </body>
1286             </html>
1287             HTML_END
1288 1 50       13 $html_fh->close()
1289             if ( $html_fh->can('close') );
1290              
1291 1 50       4 if ( $rOpts->{'frames'} ) {
1292             ##my @toc = map { $_ .= "\n" } split /\n/, ${$rtoc_string};
1293 0         0 my @toc = map { $_ . "\n" } split /\n/, ${$rtoc_string};
  0         0  
  0         0  
1294 0         0 $self->make_frame( \@toc );
1295             }
1296 1         4 return;
1297             } ## end sub close_html_file
1298              
1299             sub markup_tokens {
1300 2     2 0 8 my ( $self, $rtokens, $rtoken_type, $rlevels ) = @_;
1301 2         5 my ( @colored_tokens, $type, $token, $level );
1302 2         6 my $rlast_level = $self->{_rlast_level};
1303 2         4 my $rpackage_stack = $self->{_rpackage_stack};
1304              
1305 2         5 foreach my $j ( 0 .. @{$rtoken_type} - 1 ) {
  2         6  
1306 46         70 $type = $rtoken_type->[$j];
1307 46         67 $token = $rtokens->[$j];
1308 46         62 $level = $rlevels->[$j];
1309 46 50       82 $level = 0 if ( $level < 0 );
1310              
1311             #-------------------------------------------------------
1312             # Update the package stack. The package stack is needed to keep
1313             # the toc correct because some packages may be declared within
1314             # blocks and go out of scope when we leave the block.
1315             #-------------------------------------------------------
1316 46 100       57 if ( $level > ${$rlast_level} ) {
  46 100       85  
1317 3 100       30 if ( !$rpackage_stack->[ $level - 1 ] ) {
1318 1         6 $rpackage_stack->[ $level - 1 ] = 'main';
1319             }
1320 3         8 $rpackage_stack->[$level] = $rpackage_stack->[ $level - 1 ];
1321             }
1322 43         70 elsif ( $level < ${$rlast_level} ) {
1323 3         16 my $package = $rpackage_stack->[$level];
1324 3 50       9 if ( !$package ) { $package = 'main' }
  0         0  
1325              
1326             # if we change packages due to a nesting change, we
1327             # have to make an entry in the toc
1328 3 50       8 if ( $package ne $rpackage_stack->[ $level + 1 ] ) {
1329 0         0 $self->add_toc_item( $package, 'package' );
1330             }
1331             }
1332             else {
1333             ## level unchanged
1334             }
1335 46         61 ${$rlast_level} = $level;
  46         62  
1336              
1337             #-------------------------------------------------------
1338             # Intercept a sub name here; split it
1339             # into keyword 'sub' and sub name; and add an
1340             # entry in the toc
1341             # Fix for c250: switch from 'i' to 'S'
1342             #-------------------------------------------------------
1343 46 50 33     93 if ( $type eq 'S' && $token =~ /^(\w+\s+)(\w.*)$/ ) {
1344 0         0 $token = $self->markup_html_element( $1, 'k' );
1345 0         0 push @colored_tokens, $token;
1346 0         0 $token = $2;
1347 0         0 $type = 'S';
1348              
1349             # but don't include sub declarations in the toc;
1350             # these will have leading token types 'i;'
1351 0         0 my $signature = join EMPTY_STRING, @{$rtoken_type};
  0         0  
1352 0 0       0 if ( $signature !~ /^i;/ ) {
1353 0         0 my $subname = $token;
1354 0         0 $subname =~ s/[\s\(].*$//; # remove any attributes and prototype
1355 0         0 $self->add_toc_item( $subname, 'sub' );
1356             }
1357             }
1358              
1359             #-------------------------------------------------------
1360             # Intercept a package name here; split it
1361             # into keyword 'package' and name; add to the toc,
1362             # and update the package stack
1363             #-------------------------------------------------------
1364             # Fix for c250: switch from 'i' to 'P' and allow 'class' or 'package'
1365 46 50 33     78 if ( $type eq 'P' && $token =~ /^(\w+\s+)(\w.*)$/ ) {
1366 0         0 $token = $self->markup_html_element( $1, 'k' );
1367 0         0 push @colored_tokens, $token;
1368 0         0 $token = $2;
1369 0         0 $type = 'i';
1370 0         0 $self->add_toc_item( "$token", 'package' );
1371 0         0 $rpackage_stack->[$level] = $token;
1372             }
1373              
1374 46         99 $token = $self->markup_html_element( $token, $type );
1375 46         92 push @colored_tokens, $token;
1376             }
1377 2         12 return ( \@colored_tokens );
1378             } ## end sub markup_tokens
1379              
1380             sub markup_html_element {
1381 46     46 0 86 my ( $self, $token, $type ) = @_;
1382              
1383 46 100       93 return $token if ( $type eq 'b' ); # skip a blank token
1384 25 50       72 return $token if ( $token =~ /^\s*$/ ); # skip a blank line
1385 25         41 $token = escape_html($token);
1386              
1387             # get the short abbreviation for this token type
1388 25         58 my $short_name = $token_short_names{$type};
1389 25 100       47 if ( !defined($short_name) ) {
1390 4         7 $short_name = "pu"; # punctuation is default
1391             }
1392              
1393             # handle style sheets..
1394 25 50       48 if ( !$rOpts->{'nohtml-style-sheets'} ) {
1395 25 100       51 if ( $short_name ne 'pu' ) {
1396 21         59 $token = qq(<span class="$short_name">) . $token . "</span>";
1397             }
1398             }
1399              
1400             # handle no style sheets..
1401             else {
1402 0         0 my $color = $html_color{$short_name};
1403              
1404 0 0 0     0 if ( $color && ( $color ne $rOpts->{'html-color-punctuation'} ) ) {
1405 0         0 $token = qq(<font color="$color">) . $token . "</font>";
1406             }
1407 0 0       0 if ( $html_italic{$short_name} ) { $token = "<i>$token</i>" }
  0         0  
1408 0 0       0 if ( $html_bold{$short_name} ) { $token = "<b>$token</b>" }
  0         0  
1409             }
1410 25         45 return $token;
1411             } ## end sub markup_html_element
1412              
1413             sub escape_html {
1414              
1415 26     26 0 43 my $token = shift;
1416 26 50 33     76 if ( $missing_html_entities || !$rOpts_html_entities ) {
1417 0         0 $token =~ s/\&/&amp;/g;
1418 0         0 $token =~ s/\</&lt;/g;
1419 0         0 $token =~ s/\>/&gt;/g;
1420 0         0 $token =~ s/\"/&quot;/g;
1421             }
1422             else {
1423 26         58 HTML::Entities::encode_entities($token);
1424             }
1425 26         368 return $token;
1426             } ## end sub escape_html
1427              
1428             sub finish_formatting {
1429              
1430             # called after last line
1431 1     1 0 3 my $self = shift;
1432 1         5 $self->close_html_file();
1433 1         3 return;
1434             } ## end sub finish_formatting
1435              
1436             sub write_line {
1437              
1438 2     2 0 6 my ( $self, $line_of_tokens ) = @_;
1439 2 50       8 return unless $self->{_html_file_opened};
1440 2         4 my $html_pre_fh = $self->{_html_pre_fh};
1441 2         6 my $line_type = $line_of_tokens->{_line_type};
1442 2         4 my $input_line = $line_of_tokens->{_line_text};
1443 2         5 my $line_number = $line_of_tokens->{_line_number};
1444 2         5 chomp $input_line;
1445              
1446             # markup line of code..
1447 2         4 my $html_line;
1448 2 50       6 if ( $line_type eq 'CODE' ) {
1449 2         4 my $rtoken_type = $line_of_tokens->{_rtoken_type};
1450 2         5 my $rtokens = $line_of_tokens->{_rtokens};
1451 2         5 my $rlevels = $line_of_tokens->{_rlevels};
1452              
1453 2 50       11 if ( $input_line =~ /(^\s*)/ ) {
1454 2         7 $html_line = $1;
1455             }
1456             else {
1457 0         0 $html_line = EMPTY_STRING;
1458             }
1459 2         9 my ($rcolored_tokens) =
1460             $self->markup_tokens( $rtokens, $rtoken_type, $rlevels );
1461 2         3 $html_line .= join EMPTY_STRING, @{$rcolored_tokens};
  2         14  
1462             }
1463              
1464             # markup line of non-code..
1465             else {
1466 0         0 my $line_character;
1467 0 0       0 if ( $line_type eq 'HERE' ) { $line_character = 'H' }
  0 0       0  
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
1468 0         0 elsif ( $line_type eq 'HERE_END' ) { $line_character = 'h' }
1469 0         0 elsif ( $line_type eq 'FORMAT' ) { $line_character = 'H' }
1470 0         0 elsif ( $line_type eq 'FORMAT_END' ) { $line_character = 'h' }
1471 0         0 elsif ( $line_type eq 'SKIP' ) { $line_character = 'H' }
1472 0         0 elsif ( $line_type eq 'SKIP_END' ) { $line_character = 'h' }
1473 0         0 elsif ( $line_type eq 'SYSTEM' ) { $line_character = 'c' }
1474             elsif ( $line_type eq 'END_START' ) {
1475 0         0 $line_character = 'k';
1476 0         0 $self->add_toc_item( '__END__', '__END__' );
1477             }
1478             elsif ( $line_type eq 'DATA_START' ) {
1479 0         0 $line_character = 'k';
1480 0         0 $self->add_toc_item( '__DATA__', '__DATA__' );
1481             }
1482             elsif ( $line_type =~ /^POD/ ) {
1483              
1484             # fix for c250: changed 'P' to 'pd' here and in %token_short_names
1485             # to allow use of 'P' as new package token type
1486 0         0 $line_character = 'pd';
1487 0 0       0 if ( $rOpts->{'pod2html'} ) {
1488 0         0 my $html_pod_fh = $self->{_html_pod_fh};
1489 0 0       0 if ( $line_type eq 'POD_START' ) {
1490              
1491 0         0 my $rpre_string_stack = $self->{_rpre_string_stack};
1492 0         0 my $rpre_string = $rpre_string_stack->[-1];
1493              
1494             # if we have written any non-blank lines to the
1495             # current pre section, start writing to a new output
1496             # string
1497 0 0       0 if ( ${$rpre_string} =~ /\S/ ) {
  0         0  
1498 0         0 my $pre_string;
1499 0         0 $html_pre_fh =
1500             Perl::Tidy::IOScalar->new( \$pre_string, 'w' );
1501 0         0 $self->{_html_pre_fh} = $html_pre_fh;
1502 0         0 push @{$rpre_string_stack}, \$pre_string;
  0         0  
1503              
1504             # leave a marker in the pod stream so we know
1505             # where to put the pre section we just
1506             # finished.
1507 0         0 my $for_html = '=for html'; # don't confuse pod utils
1508 0         0 $html_pod_fh->print(<<EOM);
1509              
1510             $for_html
1511             <!-- pERLTIDY sECTION -->
1512              
1513             EOM
1514             }
1515              
1516             # otherwise, just clear the current string and start
1517             # over
1518             else {
1519 0         0 ${$rpre_string} = EMPTY_STRING;
  0         0  
1520 0         0 $html_pod_fh->print("\n");
1521             }
1522             }
1523 0         0 $html_pod_fh->print( $input_line . "\n" );
1524 0 0       0 if ( $line_type eq 'POD_END' ) {
1525 0         0 $self->{_pod_cut_count}++;
1526 0         0 $html_pod_fh->print("\n");
1527             }
1528 0         0 return;
1529             }
1530             }
1531 0         0 else { $line_character = 'Q' }
1532 0         0 $html_line = $self->markup_html_element( $input_line, $line_character );
1533             }
1534              
1535             # add the line number if requested
1536 2 50       15 if ( $rOpts->{'html-line-numbers'} ) {
1537 0 0       0 my $extra_space =
    0          
    0          
1538             ( $line_number < 10 ) ? SPACE x 3
1539             : ( $line_number < 100 ) ? SPACE x 2
1540             : ( $line_number < 1000 ) ? SPACE
1541             : EMPTY_STRING;
1542 0         0 $html_line = $extra_space . $line_number . SPACE . $html_line;
1543             }
1544              
1545             # write the line
1546 2         15 $html_pre_fh->print("$html_line\n");
1547 2         36 return;
1548             } ## end sub write_line
1549             1;