Rev 1295 | Blame | Compare with Previous | Last modification | View Log | RSS feed
################################################################################ Codestriker: Copyright (c) 2001, 2002 David Sitsky. All rights reserved.# sits@users.sourceforge.net## This program is free software; you can redistribute it and modify it under# the terms of the GPL.# Collection of routines for rendering HTML output.package Codestriker::Http::Render;use strict;use DBI;use CGI::Carp 'fatalsToBrowser';use HTML::Entities ();# Colour to use when displaying the line number that a comment is being made# against.my $CONTEXT_COLOUR = "red";sub _normal_mode_start( $ );sub _normal_mode_finish( $ );sub _coloured_mode_start( $ );sub _coloured_mode_finish( $ );# New lines within a diff block.my @diff_new_lines = ();# The corresponding lines they refer to.my @diff_new_lines_numbers = ();# The corresponding offsets they refer to.my @diff_new_lines_offsets = ();# Old lines within a diff block.my @diff_old_lines = ();# The corresponding lines they refer to.my @diff_old_lines_numbers = ();# A record of added and removed lines for a given diff block when displaying a# file in a popup window, along with their offsets.my @view_file_minus = ();my @view_file_plus = ();my @view_file_minus_offset = ();my @view_file_plus_offset = ();# What colour a line should appear if it has a comment against it.my $COMMENT_LINE_COLOUR = "red";# Constructor for rendering complex data.sub new ($$$$$$$\%\@$$\@\@\@\@$$) {my ($type, $query, $url_builder, $parallel, $max_digit_width, $topic,$mode, $comments, $tabwidth, $repository, $filenames_ref,$revisions_ref, $binaries_ref, $numchanges_ref, $max_line_length,$brmode, $fview) = @_;# Record all of the above parameters as instance variables, which remain# constant while we render code lines.my $self = {};$self->{query} = $query;$self->{url_builder} = $url_builder;$self->{parallel} = $parallel;$self->{max_digit_width} = $max_digit_width;$self->{topic} = $topic;$self->{mode} = $mode;if (! defined $brmode) {$brmode = $Codestriker::default_topic_br_mode;}if (! defined $fview) {$fview = $Codestriker::default_file_to_view;}$self->{brmode} = $brmode;$self->{fview} = $fview;$self->{comments} = $comments;$self->{tabwidth} = $tabwidth;$self->{repository} = $repository;$self->{filenames_ref} = $filenames_ref;$self->{revisions_ref} = $revisions_ref;$self->{binaries_ref} = $binaries_ref;$self->{numchanges_ref} = $numchanges_ref;$self->{max_line_length} = $max_line_length;$self->{old_linenumber} = 1;$self->{new_linenumber} = 1;# Get the main entry to the databasemy $topic_obj = Codestriker::Model::Topic->new($self->{topic});# Check for readonly$self->{topic_state} = $topic_obj->{topic_state};# Build a hash from filenumber|fileline|new -> comment array, so that# when rendering, lines can be coloured appropriately. Also build a list# of what points in the review have a comment. Also record a mapping# from filenumber|fileline|new -> the comment number.my %comment_hash = ();my @comment_locations = ();my %comment_location_map = ();for (my $i = 0; $i <= $#$comments; $i++) {my $comment = $$comments[$i];my $key = $comment->{filenumber} . "|" . $comment->{fileline} . "|" .$comment->{filenew};if (! exists $comment_hash{$key}) {push @comment_locations, $key;$comment_location_map{$key} = $#comment_locations;}push @{ $comment_hash{$key} }, $comment;}$self->{comment_hash} = \%comment_hash;$self->{comment_locations} = \@comment_locations;$self->{comment_location_map} = \%comment_location_map;# Also have a number of additional private variables which need to# be initialised.$self->{diff_current_filename} = "";$self->{diff_current_revision} = "";$self->{diff_current_repmatch} = 0;# Check if the repository has an associated LXR mapping, and if so,# setup a db connection and prepare a select statement.if (defined $repository) {my $value = $Codestriker::lxr_map->{$repository->toString()};if (defined $value) {my %lxr = %{ $value };my $passwd = $lxr{password};if (! defined $passwd) {# For backwards compatibility.$passwd = $lxr{passwd};}my $dbh = DBI->connect($lxr{db}, $lxr{user}, $passwd,{AutoCommit=>0, RaiseError=>1})|| die "Couldn't connect to database: " . DBI->errstr;my $select_ids =$dbh->prepare_cached('SELECT count(symname) FROM symbols where symname = ?');$self->{idhashref} = {};$self->{idhashsth} = $select_ids;$self->{idhashdbh} = $dbh;$self->{lxr_base_url} = $lxr{url};}else {# No LXR mapping defined for this repository.$self->{idhashref} = undef;}}else {# Topic has no repository, so no LXR mapping.$self->{idhashref} = undef;}bless $self, $type;}# cleanup, disconnect from the lxr database if connectedsub DESTROY {my $self = shift;$self->{idhashdbh}->disconnect() if exists $self->{idhashdbh};}# Given an identifier, wrap it within the appropriate <A HREF> tag if it# is a known identifier to LXR, otherwise just return the id. To avoid# excessive crap, only consider those identifiers which are at least 4# characters long.sub lxr_ident($$) {my ($self, $id) = @_;my $idhashref = $self->{idhashref};if (length($id) >= 4) {# Check if the id has not yet been found in lxr.if (not exists $idhashref->{$id}) {$idhashref->{$id} = 0; # By default not found.my $sth = $self->{idhashsth}; # DB statement handle.# Fetch ids from lxr and store in hash.$sth->execute($id);($idhashref->{$id}) = $sth->fetchrow_array();}}# Check if the id has been found in lxr.if ($$idhashref{$id}) {return "<A HREF=\"" . $self->{lxr_base_url} . "$id\" " ."CLASS=\"fid\">$id</A>";} else {return $id;}}# Parse the line and product the appropriate hyperlinks to LXR.# Currently, this is very Java/C/C++ centric, but it will do for now.sub lxr_data($$) {my ($self, $data) = @_;# Don't do anything if LXR is not enabled for this topic.return $data if ! defined $self->{idhashref};# If the line is just a comment, don't do any processing. Note this code# isn't bullet-proof, but its good enough most of the time.$_ = $data;return $data if (/^(\s| )*\/\// || /^(\s| ){0,10}\*/ ||/^(\s| ){0,10}\/\*/ ||/^(\s| )*\*\/(\s| )*$/);# Handle package Java statements.if ($data =~ /^(package(\s| )+)([\w\.]+)(.*)$/) {return $1 . $self->lxr_ident($3) . $4;}# Handle Java import statements.if ($data =~ /^(import(\s| )+)([\w\.]+)\.(\w+)((\s| )*)(.*)$/) {return $1 . $self->lxr_ident($3) . "." . $self->lxr_ident($4) . "$5$7";}# Handle #include statements. Note, these aren't identifier lookups, but# need to be mapped to http://localhost.localdomain/lxr/xxx/yyy/incfile.h# Should include the current filename in the object for matching purposes.# if (/^(\#\s*include\s+[\"<])(.*?)([\">].*)$/) {# return $1 . $self->lxr_ident($2) . $3;# }# Break the string into potential identifiers, and look them up to see# if they can be hyperlinked to an LXR lookup.my $idhashref = $self->{idhashref};my @data_tokens = split /([A-Za-z][\w]+)/, $data;my $newdata = "";my $in_comment = 0;my $eol_comment = 0;for (my $i = 0; $i <= $#data_tokens; $i++) {my $token = $data_tokens[$i];if ($token =~ /^[A-Za-z]/) {if ($eol_comment || $in_comment) {# Currently in a comment, don't LXRify.$newdata .= $token;} elsif ($token eq "nbsp" || $token eq "quot" || $token eq "amp" ||$token eq "lt" || $token eq "gt") {# HACK - ignore potential HTML entities. This needs to be# done in a smarter fashion later.$newdata .= $token;} else {$newdata .= $self->lxr_ident($token);}} else {$newdata .= $token;$token =~ s/(\s| )//g;# Check if we are entering or exiting a comment.if ($token =~ /\/\//) {$eol_comment = 1;} elsif ($token =~ /\*+\//) {$in_comment = 0;} elsif ($token =~ /\/\*/) {$in_comment = 1;}}}return $newdata;}# Render a delta. If the filename has changed since the last delta, output the# appropriate file headers. Pass in the delta object you want to render.sub delta ($$$$$$$$$$) {my ($self, $delta) = @_;my $filename = $delta->{filename};my $filenumber = $delta->{filenumber},my $revision = $delta->{revision};my $old_linenumber = $delta->{old_linenumber};my $new_linenumber = $delta->{new_linenumber};my $text = $delta->{text};my $description = $delta->{description};my $binary = $delta->{binary};my $repmatch = $delta->{repmatch};# Don't do anything for binary files.return if $binary;my $query = $self->{query};if ($delta->is_delta_new_file() == 0){# Check if the file heading needs to be output.if ($self->{diff_current_filename} ne $filename) {$self->delta_file_header($filename, $revision, $repmatch);}# Display the delta heading.$self->delta_heading($filenumber, $revision, $old_linenumber,$new_linenumber, $description, $repmatch);# Now render the actual diff text itself.$self->delta_text($filename, $filenumber, $revision, $old_linenumber,$new_linenumber, $text, $repmatch, 1, 1);}else{# Special formatting for full file upload that is not a diff.# If it not a diff, show the entire delta (actually the file# contents) in a single column.$self->delta_file_header($filename, $revision, $repmatch);print $query->Tr($query->td(" "), $query->td(" "),"\n");my @lines = split /\n/, $text;for (my $i = 0; $i <= $#lines; $i++) {my $line = $lines[$i];my $rendered_left_linenumber =$self->render_linenumber($i+1, $filenumber,1,1);# Removed the delta text, where + is added to the start of each# line. Also make sure the line is suitably escaped.$line =~ s/^\+//;$line = HTML::Entities::encode($line);my $cell = $self->render_coloured_cell($line);my $cell_class =$self->{mode} == $Codestriker::COLOURED_MODE ? "n" : "msn";print $query->Tr($query->td($rendered_left_linenumber),$query->td({-class=>$cell_class}, $cell),"\n");}}}# Output the header for a series of deltas for a specific file.sub delta_file_header ($$$$) {my ($self, $filename, $revision, $repmatch) = @_;my $filename_short = get_filename($filename);my $query = $self->{query};# We need the file names for building the forward and backward# url Strings.my $filenames = $self->{filenames_ref};# Close the table, update the current filename, and open a new table.print $query->end_table();$self->{diff_current_filename} = $filename;$self->{diff_current_revision} = $revision;$self->{diff_current_repmatch} = $repmatch;$self->print_coloured_table();# Url to the table of contents on the same page.my $contents_url =$self->{url_builder}->view_url($self->{topic}, -1,$self->{mode}, $self->{brmode},$self->{fview}). "#contents";# Variables to store the navigation Urls.my $fwd_index = "";my $bwd_index = "";my $fwd_url = "";my $bwd_url = "";# Get the current file index.my $cfi = $self->{fview};# Store the current view mode, single view = 0, all files = -1.my $vmode = $self->{fview} == -1 ? -1 : 0;# No better idea how I can get the array index of the current file. In the# single display mode you got it through fview - but in multi mode?if ($cfi == -1) {for (my $i = 0; $i <= $#$filenames; $i++) {if ($$filenames[$i] eq $filename) {$cfi = $i;last;}}}# Check the bounds for the previous and next browser. A value of -1# indicates there it is not a valid link.$fwd_index = ($cfi+1 > $#$filenames ? -1 : $cfi+1);$bwd_index = ($cfi-1 < 0 ? -1 : $cfi-1);# Build the urls for next and previous file. Differ through $vmode# between all and single file review.if ($fwd_index != -1) {$fwd_url = $self->{url_builder}->view_url($self->{topic}, -1,$self->{mode},$self->{brmode},$vmode == -1 ? -1 : $fwd_index). "#$$filenames[$fwd_index]";}if ($bwd_index != -1) {$bwd_url = $self->{url_builder}->view_url($self->{topic}, -1,$self->{mode},$self->{brmode},$vmode == -1 ? -1 : $bwd_index). "#$$filenames[$bwd_index]";}# Generate the text for the link to add a file-level comment.my $add_file_level_comment_text =$self->render_comment_link($cfi, -1, 1, "[Add File Comment]","file_comment", undef);if ($repmatch && $revision ne $Codestriker::ADDED_REVISION &&$revision ne $Codestriker::PATCH_REVISION) {# File matches something in the repository. Link it to# the repository viewer if it is defined.my $cell = "";my $revision_text = "revision $revision";my $file_url = "";if (defined $self->{repository}) {$file_url = $self->{repository}->getViewUrl($filename);}if ($file_url eq "") {# Output the header without hyperlinking the filename.$cell = $query->td({-class=>'file', -colspan=>'3'},"Diff for ",$query->a({name=>$filename},$filename_short),$revision_text);}else {# Link the filename to the repository system with more information# about it.$cell = $query->td({-class=>'file', -colspan=>'3'},"Diff for ",$query->a({href=>$file_url,name=>$filename},$filename_short),$revision_text);}# Output the "back to contents" link and some browsing links# for visiting the previous and next file (<<, >>), in# addition to the "add file-level comment" link.print $query->Tr($cell, # = file header$query->td({-class=>'file', align=>'right'},"$add_file_level_comment_text ",($bwd_url ne "" ? $query->a({href=>$bwd_url},"[<<]") : ""),$query->a({href=>$contents_url},"[Top]"),($fwd_url ne "" ? $query->a({href=>$fwd_url},"[>>]") : "")));} else {# No match in repository, or a new file.print $query->Tr($query->td({-class=>'file', -colspan=>'3'},"File ",$query->a({name=>$filename},$filename)),$query->td({-class=>'file', align=>'right'},"$add_file_level_comment_text ",($bwd_url ne "" ? $query->a({href=>$bwd_url},"[<<]") : ""),$query->a({href=>$contents_url},"[Top]"),($fwd_url ne "" ? $query->a({href=>$fwd_url},"[>>]") : "")));}}# Output the delta heading, which consists of links to view the old and new# file in its entirety.sub delta_heading ($$$$$$$) {my ($self, $filenumber, $revision, $old_linenumber, $new_linenumber,$description, $repmatch) = @_;my $query = $self->{query};# Create some blank space.print $query->Tr($query->td(" "), $query->td(" "),$query->td(" "), $query->td(" "), "\n");# Output a diff block description if one is available, in a separate# row.if ($description ne "") {my $description_escaped = HTML::Entities::encode($description);print $query->Tr($query->td({-class=>'line', -colspan=>'2'},$description_escaped),$query->td({-class=>'line', -colspan=>'2'},$description_escaped));}if ($repmatch && $revision ne $Codestriker::ADDED_REVISION &&$revision ne $Codestriker::PATCH_REVISION) {# Display the line numbers corresponding to the patch, with links# to the entire file.my $url_builder = $self->{url_builder};my $topic = $self->{topic};my $mode = $self->{mode};my $url_old_full =$url_builder->view_file_url($topic, $filenumber, 0,$old_linenumber, $mode, 0);my $url_old = "javascript: myOpen('$url_old_full','CVS')";my $url_old_both_full =$url_builder->view_file_url($topic, $filenumber, 0,$old_linenumber, $mode, 1);my $url_old_both ="javascript: myOpen('$url_old_both_full','CVS')";my $url_new_full =$url_builder->view_file_url($topic, $filenumber, 1,$new_linenumber, $mode, 0);my $url_new = "javascript: myOpen('$url_new_full','CVS')";my $url_new_both_full =$url_builder->view_file_url($topic, $filenumber, 1,$new_linenumber, $mode, 1);my $url_new_both = "javascript: myOpen('$url_new_both_full','CVS')";print $query->Tr($query->td({-class=>'line', -colspan=>'2'},$query->a({href=>$url_old}, "Line " .$old_linenumber) ." | " .$query->a({href=>$url_old_both},"Parallel")),$query->td({-class=>'line', -colspan=>'2'},$query->a({href=>$url_new}, "Line " .$new_linenumber) ." | " .$query->a({href=>$url_new_both},"Parallel"))),"\n";} else {# No match in the repository - or a new file. Just display# the headings.print $query->Tr($query->td({-class=>'line', -colspan=>'2'},"Line $old_linenumber"),$query->td({-class=>'line', -colspan=>'2'},"Line $new_linenumber")),"\n";}}# Output the delta text chunk in the coloured format.sub delta_text ($$$$$$$$$$$) {my ($self, $filename, $filenumber, $revision, $old_linenumber,$new_linenumber, $text, $repmatch, $new, $link) = @_;my $query = $self->{query};# Split up the lines, and display them, with the appropriate links.my @lines = split /\n/, $text;$self->{old_linenumber} = $old_linenumber;$self->{new_linenumber} = $new_linenumber;for (my $i = 0; $i <= $#lines; $i++) {my $line = $lines[$i];if ($self->{parallel}) {$self->display_coloured_data($filenumber, $line, $link);} else {$self->display_single_filedata($filenumber, $line, $new, $link);}}# Render the diff blocks.if ($self->{parallel}) {$self->render_changes($filenumber, $link);} else {$self->flush_monospaced_lines($filenumber, $self->{max_line_length},$new, $link);}}# Display a line for coloured data. Note special handling is done for# unidiff formatted text, to output it in the "coloured-diff" style. This# requires storing state when retrieving each line.sub display_coloured_data ($$$$) {my ($self, $filenumber, $data, $link) = @_;my $query = $self->{query};# Escape the data.$data = HTML::Entities::encode($data);my $leftline = $self->{old_linenumber};my $rightline = $self->{new_linenumber};if ($data =~ /^\-(.*)$/) {# Line corresponds to something which has been removed.add_old_change($1, $leftline);$leftline++;} elsif ($data =~ /^\+(.*)$/) {# Line corresponds to something which has been removed.add_new_change($1, $rightline);$rightline++;} elsif ($data =~ /^\\/) {# A diff comment such as "No newline at end of file" - ignore it.} else {# Strip the first space off the diff for proper alignment.$data =~ s/^\s//;# Render the previous diff changes visually.$self->render_changes($filenumber, $link);# Render the current line for both cells.my $celldata = $self->render_coloured_cell($data);# Determine the appropriate classes to render.my $cell_class =$self->{mode} == $Codestriker::COLOURED_MODE ? "n" : "msn";my $rendered_left_linenumber =$self->render_linenumber($leftline, $filenumber, 0, $link);my $rendered_right_linenumber =($leftline == $rightline && !$self->{parallel}) ?$rendered_left_linenumber :$self->render_linenumber($rightline, $filenumber, 1, $link);print $query->Tr($query->td($rendered_left_linenumber),$query->td({-class=>$cell_class}, $celldata),$query->td($rendered_right_linenumber),$query->td({-class=>$cell_class}, $celldata),"\n");$leftline++;$rightline++;}# Update the left and right line nymber state variables.$self->{old_linenumber} = $leftline;$self->{new_linenumber} = $rightline;}# Render a cell for the coloured diff.sub render_coloured_cell($$){my ($self, $data) = @_;if (! defined $data || $data eq "") {return " ";}# Replace spaces and tabs with the appropriate number of 's.$data = tabadjust($self->{tabwidth}, $data, 1);if ($self->{brmode} == $Codestriker::LINE_BREAK_ASSIST_MODE) {$data =~ s/^(\s+)/my $sp='';for(my $i=0;$i<length($1);$i++){$sp.=' '}$sp;/ge;}else {$data =~ s/\s/ /g;}# Add LXR links to the output.$data = $self->lxr_data($data);# Unconditionally add a at the start for better alignment.return " $data";}# Indicate a line of data which has been removed in the diff.sub add_old_change($$) {my ($data, $linenumber) = @_;push @diff_old_lines, $data;push @diff_old_lines_numbers, $linenumber;}# Indicate that a line of data has been added in the diff.sub add_new_change($$) {my ($data, $linenumber) = @_;push @diff_new_lines, $data;push @diff_new_lines_numbers, $linenumber;}# Render the current diff changes, if there is anything.sub render_changes($$$) {my ($self, $filenumber, $link) = @_;return if ($#diff_new_lines == -1 && $#diff_old_lines == -1);my ($arg1, $arg2, $arg3, $arg4);my $mode = $self->{mode};if ($#diff_new_lines != -1 && $#diff_old_lines != -1) {# Lines have been added and removed.if ($mode == $Codestriker::COLOURED_MODE) {$arg1 = "c"; $arg2 = "cb"; $arg3 = "c"; $arg4 = "cb";} else {$arg1 = "msc"; $arg2 = "mscb"; $arg3 = "msc"; $arg4 = "mscb";}} elsif ($#diff_new_lines != -1 && $#diff_old_lines == -1) {# New lines have been added.if ($mode == $Codestriker::COLOURED_MODE) {$arg1 = "a"; $arg2 = "ab"; $arg3 = "a"; $arg4 = "ab";} else {$arg1 = "msa"; $arg2 = "msab"; $arg3 = "msa"; $arg4 = "msab";}} else {# Lines have been removed.if ($mode == $Codestriker::COLOURED_MODE) {$arg1 = "r"; $arg2 = "rb"; $arg3 = "r"; $arg4 = "rb";} else {$arg1 = "msr"; $arg2 = "msrb"; $arg3 = "msr"; $arg4 = "msrb";}}$self->render_inplace_changes($arg1, $arg2, $arg3, $arg4, $filenumber,$link);# Now that the diff changeset has been rendered, remove the state data.@diff_new_lines = ();@diff_new_lines_numbers = ();@diff_old_lines = ();@diff_old_lines_numbers = ();}# Render the inplace changes in the current diff change set.sub render_inplace_changes($$$$$$$){my ($self, $old_col, $old_notpresent_col, $new_col,$new_notpresent_col, $filenumber, $link) = @_;my $old_data;my $new_data;my $old_data_line;my $new_data_line;while ($#diff_old_lines != -1 || $#diff_new_lines != -1) {# Retrieve the next lines which were removed (if any).if ($#diff_old_lines != -1) {$old_data = shift @diff_old_lines;$old_data_line = shift @diff_old_lines_numbers;} else {undef($old_data);undef($old_data_line);}# Retrieve the next lines which were added (if any).if ($#diff_new_lines != -1) {$new_data = shift @diff_new_lines;$new_data_line = shift @diff_new_lines_numbers;} else {undef($new_data);undef($new_data_line);}my $render_old_data = $self->render_coloured_cell($old_data);my $render_new_data = $self->render_coloured_cell($new_data);# Set the colours to use appropriately depending on what is defined.my $render_old_colour = $old_col;my $render_new_colour = $new_col;if (defined $old_data && ! defined $new_data) {$render_new_colour = $new_notpresent_col;} elsif (! defined $old_data && defined $new_data) {$render_old_colour = $old_notpresent_col;}my $parallel = $self->{parallel};my $query = $self->{query};print $query->Tr($query->td($self->render_linenumber($old_data_line,$filenumber,0, $link)),$query->td({-class=>"$render_old_colour"},$render_old_data),$query->td($self->render_linenumber($new_data_line,$filenumber,1, $link)),$query->td({-class=>"$render_new_colour"},$render_new_data), "\n");}}# Render a linenumber as a hyperlink. If the line already has a# comment made against it, render it with $comment_line_colour. The# title of the link should be set to the comment digest, and the# status line should be set if the mouse moves over the link.# Clicking on the link will take the user to the add comment page.sub render_linenumber($$$$$) {my ($self, $line, $filenumber, $new, $link) = @_;if (! defined $line) {return " ";}# Determine what class to use when rendering the number.my ($comment_class, $no_comment_class);if ($self->{mode} == $Codestriker::COLOURED_MODE) {$comment_class = "com";$no_comment_class = "nocom";} else {$comment_class = "smscom";$no_comment_class = "smsnocom";}# Check if the linenumber is outside the review.if ($link == 0) {return $line;}# Now render the line.return $self->render_comment_link($filenumber, $line, $new, $line,$comment_class, $no_comment_class);}# Render the supplied text within a edit comment link.sub render_comment_link {my ($self, $filenumber, $line, $new, $text,$comment_class, $no_comment_class) = @_;# Determine the anchor and edit URL for this line number.my $anchor = "$filenumber|$line|$new";my $edit_url = "javascript:eo('$filenumber','$line','$new')";# Set the anchor to this line number.my $params = {};$params->{name} = $anchor;# Only set the href attribute if the comment is in open state.if (!Codestriker::topic_readonly($self->{topic_state})) {$params->{href} = $edit_url;}# If a comment exists on this line, set span and the overlib hooks onto# it.my $query = $self->{query};my %comment_hash = %{ $self->{comment_hash} };my %comment_location_map = %{ $self->{comment_location_map} };my $comment_number = undef;if (exists $comment_hash{$anchor}) {# Determine what comment number this anchor refers to.$comment_number = $comment_location_map{$anchor};if (defined $comment_class) {$text = $query->span({-id=>"c$comment_number"}, "") .$query->span({-class=>$comment_class}, $text);}# Determine what the next comment in line is.my $index = -1;my @comment_locations = @{ $self->{comment_locations} };for ($index = 0; $index <= $#comment_locations; $index++) {last if $anchor eq $comment_locations[$index];}$params->{onmouseover} = "return overlib(comment_text[$index],STICKY,DRAGGABLE,ALTCUT);";$params->{onmouseout} = "return nd();";} else {if (defined $no_comment_class) {$text = $query->span({-class=>$no_comment_class}, $text);}}return $query->a($params, $text);}# Start hook called when about to start rendering to a page.sub start($) {my ($self) = @_;# Now create the start of the rendering tables.if ($self->{mode} == $Codestriker::NORMAL_MODE) {$self->_normal_mode_start();} else {$self->_coloured_mode_start();}}# Finished hook called when finished rendering to a page.sub finish($) {my ($self) = @_;if ($self->{mode} == $Codestriker::NORMAL_MODE) {$self->_normal_mode_finish();} else {$self->_coloured_mode_finish();}$self->_print_legend();}# Start topic view display hook for normal mode.sub _normal_mode_start($) {my ($self) = @_;print "<PRE>\n";}# Finish topic view display hook for normal mode.sub _normal_mode_finish($) {my ($self) = @_;print "</PRE>\n";}# Private functon to print the diff legend out at the bottom of the topic text page.sub _print_legend($) {my ($self) = @_;my $query = $self->{query};my $topic = $self->{topic};my $mode = $self->{mode};print $query->start_table({-cellspacing=>'0', -cellpadding=>'0',-border=>'0'}), "\n";print $query->Tr($query->td(" "), $query->td(" "));print $query->Tr($query->td({-colspan=>'2'}, "Legend:"));print $query->Tr($query->td({-class=>'rf'},"Removed"),$query->td({-class=>'rb'}, " "));print $query->Tr($query->td({-class=>'cf',-align=>"center", -colspan=>'2'},"Changed"));print $query->Tr($query->td({-class=>'ab'}, " "),$query->td({-class=>'af'},"Added"));print $query->end_table(), "\n";}# Start topic view display hook for coloured mode. This displays a simple# legend, displays the files involved in the review, and opens up the initial# table.sub _coloured_mode_start($) {my ($self) = @_;my $query = $self->{query};my $topic = $self->{topic};my $mode = $self->{mode};my $brmode = $self->{brmode};my $fview = $self->{fview};my $display_all_url =$self->{url_builder}->view_url($topic, -1, $mode, $brmode, -1);my $display_single_url =$self->{url_builder}->view_url($topic, -1, $mode, $brmode, 0);# Print out the "table of contents".my $filenames = $self->{filenames_ref};my $revisions = $self->{revisions_ref};my $binaries = $self->{binaries_ref};my $numchanges = $self->{numchanges_ref};print $query->p;print $query->start_table({-cellspacing=>'0', -cellpadding=>'0',-border=>'0'}), "\n";# Include a link to view all files in a topic, if we are in single# display mode.if ($fview != -1) {print $query->Tr($query->td($query->a({name=>"contents"},"Files in Topic: ("),$query->a({href=>$display_all_url},"view all files"), ")"),$query->td(" ")), "\n";}else {print $query->Tr($query->td($query->a({name=>"contents"},"Files in Topic:")),$query->td(" ")), "\n";}my $url_builder = $self->{url_builder};for (my $i = 0; $i <= $#$filenames; $i++) {my $filename = $$filenames[$i];my $filename_short = get_filename($filename);my $revision = $$revisions[$i];my $numchange = $$numchanges[$i];my $href_filename =$url_builder->view_url($topic, -1, $mode, $brmode, $i) ."#" . "$filename";my $anchor_filename =$url_builder->view_url($topic, -1, $mode, $brmode, -1) ."#" . "$filename";my $tddata = $$binaries[$i] ? $filename :$query->a({href=>$href_filename}, "$filename_short");if ($fview == -1) {# Add a jump to link for the all files view.$tddata = "[" . $query->a({href=>$anchor_filename}, "Jump to") . "] " . $tddata;}my $lineData = "";if ($numchange ne "") {$lineData = " <FONT size=-1>{$numchange}</FONT>";}my $class = "";$class = "af" if ($revision eq $Codestriker::ADDED_REVISION);$class = "rf" if ($revision eq $Codestriker::REMOVED_REVISION);$class = "cf" if ($revision eq $Codestriker::PATCH_REVISION);if ($revision eq $Codestriker::ADDED_REVISION ||$revision eq $Codestriker::REMOVED_REVISION ||$revision eq $Codestriker::PATCH_REVISION) {# Added, removed or patch file.print $query->Tr($query->td({-class=>"$class", -colspan=>'2'},$tddata),$query->td({-class=>"$class"}, $lineData)) . "\n";} else {# Modified file.print $query->Tr($query->td({-class=>'cf'}, $tddata),$query->td({-class=>'cf'}, " $revision"),$query->td({-class=>'cf'}, $lineData)) ."\n";}}print $query->end_table() . "\n";# Render the "Add comment to topic" link.print $query->p;print $self->render_comment_link(-1, -1, 1, "Add General Comment","general_comment", undef);print " to topic.";print $query->p;print $query->start_table() ;}# Render the initial start of the coloured table, with an empty row setting# the widths.sub print_coloured_table($){my ($self) = @_;my $query = $self->{query};print $query->start_table({-width=>'100%',-border=>'0',-cellspacing=>'0',-cellpadding=>'0'}), "\n";print $query->Tr($query->td({-width=>'2%'}, " "),$query->td({-width=>'48%'}, " "),$query->td({-width=>'2%'}, " "),$query->td({-width=>'48%'}, " "), "\n");}# Finish topic view display hook for coloured mode.sub _coloured_mode_finish ($) {my ($self) = @_;if ($self->{fview} != -1) {# Show the current file header again for navigation purposes when# viewing a single file at a time.$self->delta_file_header($self->{diff_current_filename},$self->{diff_current_revision},$self->{diff_current_repmatch});}print "</TABLE>\n";# Render the "Add comment to topic" link.my $query = $self->{query};print $query->p;print $self->render_comment_link(-1, -1, 1, "Add General Comment","general_comment", undef);print " to topic.";print $query->p;}# Display a line for a single file view.sub display_single_filedata ($$$$$) {my ($self, $filenumber, $data, $new, $link) = @_;my $leftline = $self->{old_linenumber};my $rightline = $self->{new_linenumber};my $max_line_length = $self->{max_line_length};# Handling of either an old or new view.if ($data =~ /^\-(.*)$/o) {# A removed line.$self->add_minus_monospace_line($1, $leftline++);} elsif ($data =~ /^\+(.*)$/o) {# An added line.$self->add_plus_monospace_line($1, $rightline++);} else {# An unchanged line, output it and anything pending, and remove# the leading space for alignment reasons.$data =~ s/^\s//;$self->flush_monospaced_lines($filenumber, $max_line_length, $new,$link);my $linenumber = $new ? $rightline : $leftline;print $self->render_monospaced_line($filenumber, $linenumber, $new,$data, $link,$max_line_length, "");$leftline++;$rightline++;}# Update the left and right line nymber state variables.$self->{old_linenumber} = $leftline;$self->{new_linenumber} = $rightline;}# Print out a line of data with the specified line number suitably aligned,# and with tabs replaced by spaces for proper alignment.sub render_monospaced_line ($$$$$$$$) {my ($self, $filenumber, $linenumber, $new, $data, $link,$max_line_length, $class) = @_;# Convert any identifier to their LXR links.$data = $self->lxr_data(HTML::Entities::encode($data));my $prefix = "";my $digit_width = length($linenumber);my $max_digit_width = $self->{max_digit_width};for (my $i = 0; $i < ($max_digit_width - $digit_width); $i++) {$prefix .= " ";}# Determine what class to use when rendering the number.my ($comment_class, $no_comment_class);if ($self->{parallel} == 0) {$comment_class = "mscom";$no_comment_class = "msnocom";} else {if ($self->{mode} == $Codestriker::COLOURED_MODE) {$comment_class = "com";$no_comment_class = "nocom";} else {$comment_class = "smscom";$no_comment_class = "smsnocom";}}my $line_cell = "";if ($link == 0) {# A line outside of the review. Just render the line number, as# the "name" of the linenumber should not be used.$line_cell = "$prefix$linenumber";} else {$line_cell = $prefix .$self->render_comment_link($filenumber, $linenumber, $new,$linenumber, $comment_class,$no_comment_class);}# Render the line data. If the user clicks on a topic line, the# edit window is focused to the appropriate line.my $query = $self->{query};# Replace the line data with spaces.my $newdata = tabadjust($self->{tabwidth}, $data, 0);if ($class ne "") {# Add the appropriate number of spaces to justify the data to a length# of $max_line_length, and render it within a SPAN to get the correct# background colour.my $padding = $max_line_length - length($data);for (my $i = 0; $i < ($padding); $i++) {$newdata .= " ";}return "$line_cell " .$query->span({-class=>"$class"}, $newdata) . "\n";}else {return "$line_cell $newdata\n";}}# Record a plus line.sub add_plus_monospace_line ($$$) {my ($self, $linedata, $offset) = @_;push @view_file_plus, $linedata;push @view_file_plus_offset, $offset;}# Record a minus line.sub add_minus_monospace_line ($$$) {my ($self, $linedata, $offset) = @_;push @view_file_minus, $linedata;push @view_file_minus_offset, $offset;}# Flush the current diff chunk. Note if the original file is being rendered,# the minus lines are used, otherwise the plus lines.sub flush_monospaced_lines ($$$$$) {my ($self, $filenumber, $max_line_length, $new, $link) = @_;my $class = "";if ($#view_file_plus != -1 && $#view_file_minus != -1) {# This is a change chunk.$class = "msc";}elsif ($#view_file_plus != -1) {# This is an add chunk.$class = "msa";}elsif ($#view_file_minus != -1) {# This is a remove chunk.$class = "msr";}if ($new) {for (my $i = 0; $i <= $#view_file_plus; $i++) {print $self->render_monospaced_line($filenumber,$view_file_plus_offset[$i],$new,$view_file_plus[$i], $link,$max_line_length, $class);}}else {for (my $i = 0; $i <= $#view_file_minus; $i++) {print $self->render_monospaced_line($filenumber,$view_file_minus_offset[$i],$new,$view_file_minus[$i], $link,$max_line_length, $class);}}$#view_file_minus = -1;$#view_file_minus_offset = -1;$#view_file_plus = -1;$#view_file_plus_offset = -1;}# Replace the passed in string with the correct number of spaces, for# alignment purposes.sub tabadjust ($$$) {my ($tabwidth, $input, $htmlmode) = @_;$_ = $input;if ($htmlmode) {1 while s/\t+/' ' x(length($&) * $tabwidth - length($`) % $tabwidth)/eo;}else {1 while s/\t+/' ' x(length($&) * $tabwidth - length($`) % $tabwidth)/eo;}return $_;}# Retrieve the data that forms the "context" when submitting a comment.sub get_context ($$$$$$$$$) {my ($type, $targetline, $context, $html_view, $old_startline,$new_startline, $text, $new) = @_;# Break the text into lines.my @document = split /\n/, $text;# Calculate the location of the target line within the diff chunk.my $offset;my $old_linenumber = $old_startline;my $new_linenumber = $new_startline;for ($offset = 0; $offset <= $#document; $offset++) {my $data = $document[$offset];# Check if the target line as been found.if ($data =~ /^ /o) {last if ($new && $new_linenumber == $targetline);last if ($new == 0 && $old_linenumber == $targetline);$old_linenumber++;$new_linenumber++;} elsif ($data =~ /^\+/o) {last if ($new && $new_linenumber == $targetline);$new_linenumber++;} elsif ($data =~ /^\-/o) {last if ($new == 0 && $old_linenumber == $targetline);$old_linenumber++;}}# Get the minimum and maximum line numbers for this context, and return# the data. The line of interest will be rendered appropriately.my $min_line = ($offset - $context < 0 ? 0 : $offset - $context);my $max_line = $offset + $context;my $context_string = "";for (my $i = $min_line; $i <= $max_line && $i <= $#document; $i++) {my $linedata = $document[$i];if ($html_view) {if ($i == $offset) {$context_string .="<font color=\"$CONTEXT_COLOUR\">" .HTML::Entities::encode($linedata) . "</font>\n";} else {$context_string .= HTML::Entities::encode("$linedata") ."\n";}} else {# This is the context for emails.$context_string .= ($i == $offset) ? "* " : " ";$context_string .= $linedata . "\n";}}return $context_string;}sub get_filename ($) {my ($extended_path) = @_;my $normal_path = $extended_path;my $path_separation = "/";if ($extended_path =~ /^(.*)@@(.*)/) {$normal_path = $1; # before clearcase extended pathmy $other_path = $2; # rest of the path that have clearcase Multi version paths# Before manipulating, see what kind of path separation is used# Assuming, only either \ or / is used.if ($extended_path =~ /\\/) {$path_separation = "\\";}my @nodes = split(/[\/\\]/, $other_path);my $temp;while (@nodes) {$temp = $nodes[0];if ($temp =~ /^[0-9]+$/ && $nodes[1]) { # leaf nodes with version$normal_path = $normal_path . $path_separation . $nodes[1];}shift(@nodes);}}return $normal_path;}1;