#!/hgdv/software/IRIX6/bin/perl

#                                CVSHTMLDIFF
#
# Does a 'cvs diff' on a HTML file
# and produces a new HTML file showing the differences
# highlighted by coloring.
#
# Synopsis:
#   cvshtmldiff [options..] file.html
#   -o out.html   output html file (default is file_diff.html)
#   -r rev        do the diff against this revision (default: head of branch)
#   -q            don't give feedback about which revision has been retrieved
#
# Notice:
#  It can be arbitrarily complex to produce correct HTML from a diff!
#  Therefore, no attempt is being made to do that. ;-)
#  If the results are unacceptable, you might want to try 'tidy'
#  on the resulting HTML file (can be obtained from w3.org).
#
#  Note also, that some changes in HTML files may not be visible!
#  (such as inserting an anchor)
#  Such changes will not be visible in the resulting HTML file either.
#
#  A legend ist inserted right after the <BODY>.
#  If there is no <BODY> tag, then there will be no legend ;-)
#
# Author:
#  Gabriel Zachmann (zach@igd.fhg.de,Gabriel.Zachmann@gmx.net)
#  Feb 2000.
#
# TODO:
# - es geht nur zeilenweise, d.h. es wird mehr markiert als sich wirklich
#   geaendert hat. Kann man das fixen dass er da noch Anfang und Ende
#   mitvergleicht?
#




####################################
# Things you might want to customize

%regionstart = (
		"d" => "<FONT COLOR=red>\n",
		"a" => "<FONT COLOR=green>\n",
		"c" => "<FONT COLOR=fuchsia>\n"
);
#		"d" => "<FONT COLOR=red SIZE=-1>\n",

%regionend = (
		"d" => "</FONT>\n",
		"a" => "</FONT>\n",
		"c" => "</FONT>\n"
);

# Warning: regionstart and regionend will be inserted inside <TD>..</TD>, too!

$legend = "<B><PRE>
<FONT COLOR=red    >red     = deleted<\/FONT>
<FONT COLOR=green  >green   = appended<\/FONT>
<FONT COLOR=fuchsia>purple  = changed<\/FONT>
</B></PRE>\n";

#
####################################


require 5.004;
require "getopts.pl";


#  parse options / parameters

&Getopts('hqo:r:');

$infile = shift;

if ( $opt_h || $infile eq "" )
{
	print "usage: cvshtmldiff [options..] file.html\n";
	print "-o out.html   output html file (default is file_diff.html)\n";
	print "-r rev        do the diff against this revision (default: head of branch)\n"; 
	print "-q            don't give feedback about which revision has been retrieved\n";
	print "-h            this help\n";
	exit 0;
}

if ( $opt_r )	{ $rev = "-r$opt_r"; }
else			{  $rev = ""; }

if ( $opt_o )
{
	$outfile = $opt_o;
}
else
{
	($outfile,$dummy) = $infile =~ /^(.*?)(.html?)?$/;
	$outfile .= "_diff.html";
}
if ( length($outfile) == 0 )
{
	print STDERR "cvshtmldiff: no output file!\n";
	exit 1;
}

$tmpdir = $ENV{"TMPDIR"};
if ( ! $tmpdir )
{
	$tmpdir = "/tmp";
}

#   do diff

$difffile = $tmpdir . "/cvshtmldiff-$$";
$err = system( "cvs diff $opt_r $infile > $difffile" );


#   parse 'cvs diff' and merge it into htmlfile

if ( ! open(OUTFILE, "> $outfile") )
{
	die "\ncvshtmldiff: could not open output file $outfile: $!\n\n";
}
if ( ! open(INFILE, "< $infile") )
{
	die "\ncvshtmldiff: could not open input file $infile: $!\n\n";
}
if ( ! open(DIFFFILE, "< $difffile") )
{
	die "\ncvshtmldiff: could not open diff file $difffile: $!\n\n";
}

#    main

$line = 0;
# $line = # lines read from infile and copied to outfile

DIFF:
while ( $chunk = <DIFFFILE> )
{
	# read difffile up to next diff chunk
	while ( ! ( ($m1,$m2,$op,$n1,$n2) =
			    $chunk =~ m/^(\d+)(?:,(\d+))?([adc])(\d+)(?:,(\d+))?$/o ) )
	{
		if ( $chunk =~ m/^retrieving revision/o && ! $opt_q )
		{
			# feedback
			print $chunk;
		}
		if ( ! ($chunk = <DIFFFILE>) )
		{
			last DIFF;
		}
	}

	# sanity check
	if ( $m1 eq "" || $n1 eq "" || $op eq "" )
	{
		print STDERR "\ncvshtmldiff: Error: couldn't parse chunk\n";
		print STDERR $chunk;
		print STDERR "from diff file $difffile!\n";
		next;
	}

	if ( $op eq "a" || $op eq "c" )
	{
		# append or change (text ist already in infile)

		if ( $op eq "a" && $m2 )
		{
			print STDERR "\ncvshtmldiff: Warning: append chunk $chunk\n";
			print STDERR "has 2 line numbers w/ respect to file 1!\n";
			print STDERR "Don't know how to handle that.\n";
		}

		$err = xfer_upto( $n1-1 );
		if ( $err ) { last; }

		# copy changed/appended text
		if ( ! $n2 ) { $n2 = $n1; }
		$err = xfer_upto( $n2, $regionstart{$op}, $regionend{$op} );
		if ( $err ) { last; }
	}
	elsif ( $op eq "d" )
	{
		# delete (deleted text is in diff file)

		if ( $n2 )
		{
			print STDERR "\ncvshtmldiff: Warning: delete chunk $chunk\n";
			print STDERR "has 2 line numbers w/ respect to file 2!\n";
			print STDERR "Don't know how to handle that.\n";
		}

		$err = xfer_upto( $n1 );
		if ( $err ) { last; }

		# copy deleted text to output file
		if ( ! $m2 ) { $m2 = $m1; }
		$err = copy_diff( $m2-$m1+1, $regionstart{$op}, $regionend{$op} );
		if ( $err ) { last; }
	}
	else
	{
		print STDERR "\ncvshtmldiff: Error: don't know about operator ";
		print STDERR "\'$op\'\nin chunk $chunk of diff file $difffile!\n";
		next;
	}

}

# transfer rest of infile to outfile
while ( <INFILE> )
{
	print OUTFILE $_;
}


close OUTFILE;
close INFILE;
close DIFFFILE;
exit 0;


#
#  Copy lines from infile to outfile
#  up to, and including, line $1.
#  The first line in a file is # 1.
#  Enclose block of copied lines in $rs .. $re.
#  Global variables: $line
#

sub xfer_upto
{
	my ($upto,$rs,$re) = @_;

	if ( $line > $upto )								# = is ok, can happen
	{
		print STDERR "\ncvshtmldiff: xfer_upto: Bug: upto ($upto) < ";
		print STDERR "line ($line) on entry!\n";
		return 1;
	}

	if ( $rs )	{ print OUTFILE $rs; }

	while ( $line < $upto )
	{
		$_ = <INFILE>;

		if ( $_ eq "" )
		{
			print STDERR "\ncvshtmldiff: Error: input file $infile ";
			print STDERR "exhausted!\n";
			print STDERR "while trying to process chunk $chunk\n";
			print STDERR "from diff file $difffile\n";
			return 1;
		}

		# insert legend
		s/(<BODY[^>]*>)/$1\n$legend\n/oi;

		if ( $rs )
		{
			# insert regionstart/-end inside <TD></TD>
			s/(<TD[^>]*>)/$1$rs/ogi;
			s/(<\/TD>)/$re$1/ogi;
		}

		print OUTFILE $_;
		$line ++ ;
	}

	# print region-end twice, just in case we got out of sync
	if ( $re )	{ print OUTFILE $re; print OUTFILE $re; }

	return 0;
}



# copy $1 many lines from difffile to outfile
#  Enclose block of copied lines in $rs .. $re.

sub copy_diff
{
	my ($l,$rs,$re) = @_;

	if ( $l <= 0 )
	{
		print STDERR "\ncvshtmldiff: copy_upto: Bug: #lines ($l) <= 0!\n ";
		return 1;
	}

	if ( $rs )	{ print OUTFILE $rs; }

	while ( $l > 0 )
	{
		$_ = <DIFFFILE>;
		if ( $_ eq "" )
		{
			print STDERR "\ncvshtmldiff: Error: diff file $difffile ";
			print STDERR "exhausted!\n";
			print STDERR "while trying to append deleted lines $m1-$m2!\n";
			print STDERR "of chunk $chunk from diff file $difffile\n";
			return 1;
		}

		s/< //o;

		if ( $rs )
		{
			# insert regionstart/-end inside <TD></TD>
			s/(<TD[^>]*>)/$1$rs/ogi;
			s/(<\/TD>)/$re$1/ogi;
		}

		print OUTFILE $_;
		$l -- ;
	}

	# print region-end twice, just in case we got out of sync
	if ( $re )	{ print OUTFILE $re; print OUTFILE $re; }

	return 0;
}


