{ /*************************************************************************** uTextCompare.pas ----------------------- This unit contains differ class for two TStrings ***************************************************************************/ *************************************************************************** * * * This source 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 code 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. * * * * A copy of the GNU General Public License is available on the World * * Wide Web at . You can also * * obtain it by writing to the Free Software Foundation, * * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * * *************************************************************************** This unit is part of Seksi Commander Initial developer: Radek Cervinka Last modification: 12.4.03 } unit uTextCompare; { $mode objfpc}{ $H+} interface uses Classes; function TextCompare(lsLeft, lsRight:TStrings):Integer; type TTextCompare=Class FlsLeft, FlsRight: TStrings; // what compare FiPosLeft, FiPosRight:Integer; FiPosLeftOrig, FiPosRightOrig:Integer; // position in original file private procedure ScanToDiff; procedure AppendLines; function NumberLine(const sLine:String; var iCount:Integer):String; procedure NumberActualLines; // function ResyncRight2Left:Boolean; procedure InsertLines(lst:TStrings; iPos:Integer; iCount:Integer; lstNotify:TStrings; var iOrigPos:Integer); // function ResyncLeft2Right:Boolean; function CountResyncLeft2Right(iResyncSecond:Integer):Integer; function CountResyncRight2Left:Integer; function CmpLines(sLine1, sLine2:String):Integer; function CheckString(const s1, s2:String; iPos1, iPos2:Integer):Integer; public function CompareMain:Integer; procedure SetTStrings(ALeft, ARight:TStrings); end; implementation uses SysUtils; procedure TTextCompare.AppendLines; var xIndex:Integer; iCount:Integer; begin iCount:=Abs(FlsRight.Count-FlsLeft.Count); if FlsRight.Count>FlsLeft.Count then begin for xIndex:= 0 to iCount-1 do begin FlsLeft.AddObject('+++', TObject(2)); FlsRight.Objects[FiPosRight+xIndex]:=TObject(2); FlsRight[FiPosRight+xIndex]:=NumberLine(FlsRight[FiPosRight+xIndex], FiPosRightOrig); end; end else begin for xIndex:= 0 to iCount-1 do begin FlsRight.AddObject('+++', TObject(2)); FlsLeft.Objects[FiPosLeft+xIndex]:=TObject(2); FlsLeft[FiPosLeft+xIndex]:=NumberLine(FlsLeft[FiPosLeft+xIndex], FiPosLeftOrig); end; end; end; function TTextCompare.NumberLine(const sLine: String; var iCount: Integer): String; begin Result:=Format('%5d: %s',[iCount,sLine]); inc(iCount); end; procedure TTextCompare.NumberActualLines; begin FlsLeft[FiPosLeft]:=NumberLine(FlsLeft[FiPosLeft], FiPosLeftOrig); FlsRight[FiPosRight]:=NumberLine(FlsRight[FiPosRight], FiPosRightOrig); end; function TTextCompare.CmpLines(sLine1, sLine2:String):Integer; var xIndex, xIndex2:Integer; iSame:Integer; iLine1Len:Integer; // for optimalization only begin Result:=0; xIndex:=0; sLine1:=Trim(sLine1); sLine2:=Trim(sLine2); iLine1Len:=Length(sLine1); if (sLine1='') or (sLine2 ='') then Exit; while (xIndex sLine2[xIndex2] then Continue; iSame:=CheckString(sLine1, sLine2, xIndex, xIndex2); // how much chars is same? if iSame>Result then Result:=iSame; if iSame + xIndex >iLine1Len then // we found max of possible Exit; if iLine1Len - xIndex < Result then // there is no chance to find big then Result Exit; end; end; end; function TextCompare(lsLeft, lsRight:TStrings):Integer; begin with TTextCompare.Create do begin try SetTStrings(lsLeft, lsRight); Result:=CompareMain; finally Free; end; end; end; function TTextCompare.CompareMain:Integer; var iResyncToLeft, iResyncToRight: Integer; // i:Integer; begin FiPosLeft :=0; FiPosRight :=0; FiPosLeftOrig:=1; FiPosRightOrig:=1; Result:=0; // how much changes FlsLeft.Add(''); FlsRight.Add(''); // add one line for security (at the end is removed - in finally) try repeat ScanToDiff; // determine how much lines is different in both direction (left2right and right2left) iResyncToLeft:=CountResyncRight2Left; iResyncToRight:=CountResyncLeft2Right(iResyncToLeft); // no possible resync if (iResyncToLeft = 0) and (iResyncToRight = 0) then begin FlsLeft.Objects[FiPosLeft]:=TObject(2); // notify different lines FlsRight.Objects[FiPosRight]:=TObject(2); NumberActualLines; inc(FiPosLeft); inc(FiPosRight); Continue; end; if ((iResyncToLeft< iResyncToRight) or (iResyncToRight = 0)) and (iResyncToLeft<>0) then begin InsertLines(FlsLeft,FiPosLeft, iResyncToLeft, FlsRight, FiPosRightOrig ); FiPosLeft:=FiPosLeft + iResyncToLeft; FiPosRight:=FiPosLeft ; NumberActualLines; inc(FiPosRight); FiPosLeft:=FiPosRight ; inc(Result); end else begin if iResyncToRight>0 then InsertLines(FlsRight,FiPosRight, iResyncToRight, FlsLeft, FiPosLeftOrig ) else inc(iResyncToRight); FiPosRight:=FiPosRight + iResyncToRight ; FiPosLeft:=FiPosRight ; NumberActualLines; inc(FiPosRight); FiPosLeft:=FiPosRight ; inc(Result); end; until (FiPosLeft= FlsLeft.Count) or (FiPosRight = FlsRight.Count); AppendLines; // append lines at the end (both list have to same line count) finally // FlsLeft.Delete(FlsLeft.Count-1); // delete added security lines // FlsRight.Delete(FlsRight.Count-1); // hack: lazarus Synedit sometimes not show last line (i think taht is scrollbar related problem) // so apppended line is only for safe (last line is appended line) end; end; procedure TTextCompare.InsertLines(lst: TStrings; iPos, iCount: Integer; lstNotify:TStrings; var iOrigPos:Integer); var xIndex:Integer; begin for xIndex:= 0 to iCount-1 do begin lst.InsertObject(iPos,'+++', TObject(2)); lstNotify.Objects[iPos+xIndex]:=TObject(2); lstNotify[iPos+xIndex]:=NumberLine(lstNotify[iPos+xIndex], iOrigPos); end; end; function TTextCompare.CountResyncLeft2Right(iResyncSecond:Integer): Integer; var xIndex:Integer; // iMinSuccess:Integer; sComparedLine:String; begin Result:=0; sComparedLine:=Trim(FlsRight[FiPosRight]); if sComparedLine='' then Exit; for xIndex:=FiPosLeft to FlsLeft.Count -1 do begin if (iResyncSecond>0) and ((xIndex - FiPosRight)> iResyncSecond) then Break; //befored resync is better if (sComparedLine = Trim(FlsLeft[xIndex])) then // if line is exactly (without white chars) same begin Result:=xIndex - FiPosRight; Break; end; if Pos(sComparedLine, Trim(FlsLeft[xIndex]))>0 then // line look like same begin FlsLeft.Objects[xIndex]:=TObject(1); // notify change FlsRight.Objects[FiPosRight]:=TObject(1); Result:=xIndex - FiPosRight; Break; end; end; end; function TTextCompare.CountResyncRight2Left: Integer; var xIndex:Integer; // iMinSuccess:Integer; sComparedLine:String; begin Result:=0; // see second Resync :) sComparedLine:=Trim(FlsLeft[FiPosLeft]); if sComparedLine='' then Exit; for xIndex:=FiPosRight to FlsRight.Count -1 do begin if (sComparedLine = Trim(FlsRight[xIndex])) then begin Result:=xIndex - FiPosLeft; Break; end; if Pos(sComparedLine, Trim(FlsRight[xIndex]))>0 then begin FlsRight.Objects[xIndex]:=TObject(1); FlsLeft.Objects[FiPosLeft]:=TObject(1); Result:=xIndex - FiPosLeft; Break; end; end; end; procedure TTextCompare.ScanToDiff; var s:String; iMinSame:Integer; begin while (FiPosLeft Trim(FlsRight[FiPosRight])) then begin iMinSame:=(Length(s)+Length(FlsRight[FiPosRight])) div 6; //33 % must be same if (CmpLines(s, FlsRight[FiPosRight])