/*
* fhist - file history and comparison tools
* Copyright (C) 1991-1995, 1998-2002 Peter Miller;
* All rights reserved.
*
* Derived from a work
* Copyright (C) 1990 David I. Bell.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
*
* MANIFEST: functions to compare text files
*
* This program is based on the algorithm in:
* An O(ND) Difference Algorithm and Its Variations
* Eugene W. Myers
* (TR 85-6, April 10, 1985)
* Department of Computer Science
* The University of Arizona
* Tuscon, Arizona 85721
*
* Also see:
* A File Comparison Program
* Webb Miller and Eugene W. Myers
* Software Practice and Experience
* (Volume 15, No. 11, November 1985)
*
* Actual implementation and features by David I. Bell.
* Enhancements and bug fixes by Peter Miller.
*/
#include <ac/ctype.h>
#include <ac/stddef.h>
#include <ac/stdio.h>
#include <ac/string.h>
#include <ac/libintl.h>
#include <compare.h>
#include <cmalloc.h>
#include <error.h>
#include <error_intl.h>
#include <fcheck.h>
#include <input.h>
#include <input/file.h>
#include <input/file_text.h>
#include <input/quotprinenco.h>
#include <input/hexify.h>
#include <str.h>
#include <trace.h>
#define SNAKEALLOCSIZE (size_t)500 /* chunk size for snake allocation */
#define CHARALLOCSIZE (size_t)10240 /* allocation size for file data */
#define UNIQUEALLOCSIZE (size_t)512 /* unique handling size for allocation */
#define LINEALLOCSIZE (size_t)2000 /* number of lines to allocate */
#define HASHSIZE 2003 /* size of hash table */
#define CHANGEBLABINTERVAL 100 /* how often to talk about changes */
#define READBLABINTERVAL 1000 /* how often to talk about reading */
#define SNAKEBLABINTERVAL 1000 /* how often to talk about snakes */
#define TABSIZE 8 /* tab size */
#define DETABALLOCSIZE (size_t)128 /* buffer allocation size for detab */
static size_t tablesize; /* needed table size */
static size_t snakecount; /* number of snake structures used */
static int availchars; /* number of available characters */
static char *nextchar; /* next allocable character storage */
static long *V1; /* the row containing the last d */
static long *V2; /* another row */
static SNAKE *nextsnake; /* next allocable snake structure */
static SNAKE *lastsnake; /* end of allocable snake structures */
static SNAKE *endsnake; /* last snake structure in list */
static LINE **hashtable; /* hash table for lines */
static modifyline_type modifyline;
FCOMP_DATA fc; /* externally referencable data */
/*
* NAME
* canonical
*
* SYNOPSIS
* char *canonical(char *s);
*
* DESCRIPTION
* The canonical function is used to apply the -Upcase and -Spaces
* options to produce a canonical line for comparison purposes.
*
* ARGUMENTS
* s - string to canonicalize
*
* RETURNS
* A pointer to the canonical line is returned. This will be valid
* until the next call to this function. If the canonical string
* would be the same as the input string, the input string is returned.
*/
static char *
canonical(char *s)
{
static size_t bufsiz;
static char *buf;
size_t len;
int changed;
int space;
char *cp1;
char *cp2;
/*
* if no modifiers are in effect,
* no alterations will be required
*/
if (!fc.upcaseflag && !fc.spaceflag)
return s;
/*
* make sure the buffer is large enough
*/
len = strlen(s) + 1;
if (len > bufsiz)
{
if (buf)
{
bufsiz = len;
buf = r_realloc_and_check(buf, bufsiz);
}
else
{
bufsiz = len;
buf = r_alloc_and_check(bufsiz);
}
}
/*
* simple upcase is easier to handle
*/
if (fc.upcaseflag)
{
changed = 0;
cp1 = s;
cp2 = buf;
while (*cp1)
{
if (islower((unsigned char)*cp1))
{
*cp2++ = toupper((unsigned char)*cp1);
changed = 1;
}
else
*cp2++ = *cp1;
++cp1;
}
if (!changed)
return s;
*cp2 = 0;
return buf;
}
/*
* spaces are rather harder to handle
*
* keep track of whether a run of spaces has been seen.
* replace this run of spaces with a single space
* provided it is not at either end of a line.
*/
cp1 = s;
cp2 = buf;
space = 0;
changed = 0;
while (*cp1)
{
int c;
c = (unsigned char)*cp1++;
if (fc.upcaseflag && islower(c))
{
c = toupper(c);
changed = 1;
}
if (c == '\n')
{
if (space)
changed = 1;
*cp2++ = c;
space = 0;
}
else if (isspace(c))
{
if (space || c != ' ')
changed = 1;
space = 1;
}
else
{
if (space)
{
if (cp2 != buf)
*cp2++ = ' ';
else
changed = 1;
}
*cp2++ = c;
space = 0;
}
}
if (!changed)
return s;
*cp2 = 0;
return buf;
}
/*
* Allocate a string of a certain size. This routine is used instead of
* simply calling malloc in order to reduce the overhead. The allocated
* strings cannot be freed or realloced.
*/
void *
allocstr(long len)
{
char *cp;
static unsigned alignment_mask;
if (!alignment_mask)
{
struct test
{
char a;
double b;
};
unsigned alignment;
alignment = offsetof(struct test, b);
/* make sure is a power of 2 */
while ((alignment & (~alignment + 1)) != alignment)
++alignment;
alignment_mask = alignment - 1;
}
/* round length to a multiple of alignment */
len = (len + alignment_mask) & ~alignment_mask;
if (len >= UNIQUEALLOCSIZE)
return cm_alloc_and_check(len);
cp = nextchar;
if (len > availchars)
{
cp = cm_alloc_and_check(CHARALLOCSIZE);
nextchar = cp;
availchars = CHARALLOCSIZE;
}
nextchar += len;
availchars -= len;
return cp;
}
/*
* Subroutine to allocate a line structure of the appropriate size and
* add it to the hash table. If it matches an already existing line, then
* the old structure will be returned.
*/
static LINE *
addline(char *cp)
{
LINE *lp; /* current line element */
char *curcp; /* current character of string */
LINE **hashentry; /* hash entry to use */
long hash; /* hash value */
long len; /* line length */
int ch; /* current char */
char *canon;
/*
* Compute line length and hash value.
* This has to take into account the space and uppercase flags.
*/
canon = canonical(cp);
hash = 0;
for (curcp = canon; *curcp; curcp++)
{
ch = *curcp;
hash += ((hash * 101) + ch);
if (hash < 0)
hash = (hash + 1) & INFINITY;
}
len = curcp - canon;
/*
* Search proper hash chain for already existing line.
* If the hash accidentally matches, modify it and try again.
*
* Search on the real data, not the canonical form,
* that way fcomp -w -s can report more natural looking results.
*/
again:
hashentry = &hashtable[hash % HASHSIZE];
for (lp = *hashentry; lp; lp = lp->l_next)
{
if (lp->l_hash != hash)
continue;
if (strcmp(cp, lp->l_data) == 0)
return lp;
if
(
(cp != canon || lp->l_canon != lp->l_data)
&&
strcmp(canon, lp->l_canon) == 0
)
continue;
hash = ((hash * 1234321) + 1) & INFINITY;
goto again;
}
/*
* Line not found, allocate a new one
*/
if (canon != cp)
{
lp = (LINE *)allocstr(LINE_SIZE(strlen(cp)));
strcpy(lp->l_data, cp);
lp->l_canon = allocstr(len + 1);
strcpy(lp->l_canon, canon);
}
else
{
lp = (LINE *)allocstr(LINE_SIZE(len));
strcpy(lp->l_data, cp);
lp->l_canon = lp->l_data;
}
lp->l_hash = hash;
lp->l_next = *hashentry;
*hashentry = lp;
return lp;
}
static int
is_blank_string(const char *s)
{
while (*s)
if (!isspace((unsigned char)*s))
return 0;
return 1;
}
/*
* Readfile - read in a file and remember the maximum number of lines it needs.
* This routine squeezes out blank lines if specified.
* The first specified number of lines is skipped first.
* Then the specified number of lines is modified.
*/
static void
readfile(FILEINFO *fi, long skip, long modify)
{
input_ty *fp; /* file pointer for file to read in */
LINE **lines; /* pointer to lines array */
long *lnums; /* line numbers within file */
char *cp; /* current line */
long linesavail; /* available number of lines */
long linecount; /* current number of lines */
long linenumber; /* current line number */
long linelen; /* length of line read */
fi->is_binary = 0;
if (fc.verbosity > VERBOSE_DEFAULT)
error_raw("[Reading \"%s\"]", fi->f_name);
fp = fi->f_file;
if (skip > 0)
input_skip_lines(fp, skip);
linecount = 0;
linenumber = 0;
linesavail = LINEALLOCSIZE;
lines = (LINE **)cm_alloc_and_check(sizeof(LINE *) * LINEALLOCSIZE);
lnums = (long *)cm_alloc_and_check(sizeof(long) * LINEALLOCSIZE);
for (;;)
{
cp = input_readline(fp, &linelen, 0, &fi->is_binary);
if (!cp)
break;
if (++linenumber <= modify && modifyline)
cp = modifyline(cp, &linelen, (struct INFO *)0);
if
(
(fc.verbosity > VERBOSE_DEFAULT)
&&
((linenumber % READBLABINTERVAL) == 0)
)
error_raw("[%ld lines]", linenumber);
if (fc.blankflag && is_blank_string(cp))
continue;
if (linecount >= linesavail)
{
linesavail += LINEALLOCSIZE;
lines =
(LINE **)
cm_realloc_and_check(lines, sizeof(LINE *) * linesavail);
lnums =
(long *)cm_realloc_and_check(lnums, sizeof(long) * linesavail);
}
lnums[linecount] = linenumber;
lines[linecount] = addline(cp);
linecount++;
}
fi->f_lnums = lnums;
fi->f_lines = lines;
fi->f_linecount = linecount;
fi->f_linestotal = linenumber;
if (linecount > fc.maxlines)
fc.maxlines = linecount;
input_delete(fp);
if (fc.verbosity > VERBOSE_DEFAULT)
{
if (linecount == linenumber)
error_raw("[Done, %ld lines]", linenumber);
else
{
error_raw
(
"[Done, %ld lines, %ld non-blanks]",
linenumber,
linecount
);
}
}
}
/*
* Allocate a new snake structure and append it to the snake list.
*/
static SNAKE *
allocsnake(void)
{
SNAKE *sp; /* snake being allocated */
sp = nextsnake;
if (sp >= lastsnake)
{
sp = (SNAKE *)cm_alloc_and_check(sizeof(SNAKE) * SNAKEALLOCSIZE);
nextsnake = sp;
lastsnake = sp + SNAKEALLOCSIZE;
}
nextsnake++;
sp->next = NULL;
if (!fc.snakelist)
fc.snakelist = sp;
else
endsnake->next = sp;
endsnake = sp;
if (fc.verbosity > VERBOSE_DEFAULT && !(++snakecount % SNAKEBLABINTERVAL))
error_raw("[%ld snake elements]", snakecount);
return sp;
}
/*
* Routine to find the middle snake of an optimial D-path spanning
* lines A to A+N in file A to lines B to B+N in file B. Returns the
* length D of the D-path as a return value, and the upper left and
* lower right relative coordinates of a snake midway through the D-path.
*/
static long
midsnake(int depth, long A, long N, long B, long M, long *ulx, long *uly,
long *lrx, long *lry)
{
long D;
long MAXD;
long DELTA;
long odd;
long x;
long y;
long k;
long changes;
long oldx;
LINE **lp1;
LINE **lp2;
if (fc.debugflag)
{
error_raw
(
"%*ssearching: %ld,%ld to %ld,%ld midsnake: ",
depth * 2,
"",
A,
B,
A + N,
B + M
);
}
DELTA = N - M;
odd = DELTA & 1;
MAXD = (M + N + 1) / 2;
V1[1] = 0;
V2[-1] = 0;
changes = -odd - 2;
/*
* This is the main loop for searching for the snake.
* D is the distance off the diagonals, and is the number
* of changes needed to get from the upper left to the
* lower right corner of the region.
*/
for (D = 0; D <= MAXD; D++)
{
changes += 2;
if (changes > fc.maxchanges)
{
sub_context_ty *scp;
scp = sub_context_new();
sub_var_set_long(scp, "Number", fc.maxchanges);
fatal_intl(scp, i18n("More than $number changes required"));
/* NOTREACHED */
sub_context_delete(scp);
}
if
(
(fc.verbosity > VERBOSE_DEFAULT)
&&
(depth == 0)
&&
(changes > 1)
&&
((changes % CHANGEBLABINTERVAL) <= 1)
)
error_raw("[%ld changes]", changes - odd);
/*
* Examine all diagonals within current distance.
* First search from upper left to lower right,
* and then search from lower right to upper left.
*/
for (k = -D; k <= D; k += 2)
{
/*
* Find the end of the furthest forward D-path
* in diagonal k.
*/
if (k == -D || (k != D && (V1[k - 1] < V1[k + 1])))
x = V1[k + 1];
else
x = V1[k - 1] + 1;
y = x - k;
lp1 = &fc.fileA.f_lines[A + x];
lp2 = &fc.fileB.f_lines[B + y];
oldx = x;
while ((x < N) && (y < M) && ((*lp1)->l_hash == (*lp2)->l_hash))
{
x++;
y++;
lp1++;
lp2++;
}
V1[k] = x;
/*
* See if path overlaps furthest reverse D-path.
* If so, then we have found the snake.
*/
if (odd && (k >= (DELTA - (D - 1))) && (k <= (DELTA + (D - 1))))
{
if ((x + V2[k - DELTA]) >= N)
{
*ulx = oldx;
*uly = oldx - k;
*lrx = x;
*lry = y;
if (fc.debugflag)
{
error_raw
(
"%ld,%ld to %ld,%ld (odd)",
*ulx,
*uly,
*lrx,
*lry
);
}
return changes;
}
}
}
for (k = -D; k <= D; k += 2)
{
/*
* Find the end of the furthest reaching reverse
* path in diagonal k+DELTA.
*/
if (k == D || (k != -D && (V2[k + 1] < V2[k - 1])))
x = V2[k - 1];
else
x = V2[k + 1] + 1;
y = x + k;
lp1 = &fc.fileA.f_lines[A + N - x - 1];
lp2 = &fc.fileB.f_lines[B + M - y - 1];
oldx = x;
while ((x < N) && (y < M) && ((*lp1)->l_hash == (*lp2)->l_hash))
{
x++;
y++;
lp1--;
lp2--;
}
V2[k] = x;
/*
* See if path overlaps furthest forward D-path.
* If so, then we have found the snake.
*/
if (!odd && (k <= D - DELTA) && (k >= -D - DELTA))
{
if ((x + V1[k + DELTA]) >= N)
{
*ulx = N - x;
*uly = M - y;
*lrx = N - oldx;
*lry = *lrx + *uly - *ulx;
if (fc.debugflag)
{
error_raw
(
"%ld,%ld to %ld,%ld (even)",
*ulx,
*uly,
*lrx,
*lry
);
}
return changes;
}
}
}
}
fatal_raw("Middle snake procedure failed (bug)");
return 0;
}
/*
* Recursive routine to find a minimal D-path through the edit graph
* of the two input files. Arguments are the beginning line numbers in
* the files, and the number of lines to examine. This is basically a
* divide-and-conquer routine which finds the middle snake of an optimal
* D-path, then calls itself to find the remainder of the path before the
* snake and after the snake.
*/
static void
findsnake(int depth, long A, long N, long B, long M)
{
SNAKE *sp;
long ulx;
long uly;
long lrx;
long lry;
long D;
long count;
/*
* If more than one change needed, then call ourself for each part.
*/
D = midsnake(depth, A, N, B, M, &ulx, &uly, &lrx, &lry);
if ((fc.verbosity > VERBOSE_DEFAULT) && (depth == 0))
{
error_raw("[%ld change%s total]", D, ((D == 1) ? "" : "s"));
error_raw("[Finding edit path]");
}
if (D > 1)
{
if ((ulx > 0) && (uly > 0))
findsnake(depth + 1, A, ulx, B, uly);
count = lrx - ulx;
if (count > fc.maxjoin)
{
sp = allocsnake();
sp->line1 = A + ulx;
sp->line2 = B + uly;
sp->count = count;
}
N -= lrx;
M -= lry;
if ((N > 0) && (M > 0))
findsnake(depth + 1, A + lrx, N, B + lry, M);
return;
}
/*
* Only 0 or 1 change needed, so we can compute the result directly.
* First compute the snake coming from the upper left corner if any.
*/
if (N > M)
count = uly;
else
count = ulx;
if (count > fc.maxjoin)
{
sp = allocsnake();
sp->line1 = A;
sp->line2 = B;
sp->count = count;
}
/*
* Finally compute the snake coming from the lower right corner if any.
*/
count = lrx - ulx;
if (count > fc.maxjoin)
{
sp = allocsnake();
sp->line1 = A + ulx;
sp->line2 = B + uly;
sp->count = count;
}
}
/*
* Routine to generate the D-path list and compute the number
* of insertions, deletions and matching lines found.
*/
static void
makesnakes(void)
{
SNAKE *sp; /* current snake element */
long line1; /* current line in file A */
long line2; /* current line in file B */
if (fc.verbosity > VERBOSE_DEFAULT)
error_raw("[Beginning comparisons]");
tablesize = fc.maxlines * 2 + 1;
V1 = (long *)cm_alloc_and_check(sizeof(long) * tablesize);
V2 = (long *)cm_alloc_and_check(sizeof(long) * tablesize);
V1 += fc.maxlines;
V2 += fc.maxlines;
if ((fc.fileA.f_linecount > 0) && (fc.fileB.f_linecount > 0))
findsnake(0, 0L, fc.fileA.f_linecount, 0L, fc.fileB.f_linecount);
/*
* End the list with the lower right endpoint
*/
sp = allocsnake();
sp->line1 = fc.fileA.f_linecount;
sp->line2 = fc.fileB.f_linecount;
sp->count = 0;
if (fc.verbosity > VERBOSE_DEFAULT)
error_raw("[Comparisons complete]");
if (fc.debugflag)
{
error_raw("%8s%8s%8s", "line1", "line2", "count");
for (sp = fc.snakelist; sp; sp = sp->next)
error_raw("%8ld%8ld%8ld", sp->line1, sp->line2, sp->count);
}
/*
* Scan the snake list and calculate the number of inserted,
* deleted, and matching lines.
*/
line1 = 0;
line2 = 0;
for (sp = fc.snakelist; sp; sp = sp->next)
{
fc.deletes += (sp->line1 - line1);
fc.inserts += (sp->line2 - line2);
fc.matches += sp->count;
line1 = sp->line1 + sp->count;
line2 = sp->line2 + sp->count;
}
}
/*
* Print the specified range of line numbers for both files.
* The given string is also typed as part of the message.
* This routine handles both full and line number output.
* The real line numbers in each file are typed.
*/
static void
printrange(FILE *fp, const char *msg, long line1, long count1, long line2,
long count2)
{
long beg1;
long end1;
long beg2;
long end2; /* real beginning and ending line numbers */
string_ty *buf1;
string_ty *buf2;
end1 = line1 + count1 - 1;
end2 = line2 + count2 - 1;
if (line1 >= fc.fileA.f_linecount)
{
beg1 = fc.fileA.f_linestotal + 1;
end1 = beg1;
}
else
{
beg1 = fc.fileA.f_lnums[line1];
end1 = fc.fileA.f_lnums[end1];
}
if (line2 >= fc.fileB.f_linecount)
{
beg2 = fc.fileB.f_linestotal + 1;
end2 = beg2;
}
else
{
beg2 = fc.fileB.f_lnums[line2];
end2 = fc.fileB.f_lnums[end2];
}
if (beg1 == end1)
{
if (fc.hexify_flag)
buf1 = str_format("0x%lX", beg1 - 1);
else
buf1 = str_format("%ld", beg1);
}
else
{
if (fc.hexify_flag)
buf1 = str_format("0x%lX-0x%lX", beg1 - 1, end1 - 1);
else
buf1 = str_format("%ld-%ld", beg1, end1);
}
if (beg2 == end2)
{
if (fc.hexify_flag)
buf2 = str_format("0x%lX", beg2 - 1);
else
buf2 = str_format("%ld", beg2);
}
else
{
if (fc.hexify_flag)
buf2 = str_format("0x%lX-0x%lX", beg2 - 1, end2 - 1);
else
buf2 = str_format("%ld-%ld", beg2, end2);
}
if (fc.editscriptflag)
{
fprintf(fp, "%c %s %s\n", *msg, buf1->str_text, buf2->str_text);
}
else
{
fprintf
(
fp,
"\n******************** %s [A %s, B %s]:\n",
msg,
buf1->str_text,
buf2->str_text
);
}
str_free(buf1);
str_free(buf2);
}
/*
* Translate all tabs to the appropriate number of spaces in a line.
* This is used when the line is not output at a nice tab boundary,
* so that the output still looks correct. Returns the new converted
* string, which is only valid until the next call.
*/
static char *
detab(const char *str)
{
int pos; /* current character position */
int ch; /* current character */
char *destcp; /* destination pointer */
static char *strbuf; /* string buffer */
static int maxlen; /* maximum size of string buffer */
if (!maxlen)
{
maxlen = DETABALLOCSIZE;
strbuf = r_alloc_and_check(maxlen + TABSIZE + 1);
}
destcp = strbuf;
pos = 0;
for (;;)
{
ch = *str++;
if (!ch)
break;
if (pos >= maxlen)
{
maxlen += DETABALLOCSIZE;
strbuf = r_realloc_and_check(strbuf, maxlen + TABSIZE + 1);
destcp = strbuf + pos;
}
if (ch != '\t')
{
*destcp++ = ch;
pos++;
}
else
{
for (;;)
{
*destcp++ = ' ';
pos++;
if ((pos % TABSIZE) == 0)
break;
}
}
}
*destcp = '\0';
return strbuf;
}
/*
* Print the specified number of lines of a file beginning at the
* specified line number. This prints imbedded blank lines too.
* The lines can be preceeded by line number if desired. Context
* lines can be added also. Tabs are converted to the appropriate
* number of spaces if line numbering or tags are supplied.
*/
static void
printlines(FILE *fp, FILEINFO *fi, long line, long count, const char *tagstr)
{
LINE **lines; /* data of file */
long *lnum; /* line numbers */
char *data; /* current data line */
long curlnum; /* current line number */
line = line - fc.context - 1;
count += (2 * fc.context);
if (line < 0)
{
count += line;
line = 0;
}
if ((line + count) > fi->f_linecount)
count = fi->f_linecount - line;
lines = fi->f_lines + line;
lnum = fi->f_lnums + line;
curlnum = *lnum;
while (count-- > 0)
{
while (curlnum < *lnum)
{
if (fc.whatflag)
fprintf(fp, "%s\n", tagstr);
else if (fc.numflag)
fprintf(fp, "%c%ld\n", fi->f_tag, curlnum);
else
putc('\n', fp);
curlnum++;
}
if (fc.whatflag)
fprintf(fp, "%s ", tagstr);
else if (fc.numflag)
fprintf(fp, "%c%-7ld ", fi->f_tag, curlnum);
data = (*lines)->l_data;
if (fc.whatflag)
data = detab(data);
fputs(data, fp);
lines++;
lnum++;
curlnum++;
}
}
/*
* Dump lines which match, either just the line numbers or with the text.
*/
void
dumpmatch(char *outputname)
{
FILE *fp;
SNAKE *sp;
if ((fc.verbosity > VERBOSE_DEFAULT) && outputname)
error_raw("[Writing matching lines to \"%s\"]", outputname);
if (outputname)
{
fp = fopen_and_check(outputname, "w");
}
else
{
fp = stdout;
outputname = gettext("standard output");
}
if (fc.editscriptflag)
fprintf(fp, "A %s\nB %s\n", fc.fileA.f_name, fc.fileB.f_name);
else
{
fprintf
(
fp,
"FILE A: %s\nFILE B: %s\n",
fc.fileA.f_name,
fc.fileB.f_name
);
fprintf
(
fp,
"TOTALS: %ld inserted %ld deleted %ld matched\n",
fc.inserts,
fc.deletes,
fc.matches
);
}
for (sp = fc.snakelist; sp; sp = sp->next)
{
if (sp->count <= 0)
continue;
printrange(fp, "MATCH", sp->line1, sp->count, sp->line2, sp->count);
if (!fc.editscriptflag)
printlines(fp, &fc.fileB, sp->line2 + 1, sp->count, "");
}
if (fc.editscriptflag)
fprintf(fp, "T %ld %ld %ld\n", fc.inserts, fc.deletes, fc.matches);
fflush_and_check(fp, outputname);
fclose_and_check(fp, outputname);
}
/*
* Dump the edit script in the normal manner, showing changed line
* numbers along with the relevent text.
*/
void
dumpnormal(char *outputname)
{
FILE *fp; /* output file */
SNAKE *sp; /* current snake */
long line1;
long line2; /* current line numbers */
long count1;
long count2; /* line counts */
if ((fc.verbosity > VERBOSE_DEFAULT) && outputname)
error_raw("[Writing differences to \"%s\"]", outputname);
if (outputname)
{
fp = fopen_and_check(outputname, "w");
}
else
{
fp = stdout;
outputname = gettext("standard output");
}
if (fc.quickflag)
{
fprintf
(
fp,
"Files \"%s\" and \"%s\":\n",
fc.fileA.f_name,
fc.fileB.f_name
);
fprintf
(
fp,
"%ld inserted %ld deleted %ld matched\n",
fc.inserts,
fc.deletes,
fc.matches
);
goto done;
}
if ((fc.inserts == 0) && (fc.deletes == 0))
{
if (fc.verbosity)
{
fprintf
(
fp,
"Files \"%s\" and \"%s\" are identical\n",
fc.fileA.f_name,
fc.fileB.f_name
);
}
goto done;
}
fprintf
(
fp,
"FILE A: %s\nFILE B: %s\nTOTALS: %ld inserted %ld deleted %ld matched\n",
fc.fileA.f_name,
fc.fileB.f_name,
fc.inserts,
fc.deletes,
fc.matches
);
line1 = 0;
line2 = 0;
for (sp = fc.snakelist; sp; sp = sp->next)
{
count1 = sp->line1 - line1;
count2 = sp->line2 - line2;
if (count1 && count2)
{
printrange(fp, "REPLACE", line1, count1, line2, count2);
printlines(fp, &fc.fileA, line1 + 1, count1, "");
fputs("******************** WITH:\n", fp);
printlines(fp, &fc.fileB, line2 + 1, count2, "");
}
else if (count1)
{
printrange(fp, "DELETE", line1, count1, line2, 1L);
printlines(fp, &fc.fileA, line1 + 1, count1, "");
}
else if (count2)
{
printrange(fp, "INSERT", line1, 1L, line2, count2);
printlines(fp, &fc.fileB, line2 + 1, count2, "");
}
line1 = sp->line1 + sp->count;
line2 = sp->line2 + sp->count;
}
done:
fflush_and_check(fp, outputname);
fclose_and_check(fp, outputname);
}
/*
* Dump all of both files showing what happened to each line.
*/
void
dumpwhat(char *outputname)
{
FILE *fp;
SNAKE *sp;
long line1;
long line2; /* current line numbers */
long count1;
long count2; /* line counts */
if ((fc.verbosity > VERBOSE_DEFAULT) && outputname)
error_raw("[Writing what changes were made to \"%s\"]", outputname);
if (outputname)
{
fp = fopen_and_check(outputname, "w");
}
else
{
fp = stdout;
outputname = "(stdandard output)";
}
fprintf(fp, "FILE A: %s\n", fc.fileA.f_name);
fprintf(fp, "FILE B: %s\n", fc.fileB.f_name);
fprintf
(
fp,
"TOTALS: %ld inserted %ld deleted %ld matched\n",
fc.inserts,
fc.deletes,
fc.matches
);
line1 = 0;
line2 = 0;
for (sp = fc.snakelist; sp; sp = sp->next)
{
count1 = sp->line1 - line1;
count2 = sp->line2 - line2;
if (count1 && count2)
{
printlines(fp, &fc.fileA, line1 + 1, count1, "|-");
printlines(fp, &fc.fileB, line2 + 1, count2, "|+");
}
else if (count1)
printlines(fp, &fc.fileA, line1 + 1, count1, "|-");
else if (count2)
printlines(fp, &fc.fileB, line2 + 1, count2, "|+");
printlines(fp, &fc.fileB, sp->line2 + 1, sp->count, " ");
line1 = sp->line1 + sp->count;
line2 = sp->line2 + sp->count;
}
fflush_and_check(fp, outputname);
fclose_and_check(fp, outputname);
}
/*
* Dump only the line numbers which have changed.
* This is useful for machine processing of the differences.
*/
void
dumplines(char *outputname)
{
FILE *fp; /* output file */
SNAKE *sp; /* current snake */
long line1;
long line2; /* previous line numbers */
long count1;
long count2; /* count of line numbers */
if ((fc.verbosity > VERBOSE_DEFAULT) && outputname)
error_raw("[Writing edit script to \"%s\"]", outputname);
if (outputname)
{
fp = fopen_and_check(outputname, "w");
}
else
{
fp = stdout;
outputname = gettext("standard output");
}
line1 = 0;
line2 = 0;
fprintf(fp, "A %s\nB %s\n", fc.fileA.f_name, fc.fileB.f_name);
for (sp = fc.snakelist; sp; sp = sp->next)
{
count1 = sp->line1 - line1;
count2 = sp->line2 - line2;
if (count1 && count2)
printrange(fp, "R", line1, count1, line2, count2);
else if (count1)
printrange(fp, "D", line1, count1, line2, 1L);
else if (count2)
printrange(fp, "I", line1, 1L, line2, count2);
line1 = sp->line1 + sp->count;
line2 = sp->line2 + sp->count;
}
fprintf(fp, "T %ld %ld %ld\n", fc.inserts, fc.deletes, fc.matches);
fflush_and_check(fp, outputname);
fclose_and_check(fp, outputname);
}
/*
* Reinitialize for a new file comparison.
* This zeroes out our static variables so that another
* file comparison will work correctly.
*/
void
fcompreset(void)
{
tablesize = 0;
snakecount = 0;
availchars = 0;
fc.inserts = 0;
fc.deletes = 0;
fc.matches = 0;
fc.snakelist = NULL;
nextchar = NULL;
V1 = NULL;
V2 = NULL;
nextsnake = NULL;
lastsnake = NULL;
endsnake = NULL;
hashtable = NULL;
}
void
binary_warning(const char *filename)
{
sub_context_ty *scp;
scp = sub_context_new();
sub_var_set_charstar(scp, "File_Name", filename);
error_intl(scp, i18n("warning: $filename is binary"));
sub_context_delete(scp);
}
void
binary_fatal(const char *filename)
{
sub_context_ty *scp;
scp = sub_context_new();
sub_var_set_charstar(scp, "File_Name", filename);
fatal_intl(scp, i18n("$filename is binary"));
/* NOTREACHED */
sub_context_delete(scp);
}
/*
* Routine to do the comparison of two files.
* If either of the input filenames is NULL, then this indicates
* that the file has already been read by an earlier call.
*/
void
fcomp(char *nameA, char *nameB)
{
long changes; /* total number of changes */
trace(("fcomp(nameA = \"%s\", nameB = \"%s\")\n{\n", nameA, nameB));
fc.fileA.f_tag = 'A';
fc.fileB.f_tag = 'B';
if (nameA)
{
fc.fileA.f_name = nameA;
if (fc.hexify_flag)
{
fc.fileA.f_file = input_hexify(input_file_open(nameA), 1);
}
else if (fc.binary)
{
fc.fileA.f_file =
input_quoted_printable_encode(input_file_open(nameA), 1);
}
else
fc.fileA.f_file = input_file_text_open(nameA);
}
if (nameB)
{
fc.fileB.f_name = nameB;
if (fc.hexify_flag)
{
fc.fileB.f_file = input_hexify(input_file_open(nameB), 1);
}
else
fc.fileB.f_file = input_file_text_open(nameB);
}
if (!nameA && !nameB)
{
fatal_intl(0, i18n("input file names not supplied"));
}
if (!hashtable)
{
int j;
hashtable = (LINE **)cm_alloc_and_check(HASHSIZE * sizeof(LINE *));
for (j = 0; j < HASHSIZE; ++j)
hashtable[j] = 0;
}
if (nameA)
{
readfile(&fc.fileA, fc.fileAskip, fc.fileAmodify);
if (fc.fileA.is_binary)
{
binary_warning(nameA);
if (fc.cant_do_binary)
return;
}
}
if (nameB)
{
readfile(&fc.fileB, fc.fileBskip, fc.fileBmodify);
if (fc.fileB.is_binary)
{
binary_warning(nameB);
if (fc.cant_do_binary)
return;
}
}
changes = fc.fileA.f_linecount - fc.fileB.f_linecount;
if (changes < 0)
changes = -changes;
if (changes > fc.maxchanges)
{
sub_context_ty *scp;
scp = sub_context_new();
sub_var_set_long(scp, "Number", fc.maxchanges);
fatal_intl(scp, i18n("More than $number changes required"));
/* NOTREACHED */
sub_context_delete(scp);
}
makesnakes();
trace(("}\n"));
}
void
modifyline_register(modifyline_type func)
{
modifyline = func;
}
syntax highlighted by Code2HTML, v. 0.9.1