2010. augusztus 30., hétfő

A simple File Comparison Utility


Problem/Question/Abstract:

Sometimes you are only interested in knowing if two files are the same- you might have hit return a few times in the editor so one looks bigger than the other and has a later save date but it might be the same otherwise...

Answer:

The utility listed below (both .pas and .dfm source) accepts as input two file names. For convenience these filenames (with associated paths) are saved out between runs and you can copy the filename from the first box to the second (click the red down arrow) - it combines the first filename with the existing 2nd path.  Both edit boxes allow you to browse for files.

File comparison is simple and fast. Each file is read into a memory stream and then a count of each of the 256 possible characters is made. You could argue that by cutting and moving text elsewhere in a text file file that this would break my method (as char counts would be unaffected) and you'd be right but I think for most purposes this method is probably sufficient and it works with binary as well as text files. I realise a CRC calculation could also be added- feel free to do so.

When there are differences the output is a string showing each character value (0-255) followed by the count in brackets.

For something thrown together quickly in an hour or so, it has served me well and compares files of a few megabytes pretty quickly.

Pascal Source

unit viewdiff;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, Buttons;

type
  TDBDiff = class
    Ffilename1: string;
    FfileName2: string;
    FBuff1: TmemoryStream;
    FBuff2: TmemoryStream;
    FCounts1: array[0..255] of integer;
    FCounts2: array[0..255] of integer;
    FProcessed: boolean;
    fDifferenceStr: string;
    FDifferent: boolean;
  private

    function GetDiffCount(ch: char): integer;
    function GetDifferences: boolean;
    procedure Clear;
    procedure BuildDiffTable(Mem1, Mem2: pointer; size1, size2: integer);
    procedure BuildDifferenceStr;
    function CheckIfSame: boolean;
  public
    constructor Create;
    destructor Destroy; override;
    property Different: boolean read GetDifferences;
    property DifferenceStr: string read fDifferenceStr;
    property DiffCount[ch: char]: integer read GetDiffCount;
    property Filename1: string read FFilename1 write FFilename1;
    property Filename2: string read FFilename2 write FFilename2;
  end; // TdbDiff

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    fileopen: TOpenDialog;
    Edit1: TEdit;
    Edit2: TEdit;
    GoBtn: TButton;
    btnCopyDown: TBitBtn;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure GoBtnClick(Sender: TObject);
    procedure FormActivate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure btnCopyDownClick(Sender: TObject);
  private
    procedure CheckGoBtn;
    procedure LoadEditBoxes;
    procedure SaveEditBoxes;
    { Private declarations }
  public
    { Public declarations }
    Aftercreate: boolean;
    Diff: tDbDiff;
    StartPath: string;
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

const
  editsavefilename = 'diff.ini';
  CrLf = #13#10;

procedure TForm1.Button1Click(Sender: TObject);
begin
  if Edit1.Text <> '' then
    FileOpen.Initialdir := ExtractFileDir(Edit1.Text);
  if FileOpen.execute then
    edit1.Text := FileOpen.Filename;
  CheckGobtn;
end;

procedure Tform1.checkGoBtn;
var
  filename: string;
begin
  Gobtn.enabled := false;
  Filename := trim(Edit1.Text);
  if (Filename <> '') and fileexists(Filename) then
  begin
    Filename := trim(Edit2.Text);
    if (Filename <> '') and fileexists(Filename) then
      GoBtn.Enabled := True;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  if Edit2.Text <> '' then
    FileOpen.Initialdir := ExtractFileDir(Edit2.Text);
  if FileOpen.execute then
    edit2.Text := FileOpen.Filename;
  CheckGoBtn;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Aftercreate := true;
end;

{ TDBDiff }

procedure TdbDiff.Clear;
begin
  fFilename1 := '';
  fFilename2 := '';
  fillchar(fCounts1, sizeof(fcounts1), 0);
  fillchar(fCounts2, sizeof(fcounts2), 0);
  fDifferenceStr := '';
  fProcessed := false;
end;

constructor TDBDiff.Create;
begin
  fBuff1 := TmemoryStream.Create;
  fBuff2 := TmemoryStream.Create;
  Clear;
end;

destructor TDBDiff.Destroy;
begin
  FBuff2.Free;
  FBuff1.Free;
end;

function TDBDiff.GetDiffCount(ch: char): integer;
begin
  result := fCounts1[ord(ch)] - Fcounts2[ord(ch)];
end;

procedure TdbDiff.BuildDifferenceStr;
var
  Index: integer;
begin
  fDifferenceStr := '';
  for Index := 0 to 255 do
    if fcounts1[Index] <> fCounts2[Index] then
    begin
      fDifferenceStr := fDifferencestr +
        ' #' + inttostr(Index) + '(' + inttostr(fcounts1[Index] - Fcounts2[Index]) +
          ')';
    end;
end;

function TDBDiff.CheckIfSame: boolean;
var
  Index: integer;
begin
  result := true;
  for Index := 0 to 255 do
    if fcounts1[Index] <> fcounts2[Index] then
    begin
      Result := false;
      exit;
    end;
end;

procedure TDBDiff.BuildDiffTable(mem1, mem2: pointer; size1, size2: integer);
type
  Bytemap = array[0..2000000000] of byte;
  BytemapPtr = ^ByteMap;
var
  MapPtr: ByteMapPtr;
  Index: integer;
begin
  MapPtr := ByteMapPtr(mem1);
  for Index := 0 to size1 - 1 do
    inc(fcounts1[MapPtr^[Index]]);
  MapPtr := ByteMapPtr(mem2);
  for Index := 0 to size2 - 1 do
    inc(fcounts2[MapPtr^[Index]]);
end;

function TDBDiff.GetDifferences: boolean;
var
  fs: TFileStream;
begin
  if fProcessed then
    Result := Fdifferent
  else
  begin
    Result := false;
    if (trim(Ffilename1) = '') or (trim(FFilename2) = '') then
      exit;
    fProcessed := true;
    fs := TfileStream.Create(fFilename1, fmOpenRead);
    fbuff1.LoadFromStream(fs);
    fs.free;
    fs := TfileStream.Create(fFilename2, fmOpenRead);
    fbuff2.LoadFromStream(fs);
    fs.free;
    BuildDiffTable(fbuff1.memory, fbuff2.memory, fbuff1.size, fbuff2.size);
    BuildDifferenceStr;
    Result := not CheckIfSame;
  end;
end;

procedure TForm1.GoBtnClick(Sender: TObject);

begin
  diff.Clear;
  diff.Filename1 := edit1.text;
  diff.Filename2 := edit2.text;
  if diff.Different then
    ShowMessage(
      'Differences between ' + Crlf +
      diff.Filename1 + Crlf +
      diff.Filename2 + Crlf + Crlf +
      diff.DifferenceStr)
  else
    ShowMessage('Files identical');
end;

procedure TForm1.FormActivate(Sender: TObject);
begin
  if AfterCreate then
  begin
    AFterCreate := false;
    diff := tdbdiff.Create;
    GetDir(0, StartPath);
    if StartPath[Length(StartPath)] <> '\' then
      StartPath := StartPath + '\';
    LoadEditBoxes;
  end;
end;

procedure Tform1.LoadEditBoxes;
var
  tf: textfile;
  s: string;
begin
  if fileexists(StartPath + EditSaveFilename) then
  begin
    assignfile(tf, StartPath + EditSavefilename);
    reset(tf);
    try
      readln(tf, s);
      edit1.text := s;
      readln(tf, s);
      edit2.text := s;
    finally
      Closefile(Tf);
      CheckGoBtn;
    end;
  end;
end;

procedure Tform1.SaveEditBoxes;
var
  tf: textfile;
  s: string;
begin
  assignfile(tf, StartPath + EditSavefilename);
  rewrite(tf);
  try
    s := edit1.text;
    writeln(tf, s);
    s := edit2.text;
    writeln(tf, s);
  finally
    Closefile(Tf);
  end;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  SaveEditBoxes;
end;

procedure TForm1.btnCopyDownClick(Sender: TObject);
begin
  edit2.text := ExtractFileDir(Edit2.Text) + '\' +
    ExtractFileName(Edit1.Text);
end;

end.

DFM Source

object Form1: TForm1
  Left = 338
    Top = 555
    Width = 462
    Height = 172
    Caption = 'Difference Utility'
    Color = clBtnFace
    Font.Charset = DEFAULT_CHARSET
    Font.Color = clWindowText
    Font.Height = -11
    Font.Name = 'MS Sans Serif'
    Font.Style = []
    OldCreateOrder = False
    OnActivate = FormActivate
    OnCreate = FormCreate
    OnDestroy = FormDestroy
    PixelsPerInch = 96
    TextHeight = 13
    object Button1: TButton
    Left = 12
      Top = 30
      Width = 75
      Height = 25
      Caption = '1st File'
      TabOrder = 0
      OnClick = Button1Click
  end
  object Button2: TButton
    Left = 12
      Top = 78
      Width = 75
      Height = 25
      Caption = '2nd File'
      TabOrder = 1
      OnClick = Button2Click
  end
  object Edit1: TEdit
    Left = 96
      Top = 30
      Width = 343
      Height = 21
      Anchors = [akLeft, akTop, akRight]
      TabOrder = 2
  end
  object Edit2: TEdit
    Left = 96
      Top = 78
      Width = 343
      Height = 21
      Anchors = [akLeft, akTop, akRight]
      TabOrder = 3
  end
  object GoBtn: TButton
    Left = 96
      Top = 114
      Width = 75
      Height = 25
      Caption = 'Compare'
      Enabled = False
      TabOrder = 4
      OnClick = GoBtnClick
  end
  object btnCopyDown: TBitBtn
    Left = 240
      Top = 54
      Width = 26
      Height = 23
      TabOrder = 5
      OnClick = btnCopyDownClick
      Glyph.Data = {
    76010000424D7601000000000000760000002800000020000000100000000100
    0400000000000001000000000000000000001000000010000000000000000000
    800000800000008080008000000080008000808000007F7F7F00BFBFBF000000
    FF0000FF000000FFFF00FF000000FF00FF00FFFF0000FFFFFF00333333303333
    333333333337F33333333333333033333333333333373F333333333333090333
    33333333337F7F33333333333309033333333333337373F33333333330999033
    3333333337F337F33333333330999033333333333733373F3333333309999903
    333333337F33337F33333333099999033333333373333373F333333099999990
    33333337FFFF3FF7F33333300009000033333337777F77773333333333090333
    33333333337F7F33333333333309033333333333337F7F333333333333090333
    33333333337F7F33333333333309033333333333337F7F333333333333090333
    33333333337F7F33333333333300033333333333337773333333}
    NumGlyphs = 2
  end
  object fileopen: TOpenDialog
    DefaultExt = '*'
      Filter = 'Any File|*.*'
      Left = 354
  end
end

Nincsenek megjegyzések:

Megjegyzés küldése