NonSoft

2つのテキストファイルの差分を解析するサンプル(VB6) 2.04版

 サンプルソース
<このサンプルの概要>
テキスト差分を解析するロジックに興味を持ち色々調べましたが、良い公式を見つける事
が出来ませんでした。なぜテキスト差分という、今や当たり前のキーワードが公開されて
いないのか疑問に思いつつ、仕方無く自力(独自アルゴリズム)で考えました。
このサンプルは、2つのテキストファイルを比較し差分解析結果を返します。まず、変更
前/後のテキストファイルを読み込みます。読み込んだ変更前ファイルを先頭行から差分
解析し変更後ファイルと一致するかを確認します。一致していれば解析済み行としてカウ
ントアップします。一致していなければ次の一致行を探します。そこで見つかった一致行
までが差分となります。しかし、変更前ファイルからのみ差分解析すると、実際より差分
が大きくなってしまうので変更後ファイルからの差分解析も行います。変更前ファイルか
らの差分解析結果と変更後ファイルからの差分解析結果のを比較し一致行以降の一致行数
が多い方を採用します。そうして得た結果を配列に並べ、変更状態を示す文字列(UPD,ADD,
DEL) と共に返します。引数には変更前/後ファイル名以外に精度微調整パラメータ(P1:
一致とみなす文字数、P2:一致とみなす行数)があります。これは、不一致行を発見して
から次に一致行とみなる条件です。この値を調整する事により差分解析結果が変わります。
変更前/後のテキストファイルに少しの変更しか無い場合はp1を小さくした方がより現
実的な差分になります。また、変更前/後のテキストファイルに大きな変更を行なった場
合はp1の値を大きくした方が全体的な差分が分かりやすくなります。

このサンプルは「テキスト差分表示ツール」の「Ver2.04」 で使用しているもので最新版
とは異なりますが、基本的な考え方は一緒です。最新版では、不一致行の中から一致行を
再度探す処理を入れ精度を高くしていますが、基本的にはバージョンで充分だと思ってい
ます。また、最新版では文字コードの自動判定を行なっていますが、このサンプルはSJ
ISコードのテキストファイルのみ差分解析が可能です。

少し新しいサンプルソースもあります。(差分解析ロジックVer5.01)
2つのテキストファイルの差分を解析するサンプル(VB)

VB.NET版のサンプルソースもあります。(差分解析ロジックVer5.01)
2つのテキストファイルの差分を解析するサンプル(VB.NET)

「テキスト差分表示ツール」をダウンロード頂けます。
テキスト差分表示ツール

「ネット(Web)で手軽にテキスト差分表示サービス!!」をお試し頂けます。
テキスト差分表示ツール.NET

'
' 関数名    : TextDiff
' 返り値    : テキスト差分解析結果2次元配列
'           : (正常時)
'           : 配列番号=0はタイトル行、配列番号=1以降にテキストデータ
'           : 配列(0,0) = "TYPE"
'           : 配列(1,0) = "NO."
'           : 配列(2,0) = file0PATH
'           : 配列(3,0) = "NO."
'           : 配列(4,0) = file1PATH
'           : 配列(0,1〜) = 変更状態を示す文字列(UPD,ADD,DEL)
'           : 配列(1,1〜) = file0の行番号
'           : 配列(2,1〜) = file0の各行テキストデータ
'           : 配列(3,1〜) = file1の行番号
'           : 配列(4,1〜) = file1の各行テキストデータ
'           : (エラー時)
'           : 配列(0,0) = -1
'           : 配列(1,0) = エラーメッセージ
' 引き数    : file0(i) : 変更前テキストファイル
'           : file1(i) : 変更後テキストファイル
'           : p1   (i) : 精度微調整パラメータ(一致とみなす文字数)
'           : p2   (i) : 精度微調整パラメータ(一致とみなす行数)
' 機能説明  : テキストファイルを比較し、テキスト差分解析結果を返します
' 備考      : テキスト差分解析ロジックVer2.04
' 著作権    : Copyright(C) 2008 のん All rights reserved
'
Public Function TextDiff(ByVal file0 As String, ByVal file1 As String, _
                         Optional ByVal p1 As Long = 5, _
                         Optional ByVal p2 As Long = 1) As String()
    On Error Resume Next

    Dim rtn() As String             ' テキスト差分解析結果2次元配列定義
    ReDim rtn(4, 0)                 ' テキスト差分解析結果2次元配列定義
    
    Dim dfp As Long                 ' テキスト差分解析結果の現在の配列番号保存用
    Dim diff As Boolean             ' テキスト差分発見フラグ
    Dim i As Long                   ' カウンタ(変更前テキストファイル側)
    Dim j As Long                   ' カウンタ(変更後テキストファイル側)
    Dim k As Long                   ' カウンタ(汎用)
    Dim tmps As String              ' 文字列(汎用)
    Dim fit0 As Long                ' 一致行番号(変更前テキストファイル側)
    Dim fit1 As Long                ' 一致行番号(変更後テキストファイル側)
    Dim sumi0 As Long               ' 解析済行番号(変更前テキストファイル側)
    Dim sumi1 As Long               ' 解析済行番号(変更後テキストファイル側)
    Dim OrgFile0() As String        ' 変更前テキストファイル読込配列
    Dim OrgFile1() As String        ' 変更後テキストファイル読込配列
    Dim txtfile0 As TextStream      ' 変更前テキストファイルストリーム
    Dim txtfile1 As TextStream      ' 変更後テキストファイルストリーム
    Dim fso As FileSystemObject     ' ファイルシステムオブジェクト
    Set fso = CreateObject("Scripting.FileSystemObject")
    
    ' ファイル読込領域クリア
    ReDim OrgFile0(0)
    ReDim OrgFile1(0)
    
    If file0 <> "" Then
        ' 変更前ファイル存在チェック
        If fso.FileExists(file0) = False Then
            rtn(0, 0) = "-1"
            rtn(1, 0) = "ファイルが存在しません" & vbCrLf & vbCrLf & file0
            TextDiff = rtn
            Exit Function
        End If
        ' 変更前ファイルオープン
        Set txtfile0 = fso.OpenTextFile(file0, ForReading, False)
        If txtfile0 Is Nothing Then
            rtn(0, 0) = "-1"
            rtn(1, 0) = "ファイルを読み込めません " & vbCrLf & vbCrLf & file0
            TextDiff = rtn
            Exit Function
        End If
        ' 変更前ファイル読込
        Do While txtfile0.AtEndOfStream <> True
            ReDim Preserve OrgFile0(UBound(OrgFile0) + 1)
            OrgFile0(UBound(OrgFile0)) = txtfile0.ReadLine
        Loop
        ' 変更前ファイルクローズ
        txtfile0.Close
    End If
    
    If file1 <> "" Then
        ' 変更前ファイル存在チェック
        If fso.FileExists(file1) = False Then
            rtn(0, 0) = "-1"
            rtn(1, 0) = "ファイルが存在しません" & vbCrLf & vbCrLf & file1
            TextDiff = rtn
            Exit Function
        End If
        ' 変更後ファイルオープン
        Set txtfile1 = fso.OpenTextFile(file1, ForReading, False)
        If txtfile1 Is Nothing Then
            rtn(0, 0) = "-1"
            rtn(1, 0) = "ファイルを読み込めません " & vbCrLf & vbCrLf & file1
            TextDiff = rtn
            Exit Function
        End If
        ' 変更後ファイル読込
        Do While txtfile1.AtEndOfStream <> True
            ReDim Preserve OrgFile1(UBound(OrgFile1) + 1)
            OrgFile1(UBound(OrgFile1)) = txtfile1.ReadLine
        Loop
        ' 変更後ファイルクローズ
        txtfile1.Close
    End If
    
    ' テキスト差分解析開始
    sumi0 = 0
    sumi1 = 0
    Do
        If sumi0 >= UBound(OrgFile0) And sumi1 >= UBound(OrgFile1) Then
            ' テキスト差分解析終了
            Exit Do
        End If
        
        ' テキスト差分発見フラグクリア
        diff = False

        ' 変更前ファイルの対象行を変更後ファイルから検索
        i = UBound(OrgFile0) + 1
        j = UBound(OrgFile1) + 1
        For i = sumi0 + 1 To UBound(OrgFile0)
            For j = sumi1 + 1 To UBound(OrgFile1)
                If diff = True Then
                    ' 一致?
                    tmps = ""
                    For k = 0 To 99
                        ' テキスト差分発見後はp1文字以上の一致が条件
                        If i + k > UBound(OrgFile0) Then Exit For
                        If j + k > UBound(OrgFile1) Then Exit For
                        If OrgFile0(i + k) <> OrgFile1(j + k) Then Exit For
                        tmps = tmps & OrgFile0(i + k)
                        If Len(tmps) >= p1 And k >= p2 - 1 Then
                            Exit For
                        End If
                    Next
                    If OrgFile0(i + k) = OrgFile1(j + k) Then Exit For
                Else
                    ' 一致?
                    If OrgFile0(i) = OrgFile1(j) Then Exit For
                    ' テキスト差分発見
                    diff = True
                End If
            Next j
            If i <= UBound(OrgFile0) And j <= UBound(OrgFile1) Then
                ' 一致?
                If OrgFile0(i) = OrgFile1(j) Then Exit For
            End If
        Next i
        fit0 = i
        fit1 = j

        ' 画面が固まらないように
        DoEvents

        ' テキスト差分発見時の最適テキスト差分解析(逆からの比較による最適化)
        If diff = True Then
            ' 変更後ファイルの対象行を変更前ファイルから検索
            For j = sumi1 + 1 To UBound(OrgFile1)
                For i = sumi0 + 1 To UBound(OrgFile0)
                    If diff = True Then
                        ' 一致?
                        tmps = ""
                        For k = 0 To 99
                            ' テキスト差分発見後はp1文字以上の一致が条件
                            If i + k > UBound(OrgFile0) Then Exit For
                            If j + k > UBound(OrgFile1) Then Exit For
                            If OrgFile0(i + k) <> OrgFile1(j + k) Then Exit For
                            tmps = tmps & OrgFile0(i + k)
                            If Len(tmps) >= p1 And k >= p2 - 1 Then
                                Exit For
                            End If
                        Next
                        If OrgFile0(i + k) = OrgFile1(j + k) Then Exit For
                    Else
                        ' 一致?
                        If OrgFile0(i) = OrgFile1(j) Then Exit For
                        ' テキスト差分発見
                        diff = True
                    End If
                Next i
                If i <= UBound(OrgFile0) And j <= UBound(OrgFile1) Then
                    ' 一致?
                    If OrgFile0(i) = OrgFile1(j) Then Exit For
                End If
            Next j
            ' 以降の一致行数が多い方を採用
            k = 1
            Do
                If i + k > UBound(OrgFile0) And _
                   j + k > UBound(OrgFile1) Then
                    Exit Do
                End If
                If OrgFile0(i + k) <> OrgFile1(j + k) Then
                    Exit Do
                End If
                If fit0 + k > UBound(OrgFile0) And _
                   fit1 + k > UBound(OrgFile1) Then
                    fit0 = i
                    fit1 = j
                    Exit Do
                End If
                If OrgFile0(fit0 + k) <> OrgFile1(fit1 + k) Then
                    fit0 = i
                    fit1 = j
                    Exit Do
                End If
                k = k + 1
            Loop
        End If
        
        ' 変更行には改行が一致している可能性があるため
        ' 再度、一致行をテキスト差分から除去する
        If fit0 - 1 - sumi0 > 0 And fit1 - 1 - sumi1 > 0 Then
            Do While OrgFile0(fit0 - 1) = OrgFile1(fit1 - 1)
                fit0 = fit0 - 1
                fit1 = fit1 - 1
            Loop
        End If
        
        ' 変更行をテキスト差分解析結果2次元配列へ設定
        If fit0 - 1 - sumi0 > 0 And fit1 - 1 - sumi1 > 0 Then
            dfp = UBound(rtn, 2)
            ' 変更行をテキスト差分解析結果2次元配列に設定(変更前)
            For k = sumi0 + 1 To fit0 - 1
                If UBound(rtn, 2) < dfp + k - sumi0 Then
                    ReDim Preserve rtn(4, UBound(rtn, 2) + 1)
                End If
                rtn(0, dfp + k - sumi0) = "UPD"
                rtn(1, dfp + k - sumi0) = k
                rtn(2, dfp + k - sumi0) = OrgFile0(k)
            Next k
            ' 変更行をテキスト差分解析結果2次元配列に設定(変更後)
            For k = sumi1 + 1 To fit1 - 1
                If UBound(rtn, 2) < dfp + k - sumi1 Then
                    ReDim Preserve rtn(4, UBound(rtn, 2) + 1)
                End If
                rtn(0, dfp + k - sumi1) = "UPD"
                rtn(3, dfp + k - sumi1) = k
                rtn(4, dfp + k - sumi1) = OrgFile1(k)
            Next k
        ElseIf fit0 - 1 - sumi0 > 0 And fit1 - 1 - sumi1 <= 0 Then
            ' 削除行をテキスト差分解析結果2次元配列に設定
            For k = sumi0 + 1 To fit0 - 1
                ReDim Preserve rtn(4, UBound(rtn, 2) + 1)
                rtn(0, UBound(rtn, 2)) = "DEL"
                rtn(1, UBound(rtn, 2)) = k
                rtn(2, UBound(rtn, 2)) = OrgFile0(k)
            Next k
        ElseIf fit0 - 1 - sumi0 <= 0 And fit1 - 1 - sumi1 > 0 Then
            ' 追加行をテキスト差分解析結果2次元配列に設定
            For k = sumi1 + 1 To fit1 - 1
                ReDim Preserve rtn(4, UBound(rtn, 2) + 1)
                rtn(0, UBound(rtn, 2)) = "ADD"
                rtn(3, UBound(rtn, 2)) = k
                rtn(4, UBound(rtn, 2)) = OrgFile1(k)
            Next k
        End If
        
        ' 一致行をテキスト差分解析結果2次元配列に設定
        If fit0 <= UBound(OrgFile0) And fit1 <= UBound(OrgFile1) Then
            ReDim Preserve rtn(4, UBound(rtn, 2) + 1)
            rtn(1, UBound(rtn, 2)) = fit0
            rtn(2, UBound(rtn, 2)) = OrgFile0(fit0)
            rtn(3, UBound(rtn, 2)) = fit1
            rtn(4, UBound(rtn, 2)) = OrgFile1(fit1)
        End If
        
        ' 解析済み行の設定
        sumi0 = fit0
        sumi1 = fit1
    Loop
    
    ' タイトル行設定
    rtn(0, 0) = "TYPE"
    rtn(1, 0) = "NO."
    rtn(2, 0) = file0
    rtn(3, 0) = "NO."
    rtn(4, 0) = file1
    
    ' ファイルシステムオブジェクト解放
    Set fso = Nothing
    
    ' リターン
    TextDiff = rtn
End Function