File: listings.perl

package info (click to toggle)
latex2html 2023-debian1-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 6,924 kB
  • sloc: perl: 34,024; makefile: 437; sh: 32
file content (520 lines) | stat: -rw-r--r-- 17,206 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
# -*- perl -*-
#
# $Id: verbatim.perl,v 1.10 2001/11/29 21:44:13 RRM Exp $
#
# listings.perl
#   Georgy Salnikov <sge@nmr.nioch.nsc.ru> 13/10/20
#
# Extension to LaTeX2HTML V2018 to partly support the "listings" package.
#
# Partly derived from verbatim.perl and verbatimfiles.perl
#
# Change Log:
# ===========
#
# $Log: verbatim.perl,v $
#
# Note:
# This module provides translation for the \lstlisting environment
# and for some more commands of the listings.sty package
#
# Handling decisions are done together with verbatim by LaTeX2HTML main program
#
# Several global variables and arrays are defined in the initialization block
# of the LaTeX2HTML main program:
#
# %lstset_current
# %lstset_style
# $lst_name
# $lst_last_counter
# %lst_auto_counter
#

package main;

# This package very probably may be used in listings
&do_require_package("color");

# Implementation of \lstinputlisting[]{}, preparation only
sub do_cmd_lstinputlisting {
  local($_) = @_;
  local($outer,$file);

  local($dum,$option) = &get_verb_optional_argument;
  $file = &missing_braces unless (
    (s/$next_pair_pr_rx/$file=$2;''/eo)
    ||(s/$next_pair_rx/$file=$2;''/eo));
  $outer = $_;

  local($closures,$reopens) = &preserve_open_tags;
  my ($verb_pre, $verb_post) = ('<PRE>','</PRE>');
  if ($USING_STYLES) {
    $env_id .= ' CLASS="verbatim"' unless ($env_id =~ /(^|\s)CLASS\s*\=/i);
    $verb_pre =~ s/>/ $env_id>/;
  }

  # %verbatim not coupled to a dbm => will not work in subprocesses, but don't mind
  $verbatim{++$global{'verbatim_counter'}} = $option.$file;

  # Do nothing here, just wrap into a verbatim-like lstlisting environment.
  # File reading and decorating postponed to &process_lstlisting.
  join('', $closures, $verb_pre
       , $verbatim_mark, 'lstfile', $global{'verbatim_counter'}
       , '#', $verb_post, $reopens, $outer);
}

# Implementation of \lstset{}, preparation only
sub do_cmd_lstset {
  local($_) = @_;
  local($option) = &missing_braces unless (
    (s/$next_pair_pr_rx/$option=$2;''/eo)
    ||(s/$next_pair_rx/$option=$2;''/eo));
  local($outer) = $_;

  # In preamble this just initializes the defaults
  if ($PREAMBLE) {
    my(%opts) = &lst_parse_options($option);
    @lstset_current{keys %opts} = (values %opts);
    return $outer;
  }

  # Do nothing here, just wrap into a verbatim-like lstset environment.
  # Processing options will be done later by &process_lstlisting.
  local($closures,$reopens) = &preserve_open_tags;
  $verbatim{++$global{'verbatim_counter'}} = $option;
  join('', $closures, $verbatim_mark, 'lstset', $global{'verbatim_counter'},
       '#', $reopens, $outer);
}

# Implementation of \lstdefinestyle{}{}, preparation only
sub do_cmd_lstdefinestyle {
  local($_) = @_;
  local($style) = &missing_braces unless (
    (s/$next_pair_pr_rx/$style=$2;''/eo)
    ||(s/$next_pair_rx/$style=$2;''/eo));
  local($option) = &missing_braces unless (
    (s/$next_pair_pr_rx/$option=$2;''/eo)
    ||(s/$next_pair_rx/$option=$2;''/eo));
  local($outer) = $_;

  # In preamble this just creates the new style
  if ($PREAMBLE) {
    my(%opts) = &lst_parse_options($option);
    @{$lstset_style{$style}}{keys %opts} = (values %opts);
    return $outer;
  }

  # Do nothing here, just wrap into a verbatim-like lststyle environment.
  # Processing options will be done later by &process_lstlisting.
  local($closures,$reopens) = &preserve_open_tags;
  $verbatim{++$global{'verbatim_counter'}} = join(',', $style, $option);
  join('', $closures, $verbatim_mark, 'lststyle', $global{'verbatim_counter'},
       '#', $reopens, $outer);
}

# Implementation of \lstname, not much to do
sub do_cmd_lstname { $lst_name . $_[0]; }

# This is the main driver subroutine for the lstlisting environment
sub process_lstlisting {
  local($lst_pre, $lst_post, $lst_cmd, $_) = @_;

  # Check if it was an auxiliary command wrapped into an environment
  if ($lst_cmd eq 'lstset')   { return &set_lstset($_);   }
  if ($lst_cmd eq 'lststyle') { return &set_lststyle($_); }

  # Process an actual lstlisting environment
  local($option,$dum) = &get_verb_optional_argument;
  local($contents) = $_;

  # Fetch current defaults and apply specified options...
  my(%curopts) = %lstset_current;
  my(%opts) = &lst_parse_options($option);

  # First of all, apply either specified or default style if given
  if ($opts{'style'} ne '') {
    @curopts{keys %{$lstset_style{$opts{'style'}}}} =
      (values %{$lstset_style{$opts{'style'}}});
  }
  elsif ($lstset_current{'style'} ne '') {
    @curopts{keys %{$lstset_style{$lstset_current{'style'}}}} =
      (values %{$lstset_style{$lstset_current{'style'}}});
  }

  # Now apply all the other specified options
  @curopts{keys %opts} = (values %opts);

  $lst_name = $curopts{'name'};

  # For wrapped lstinputlisting - replace file name with file contents
  if ($lst_cmd eq 'lstfile') {
    my($dir);
    my($file) = $contents;
    $lst_name = $file;
    my($file2) = "$file.tex";
    if ($file !~ /\.tex$/) {
      # 2nd choice is better than 1st - TeXnical quirk
      ($file,$file2) = ($file2,$file);
    }
    my($inputpath) = $curopts{'inputpath'};
    my($found) = 0;
    foreach $dir ("$texfilepath", split(/:/,$ENV{'TEXINPUTS'})) { 
      if (-f ($_ = "$dir/$inputpath/$file") ||
	  -f ($_ = "$dir/$inputpath/$file2") ||
	  -f ($_ = "$dir/$file") ||
	  -f ($_ = "$dir/$file2")) {
	$found = 1;
	# overread $_ with file contents
	&slurp_input($_);
	last;
      }
    }
    &write_warnings("No file <$file> for lstinputlisting.") unless $found;
    # pre_process file contents
    if (defined &replace_all_html_special_chars) {
      &replace_all_html_special_chars;
    } else {
      &replace_html_special_chars;
    }
    s/\n$//;		# vertical space is contributed by </PRE> already.
    $contents = $_;
  }

  # Interpret the rest of options in sequence...
  # Line numbering (numbers,stepnumber,numberfirstline,numbersep,firstnumber)
  my($i, $counter, $cline, $nlines);
  $cline = '';
  $i = $counter = $nlines = 0;
  my($step) = sprintf("%.0f", $curopts{'stepnumber'});
  my($incr) = $step<=>0;
  $curopts{'numbers'} = 'none' if (! $incr);
  my($nspaces);
  ($nspaces, $dum) = &convert_length ($curopts{'numbersep'}, 1);
  $nspaces = sprintf("%.0f", $nspaces/10);
  my($fcount) = $curopts{'firstnumber'};
  if ($fcount eq 'auto') {
    $fcount = 1;
    if ($lst_name ne '' && $curopts{'numbers'} ne 'none') {
      $lst_auto_counter{$lst_name} = 1
	unless (exists ($lst_auto_counter{$lst_name}));
      $fcount = $lst_auto_counter{$lst_name};
    }
  } elsif ($fcount eq 'last') {
    $fcount = $lst_last_counter;
  } else {
    $fcount = sprintf("%.0f", $fcount);
  }

  # Captioning options group (title, caption, captionpos)
  my($title, $caption, $cappos);
  $cappos = 'TOP';
  $cappos = 'BOTTOM' if ($curopts{'captionpos'} eq 'b');
  $title  = '';
  if ($curopts{'title'} ne '') {
    $_ = $curopts{'title'};
    # Replace braces with marks and try to interpret this as bracketed command
    &lst_translate_option;
    &replace_markers;
    s/^\s+//;
    s/\s+$//;
    $title = $_;
  }
  $caption = '';
  if ($curopts{'caption'} ne '') {
    $_ = $curopts{'caption'};
    # Replace braces with marks and try to interpret this as bracketed command
    &lst_translate_option;
    &replace_markers;
    # Evtl separate short and full captions
    ($option,$dum) = &get_verb_optional_argument;
    s/^\s+//;
    s/\s+$//;
    $_ = $option if ($_ eq '');
    s/^\s+//;
    s/\s+$//;
    $caption = $_;
  }
  # If caption empty, use title
  if ($caption ne '') {
    $caption = 'Listing: ' . $caption;
  } else {
    $caption = $title;
  }

  # basicstyle option: convert it to a pair of opening and closing HTML tags
  my($bstyle_open, $bstyle_close);
  $_ = $curopts{'basicstyle'};
  # Replace braces with marks and try to interpret this as bracketed command
  &lst_translate_option;
  s/$O\d+$C//go;		# Get rid of bracket id's
  s/$OP\d+$CP//go;		# Get rid of processed bracket id's
  $bstyle_open = $bstyle_close = ''
    unless (s/^\s*((<\w+[^>]*>\s*)+)[^<]*((<\/\w+>\s*)+)$/$bstyle_open=$1;$bstyle_close=$3;''/eo);
  $bstyle_open  = $bstyle_open."\n"  if ($bstyle_open  ne '');
  $bstyle_close = "\n".$bstyle_close if ($bstyle_close ne '');

  # Framing options group (frame, framesep, framerule)
  my($frame) = 'VOID';
  $frame = 'LHS'    if ($curopts{'frame'} eq 'leftline');
  $frame = 'ABOVE'  if ($curopts{'frame'} eq 'topline');
  $frame = 'BELOW'  if ($curopts{'frame'} eq 'bottomline');
  $frame = 'HSIDES' if ($curopts{'frame'} eq 'lines');
  $frame = 'BORDER'
    if ($curopts{'frame'} eq 'single' || $curopts{'frame'} eq 'shadowbox');
  if ($curopts{'frame'} =~ /^[trblTRBL]+$/) {
    my($t, $r, $b, $l, $c);
    foreach $c (split //, $curopts{'frame'}) {
      $t = 1 if ("\L$c" eq 't');
      $r = 1 if ("\L$c" eq 'r');
      $b = 1 if ("\L$c" eq 'b');
      $l = 1 if ("\L$c" eq 'l');
    }
    if ($t && $r && $b && $l) { $frame = 'BORDER'; }
    elsif ($t && $b)          { $frame = 'HSIDES'; }
    elsif ($r && $l)          { $frame = 'VSIDES'; }
    elsif ($t)                { $frame = 'ABOVE';  }
    elsif ($b)                { $frame = 'BELOW';  }
    elsif ($l)                { $frame = 'LHS';    }
    elsif ($r)                { $frame = 'RHS';    }
  }
  my($framesep);
  ($framesep, $dum) = &convert_length ($curopts{'framesep'}, 1);
  $framesep = sprintf("%.0f", $framesep);
  my($framerule);
  ($framerule, $dum) = &convert_length ($curopts{'framerule'}, 1);
  $framerule = sprintf("%.0f", $framerule);
  if ($framerule == 1) { # Special case specifying a very thin border
    $framerule = '';
  } else {
    $framerule = " BORDER=\"$framerule\"";
  }

  # Coloring options group (backgroundcolor, rulecolor)
  my($bgcolor);
  $_ = $curopts{'backgroundcolor'};
  if (/^\s*\\/) {
    # Translate as a command and extract color value from the HTML tag
    &lst_translate_option;
    s/$O\d+$C//go;		# Get rid of bracket id's
    s/$OP\d+$CP//go;		# Get rid of processed bracket id's
    # Extract color value or clear the malformed contents
    $_ = ''
      unless (s/^\s*<FONT\s+COLOR\s*=\s*"\s*// && s/\s*"\s*>\s*<\/FONT>\s*$//);
  } else {
    # Must be a \color command
    $_ = '';
  }
  $bgcolor = $_;
  $bgcolor = " BGCOLOR=\"$bgcolor\"" unless ($bgcolor eq '');
  my($rulecolor);
  $_ = $curopts{'rulecolor'};
  if (/^\s*\\/) {
    # Translate as a command and extract color value from the HTML tag
    &lst_translate_option;
    s/$O\d+$C//go;		# Get rid of bracket id's
    s/$OP\d+$CP//go;		# Get rid of processed bracket id's
    # Extract color value or clear the malformed contents
    $_ = ''
      unless (s/^\s*<FONT\s+COLOR\s*=\s*"\s*// && s/\s*"\s*>\s*<\/FONT>\s*$//);
  } else {
    # Must be a \color command
    $_ = '';
  }
  $rulecolor = $_;
  $rulecolor = " BORDERCOLOR=\"$rulecolor\"" unless ($rulecolor eq '');

  $lst_pre  = $bstyle_open.$lst_pre."\n";
  $lst_post = "\n".$lst_post.$bstyle_close;
  $_ = $contents;

  # Evtl generate line numbers
  if ($curopts{'numbers'} eq 'left') {
    # Insert numbers from the left side.
    $counter = $fcount-$incr;
    s/^/$i++; $counter+=$incr;
    (($counter % $step) && ($curopts{'numberfirstline'} ne 'true' || $i > 1)) ?
      ('     ' . ' ' x $nspaces) :
      (sprintf("%5d",$counter) . ' ' x $nspaces)/mge;
    $lst_last_counter = $counter+$incr;
    $lst_auto_counter{$lst_name} = $lst_last_counter
      if ($curopts{'firstnumber'} eq 'auto' && $lst_name ne '');
  }
  elsif ($curopts{'numbers'} eq 'right' && $HTML_VERSION > 2.1) {
    # Inserting right padded numbers for every line is tricky.
    # Do it as a table with two huge columns with verbatim contents.
    # But only if HTML version is high enough...
    s/$/$nlines++;''/mge;
    for ($counter=$fcount; $i<$nlines; $i++) {
      $cline .= (($counter % $step) &&
		 ($curopts{'numberfirstline'} ne 'true' || $i > 0)) ?
	(' ' x $nspaces . "     \n") :
	(' ' x $nspaces . sprintf("%d\n",$counter));
      $counter += $incr;
    }
    $lst_last_counter = $counter;
    $lst_auto_counter{$lst_name} = $lst_last_counter
      if ($curopts{'firstnumber'} eq 'auto' && $lst_name ne '');
    $cline =~ s/\n$//;
    $_ = "<TABLE><TR><TD>"
      .$lst_pre.$_.$lst_post."</TD><TD ALIGN=\"RIGHT\">"
      .$lst_pre.$cline.$lst_post."</TD></TR></TABLE>";
    $lst_pre = $lst_post = '';
  }

  # Frames, captions and coloring are generated also as a synthetic table
  if ($HTML_VERSION > 2.1) {
    $lst_pre = "<TABLE FRAME=\"$frame\" CELLPADDING=\"$framesep\""
      .$framerule.$bgcolor.$rulecolor.">"
      .(($caption ne '') ?
	("\n<CAPTION ALIGN=\"$cappos\">".$caption."</CAPTION>\n") : '')
      ."<TR><TD>\n".$lst_pre;
    $lst_post = $lst_post."\n</TD></TR></TABLE>";
  }

  # WHEW !!!
  $lst_pre.$_.$lst_post."\n";
}

# Driver subroutine for the real processing of \lstinline command
sub process_lstinline {
  local($_, $contents) = @_;

  # Get lstinline options
  local($option,$dum) = &get_verb_optional_argument;

  # Fetch current defaults and apply specified options...
  my(%curopts) = %lstset_current;
  my(%opts) = &lst_parse_options($option);

  # First of all, apply either specified or default style if given
  if ($opts{'style'} ne '') {
    @curopts{keys %{$lstset_style{$opts{'style'}}}} =
      (values %{$lstset_style{$opts{'style'}}});
  }
  elsif ($lstset_current{'style'} ne '') {
    @curopts{keys %{$lstset_style{$lstset_current{'style'}}}} =
      (values %{$lstset_style{$lstset_current{'style'}}});
  }

  # Now apply all the other specified options
  @curopts{keys %opts} = (values %opts);

  # basicstyle seems the only option which can be really useful for lstinline
  my($bstyle_open, $bstyle_close);
  $_ = $curopts{'basicstyle'};
  # Replace braces with marks and try to interpret this as bracketed command
  &lst_translate_option;
  s/$O\d+$C//go;		# Get rid of bracket id's
  s/$OP\d+$CP//go;		# Get rid of processed bracket id's
  $bstyle_open = $bstyle_close = ''
    unless (s/^\s*((<\w+[^>]*>\s*)+)[^<]*((<\/\w+>\s*)+)$/$bstyle_open=$1;$bstyle_close=$3;''/eo);

  # Make the actual lstinline output
  $bstyle_open.'<code>'.$contents.'</code>'.$bstyle_close;
}

# Driver subroutine for \lstset processing
# Just copy the given set of options to the default options set
sub set_lstset {
  local($_) = @_;
  my(%opts) = &lst_parse_options($_);
  @lstset_current{keys %opts} = (values %opts);
  '';
}

# Driver subroutine for \lstdefinestyle
# Similar to the preceding but maintains distinct named options sets (styles)
sub set_lststyle {
  local($_) = @_;
  local($style) = '' unless (s/^(\w+),/$style=$1;''/eo);
  return '' if ($style eq '');
  my(%opts) = &lst_parse_options($_);
  @{$lstset_style{$style}}{keys %opts} = (values %opts);
  '';
}

# Option parser for lstlisting commands/envs family, this can be tricky
sub lst_parse_options {
  local($_) = @_;

  # First get rid of comment marks
  s/(\\\w+)$comment_mark\d*\s*?\n[ \t]*/$1 \n/go;
  s/($comment_mark\d*\s*)+\n[ \t]*/\n/go;

  # Find any kinds of brackets to keep them intact
  # for the case if they might have commas inside
  my(@fields, @chunks);
  my($before, $match, $after);
  while (/$any_next_pair_pr_rx|$any_next_pair_rx4|$opt_arg_rx|$verb_braces_rx/)
  {
    ($before, $match, $after) = ($`, $&, $');
    @chunks = split(/,/, $before);
    $fields[$#fields] .= shift(@chunks) if @fields;
    push(@fields, @chunks);
    $fields[$#fields] .= $match;
    $_ = $after;
  }
  @chunks = split(/,/);
  $fields[$#fields] .= shift(@chunks) if @fields;
  push(@fields, @chunks);

  # All options are separated, now split them to option name and value
  my(%opts);
  my($par, $val);
  foreach (@fields) {
    next unless ($par, $val) = /^\s*(\w+?)\s*=\s*(.*)\s*$/s;
    $opts{$par} = $val;
  }
  %opts;
}

# Replace braces with marks and try to interpret this as a bracketed command
sub lst_translate_option {
  # Modifies $_
  &mark_string($_);
  my($br_id) = ++$global{'max_id'};
  $_ = $O.$br_id.$C.$_.$O.$br_id.$C;
  $_ = &translate_environments($_);
  $_ = &translate_commands($_);
}

# &get_next_optional_argument is not well suited for lstlisting
# Here is a special version of &get_next_optional_argument
sub get_verb_optional_argument {
  local($next, $pat);
  my($before, $match, $after);
  $next = $pat = $match = '';
  if (s/^(\[)/$pat=$1;''/eo) {
    # Find any kinds of braces not to stuck on [] evtl nested between them
    while (/$any_next_pair_pr_rx|$any_next_pair_rx4|$verb_braces_rx|\]/) {
      ($before, $match, $after) = ($`, $&, $');
      $next .= $before;
      $pat  .= $before;
      if ($match eq ']') {
	$pat .= $match;
	$_ = $after;
	# Before returning, remove comment mark and newline after closing ]
	s/^[ \t]*($comment_mark\d*[ \t]*)*\n//o;
	last;
      }
      $next .= $match;
      $pat  .= $match;
      $_ = $after;
    }
    if ($match ne ']') {
      # Closing ] not found, revert back $_ and clear arguments
      $_ = $pat . $_;
      $next = $pat = '';
    }
  }
  # Imitate return of &get_next_optional_argument
  ($next, $pat);
}

&process_commands_wrap_deferred (<<_RAW_ARG_DEFERRED_CMDS_);
lstinputlisting # [] # {}
lstdefinestyle # {} # {}
lstset # {}
_RAW_ARG_DEFERRED_CMDS_

1;			# Must be last line