#!/usr/bin/perl -w

=head1 NAME

def-differ.pl - compare term defs between two OBO files

=head1 SYNOPSIS

 def-differ.pl --file_1 old_gene_ontology.obo --file_2 gene_ontology.obo
 -o results.txt

=head1 DESCRIPTION

Compares the defs in two OBO files and records the differences between them

=head2 Input parameters

=head3 Required

=over

=item -o || --output /path/to/file_name

output file for results

=back

=head3 Configuration options

=over

=item Comparing two existing files

Enter the two files using the following syntax:

 -f1 /path/to/file_name  -f2 /path/to/file_2_name

where f1 is the "old" ontology file and f2 is the "new" file

=item Comparing ontology files from two different dates

Enter the file and the two dates using this syntax:

 -f /path/to/file -d1 "data one here" -d2 "date two here"

The dates must be in a CVS-parseable form, e.g. "01 Dec 2010" or "2010-01-30" (YYYY-MM-DD)

If -f is left blank, the default file used is /go/ontology/editors/gene_ontology_write.obo

=back

=head3 Optional switches

=over

=item -c || --db_counts

Get annotation counts for any terms that have changed from a database. Note that
the appropriate DB connection parameters should be entered into the "defaults"
at the top of the file.

=item -m || --output_mode

Choose the file format for output, either text or html. Defaults to HTML

=item -g || --go_options

Use the defaults set in the 'go_options' hash at the top of the file.

=item -v || --verbose

prints various messages

=back

=cut


use strict;
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
use Text::WordDiff;

{	package Text::WordDiff::HTMLTwoLines;
	use strict;
	use HTML::Entities qw(encode_entities);
	use base 'Text::WordDiff::Base';
	use Data::Dumper;

	sub file_header {
		my $self = shift;
		my $header = $self->SUPER::file_header(@_);

		if ($header)
		{	$self->{__str1} = $self->{__str2} = qq{<span class="file"><span class="fileheader">$header</span>};
		}
		else
		{	$self->{__str1} = $self->{__str2} = '<span class="file">';
		}
		return '';

		return '<span class="file">' unless $header;
		return qq{<span class="file"><span class="fileheader">$header</span>};
	}

	sub hunk_header {
		my $self = shift;
	#	$self->{__str1} .= '<span class="hunk">';
	#	$self->{__str2} .= '<span class="hunk">';
		return '';
	}
	sub hunk_footer {
		my $self = shift;
	#	$self->{__str1} .= '</span>';
	#	$self->{__str2} .= '</span>';
		return '';
	}

	sub file_footer {
		my $self = shift;
		$self->{__str1} .= '</span>';
		$self->{__str2} .= '</span>';
		return $self->{__str1} . $self->{__str2};
	}

	sub same_items {
		my $self = shift;
		$self->{__str1} .= encode_entities( join '', @_ );
		$self->{__str2} .= encode_entities( join '', @_ );
		return '';
	}

	sub delete_items {
		my $self = shift;
		$self->{__str1} .= '<del>' . encode_entities( join '', @_ ) . '</del>';
		return '';
	}

	sub insert_items {
		my $self = shift;
		$self->{__str2} .= '<ins>' . encode_entities( join '', @_ ) . '</ins>';
		return '';
	}

	1;
}

package main;

use DBI;
use DBD::mysql;
use Template;
use Text::WordDiff;

my $defaults = {
	f => 'go/ontology/editors/gene_ontology_write.obo',
	dbname => $ENV{GO_DBNAME} || 'go_latest_lite',
	dbhost => $ENV{GO_DBHOST} || 'spitz',
	dbuser => $ENV{GO_DBUSER} || '',
	dbpass => $ENV{GO_DBPASS} || '',
	dbport => $ENV{GO_DBSOCKET} || '',
	dbdriver => $ENV{GO_DBDRIVER} || 'mysql',
	install_dir => 'http://www.geneontology.org/', ## base dir for URLs in html
	go_root => $ENV{GO_CVSROOT} || "",
	mode => 'html',
};

my $go_options = {
	d1          => '1 week ago',
	d2          => 'today',
	db_counts   => 1,
	output      => 'go/doc/GO.def_diffs.html',
	go_root     => '/Users/gwg/',
};

run_script($defaults, $go_options, \@ARGV);

exit(0);

sub run_script {
	my $options = parse_options(@_);

	# check verbosity
	if (! defined $options->{verbose})
	{	$options->{verbose} = $ENV{GO_VERBOSE} || 0;
	}

	## OK, looks like we're going to have to compare the files.
	print STDERR "Parsed options. Now starting script...\n" if $options->{verbose};

	my $data;
	open(OUT, ">" . $options->{'output'}) or die("Could not create " . $options->{output} . ".html : $!");

	open(FH, "<" . $options->{'f1'}) or die("Could not open f1, " . $options->{'f1'} . "! $!");
	my @arr;
	# remove and parse the header
	{	local $/ = "\n[";
		@arr = grep { /(^date: | cvs version)/i } split("\n", <FH> );
		## date: 04:01:2011 16:56
		## remark: cvs version: $Revision: 1.6 $
		foreach (@arr)
		{	if ($_ =~ /date: (.+)$/)
			{	$data->{f1}{header}{date} = $1;
			}
			elsif ($_ =~ /cvs version: \$Revision: (\S+)/)
			{	$data->{f1}{header}{cvs} = $1;
			}
		}
		if (! $data->{f1}{header} || ! $data->{f1}{header}{date} || ! $data->{f1}{header}{cvs})
		{	warn "Could not find the data-version tag or cvs Revision no of f1, " . $options->{'f1'};
		}
	#	print STDERR "Parsed $f header; starting body\n" if $options->{verbose};
		my @lines;
		{	local $/ = "\n[";
			while (<FH>)
			{	if (/^(\S+)\]\s*.*?^id:\s*(\S+)/sm)
				{	# extract the interesting data
					if ($1 eq "Term")
					{	my $h;
						map {
							if (/(.+?): ?(.+)/)
							{	my ($key, $val) = ($1, $2);
								if ($val =~ / \! /)
								{	$val =~ s/(.+) \!.*/$1/;
								}
								$h->{$key} = $val;
							}
						} grep { /^(id|name|def|is_obsolete):/ } split("\n", $_);
						if ($h->{def})
						{	## clip off the def xrefs
							if ($h->{def} =~ /^\"(.*)\"\s*(\[.*)/)
							{	$h->{def} = $1;
								$h->{simple_def} = lc($h->{def});
								$h->{simple_def} =~ s/[^a-z0-9 ]//g;
							}
							else
							{	warn "Could not parse def for " . $h->{id};
							}
						}
						$data->{f1}{ $h->{id} } = $h;
					}
				}
			}
		}
	}
	close(FH);

	print STDERR "Parsed " . $options->{f1} . "\n" if $options->{verbose};

	open(FH, "<" . $options->{'f2'}) or die("Could not open f2, " . $options->{'f2'} . "! $!");
	# remove and parse the header
	{	local $/ = "\n[";
		@arr = grep { /(^date: | cvs version)/i } split("\n", <FH> );
		foreach (@arr)
		{	if ($_ =~ /date: (.+)$/)
			{	$data->{f2}{header}{date} = $1;
			}
			elsif ($_ =~ /cvs version: \$Revision: (\S+)/)
			{	$data->{f2}{header}{cvs} = $1;
			}
		}
		if (! $data->{f2}{header} || ! $data->{f2}{header}{date} || ! $data->{f2}{header}{cvs})
		{	warn "Could not find the data-version tag or cvs Revision no of f2, " . $options->{'f2'};
		}
	#	print STDERR "Parsed $f header; starting body\n" if $options->{verbose};
		my @lines;
		{	local $/ = "\n[";
			while (<FH>)
			{	if (/^(\S+)\]\s*.*?^id:\s*(\S+)/sm)
				{	# extract the interesting data
					if ($1 eq "Term")
					{	my $h;
						map {
							if (/(.+?): ?(.+)/)
							{	my ($key, $val) = ($1, $2);
								if ($val =~ / \! /)
								{	$val =~ s/(.+) \!.*/$1/;
								}
								$h->{$key} = $val;
							}
						} grep { /^(id|name|def|is_obsolete):/ } split("\n", $_);

						if ($h->{def})
						{	## clip off the def xrefs
							if ($h->{def} =~ /^\"(.*)\"\s*(\[.*)/)
							{	$h->{def} = $1;
								$h->{simple_def} = lc($h->{def});
								$h->{simple_def} =~ s/[^a-z0-9 ]//g;
							}
							else
							{	warn "Could not parse def for " . $h->{id};
							}
						}

						if ($data->{f1}{ $h->{id} })
						{	## existing term
							if ($data->{f1}{$h->{id}}{simple_def} && $h->{simple_def} && $h->{simple_def} ne $data->{f1}{ $h->{id} }{simple_def})
							{	if ($h->{is_obsolete})
								{	print STDERR "Got an obsolete term!\n";
									$h->{simple_def} =~ s/^obsolete\s*//;
									if ($h->{simple_def} eq $data->{f1}{$h->{id}}{simple_def})
									{	## term has been obsoleted. Don't show.
										print STDERR "Ignoring obsolete term " . $h->{id} . "\n";
									}
									else
									{	print STDERR "Including obsolete term " . $h->{id} . "\n";
										$data->{changed}{$h->{id}}++;
										$data->{f2}{$h->{id}} = $h;
									}
								}
								else
								{	$data->{changed}{$h->{id}}++;
									$data->{f2}{$h->{id}} = $h;
								}
							}
						}
					}
				}
			}
		}
	}
	close(FH);

	foreach (keys %{$data->{changed}})
	{	my $diff = word_diff(\$data->{f1}{$_}{def}, \$data->{f2}{$_}{def}, { STYLE => 'Text::WordDiff::HTMLTwoLines' });
		if ($diff)
		{	my @arr = split('</span><span class="file"', $diff, 2);
			$data->{f1}{$_}{def_diffs} = $arr[0] . "</span>";
			$data->{f2}{$_}{def_diffs} = '<span class="file"' . $arr[1];
		}
	}

#	exit(0);

	print STDERR "Parsed " . $options->{f2} . "\nGathering results for printing...\n" if $options->{verbose};

	## optional: connect to a db to find annotations
	if ($options->{db_counts})
	{	## connect
		my $dsn = "DBI:" . $options->{dbdriver}
		. ":database=" . $options->{dbname}
		. ";host=" . $options->{dbhost};
		if ($options->{dbport})
		{	$dsn .= ";port=" . $options->{dbport};
		}

		my $dbh = DBI->connect( $dsn, $options->{dbuser}, $options->{dbpass} ) or warn "Could not connection to the database: " . $DBI::errstr;
		if ($dbh)
		{	$options->{db_data} = 1;

			my $i_data = $dbh->selectall_hashref( 'SELECT * FROM instance_data', 'release_name' );
			## save this info
			foreach (keys %$i_data)
			{	$options->{db_data} = $i_data->{$_};
				last;
			}
#			print STDERR "options->{db_data}: " . Dumper($options->{db_data}) . "\n\n";

			## get direct associations
			my $direct = $dbh->selectall_arrayref( 'SELECT term.acc, COUNT(association.id) FROM association INNER JOIN term ON term.id=association.term_id WHERE term.acc IN ("' . join('","', keys %{$data->{changed}}) . '") GROUP BY term.id' );
			if ($direct && @$direct)
			{	foreach (@$direct)
				{	## [ term.acc, count ]
					$data->{f2}{ $_->[0] }{direct} = $_->[1];
				}
			}
			## get indirect associations
			my $indirect = $dbh->selectall_arrayref( 'SELECT term.acc, COUNT(association.id) FROM term INNER JOIN graph_path ON term.id=graph_path.term1_id INNER JOIN association ON association.term_id=graph_path.term2_id WHERE term.acc IN ("' . join('","', keys %{$data->{changed}}) . '") GROUP BY term.id' );
			if ($indirect && @$indirect)
			{	foreach (@$indirect)
				{	## [ term.acc, count ]
					$data->{f2}{ $_->[0] }{indirect} = $_->[1];
				}
			}
		}
	}

	## clean up our mess

#	if ($options->{f_moved})
#	{	## move this back
#		my $cmd = "cp " . $options->{f_moved} . " " . $options->{f};
#		`$cmd`;
#	}
#	## delete the new and old files
#	foreach my $f qw(f1 f2)
#	{	my $cmd = "rm " . $options->{$f};
#		`$cmd`;
#	}

	if (! $data->{changed})
	{	close OUT;
		die("No changed definitions were found in the files specified.");
	}
	my $tt = Template->new({
		INCLUDE_PATH => $options->{tmpl_inc_paths},
	#	INTERPOLATE  => 1,
	}) || die("$Template::ERROR\n");
	my $tmpl_vars = { data => $data, options => $options };

	my $tmpl;
	if ($options->{'mode'} eq 'txt')
	{	$tmpl = txt_tmpl();
	}
	else
	{	$tmpl = html_tmpl();
	}

	## write the data to the template, and then load up the resulting page.
	$tt->process(\$tmpl, $tmpl_vars, \*OUT) || die($tt->error());
	close(OUT);
}

# parse the options from the command line
sub parse_options {
	my ($opt, $go_opt, $args) = @_;

	while (@$args && $args->[0] =~ /^\-/) {
		my $o = shift @$args;
		if ($o eq '-g' || $o eq '--go_options') {
			## OK, we're going to use the GO options. Stop parsing options now.
			%$opt = ( %$opt, %$go_opt);

		}
		## VITAL if d1 and d2 are being used
		elsif ($o eq '-f' || $o eq '--file' || $o eq '--file') {
			if (@$args && $args->[0] !~ /^\-/)
			{	$opt->{f} = shift @$args;
			}
		}
		elsif ($o eq '-f1' || $o eq '--file_1' || $o eq '--file_one') {
			if (@$args && $args->[0] !~ /^\-/)
			{	$opt->{f1} = shift @$args;
			}
		}
		elsif ($o eq '-f2' || $o eq '--file_2' || $o eq '--file_two') {
			if (@$args && $args->[0] !~ /^\-/)
			{	$opt->{f2} = shift @$args;
			}
		}
		elsif ($o eq '-d1' || $o eq '--date_1' || $o eq '--date_one') {
			if (@$args && $args->[0] !~ /^\-/)
			{	$opt->{d1} = shift @$args;
				$opt->{d1} =~ s/(^["']|["']$)//g;
			}
		}
		elsif ($o eq '-d2' || $o eq '--date_2' || $o eq '--date_two') {
			if (@$args && $args->[0] !~ /^\-/)
			{	$opt->{d2} = shift @$args;
				$opt->{d2} =~ s/(^["']|["']$)//g;
			}
		}
		elsif ($o eq '-o' || $o eq '--output') {
			if (@$args && $args->[0] !~ /^\-/)
			{	$opt->{output} = shift @$args;
			}
		}
		elsif ($o eq '-m' || $o eq '--output_mode') {
			if (@$args && $args->[0] !~ /^\-/)
			{	$opt->{mode} = shift @$args;
			}
		}
		elsif ($o eq '-c' || $o eq '--db_counts') {
			$opt->{db_counts} = 1;
		}
		elsif ($o eq '-h' || $o eq '--help') {
			system("perldoc", $0);
			exit(0);
		}
		elsif ($o eq '-v' || $o eq '--verbose') {
			$opt->{verbose} = 1;
		}
		else {
			die_msg( "Error: no such option: $o" );
		}
	}
	return check_options($opt);
}


# process the input params
sub check_options {
	my $opt = shift;
	my $errs;

	if (!$opt)
	{	die_msg( "Error: please ensure you have specified the input file(s) and/or date(s) and an output file." );
	}

	if ($ENV{DEBUG})
	{	$opt->{verbose} = 1;
	}

	if ($opt->{mode})
	{	if ($opt->{mode} ne 'html' && $opt->{mode} ne 'txt' && $opt->{mode} ne 'text')
		{	push @$errs, "the output mode " . $opt->{mode} . " is not valid\n";
		}
		if ($opt->{mode} eq 'text')
		{	$opt->{mode} = 'txt';
		}
	}
	else
	{	print STDERR "No mode set!\n";
	}

	if (!$opt->{output})
	{	push @$errs, "specify an output file using -o /path/to/<file_name>";
	}
	else
	{	## append file extensions if required
		if ($opt->{mode} eq 'txt' && $opt->{output} !~ /\.txt/)
		{	$opt->{output} .= ".txt";
		}
		elsif ($opt->{mode} eq 'html' && $opt->{output} !~ /\.s?html/)
		{	$opt->{output} .= ".html";
		}
		if (-e $opt->{output} && ! -w $opt->{output})
		{	warn "The file " . $opt->{output} . " exists and cannot be written to!";
		}
	}

	## INPUT OPTIONS:
	## - specify f1 and f2
	## - specify f, d1, d2
	## - specify f, d1 and use most recent file as d2

	if ($opt->{f1} || $opt->{f2})
	{	foreach my $f qw(f1 f2)
		{	if (!$opt->{$f})
			{	push @$errs, "specify an input file using -$f /path/to/<file_name>";
			}
			elsif (! -e $opt->{$f})
			{	push @$errs, "the file " . $opt->{$f} . " could not be found.\n";
			}
			elsif (! -r $opt->{$f} || -z $opt->{$f})
			{	push @$errs, "the file " . $opt->{$f} . " could not be read.\n";
			}
		}
	}
	elsif ($opt->{d1} || $opt->{d2})
	{	## OK, if we are getting files from CVS, we need to fetch the files and store them somewhere
		## move any existing file out of the way
		my $f_name = $opt->{f};
		print STDERR "f_name: " . $f_name . "\n";
		if (-e $f_name)
		{	my $cmd = "cp $f_name $f_name" . "-current";
			`$cmd`;
			warn "Moved current $f_name to $f_name" . "-current";
			$opt->{f_moved} = $f_name . "-current";
		}

		## how do we make sure that the date format is correct?!
		$opt->{f1} = $f_name . "-old";
		$opt->{f2} = $f_name . "-new";
		$opt->{d1_cmd} = '-D "' . $opt->{d1} . '" ';
		if (! $opt->{d2})
		{	$opt->{d2_cmd} = '-A';
		}
		else
		{	$opt->{d2_cmd} = '-D "' . $opt->{d2} . '" ';
		}

		if ($opt->{go_root} && $opt->{go_root} =~ /\w/)
		{	## change directory to the appropriate CVS root dir
			chdir $opt->{go_root};
			## make sure that $f_name doesn't have $ENV{CVSROOT} in it?
		}
		else
		{	if ($f_name =~ /(.*?)(go\/ontology\/.+)/)
			{	## use $1 as the GO_CVSROOT
				warn "No GO_CVSROOT specified: using " . ( $1 || "current directory");
				if ($1)
				{	chdir $1;
				}
				$f_name = $2;
			}
			else
			{	warn "Help! No GO_CVSROOT specified: using current directory!";
			}
		}

		## check out the first file, move it to $file-old
		foreach my $x qw(1 2)
		{	my $cmd = "cvs -q -d:pserver:anonymous\@cvs.geneontology.org:/anoncvs co " . $opt->{"d" . $x . "_cmd"} . $f_name;
			my $status = `$cmd 2>&1`;
			if ($status =~ /\w/)
			{	print STDERR "status: $status\n";
				if ($status !~ /[UA] $f_name/)
				{	die "Problem with CVS: $status";
				}
			}
			$cmd = "cp $f_name ". $opt->{"f" . $x};
			`$cmd`;
		}

		## we should just check that the files are there, I guess...
		foreach my $f qw(f1 f2)
		{	if (! -e $opt->{$f})
			{	push @$errs, "the file " . $opt->{$f} . " could not be found.\n";
			}
			elsif (! -r $opt->{$f} || -z $opt->{$f})
			{	push @$errs, "the file " . $opt->{$f} . " could not be read.\n";
			}
		}
	}
	else
	{	push @$errs, "specify either a pair of files to compare or file dates to compare";
	}

	if ($errs && @$errs)
	{	die_msg( "Error: please correct the following parameters to run the script:\n" . ( join("\n", map { " - " . $_ } @$errs ) ) );
	}

	## quick 'diff' check of whether the files are identical or not
	my $cmd = "diff -w -q -i '" . $opt->{f1} . "' '" . $opt->{f2} . "'";

	my $status = `$cmd`;
	die "The two files specified appear to be identical!" if ! $status;

	return $opt;
}


sub die_msg {
	my $msg =  shift || "";
	die join("\n", $msg, "The help documentation can be accessed with the command\n\tdef-differ.pl --help\n");
}


sub html_tmpl {
return
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
	  "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
	<title>Ontology Comparison Report</title>
	<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
	<link rel="stylesheet" type="text/css" href="[% install_dir %]/stylesheets/stylesheet.css">
	<!--[if lte IE 7]><link rel="stylesheet" type="text/css" href="[% install_dir %]/stylesheets/ie-fix.css"><![endif]-->
	<style type="text/css">
<!-- .fbox { width: 10em; float: right; text-align: right; }
ins { background: #9f9; text-decoration: none;  }
del { background: #F99; text-decoration: none; }
-->
	</style>
	<script src="[% install_dir %]/enhancer.js" type="text/javascript"></script>
</head>
<body>
<div id="fullpage">
<div id="header">
	<p id="top"><a href="http://www.geneontology.org/"> the Gene Ontology </a></p>
	<form title="Search the GO database or the GO website" action="http://www.geneontology.org/cgi-bin/chooser.cgi" method="get" name="searchBox">
			<fieldset class="R" id="topSrchDiv"><legend>Search</legend><label for="topSrchInput" accesskey="4" title="Search GO; access key 4" class="b">Search</label><input type="text" id="topSrchInput" name="search_query" title="Enter a search phrase" class="textBox">
			<br>
			<label for="topSrchSelect" class="noDisplay">Search type </label>
			<select id="topSrchSelect" name="search_constraint">
				<option title="Search the GO database for a gene product" value="gp" selected>
					gene or protein name
				</option>
				<option title="Search the GO database for a GO term or ID" value="terms">
					GO term or ID
				</option>
				<option title="Search the GO website" value="website">
					GO website
				</option>
			</select>
			<input title="Submit your query" type="submit" value="go!" class="button">
		</fieldset>
	</form>
</div>
<div id="wrapper">
	<div id="main">
<h1>Gene Ontology OBO file definition changes</h1>
<p>Files used:</p>
<ul>
<li><b>file 1 (old)</b>: [% data.f1.header.date || "date unknown" %], cvs revision [% data.f1.header.cvs || "unknown" %]</li>
<li><b>file 2 (new)</b>: [% data.f2.header.date || "date unknown" %], cvs revision [% data.f2.header.cvs || "unknown" %]</li>
<li><b>Database</b>: [% options.db_data.release_type || "unknown DB type" %], [% options.db_data.release_name || "unknown DB release name" %]</li>
</ul>
<div class="block">
<h2>Terms with changed definitions</h2>
[% FOREACH id IN data.changed.keys.sort %]
<div class="zero">
[%		IF options.db_data;
%]<p class="fbox">[%
			IF data.f2.$id.indirect && data.f2.$id.indirect != 0;
	%]<a href="http://amigo.geneontology.org/cgi-bin/term-assoc.cgi?term=[% id %]">[% data.f2.$id.indirect %] / [% data.f2.$id.direct %] direct</a>[%
			ELSE;
%]0 annotations[%
			END;
%]</p>
[%		END;
%]<h3><a href="http://amigo.geneontology.org/cgi-bin/amigo/term_details?term=[% id %]">[% id %] : [% data.f2.$id.name %]</a></h3>[%
		IF data.f1.$id.name != data.f2.$id.name %]
<p>(was [% data.f1.$id.name %])</p>[%
		END %]
<p class="clearR"><b>OLD</b>: [% data.f1.$id.def_diffs %]
<br><b>NEW</b>: [% data.f2.$id.def_diffs %]</p>
</div>
[% END %]
</div>
</div>
</div>
<div id="footer">
	<p class="C">
		Copyright &copy; 1999-2011 <a href="http://www.geneontology.org/" title="The Gene Ontology project website">the Gene Ontology</a>
		<br>
		<a href="http://www.geneontology.org/GO.contacts.shtml" title="Contact the GO helpdesk">Helpdesk</a> &bull; <a href="http://www.geneontology.org/GO.cite.shtml" title="How to cite the GO project">Cite</a> &bull; <a href="http://www.geneontology.org/GO.cite.shtml" title="Terms of use for the GO project">Terms of use</a><!-- &bull; <a href="http://www.geneontology.org/GO.site.map.shtml" title="GO site map">Site Map</a>--> &bull; <a href="http://go.berkeleybop.org/news4go/" title="GO News">News</a> &bull; <a href="feed://go.berkeleybop.org/news4go/rss.xml" title="GO News RSS feed">RSS</a>
		<br>
		Member of the <a rel="external" href="http://www.obofoundry.org/" title="Open Biological and Biomedical Ontologies">Open Biological and Biomedical Ontologies</a>
	</p>
</div>
</div>
</body>
</html>
';
}


sub txt_tmpl {
return
'! Gene Ontology OBO file definition changes
!
! Files used:
! file 1 (old): [% data.f1.header.date || "date unknown" %], cvs revision [% data.f1.header.cvs || "unknown" %]
! file 2 (new): [% data.f2.header.date || "date unknown" %], cvs revision [% data.f2.header.cvs || "unknown" %]
[% IF options.db_data;
%]! Database: [% options.db_data.release_type || "unknown DB type" %], [% options.db_data.release_name || "unknown DB release name" %]
[% END %]
!
! Format of first line of each entry:
! Term ID : term name  ( total # annots / # direct annots)

[% FOREACH id IN data.changed.keys.sort %]
[% id %] : [% data.f2.$id.name %]   [%
		IF options.db_data;
			IF data.f2.$id.indirect && data.f2.$id.indirect != 0;
	%]([% data.f2.$id.indirect %] / [% data.f2.$id.direct || "0" %] direct)[%
			ELSE;
%](0 annotations)[%
			END;
		END %]
[% 	IF data.f1.$id.name != data.f2.$id.name %](was [% data.f1.$id.name %])
[% 	END; %]OLD: [% data.f1.$id.def %]
NEW: [% data.f2.$id.def %]

[% END %]';
}


1;


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

=head1 AUTHOR

Amelia Ireland

=cut
