Subversion Repositories DevTools

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1293 dpurdie 1
###############################################################################
2
# Codestriker: Copyright (c) 2001, 2002 David Sitsky.  All rights reserved.
3
# sits@users.sourceforge.net
4
#
5
# This program is free software; you can redistribute it and modify it under
6
# the terms of the GPL.
7
 
8
# Collection of routines for rendering HTML output.
9
 
10
package Codestriker::Http::Render;
11
 
12
use strict;
13
use DBI;
14
use CGI::Carp 'fatalsToBrowser';
15
use HTML::Entities ();
16
 
17
# Colour to use when displaying the line number that a comment is being made
18
# against.
19
my $CONTEXT_COLOUR = "red";
20
 
21
sub _normal_mode_start( $ );
22
sub _normal_mode_finish( $ );
23
sub _coloured_mode_start( $ );
24
sub _coloured_mode_finish( $ );
25
 
26
# New lines within a diff block.
27
my @diff_new_lines = ();
28
 
29
# The corresponding lines they refer to.
30
my @diff_new_lines_numbers = ();
31
 
32
# The corresponding offsets they refer to.
33
my @diff_new_lines_offsets = ();
34
 
35
# Old lines within a diff block.
36
my @diff_old_lines = ();
37
 
38
# The corresponding lines they refer to.
39
my @diff_old_lines_numbers = ();
40
 
41
# A record of added and removed lines for a given diff block when displaying a
42
# file in a popup window, along with their offsets.
43
my @view_file_minus = ();
44
my @view_file_plus = ();
45
my @view_file_minus_offset = ();
46
my @view_file_plus_offset = ();
47
 
48
# What colour a line should appear if it has a comment against it.
49
my $COMMENT_LINE_COLOUR = "red";
50
 
51
# Constructor for rendering complex data.
52
sub new ($$$$$$$\%\@$$\@\@\@\@$$) {
53
    my ($type, $query, $url_builder, $parallel, $max_digit_width, $topic,
54
	$mode, $comments, $tabwidth, $repository, $filenames_ref,
55
	$revisions_ref, $binaries_ref, $numchanges_ref, $max_line_length,
56
	$brmode, $fview) = @_;
57
 
58
    # Record all of the above parameters as instance variables, which remain
59
    # constant while we render code lines.
60
    my $self = {};
61
    $self->{query} = $query;
62
    $self->{url_builder} = $url_builder;
63
    $self->{parallel} = $parallel;
64
    $self->{max_digit_width} = $max_digit_width;
65
    $self->{topic} = $topic;
66
    $self->{mode} = $mode;
67
    if (! defined $brmode) {
68
        $brmode = $Codestriker::default_topic_br_mode;
69
    }
70
    if (! defined $fview) {
71
	$fview = $Codestriker::default_file_to_view;
72
    }	
73
    $self->{brmode} = $brmode;
74
    $self->{fview}  = $fview;
75
    $self->{comments} = $comments;
76
    $self->{tabwidth} = $tabwidth;
77
    $self->{repository} = $repository;
78
    $self->{filenames_ref} = $filenames_ref;
79
    $self->{revisions_ref} = $revisions_ref;
80
    $self->{binaries_ref} = $binaries_ref;
81
    $self->{numchanges_ref} = $numchanges_ref;
82
    $self->{max_line_length} = $max_line_length;
83
    $self->{old_linenumber} = 1;
84
    $self->{new_linenumber} = 1;
85
 
86
    # Get the main entry to the database
87
    my $topic_obj = Codestriker::Model::Topic->new($self->{topic});
88
    # Check for readonly
89
    $self->{topic_state} = $topic_obj->{topic_state};
90
 
91
    # Build a hash from filenumber|fileline|new -> comment array, so that
92
    # when rendering, lines can be coloured appropriately.  Also build a list
93
    # of what points in the review have a comment.  Also record a mapping
94
    # from filenumber|fileline|new -> the comment number.
95
    my %comment_hash = ();
96
    my @comment_locations = ();
97
    my %comment_location_map = ();
98
    for (my $i = 0; $i <= $#$comments; $i++) {
99
	my $comment = $$comments[$i];
100
	my $key = $comment->{filenumber} . "|" . $comment->{fileline} . "|" .
101
	    $comment->{filenew};
102
	if (! exists $comment_hash{$key}) {
103
	    push @comment_locations, $key;
104
	    $comment_location_map{$key} = $#comment_locations;
105
	}
106
        push @{ $comment_hash{$key} }, $comment;
107
    }
108
    $self->{comment_hash} = \%comment_hash;
109
    $self->{comment_locations} = \@comment_locations;
110
    $self->{comment_location_map} = \%comment_location_map;
111
 
112
    # Also have a number of additional private variables which need to
113
    # be initialised.
114
    $self->{diff_current_filename} = "";
115
    $self->{diff_current_revision} = "";
116
    $self->{diff_current_repmatch} = 0;
117
 
118
    # Check if the repository has an associated LXR mapping, and if so, 
119
    # setup a db connection and prepare a select statement.
120
    if (defined $repository) {
121
	my $value = $Codestriker::lxr_map->{$repository->toString()};
122
	if (defined $value) {
123
	    my %lxr = %{ $value };
124
 
125
	    my $passwd = $lxr{password};
126
	    if (! defined $passwd) {
127
		# For backwards compatibility.
128
		$passwd = $lxr{passwd};
129
	    }
130
	    my $dbh = DBI->connect($lxr{db}, $lxr{user}, $passwd,
131
				   {AutoCommit=>0, RaiseError=>1})
132
		|| die "Couldn't connect to database: " . DBI->errstr;
133
	    my $select_ids =
134
		$dbh->prepare_cached('SELECT count(symname) FROM symbols where symname = ?');
135
	    $self->{idhashref} = {};
136
	    $self->{idhashsth} = $select_ids;
137
	    $self->{idhashdbh} = $dbh;
138
	    $self->{lxr_base_url} = $lxr{url};
139
	}
140
	else {
141
	    # No LXR mapping defined for this repository.
142
	    $self->{idhashref} = undef;
143
	}
144
    }
145
    else {
146
	# Topic has no repository, so no LXR mapping.
147
	$self->{idhashref} = undef;
148
    }
149
 
150
    bless $self, $type;
151
}
152
 
153
# cleanup, disconnect from the lxr database if connected
154
sub DESTROY {
155
    my $self = shift;
156
    $self->{idhashdbh}->disconnect() if exists $self->{idhashdbh};
157
} 
158
 
159
 
160
# Given an identifier, wrap it within the appropriate <A HREF> tag if it
161
# is a known identifier to LXR, otherwise just return the id.  To avoid
162
# excessive crap, only consider those identifiers which are at least 4
163
# characters long.
164
sub lxr_ident($$) {
165
    my ($self, $id) = @_;
166
 
167
    my $idhashref = $self->{idhashref};
168
 
169
    if (length($id) >= 4) {
170
 
171
	# Check if the id has not yet been found in lxr.
172
    	if (not exists $idhashref->{$id}) {
173
	    $idhashref->{$id} = 0;        # By default not found.
174
	    my $sth = $self->{idhashsth}; # DB statement handle.
175
 
176
	    # Fetch ids from lxr and store in hash.
177
	    $sth->execute($id);
178
	    ($idhashref->{$id}) = $sth->fetchrow_array();
179
        }
180
    }
181
 
182
    # Check if the id has been found in lxr.
183
    if ($$idhashref{$id}) {
184
	return "<A HREF=\"" . $self->{lxr_base_url} . "$id\" " .
185
	    "CLASS=\"fid\">$id</A>";
186
    } else {
187
	return $id;
188
    }
189
}
190
 
191
# Parse the line and product the appropriate hyperlinks to LXR.
192
# Currently, this is very Java/C/C++ centric, but it will do for now.
193
sub lxr_data($$) {
194
    my ($self, $data) = @_;
195
 
196
    # Don't do anything if LXR is not enabled for this topic.
197
    return $data if ! defined $self->{idhashref};
198
 
199
    # If the line is just a comment, don't do any processing.  Note this code
200
    # isn't bullet-proof, but its good enough most of the time.
201
    $_ = $data;
202
    return $data if (/^(\s|&nbsp;)*\/\// || /^(\s|&nbsp;){0,10}\*/ ||
203
		     /^(\s|&nbsp;){0,10}\/\*/ ||
204
		     /^(\s|&nbsp;)*\*\/(\s|&nbsp;)*$/);
205
 
206
    # Handle package Java statements.
207
    if ($data =~ /^(package(\s|&nbsp;)+)([\w\.]+)(.*)$/) {
208
	return $1 . $self->lxr_ident($3) . $4;
209
    }
210
 
211
    # Handle Java import statements.
212
    if ($data =~ /^(import(\s|&nbsp;)+)([\w\.]+)\.(\w+)((\s|&nbsp;)*)(.*)$/) {
213
	return $1 . $self->lxr_ident($3) . "." . $self->lxr_ident($4) . "$5$7";
214
    }
215
 
216
    # Handle #include statements.  Note, these aren't identifier lookups, but
217
    # need to be mapped to http://localhost.localdomain/lxr/xxx/yyy/incfile.h
218
    # Should include the current filename in the object for matching purposes.
219
#    if (/^(\#\s*include\s+[\"<])(.*?)([\">].*)$/) {
220
#	return $1 . $self->lxr_ident($2) . $3;
221
#    }
222
 
223
    # Break the string into potential identifiers, and look them up to see
224
    # if they can be hyperlinked to an LXR lookup.
225
    my $idhashref = $self->{idhashref};
226
    my @data_tokens = split /([A-Za-z][\w]+)/, $data;
227
    my $newdata = "";
228
    my $in_comment = 0;
229
    my $eol_comment = 0;
230
    for (my $i = 0; $i <= $#data_tokens; $i++) {
231
	my $token = $data_tokens[$i];
232
	if ($token =~ /^[A-Za-z]/) {
233
	    if ($eol_comment || $in_comment) {
234
		# Currently in a comment, don't LXRify.
235
		$newdata .= $token;
236
	    } elsif ($token eq "nbsp" || $token eq "quot" || $token eq "amp" ||
237
		     $token eq "lt" || $token eq "gt") {
238
		# HACK - ignore potential HTML entities.  This needs to be
239
		# done in a smarter fashion later.
240
		$newdata .= $token;
241
	    } else {
242
		$newdata .= $self->lxr_ident($token);
243
	    }
244
	} else {
245
	    $newdata .= $token;
246
	    $token =~ s/(\s|&nbsp;)//g;
247
 
248
	    # Check if we are entering or exiting a comment.
249
	    if ($token =~ /\/\//) {
250
		$eol_comment = 1;
251
	    } elsif ($token =~ /\*+\//) {
252
		$in_comment = 0;
253
	    } elsif ($token =~ /\/\*/) {
254
		$in_comment = 1;
255
	    }
256
	}
257
    }
258
 
259
    return $newdata;
260
}
261
 
262
# Render a delta.  If the filename has changed since the last delta, output the
263
# appropriate file headers. Pass in the delta object you want to render.
264
sub delta ($$$$$$$$$$) {
265
    my ($self, $delta) = @_;
266
 
267
    my $filename = $delta->{filename};
268
    my $filenumber = $delta->{filenumber},
269
    my $revision = $delta->{revision};
270
    my $old_linenumber = $delta->{old_linenumber};
271
    my $new_linenumber = $delta->{new_linenumber};
272
    my $text = $delta->{text};
273
    my $description = $delta->{description};
274
    my $binary = $delta->{binary};
275
    my $repmatch = $delta->{repmatch};
276
 
277
    # Don't do anything for binary files.
278
    return if $binary;
279
 
280
    my $query = $self->{query};
281
 
282
    if ($delta->is_delta_new_file() == 0)
283
    {
284
        # Check if the file heading needs to be output.
285
        if ($self->{diff_current_filename} ne $filename) {
286
	    $self->delta_file_header($filename, $revision, $repmatch);
287
        }
288
 
289
        # Display the delta heading.
290
        $self->delta_heading($filenumber, $revision, $old_linenumber,
291
			     $new_linenumber, $description, $repmatch);
292
 
293
        # Now render the actual diff text itself.
294
        $self->delta_text($filename, $filenumber, $revision, $old_linenumber,
295
			  $new_linenumber, $text, $repmatch, 1, 1);
296
    }
297
    else
298
    {
299
	# Special formatting for full file upload that is not a diff.
300
        # If it not a diff, show the entire delta (actually the file
301
        # contents) in a single column.
302
	$self->delta_file_header($filename, $revision, $repmatch);
303
 
304
        print $query->Tr($query->td("&nbsp;"), $query->td("&nbsp;"),"\n");
305
 
306
	my @lines = split /\n/, $text;
307
        for (my $i = 0; $i <= $#lines; $i++) {
308
	    my $line = $lines[$i];
309
 
310
	    my $rendered_left_linenumber =
311
		$self->render_linenumber($i+1, $filenumber,1,1);
312
 
313
	    # Removed the delta text, where + is added to the start of each
314
	    # line.  Also make sure the line is suitably escaped.
315
	    $line =~ s/^\+//;
316
	    $line = HTML::Entities::encode($line);
317
 
318
	    my $cell = $self->render_coloured_cell($line);
319
	    my $cell_class =
320
		$self->{mode} == $Codestriker::COLOURED_MODE ? "n" : "msn";
321
 
322
	    print $query->Tr($query->td($rendered_left_linenumber),
323
			     $query->td({-class=>$cell_class}, $cell),
324
			     "\n");
325
    	}
326
    }
327
}
328
 
329
# Output the header for a series of deltas for a specific file.
330
sub delta_file_header ($$$$) {
331
    my ($self, $filename, $revision, $repmatch) = @_;
332
 
333
    my $filename_short = get_filename($filename);
334
 
335
    my $query = $self->{query};
336
 
337
    # We need the file names for building the forward and backward
338
    # url Strings.
339
    my $filenames = $self->{filenames_ref};
340
 
341
    # Close the table, update the current filename, and open a new table.
342
    print $query->end_table();
343
    $self->{diff_current_filename} = $filename;
344
    $self->{diff_current_revision} = $revision;
345
    $self->{diff_current_repmatch} = $repmatch;
346
    $self->print_coloured_table();
347
 
348
    # Url to the table of contents on the same page.
349
    my $contents_url =
350
	$self->{url_builder}->view_url($self->{topic}, -1,
351
				       $self->{mode}, $self->{brmode},
352
				       $self->{fview})
353
                                       . "#contents";
354
 
355
    # Variables to store the navigation Urls.	
356
    my $fwd_index = "";
357
    my $bwd_index = "";
358
    my $fwd_url   = "";
359
    my $bwd_url   = "";
360
 
361
    # Get the current file index.
362
    my $cfi = $self->{fview};
363
 
364
    # Store the current view mode, single view = 0, all files = -1.
365
    my $vmode = $self->{fview} == -1 ? -1 : 0; 
366
 
367
    # No better idea how I can get the array index of the current file. In the
368
    # single display mode you got it through fview - but in multi mode?
369
    if ($cfi == -1) {
370
    	for (my $i = 0; $i <= $#$filenames; $i++) {
371
	    if ($$filenames[$i] eq $filename) {
372
		$cfi = $i;
373
		last;
374
	    }
375
    	}
376
    }
377
 
378
    # Check the bounds for the previous and next browser.  A value of -1
379
    # indicates there it is not a valid link.
380
    $fwd_index = ($cfi+1 > $#$filenames ? -1 : $cfi+1);
381
    $bwd_index = ($cfi-1 < 0 ? -1 : $cfi-1);
382
 
383
    # Build the urls for next and previous file. Differ through $vmode
384
    # between all and single file review.
385
    if ($fwd_index != -1) {
386
	$fwd_url = $self->{url_builder}->view_url($self->{topic}, -1,
387
						  $self->{mode},
388
						  $self->{brmode},
389
						  $vmode == -1 ? -1 : $fwd_index)
390
                                 	          . "#$$filenames[$fwd_index]";
391
    }
392
    if ($bwd_index != -1) {
393
	$bwd_url = $self->{url_builder}->view_url($self->{topic}, -1,
394
						  $self->{mode},
395
						  $self->{brmode},
396
						  $vmode == -1 ? -1 : $bwd_index)
397
		                                  . "#$$filenames[$bwd_index]";
398
    }
399
 
400
    # Generate the text for the link to add a file-level comment.
401
    my $add_file_level_comment_text =
402
	$self->render_comment_link($cfi, -1, 1, "[Add File Comment]",
403
				   "file_comment", undef);
404
 
405
    if ($repmatch && $revision ne $Codestriker::ADDED_REVISION &&
406
	$revision ne $Codestriker::PATCH_REVISION) {
407
	# File matches something in the repository.  Link it to
408
	# the repository viewer if it is defined.
409
	my $cell = "";
410
	my $revision_text = "revision $revision";
411
	my $file_url = "";
412
	if (defined $self->{repository}) {
413
	    $file_url = $self->{repository}->getViewUrl($filename);
414
	}
415
 
416
	if ($file_url eq "") {
417
	    # Output the header without hyperlinking the filename.
418
	    $cell = $query->td({-class=>'file', -colspan=>'3'},
419
			       "Diff for ",
420
			       $query->a({name=>$filename},
421
					 $filename_short),
422
			       $revision_text);
423
	}
424
	else {
425
	    # Link the filename to the repository system with more information
426
	    # about it.
427
	    $cell = $query->td({-class=>'file', -colspan=>'3'},
428
			       "Diff for ",
429
			       $query->a({href=>$file_url,
430
					  name=>$filename},
431
					 $filename_short),
432
			       $revision_text);
433
	}
434
 
435
	# Output the "back to contents" link and some browsing links
436
	# for visiting the previous and next file (<<, >>), in
437
	# addition to the "add file-level comment" link.
438
 
439
	print $query->Tr($cell,     # = file header     
440
		         $query->td({-class=>'file', align=>'right'},
441
				    "$add_file_level_comment_text ",
442
				    ($bwd_url ne "" ? $query->a({href=>$bwd_url},"[<<]") : ""),
443
			            $query->a({href=>$contents_url},"[Top]"),
444
			            ($fwd_url ne "" ? $query->a({href=>$fwd_url},"[>>]") : "")));
445
    } else {
446
	# No match in repository, or a new file.
447
	print $query->Tr($query->td({-class=>'file', -colspan=>'3'},
448
				    "File ",
449
				    $query->a({name=>$filename},$filename)),
450
			 $query->td({-class=>'file', align=>'right'},
451
				    "$add_file_level_comment_text ",
452
				    ($bwd_url ne "" ? $query->a({href=>$bwd_url},"[<<]") : ""),
453
				    $query->a({href=>$contents_url},"[Top]"),
454
				    ($fwd_url ne "" ? $query->a({href=>$fwd_url},"[>>]") : "")));
455
    }
456
 
457
}
458
 
459
# Output the delta heading, which consists of links to view the old and new
460
# file in its entirety.
461
sub delta_heading ($$$$$$$) {
462
    my ($self, $filenumber, $revision, $old_linenumber, $new_linenumber,
463
	$description, $repmatch) = @_;
464
 
465
    my $query = $self->{query};
466
 
467
    # Create some blank space.
468
    print $query->Tr($query->td("&nbsp;"), $query->td("&nbsp;"),
469
		     $query->td("&nbsp;"), $query->td("&nbsp;"), "\n");
470
 
471
    # Output a diff block description if one is available, in a separate
472
    # row.
473
    if ($description ne "") {
474
	my $description_escaped = HTML::Entities::encode($description);
475
	print $query->Tr($query->td({-class=>'line', -colspan=>'2'},
476
				    $description_escaped),
477
			 $query->td({-class=>'line', -colspan=>'2'},
478
				    $description_escaped));
479
    }
480
 
481
    if ($repmatch && $revision ne $Codestriker::ADDED_REVISION &&
482
	$revision ne $Codestriker::PATCH_REVISION) {
483
	# Display the line numbers corresponding to the patch, with links
484
	# to the entire file.
485
	my $url_builder = $self->{url_builder};
486
	my $topic = $self->{topic};
487
	my $mode = $self->{mode};
488
	my $url_old_full =
489
	    $url_builder->view_file_url($topic, $filenumber, 0,
490
					$old_linenumber, $mode, 0);
491
	my $url_old = "javascript: myOpen('$url_old_full','CVS')";
492
 
493
	my $url_old_both_full =
494
	    $url_builder->view_file_url($topic, $filenumber, 0,
495
					$old_linenumber, $mode, 1);
496
	my $url_old_both =
497
	    "javascript: myOpen('$url_old_both_full','CVS')";
498
 
499
	my $url_new_full =
500
	    $url_builder->view_file_url($topic, $filenumber, 1,
501
					$new_linenumber, $mode, 0);
502
	my $url_new = "javascript: myOpen('$url_new_full','CVS')";
503
 
504
	my $url_new_both_full =
505
	    $url_builder->view_file_url($topic, $filenumber, 1,
506
					$new_linenumber, $mode, 1);
507
	my $url_new_both = "javascript: myOpen('$url_new_both_full','CVS')";
508
 
509
	print $query->Tr($query->td({-class=>'line', -colspan=>'2'},
510
				    $query->a({href=>$url_old}, "Line " .
511
					      $old_linenumber) .
512
				    " | " .
513
				    $query->a({href=>$url_old_both},
514
					      "Parallel")),
515
			 $query->td({-class=>'line', -colspan=>'2'},
516
				    $query->a({href=>$url_new}, "Line " .
517
					      $new_linenumber) .
518
				    " | " .
519
				    $query->a({href=>$url_new_both},
520
					      "Parallel"))),
521
				    "\n";
522
    } else {
523
	# No match in the repository - or a new file.  Just display
524
	# the headings.
525
	print $query->Tr($query->td({-class=>'line', -colspan=>'2'},
526
				    "Line $old_linenumber"),
527
			 $query->td({-class=>'line', -colspan=>'2'},
528
				    "Line $new_linenumber")),
529
	"\n";
530
    }
531
}
532
 
533
# Output the delta text chunk in the coloured format.
534
sub delta_text ($$$$$$$$$$$) {
535
    my ($self, $filename, $filenumber, $revision, $old_linenumber,
536
	$new_linenumber, $text, $repmatch, $new, $link) = @_;
537
 
538
    my $query = $self->{query};
539
 
540
    # Split up the lines, and display them, with the appropriate links.
541
    my @lines = split /\n/, $text;
542
    $self->{old_linenumber} = $old_linenumber;
543
    $self->{new_linenumber} = $new_linenumber;
544
    for (my $i = 0; $i <= $#lines; $i++) {
545
	my $line = $lines[$i];
546
	if ($self->{parallel}) {
547
	    $self->display_coloured_data($filenumber, $line, $link);
548
	} else {
549
	    $self->display_single_filedata($filenumber, $line, $new, $link);
550
	}
551
    }
552
 
553
    # Render the diff blocks.
554
    if ($self->{parallel}) {
555
	$self->render_changes($filenumber, $link);
556
    } else {
557
	$self->flush_monospaced_lines($filenumber, $self->{max_line_length},
558
				      $new, $link);
559
    }
560
}
561
 
562
# Display a line for coloured data.  Note special handling is done for
563
# unidiff formatted text, to output it in the "coloured-diff" style.  This
564
# requires storing state when retrieving each line.
565
sub display_coloured_data ($$$$) {
566
    my ($self, $filenumber, $data, $link) = @_;
567
 
568
    my $query = $self->{query};
569
 
570
    # Escape the data.
571
    $data = HTML::Entities::encode($data);
572
 
573
    my $leftline = $self->{old_linenumber};
574
    my $rightline = $self->{new_linenumber};
575
    if ($data =~ /^\-(.*)$/) {
576
	# Line corresponds to something which has been removed.
577
	add_old_change($1, $leftline);
578
	$leftline++;
579
    } elsif ($data =~ /^\+(.*)$/) {
580
	# Line corresponds to something which has been removed.
581
	add_new_change($1, $rightline);
582
	$rightline++;
583
    } elsif ($data =~ /^\\/) {
584
	# A diff comment such as "No newline at end of file" - ignore it.
585
    } else {
586
	# Strip the first space off the diff for proper alignment.
587
	$data =~ s/^\s//;
588
 
589
	# Render the previous diff changes visually.
590
	$self->render_changes($filenumber, $link);
591
 
592
	# Render the current line for both cells.
593
	my $celldata = $self->render_coloured_cell($data);
594
 
595
	# Determine the appropriate classes to render.
596
	my $cell_class =
597
	    $self->{mode} == $Codestriker::COLOURED_MODE ? "n" : "msn";
598
 
599
	my $rendered_left_linenumber =
600
	    $self->render_linenumber($leftline, $filenumber, 0, $link);
601
	my $rendered_right_linenumber =
602
	    ($leftline == $rightline && !$self->{parallel}) ?
603
	    $rendered_left_linenumber :
604
	    $self->render_linenumber($rightline, $filenumber, 1, $link);
605
 
606
	print $query->Tr($query->td($rendered_left_linenumber),
607
			 $query->td({-class=>$cell_class}, $celldata),
608
			 $query->td($rendered_right_linenumber),
609
			 $query->td({-class=>$cell_class}, $celldata),
610
			 "\n");
611
 
612
	$leftline++;
613
	$rightline++;
614
    }
615
 
616
    # Update the left and right line nymber state variables.
617
    $self->{old_linenumber} = $leftline;
618
    $self->{new_linenumber} = $rightline;
619
}
620
 
621
# Render a cell for the coloured diff.
622
sub render_coloured_cell($$)
623
{
624
    my ($self, $data) = @_;
625
 
626
    if (! defined $data || $data eq "") {
627
	return "&nbsp;";
628
    }
629
 
630
    # Replace spaces and tabs with the appropriate number of &nbsp;'s.
631
    $data = tabadjust($self->{tabwidth}, $data, 1);
632
    if ($self->{brmode} == $Codestriker::LINE_BREAK_ASSIST_MODE) {
633
	$data =~ s/^(\s+)/my $sp='';for(my $i=0;$i<length($1);$i++){$sp.='&nbsp;'}$sp;/ge;
634
    }
635
    else {
636
    $data =~ s/\s/&nbsp;/g;
637
    }
638
 
639
    # Add LXR links to the output.
640
    $data = $self->lxr_data($data);
641
 
642
    # Unconditionally add a &nbsp; at the start for better alignment.
643
    return "&nbsp;$data";
644
}
645
 
646
# Indicate a line of data which has been removed in the diff.
647
sub add_old_change($$) {
648
    my ($data, $linenumber) = @_;
649
    push @diff_old_lines, $data;
650
    push @diff_old_lines_numbers, $linenumber;
651
}
652
 
653
# Indicate that a line of data has been added in the diff.
654
sub add_new_change($$) {
655
    my ($data, $linenumber) = @_;
656
    push @diff_new_lines, $data;
657
    push @diff_new_lines_numbers, $linenumber;
658
}
659
 
660
# Render the current diff changes, if there is anything.
661
sub render_changes($$$) {
662
    my ($self, $filenumber, $link) = @_;
663
 
664
    return if ($#diff_new_lines == -1 && $#diff_old_lines == -1);
665
 
666
    my ($arg1, $arg2, $arg3, $arg4);
667
    my $mode = $self->{mode};
668
    if ($#diff_new_lines != -1 && $#diff_old_lines != -1) {
669
	# Lines have been added and removed.
670
	if ($mode == $Codestriker::COLOURED_MODE) {
671
	    $arg1 = "c"; $arg2 = "cb"; $arg3 = "c"; $arg4 = "cb";
672
	} else {
673
	    $arg1 = "msc"; $arg2 = "mscb"; $arg3 = "msc"; $arg4 = "mscb";
674
	}
675
    } elsif ($#diff_new_lines != -1 && $#diff_old_lines == -1) {
676
	# New lines have been added.
677
	if ($mode == $Codestriker::COLOURED_MODE) {
678
	    $arg1 = "a"; $arg2 = "ab"; $arg3 = "a"; $arg4 = "ab";
679
	} else {
680
	    $arg1 = "msa"; $arg2 = "msab"; $arg3 = "msa"; $arg4 = "msab";
681
	}
682
    } else {
683
	# Lines have been removed.
684
	if ($mode == $Codestriker::COLOURED_MODE) {
685
	    $arg1 = "r"; $arg2 = "rb"; $arg3 = "r"; $arg4 = "rb";
686
	} else {
687
	    $arg1 = "msr"; $arg2 = "msrb"; $arg3 = "msr"; $arg4 = "msrb";
688
	}
689
    }
690
    $self->render_inplace_changes($arg1, $arg2, $arg3, $arg4, $filenumber,
691
				  $link);
692
 
693
    # Now that the diff changeset has been rendered, remove the state data.
694
    @diff_new_lines = ();
695
    @diff_new_lines_numbers = ();
696
    @diff_old_lines = ();
697
    @diff_old_lines_numbers = ();
698
}
699
 
700
# Render the inplace changes in the current diff change set.
701
sub render_inplace_changes($$$$$$$)
702
{
703
    my ($self, $old_col, $old_notpresent_col, $new_col,
704
	$new_notpresent_col, $filenumber, $link) = @_;
705
 
706
    my $old_data;
707
    my $new_data;
708
    my $old_data_line;
709
    my $new_data_line;
710
    while ($#diff_old_lines != -1 || $#diff_new_lines != -1) {
711
 
712
	# Retrieve the next lines which were removed (if any).
713
	if ($#diff_old_lines != -1) {
714
	    $old_data = shift @diff_old_lines;
715
	    $old_data_line = shift @diff_old_lines_numbers;
716
	} else {
717
	    undef($old_data);
718
	    undef($old_data_line);
719
	}
720
 
721
	# Retrieve the next lines which were added (if any).
722
	if ($#diff_new_lines != -1) {
723
	    $new_data = shift @diff_new_lines;
724
	    $new_data_line = shift @diff_new_lines_numbers;
725
	} else {
726
	    undef($new_data);
727
	    undef($new_data_line);
728
	}
729
 
730
	my $render_old_data = $self->render_coloured_cell($old_data);
731
	my $render_new_data = $self->render_coloured_cell($new_data);
732
 
733
	# Set the colours to use appropriately depending on what is defined.
734
	my $render_old_colour = $old_col;
735
	my $render_new_colour = $new_col;
736
	if (defined $old_data && ! defined $new_data) {
737
	    $render_new_colour = $new_notpresent_col;
738
	} elsif (! defined $old_data && defined $new_data) {
739
	    $render_old_colour = $old_notpresent_col;
740
	}
741
 
742
	my $parallel = $self->{parallel};
743
 
744
	my $query = $self->{query};
745
	print $query->Tr($query->td($self->render_linenumber($old_data_line,
746
							     $filenumber,
747
							     0, $link)),
748
			 $query->td({-class=>"$render_old_colour"},
749
				    $render_old_data),
750
			 $query->td($self->render_linenumber($new_data_line,
751
							     $filenumber,
752
							     1, $link)),
753
			 $query->td({-class=>"$render_new_colour"},
754
				    $render_new_data), "\n");
755
    }
756
}
757
 
758
# Render a linenumber as a hyperlink.  If the line already has a
759
# comment made against it, render it with $comment_line_colour.  The
760
# title of the link should be set to the comment digest, and the
761
# status line should be set if the mouse moves over the link.
762
# Clicking on the link will take the user to the add comment page.
763
sub render_linenumber($$$$$) {
764
    my ($self, $line, $filenumber, $new, $link) = @_;
765
 
766
    if (! defined $line) {
767
	return "&nbsp;";
768
    }
769
 
770
    # Determine what class to use when rendering the number.
771
    my ($comment_class, $no_comment_class);
772
    if ($self->{mode} == $Codestriker::COLOURED_MODE) {
773
	$comment_class = "com";
774
	$no_comment_class = "nocom";
775
    } else {
776
	$comment_class = "smscom";
777
	$no_comment_class = "smsnocom";
778
    }
779
 
780
    # Check if the linenumber is outside the review.
781
    if ($link == 0) {
782
	return $line;
783
    }
784
 
785
    # Now render the line.
786
    return $self->render_comment_link($filenumber, $line, $new, $line,
787
				      $comment_class, $no_comment_class);
788
}
789
 
790
# Render the supplied text within a edit comment link.
791
sub render_comment_link {
792
    my ($self, $filenumber, $line, $new, $text,
793
	$comment_class, $no_comment_class) = @_;
794
 
795
    # Determine the anchor and edit URL for this line number.
796
    my $anchor = "$filenumber|$line|$new";
797
    my $edit_url = "javascript:eo('$filenumber','$line','$new')";
798
 
799
    # Set the anchor to this line number.
800
    my $params = {};
801
    $params->{name} = $anchor;
802
 
803
    # Only set the href attribute if the comment is in open state.
804
    if (!Codestriker::topic_readonly($self->{topic_state})) {
805
	    $params->{href} = $edit_url;
806
    }
807
 
808
    # If a comment exists on this line, set span and the overlib hooks onto
809
    # it.
810
    my $query = $self->{query};
811
    my %comment_hash = %{ $self->{comment_hash} };
812
    my %comment_location_map = %{ $self->{comment_location_map} };
813
    my $comment_number = undef;
814
    if (exists $comment_hash{$anchor}) {
815
	# Determine what comment number this anchor refers to.
816
	$comment_number = $comment_location_map{$anchor};
817
 
818
	if (defined $comment_class) {
819
	    $text = $query->span({-id=>"c$comment_number"}, "") .
820
		$query->span({-class=>$comment_class}, $text);
821
	}
822
 
823
	# Determine what the next comment in line is.
824
	my $index = -1;
825
	my @comment_locations = @{ $self->{comment_locations} };
826
	for ($index = 0; $index <= $#comment_locations; $index++) {
827
	    last if $anchor eq $comment_locations[$index];
828
	}
829
 
830
	$params->{onmouseover} = "return overlib(comment_text[$index],STICKY,DRAGGABLE,ALTCUT);";
831
	$params->{onmouseout} = "return nd();";
832
    } else {
833
	if (defined $no_comment_class) {
834
	    $text = $query->span({-class=>$no_comment_class}, $text);
835
	}
836
    }
837
 
838
    return $query->a($params, $text);
839
}
840
 
841
# Start hook called when about to start rendering to a page.
842
sub start($) {
843
    my ($self) = @_;
844
 
845
    # Now create the start of the rendering tables.
846
    if ($self->{mode} == $Codestriker::NORMAL_MODE) {
847
	$self->_normal_mode_start();
848
    } else {
849
	$self->_coloured_mode_start();
850
    }
851
}
852
 
853
# Finished hook called when finished rendering to a page.
854
sub finish($) {
855
    my ($self) = @_;
856
    if ($self->{mode} == $Codestriker::NORMAL_MODE) {
857
	$self->_normal_mode_finish();
858
    } else {
859
	$self->_coloured_mode_finish();
860
    }
861
 
862
    $self->_print_legend();
863
}
864
 
865
# Start topic view display hook for normal mode.
866
sub _normal_mode_start($) {
867
    my ($self) = @_;
868
    print "<PRE>\n";
869
}
870
 
871
# Finish topic view display hook for normal mode.
872
sub _normal_mode_finish($) {
873
    my ($self) = @_;
874
    print "</PRE>\n";
875
}
876
 
877
# Private functon to print the diff legend out at the bottom of the topic text page.
878
sub _print_legend($) {
879
    my ($self) = @_;
880
 
881
    my $query = $self->{query};
882
    my $topic = $self->{topic};
883
    my $mode = $self->{mode};
884
 
885
    print $query->start_table({-cellspacing=>'0', -cellpadding=>'0',
886
			       -border=>'0'}), "\n";
887
    print $query->Tr($query->td("&nbsp;"), $query->td("&nbsp;"));
888
    print $query->Tr($query->td({-colspan=>'2'}, "Legend:"));
889
    print $query->Tr($query->td({-class=>'rf'},
890
				"Removed"),
891
		     $query->td({-class=>'rb'}, "&nbsp;"));
892
    print $query->Tr($query->td({-class=>'cf',
893
				 -align=>"center", -colspan=>'2'},
894
				"Changed"));
895
    print $query->Tr($query->td({-class=>'ab'}, "&nbsp;"),
896
		     $query->td({-class=>'af'},
897
				"Added"));
898
    print $query->end_table(), "\n";
899
}
900
 
901
 
902
# Start topic view display hook for coloured mode.  This displays a simple
903
# legend, displays the files involved in the review, and opens up the initial
904
# table.
905
sub _coloured_mode_start($) {
906
    my ($self) = @_;
907
 
908
    my $query = $self->{query};
909
    my $topic = $self->{topic};
910
    my $mode = $self->{mode};
911
    my $brmode = $self->{brmode};
912
    my $fview = $self->{fview};
913
    my $display_all_url =
914
	$self->{url_builder}->view_url($topic, -1, $mode, $brmode, -1);
915
    my $display_single_url =
916
	$self->{url_builder}->view_url($topic, -1, $mode, $brmode, 0);
917
 
918
 
919
    # Print out the "table of contents".
920
    my $filenames = $self->{filenames_ref};
921
    my $revisions = $self->{revisions_ref};
922
    my $binaries = $self->{binaries_ref};
923
    my $numchanges = $self->{numchanges_ref};
924
 
925
    print $query->p;
926
    print $query->start_table({-cellspacing=>'0', -cellpadding=>'0',
927
			       -border=>'0'}), "\n";
928
 
929
 
930
    # Include a link to view all files in a topic, if we are in single
931
    # display mode.
932
    if ($fview != -1) {
933
    	print $query->Tr($query->td($query->a({name=>"contents"},
934
					      "Files in Topic: ("),
935
				    $query->a({href=>$display_all_url},
936
					      "view all files"), ")"),
937
			 $query->td("&nbsp;")), "\n";
938
    }
939
    else {
940
	print $query->Tr($query->td($query->a({name=>"contents"},
941
					      "Files in Topic:")),
942
			 $query->td("&nbsp;")), "\n";
943
    }
944
 
945
    my $url_builder = $self->{url_builder};
946
    for (my $i = 0; $i <= $#$filenames; $i++) {
947
	my $filename = $$filenames[$i];
948
    my $filename_short = get_filename($filename);
949
 
950
	my $revision = $$revisions[$i];
951
	my $numchange = $$numchanges[$i];
952
	my $href_filename =
953
	    $url_builder->view_url($topic, -1, $mode, $brmode, $i) .
954
	    "#" . "$filename";
955
	my $anchor_filename =
956
	    $url_builder->view_url($topic, -1, $mode, $brmode, -1) .
957
	    "#" . "$filename";
958
	my $tddata = $$binaries[$i] ? $filename :
959
        $query->a({href=>$href_filename}, "$filename_short");
960
 
961
	if ($fview == -1) {
962
	    # Add a jump to link for the all files view.
963
	    $tddata = "[" . $query->a({href=>$anchor_filename}, "Jump to") . "] " . $tddata;
964
	}
965
 
966
	my $lineData = "";
967
 
968
	if ($numchange ne "") {
969
	    $lineData = "&nbsp; <FONT size=-1>{$numchange}</FONT>";
970
	}
971
 
972
	my $class = "";
973
	$class = "af" if ($revision eq $Codestriker::ADDED_REVISION);
974
	$class = "rf" if ($revision eq $Codestriker::REMOVED_REVISION);
975
	$class = "cf" if ($revision eq $Codestriker::PATCH_REVISION);
976
 	if ($revision eq $Codestriker::ADDED_REVISION ||
977
 	    $revision eq $Codestriker::REMOVED_REVISION ||
978
 	    $revision eq $Codestriker::PATCH_REVISION) {
979
 	    # Added, removed or patch file.
980
	    print $query->Tr($query->td({-class=>"$class", -colspan=>'2'},
981
					$tddata),
982
			     $query->td({-class=>"$class"}, $lineData)) . "\n";
983
 	} else {
984
 	    # Modified file.
985
 	    print $query->Tr($query->td({-class=>'cf'}, $tddata),
986
 			     $query->td({-class=>'cf'}, "&nbsp; $revision"),
987
			     $query->td({-class=>'cf'}, $lineData)) .
988
 			     "\n";
989
 	}
990
    }
991
    print $query->end_table() . "\n";
992
 
993
    # Render the "Add comment to topic" link.
994
    print $query->p;
995
    print $self->render_comment_link(-1, -1, 1, "Add General Comment",
996
				     "general_comment", undef);
997
    print " to topic.";
998
    print $query->p;
999
 
1000
    print $query->start_table() ;
1001
}
1002
 
1003
# Render the initial start of the coloured table, with an empty row setting
1004
# the widths.
1005
sub print_coloured_table($)
1006
{
1007
    my ($self) = @_;
1008
 
1009
    my $query = $self->{query};
1010
    print $query->start_table({-width=>'100%',
1011
			       -border=>'0',
1012
			       -cellspacing=>'0',
1013
			       -cellpadding=>'0'}), "\n";
1014
    print $query->Tr($query->td({-width=>'2%'}, "&nbsp;"),
1015
		     $query->td({-width=>'48%'}, "&nbsp;"),
1016
		     $query->td({-width=>'2%'}, "&nbsp;"),
1017
		     $query->td({-width=>'48%'}, "&nbsp;"), "\n");
1018
}
1019
 
1020
 
1021
# Finish topic view display hook for coloured mode.
1022
sub _coloured_mode_finish ($) {
1023
    my ($self) = @_;
1024
 
1025
    if ($self->{fview} != -1) {
1026
	# Show the current file header again for navigation purposes when
1027
	# viewing a single file at a time.
1028
	$self->delta_file_header($self->{diff_current_filename},
1029
				 $self->{diff_current_revision},
1030
				 $self->{diff_current_repmatch});
1031
    }
1032
 
1033
    print "</TABLE>\n";
1034
 
1035
    # Render the "Add comment to topic" link.
1036
    my $query = $self->{query};
1037
    print $query->p;
1038
    print $self->render_comment_link(-1, -1, 1, "Add General Comment",
1039
				     "general_comment", undef);
1040
    print " to topic.";
1041
    print $query->p;
1042
}
1043
 
1044
# Display a line for a single file view.
1045
sub display_single_filedata ($$$$$) {
1046
    my ($self, $filenumber, $data, $new, $link) = @_;
1047
 
1048
    my $leftline = $self->{old_linenumber};
1049
    my $rightline = $self->{new_linenumber};
1050
    my $max_line_length = $self->{max_line_length};
1051
 
1052
    # Handling of either an old or new view.
1053
    if ($data =~ /^\-(.*)$/o) {
1054
	# A removed line.
1055
	$self->add_minus_monospace_line($1, $leftline++);
1056
    } elsif ($data =~ /^\+(.*)$/o) {
1057
	# An added line.
1058
	$self->add_plus_monospace_line($1, $rightline++);
1059
    } else {
1060
	# An unchanged line, output it and anything pending, and remove
1061
	# the leading space for alignment reasons.
1062
	$data =~ s/^\s//;
1063
	$self->flush_monospaced_lines($filenumber, $max_line_length, $new,
1064
				      $link);
1065
 
1066
	my $linenumber = $new ? $rightline : $leftline;
1067
	print $self->render_monospaced_line($filenumber, $linenumber, $new,
1068
					    $data, $link,
1069
					    $max_line_length, "");
1070
	$leftline++;
1071
	$rightline++;
1072
    }
1073
 
1074
    # Update the left and right line nymber state variables.
1075
    $self->{old_linenumber} = $leftline;
1076
    $self->{new_linenumber} = $rightline;
1077
}
1078
 
1079
# Print out a line of data with the specified line number suitably aligned,
1080
# and with tabs replaced by spaces for proper alignment.
1081
sub render_monospaced_line ($$$$$$$$) {
1082
    my ($self, $filenumber, $linenumber, $new, $data, $link,
1083
	$max_line_length, $class) = @_;
1084
 
1085
    # Convert any identifier to their LXR links.
1086
    $data = $self->lxr_data(HTML::Entities::encode($data));
1087
 
1088
    my $prefix = "";
1089
    my $digit_width = length($linenumber);
1090
    my $max_digit_width = $self->{max_digit_width};
1091
    for (my $i = 0; $i < ($max_digit_width - $digit_width); $i++) {
1092
	$prefix .= " ";
1093
    }
1094
 
1095
    # Determine what class to use when rendering the number.
1096
    my ($comment_class, $no_comment_class);
1097
    if ($self->{parallel} == 0) {
1098
	$comment_class = "mscom";
1099
	$no_comment_class = "msnocom";
1100
    } else {
1101
	if ($self->{mode} == $Codestriker::COLOURED_MODE) {
1102
	    $comment_class = "com";
1103
	    $no_comment_class = "nocom";
1104
	} else {
1105
	    $comment_class = "smscom";
1106
	    $no_comment_class = "smsnocom";
1107
	}
1108
    }
1109
 
1110
    my $line_cell = "";
1111
    if ($link == 0) {
1112
	# A line outside of the review.  Just render the line number, as
1113
	# the "name" of the linenumber should not be used.
1114
	$line_cell = "$prefix$linenumber";
1115
    } else {
1116
	$line_cell = $prefix .
1117
	    $self->render_comment_link($filenumber, $linenumber, $new,
1118
				       $linenumber, $comment_class,
1119
				       $no_comment_class);
1120
    }
1121
 
1122
    # Render the line data.  If the user clicks on a topic line, the
1123
    # edit window is focused to the appropriate line.
1124
    my $query = $self->{query};
1125
 
1126
    # Replace the line data with spaces.
1127
    my $newdata = tabadjust($self->{tabwidth}, $data, 0);
1128
 
1129
    if ($class ne "") {
1130
	# Add the appropriate number of spaces to justify the data to a length
1131
	# of $max_line_length, and render it within a SPAN to get the correct
1132
	# background colour.
1133
	my $padding = $max_line_length - length($data);
1134
	for (my $i = 0; $i < ($padding); $i++) {
1135
	    $newdata .= " ";
1136
	}
1137
	return "$line_cell " .
1138
	    $query->span({-class=>"$class"}, $newdata) . "\n";
1139
    }
1140
    else {
1141
	return "$line_cell $newdata\n";
1142
    }
1143
}
1144
 
1145
# Record a plus line.
1146
sub add_plus_monospace_line ($$$) {
1147
    my ($self, $linedata, $offset) = @_;
1148
    push @view_file_plus, $linedata;
1149
    push @view_file_plus_offset, $offset;
1150
}
1151
 
1152
# Record a minus line.
1153
sub add_minus_monospace_line ($$$) {
1154
    my ($self, $linedata, $offset) = @_;
1155
    push @view_file_minus, $linedata;
1156
    push @view_file_minus_offset, $offset;
1157
}
1158
 
1159
# Flush the current diff chunk.  Note if the original file is being rendered,
1160
# the minus lines are used, otherwise the plus lines.
1161
sub flush_monospaced_lines ($$$$$) {
1162
    my ($self, $filenumber, $max_line_length, $new, $link) = @_;
1163
 
1164
    my $class = "";
1165
    if ($#view_file_plus != -1 && $#view_file_minus != -1) {
1166
	# This is a change chunk.
1167
	$class = "msc";
1168
    }
1169
    elsif ($#view_file_plus != -1) {
1170
	# This is an add chunk.
1171
	$class = "msa";
1172
    }
1173
    elsif ($#view_file_minus != -1) {
1174
	# This is a remove chunk.
1175
	$class = "msr";
1176
    }
1177
 
1178
    if ($new) {
1179
	for (my $i = 0; $i <= $#view_file_plus; $i++) {
1180
	    print $self->render_monospaced_line($filenumber,
1181
						$view_file_plus_offset[$i],
1182
						$new,
1183
						$view_file_plus[$i], $link,
1184
						$max_line_length, $class);
1185
	}
1186
    }
1187
    else {
1188
	for (my $i = 0; $i <= $#view_file_minus; $i++) {
1189
	    print $self->render_monospaced_line($filenumber,
1190
						$view_file_minus_offset[$i],
1191
						$new,
1192
						$view_file_minus[$i], $link,
1193
						$max_line_length, $class);
1194
	}
1195
    }
1196
    $#view_file_minus = -1;
1197
    $#view_file_minus_offset = -1;
1198
    $#view_file_plus = -1;
1199
    $#view_file_plus_offset = -1;
1200
}
1201
 
1202
# Replace the passed in string with the correct number of spaces, for
1203
# alignment purposes.
1204
sub tabadjust ($$$) {
1205
    my ($tabwidth, $input, $htmlmode) = @_;
1206
 
1207
    $_ = $input;
1208
    if ($htmlmode) {
1209
	1 while s/\t+/'&nbsp;' x
1210
	    (length($&) * $tabwidth - length($`) % $tabwidth)/eo;
1211
    }
1212
    else {
1213
	1 while s/\t+/' ' x
1214
	    (length($&) * $tabwidth - length($`) % $tabwidth)/eo;
1215
    }
1216
    return $_;
1217
}
1218
 
1219
# Retrieve the data that forms the "context" when submitting a comment.
1220
sub get_context ($$$$$$$$$) {
1221
    my ($type, $targetline, $context, $html_view, $old_startline,
1222
	$new_startline, $text, $new) = @_;
1223
 
1224
    # Break the text into lines.
1225
    my @document = split /\n/, $text;
1226
 
1227
    # Calculate the location of the target line within the diff chunk.
1228
    my $offset;
1229
    my $old_linenumber = $old_startline;
1230
    my $new_linenumber = $new_startline;
1231
    for ($offset = 0; $offset <= $#document; $offset++) {
1232
 
1233
	my $data = $document[$offset];
1234
 
1235
	# Check if the target line as been found.
1236
	if ($data =~ /^ /o) {
1237
	    last if ($new && $new_linenumber == $targetline);
1238
	    last if ($new == 0 && $old_linenumber == $targetline);
1239
	    $old_linenumber++;
1240
	    $new_linenumber++;
1241
	} elsif ($data =~ /^\+/o) {
1242
	    last if ($new && $new_linenumber == $targetline);
1243
	    $new_linenumber++;
1244
	} elsif ($data =~ /^\-/o) {
1245
	    last if ($new == 0 && $old_linenumber == $targetline);
1246
	    $old_linenumber++;
1247
	}
1248
    }
1249
 
1250
    # Get the minimum and maximum line numbers for this context, and return
1251
    # the data.  The line of interest will be rendered appropriately.
1252
    my $min_line = ($offset - $context < 0 ? 0 : $offset - $context);
1253
    my $max_line = $offset + $context;
1254
    my $context_string = "";
1255
    for (my $i = $min_line; $i <= $max_line && $i <= $#document; $i++) {
1256
	my $linedata = $document[$i];
1257
	if ($html_view) {
1258
	    if ($i == $offset) {
1259
		$context_string .=
1260
		    "<font color=\"$CONTEXT_COLOUR\">" .
1261
		      HTML::Entities::encode($linedata) . "</font>\n";
1262
	    } else {
1263
		$context_string .= HTML::Entities::encode("$linedata") ."\n";
1264
	    }
1265
	} else {
1266
            # This is the context for emails.
1267
	    $context_string .= ($i == $offset) ? "* " : "  ";
1268
	    $context_string .= $linedata . "\n";
1269
	}
1270
    }
1271
    return $context_string;
1272
}
1273
 
1274
sub get_filename ($) {
1275
    my ($extended_path) = @_;
1276
    my $normal_path = $extended_path;
1277
    my $path_separation = "/";
1278
 
1279
    if ($extended_path =~ /^(.*)@@(.*)/) {
1280
        $normal_path = $1;      # before clearcase extended path
1281
        my $other_path = $2;    # rest of the path that have clearcase Multi version paths
1282
 
1283
        # Before manipulating, see what kind of path separation is used
1284
        # Assuming, only either \ or / is used.
1285
        if ($extended_path =~ /\\/) {
1286
            $path_separation = "\\";
1287
        }
1288
 
1289
        my @nodes = split(/[\/\\]/, $other_path);
1290
        my $temp;
1291
 
1292
        while (@nodes) {
1293
            $temp = $nodes[0];
1294
            if ($temp =~ /^[0-9]+$/ && $nodes[1]) {        # leaf nodes with version
1295
                $normal_path = $normal_path . $path_separation . $nodes[1];
1296
            }
1297
            shift(@nodes);
1298
        }
1299
    }
1300
 
1301
    return $normal_path;
1302
}
1303
 
1304
1;