Textdateien vergleichen

Als ich bei verschiedenen Projekten die gleichen Module und Formulare einsetzte und diese unabhängig voneinander anpasste, hatte ich plötzlich verschiedene Versionen der Objekte. Das war nicht beabsichtigt, die Objekte sollten möglichst überall gleich aussehen. Um keinen der Entwicklungsschritte zu verlieren, musste ich den Code der Objekte vergleichen. Dummerweise bietet Access keine eingebaute Funktion für so etwas, also musste ich mich selbst an die Arbeit machen und eine entsprechend Lösung programmieren. Diese sollte schlicht und einfach zwei Textdateien Zeile für Zeile miteinander vergleichen und mir die unterschiedlichen Zeilen anzeigen.

Zunächst einmal muss man sich überlegen, wie die Benutzeroberfläche eines solchen Tools aussehen könnte. Da hier zwei Dateien miteinander verglichen werden sollen, wird es auf jeden Fall zwei Textfelder zur Eingabe der Dateinamen geben beziehungsweise zwei Schaltflächen, mit denen Sie einen Dateidialog zum Auswählen der beiden Dateien aufrufen können. Darunter befinden sich der übersicht halber nebeneinander die Texte der beiden Dateien – gegebenenfalls mit den markierten unterschiedlichen Zeilen.

Wenn man nun zwei Dateien zeilenweise vergleicht, sind weitere Fragen zu beantworten:

  • Wie soll man überhaupt zwei Textdateien zeilenweise miteinander vergleichen
  • Und wie stelle ich das Ergebnis so dar, dass der Benutzer auch etwas damit anfangen kann

Kommen wir zunächst zur zweiten Frage. Im Endergebnis soll unser Formular mit den beiden nebeneinander dargestellten Dateien etwa wie in Bild 1 aussehen.

So soll das Formular später aussehen.

Bild 1: So soll das Formular später aussehen.

Gleiche Zeilen werden direkt nebeneinander dargestellt, unterschiedliche Zeilen werden nur auf der jeweiligen Seite angezeigt und außerdem rot markiert, damit man diese sofort erkennen kann.

Die erste Frage ist schon aufwendiger zu beantworten. In unserer Lösung verwenden wir etwa einen Ablauf wie den folgenden:

Wir beginnen bei der ersten Zeile der ersten Datei und vergleichen diese mit der ersten Zeile der zweiten Datei. Sind beide Zeilen gleich, ist alles okay – wir bilden einfach beide Zeilen ab. Dann schauen wir uns die zweite Zeile an, bei der beispielsweise die erste Datei noch einen zusätzlichen Kommentar am Ende der Zeile enthält. Wir stellen fest, dass die zweite Zeile nicht in beiden Dateien gleich ist. Das kann nun Verschiedenes bedeuten: Zum Beispiel kann es sein, dass sich wirklich nur die zweiten Zeilen der beiden Dateien unterscheiden. Vielleicht finden wir aber die zweite Zeile der ersten Datei auch in der dritten oder in einer späteren Zeile der zweiten Datei – oder umgekehrt! In diesem Fall schauen wir, ob die zweite Zeile der ersten Datei früher in der zweiten Datei auftaucht als die zweite Zeile der zweiten Datei in der ersten. Wenn die zweite Zeile der ersten Datei früher in der zweiten Datei auftaucht als andersherum, zeigen wir für die erste Datei einige leere Zeilen an, während für die zweite Datei die bis zur gesuchten Zeile enthaltenen Zeilen erscheinen. Erst dann sollen die beiden gleichen Zeilen nebeneinandergestellt werden.

Am besten lässt sich dies anhand eines einfachen Beispiels erklären. Schauen wir uns dazu die beiden Textdateien aus Bild 2 an. Diese enthalten jeweils einen Satz, dessen Wörter auf jeweils einer eigenen Zeile abgebildet wurden. Wenn Sie diese beiden Dateien vergleichen, sollte sich das Ergebnis aus Bild 3 ergeben.

Beispieldateien für den Vergleich

Bild 2: Beispieldateien für den Vergleich

Ergebnis des Vergleichs

Bild 3: Ergebnis des Vergleichs

Die Zeilen 0, 1 und 2 stimmen in beiden Dateien überein. Zeile 3 jedoch unterscheidet sich in den beiden Dateien. Also schauen wir zunächst, ob und wann sich der Inhalt von Zeile 3 der ersten Datei in der zweiten Datei wiederholt. Dies ist in Zeile 6 der Fall. Dann prüft der Algorithmus, wann sich Zeile 2 aus der zweiten Datei sich in der ersten Datei wiederholt. Dies ist gar nicht der Fall, also gehen wir davon aus, dass Zeile 2 der ersten Datei und Zeile 6 der zweiten Datei übereinstimmen und Zeile 3, 4 und 5 der zweiten Datei eingeschoben wurden. Dann geht der Vergleich weiter, und zwar mit der jeweils folgenden Zeile nach den zueinander passenden Zeilen (also 2 und 6). Zeile 4 der ersten Datei stimmt nicht mit Zeile 7 der zweiten Datei überein. Beide können auch im weiteren Verlauf nicht gefunden werden, also werden beide im Ergebnis in jeweils einer eigenen Zeile abgebildet.

Zeile 5 der ersten Datei und Zeile 8 der zweiten Datei stimmen wiede-rum wieder überein und werden folgerichtig nebeneinander abgebildet. Zeile 6, 7, 8 und 9 der ersten Datei finden sich hingegen nicht in der zweiten Datei wieder und erscheinen solo in je einer Zeile. Dann passen wieder Zeile 11 der ersten und Zeile 9 der zweiten Datei zueinander, die übrigen Zeilen hingegen nicht.

Formular anlegen

Das Formular der Lösung soll frmTextdateienVergleichen heißen und soll keine Daten aus einer Tabelle oder Abfrage anzeigen – also stellen wir die Eigenschaften Bildlaufleisten, Datensatzmarkierer, Navigationsschaltflächen und Trennlinien auf Nein ein.

Dateien auswählen

Beginnen wir mit der Auswahl der Dateien. Dazu fügen Sie dem Formular zwei Textfelder namens txtDatei1 und txtDatei2 hinzu. Rechts neben den Textfeldern platzieren Sie jeweils eine Schaltfläche namens cmdDatei1 und cmdDatei2.

Für die beiden Schaltflächen hinterlegen Sie eine Beim Klicken-Ereignisprozedur wie in Listing 1. Die hier verwendete Prozedur OpenFileName finden Sie im Modul mdlTools.

Private Sub cmdDatei1_Click()
    Dim strDateiendung As String
    Me!txtDatei1 = OpenFileName(CurrentProject.Path, "Textdatei auswählen", _
        "Textdatei (*.txt)|Alle Dateien (*.*)")
End Sub

Listing 1: Anzeigen eines Datei öffnen-Dialogs und Eintragen des Ergebnisses im Textfeld

Die Textfelder und Schaltflächen legen Sie etwa wie in Bild 4 im Formular an.

Textfelder zum Erfassen der Dateinamen

Bild 4: Textfelder zum Erfassen der Dateinamen

Damit ich beim Testen der Lösung nicht bei jedem Schließen und öffnen des Formulars die Dateien neu auswählen musste, habe ich die zuletzt verwendete Datei in den beiden Feldern Datei1 und Datei2 einer Tabelle namens tblOptionen gespeichert (s. Bild 5).

Optionentabelle zum Speichern der zuletzt verwendeten Dateien

Bild 5: Optionentabelle zum Speichern der zuletzt verwendeten Dateien

Das Formular ist an die Tabelle tblOptionen gebunden, die beiden Textfelder txtDatei1 und txtDatei2 an die entsprechenden Felder der Tabelle.

Tabelle zum Speichern des Vergleichs

Um das Ergebnis des Vergleichs in einer Ansicht darzustellen, gibt es mehrere Möglichkeiten – zwei Unterformulare in der Datenblattansicht, zwei Listenfelder oder auch ein einzelnes Unterformular in der Datenblattansicht.

Nach einigen Experimenten wurde klar: Man muss durch die Ergebnisse scrollen können, und dies für die Zeilen aus beiden Dateien synchron. Außerdem sollten bei Bedarf die Spaltenbreiten mit den Zeileninhalten angepasst werden können. Das Ergebnis: Es sollte ein einziges Unterformular in der Datenblattansicht mit den Zeilen der beiden Dateien und weiteren Feldern werden. Um diese Informationen nach dem Vergleich per VBA schnell in einem Formular anzeigen zu können, sollte man diese am besten in einer Tabelle speichern. Diese sieht im Entwurf wie in Bild 6 aus und hat folgende Felder:

Tabelle zum Speichern des Ergebnisses des Vergleichs

Bild 6: Tabelle zum Speichern des Ergebnisses des Vergleichs

  • ZeileID: Primärschlüssel der Tabelle
  • Unterschied: Ja/Nein-Feld, das angibt, ob sich die beiden abgebildeten Felder unterscheiden (es sollen standardmäßig alle Zeilen angezeigt werden, auch diejenigen, die in beiden Dateien identisch sind – dies könnte man gegebenenfalls variieren).
  • ZeileDatei1: Gibt die Nummer der Zeile innerhalb der jeweiligen Datei an, beginnt bei 0
  • Uebernehmen1: Ja/Nein-Feld, mit dem der Benutzer festlegen können soll, ob diese Zeile in eine neu zu erstellende dritte Datei übernommen werden soll
  • TextDatei1: Text der Zeile mit der angegebenen Zeilennummer in der ersten Datei
  • ZeileDatei2, Uebernehmen2, Textdatei2: siehe oben, nur für die zweite Textdatei

Das Ergebnis für den Vergleich der beiden Beispieldateien von oben würde etwa wie in Bild 7 in der Tabelle gespeichert werden. Diese kann genau so als Datenherkunft für das Unterformular sfmTextdateienVergleichen verwendet werden, fügen Sie dort alle Felder aus der Feldliste zum Formular hinzu. Stellen Sie außerdem die Eigenschaft Standardansicht auf Datenblatt ein.

In der Tabelle tblZeilen gespeichertes Vergleichsergebnis

Bild 7: In der Tabelle tblZeilen gespeichertes Vergleichsergebnis

Dateien vergleichen

Die Hauptarbeit beim Erstellen dieser Lösung steckte in der Programmierung der Routine, welche die Textdateien vergleicht und die Ergebnisse in die Tabelle tblZeilen einträgt. Sie finden den ersten Teil dieser Prozedur, die durch das Anklicken der Schaltfläche cmdVergleichen ausgelöst wird, in Listing 3.

Private Sub cmdVergleichen_Click()
    Dim strZeilen1() As String, strZeilen2() As String
    Dim lngAktuelleZeile1 As Long, lngAktuelleZeile2 As Long
    Dim lng1 As Long, lng2 As Long
    Dim db As DAO.Database
    Dim lngAbstand1 As Long, lngAbstand2 As Long
    Dim bolTreffer1 As Boolean, bolTreffer2 As Boolean
    Dim bolEnde1 As Boolean, bolEnde2 As Boolean
    Dim lngZeilenGesamt As Long
    Set db = CurrentDb
    db.Execute "DELETE FROM tblZeilen", dbFailOnError
    strZeilen1 = DateiInArray(Me!txtDatei1)
    strZeilen2 = DateiInArray(Me!txtDatei2)
    lngAktuelleZeile1 = 0
    lngAktuelleZeile2 = 0
    lngZeilenGesamt = UBound(strZeilen1)
    If lngZeilenGesamt < UBound(strZeilen2) Then
        lngZeilenGesamt = UBound(strZeilen2)
    End If
    SysCmd acSysCmdClearStatus
    Do While (lngAktuelleZeile1 < UBound(strZeilen1) And lngAktuelleZeile2 < UBound(strZeilen2))
        SysCmd acSysCmdSetStatus, "Zeile " & lngAktuelleZeile1 & "/" & lngZeilenGesamt
        lngAbstand1 = 0
        lngAbstand2 = 0
        Do While Not bolTreffer2 And Not bolEnde2
            If (strZeilen1(lngAktuelleZeile1) = strZeilen2(lngAktuelleZeile2 + lngAbstand2)) Then
                bolTreffer2 = True
            End If
            If lngAktuelleZeile2 + lngAbstand2 = UBound(strZeilen2) - 1 Then
                bolEnde2 = True
            End If
            If Not bolTreffer2 And Not bolEnde2 Then
                lngAbstand2 = lngAbstand2 + 1
            End If
        Loop
        Do While Not bolTreffer1 And Not bolEnde1
            If strZeilen2(lngAktuelleZeile2) = strZeilen1(lngAktuelleZeile1 + lngAbstand1) Then
                bolTreffer1 = True
            End If
            If lngAktuelleZeile1 + lngAbstand1 = UBound(strZeilen1) - 1 Then
                bolEnde1 = True
            End If
            If Not bolTreffer1 And Not bolEnde1 Then
                lngAbstand1 = lngAbstand1 + 1
            End If
        Loop

Listing 2: Textdateien vergleichen, Teil I

Die Prozedur löscht zunächst alle gegebenenfalls noch in der Tabelle tblZeilen enthaltenen Datensätze. Dann füllt sie die beiden Array-Variablen strZeilen1 und strZeilen2 mit Arrays, die aus den einzelnen Zeilen der Dateien bestehen. Das heißt, dass jedes Feld des Arrays je eine Zeile aufnimmt. Das Einlesen und Umwandeln der Dateien übernimmt dabei die Funktion DateiInArray aus Listing 2. Diese erwartet den Dateinamen als Parameter. Sie öffnet die Datei und durchläuft in einer Do While-Schleife alle Zeilen der Datei. Dabei fügt sie jede Zeile durch einen Zeilenumbruch (vbCrLf) getrennt zur Variablen strZeilen hinzu.

Public Function DateiInArray(strDateiname As String) As String()
    Dim lngFile As Long
    Dim strZeile As String
    Dim strZeilen As String
    lngFile = FreeFile
    Open strDateiname For Input As #lngFile
    Do While Not EOF(lngFile)
        Line Input #lngFile, strZeile
        strZeilen = strZeilen & strZeile & vbCrLf
    Loop
    Close #lngFile
    DateiInArray = Split(strZeilen, vbCrLf)
End Function

Listing 3: Einlesen einer Textdatei in ein Array

Aus dem Ergebnis erzeugt die Routine mit der Split-Funktion ein Array und gibt dieses an die aufrufende Prozedur zurück.

Dies geschieht für beide Textdateien, die entsprechenden Arrays befinden sich nun in den Variablen strZeilen1 und strZeilen2. Der Zähler für die aktuelle Zeile eines jeden Arrays wird mit den Variablen lngAktuelleZeile1 und lngAktuelleZeile2 jeweils auf 0 eingestellt. Dann ermittelt die Prozedur die Gesamtzahl der Zeilen der längeren Datei.

Dazu liest sie erst die Anzahl der Zeilen im ersten Array ein und prüft dann, ob die Anzahl der Zeilen im zweiten Array größer als die des ersten Arrays ist. Der größere Wert landet in der Variablen lngZeilenGesamt. Danach leert die Prozedur die Statuszeile von Access, die in der Folge den Fortschrift beim Vergleich der beiden Dateien anzeigen soll.

Dann geht es richtig los! Eine Do While-Schleife wird solange durchlaufen, bis die in lngAktuelleZeile1 und lngAktuelleZeile2 gespeicherten aktuell bearbeiteten Zeilen nicht mehr kleiner als die Gesamtzahl der Zeilen der jeweiligen Datei sind. Dabei stellt die Prozedur zunächst den Statustext von Access auf einen Ausdruck wie Zeile 1/100 ein.

Es folgen nun zwei Do While-Schleifen. Die erste ermittelt, in welcher Zeile der zweiten Datei die erste Zeile der ersten Datei erstmalig auftaucht und speichert die Anzahl der Zeilen, die dazwischen liegen, in der Variablen lngAbstand1. Die zweite Do While-Schleife erledigt diesen Job genau andersherum. Dabei stellen die Anweisungen in den beiden Do While-Schleifen auch die Variablen bolTreffer1 und bolTreffer2 ein. bolTreffer1 erhält beispielsweise den Wert True, wenn in der zweiten Datei eine Zeile gefunden werden konnte, die der aktuellen Zeile der ersten Datei entspricht, sonst erhält sie den Wert False. Für bolTreffer2 wird der Wert genau andersherum ermittelt.

Im Detail vergleicht die erste Do While-Schleife die aktuelle Zeile der ersten Datei mit der aktuellen Zeile der zweiten Datei. Sind die Zeilen gleich, stellt die Prozedur den Wert bolTreffer2 auf den Wert True ein. Ist mit dieser Zeile der zweiten Datei die letzte Zeile erreicht, stellt die Prozedur den Wert von bolEnde2 auf True ein. Enthalten die beiden Variablen bolTreffer2 und bolEnde2 nach diesen Prüfungen den Wert False, erhöht die Prozedur den Zähler lngAbstand2 um den Wert 1, sodass im nächsten Durchlauf der Schleife die aktuelle Zeile der ersten Datei mit der nächsten Zeile der zweiten Datei verglichen werden kann. Dies wiederholt sich, bis die Abbruchbedingung der Do While-Schleife erfüllt ist.

Die zweite Do While-Schleife erledigt dies umgekehrt, indem sie die aktuelle Zeile der zweiten Datei zuerst mit der aktuellen Zeile der ersten Datei vergleicht und, falls kein Treffer gefunden wurde, auch mit den folgenden Zeilen.

Am Ende der beiden Schleifen erhalten wir also die folgenden Informationen:

  • bolTreffer1: Falls True, wurde eine Zeile in der ersten Datei gefunden, die der aktuellen Zeile der zweiten Datei entspricht.
  • bolTreffer2: Falls True, wurde eine Zeile in der zweiten Datei gefunden, die der aktuellen Zeile der ersten Datei entspricht.
  • lngAbstand1: Abstand der Zeile der ersten Datei, die mit der aktuellen Zeile der zweiten Datei übereinstimmt.
  • lngAbstand2: Abstand der Zeile der zweiten Datei, die mit der aktuellen Zeile der ersten Datei übereinstimmt.

Mit diesem Resultat gehen wir in den zweiten Teil der Schleife für die aktuell untersuchten Zeilen der beiden Dateien (s. Listing 4).

        If bolTreffer1 = True Then
            If bolTreffer2 = True Then
                If lngAbstand1 = 0 And lngAbstand2 = 0 Then
                Einfuegen12 db, lngAktuelleZeile1, strZeilen1(lngAktuelleZeile1), _
                    lngAktuelleZeile2, strZeilen2(lngAktuelleZeile2)
                Else
                    If lngAbstand1 <= lngAbstand2 Then
                    For lng1 = lngAktuelleZeile1 To lngAktuelleZeile1 + lngAbstand1 - 1
                            Einfuegen1 db, lngAktuelleZeile1, strZeilen1(lng1)
                            lngAktuelleZeile1 = lngAktuelleZeile1 + 1
                        Next lng1
                        Einfuegen12 db, lngAktuelleZeile1, strZeilen1(lngAktuelleZeile1), _
                            lngAktuelleZeile2, strZeilen2(lngAktuelleZeile2)
                    Else
                    For lng2 = lngAktuelleZeile2 To lngAktuelleZeile2 + lngAbstand2 - 1
                            Einfuegen2 db, lngAktuelleZeile2, strZeilen2(lng2)
                            lngAktuelleZeile2 = lngAktuelleZeile2 + 1
                        Next lng2
                        Einfuegen12 db, lngAktuelleZeile1, strZeilen1(lngAktuelleZeile1), _
                            lngAktuelleZeile2, strZeilen2(lngAktuelleZeile2)
                    End If
                End If
            Else
            For lng1 = lngAktuelleZeile1 To lngAktuelleZeile1 + lngAbstand1 - 1
                    Einfuegen1 db, lngAktuelleZeile1, strZeilen1(lng1)
                    lngAktuelleZeile1 = lngAktuelleZeile1 + 1
                Next lng1
                Einfuegen12 db, lngAktuelleZeile1, strZeilen1(lngAktuelleZeile1), _
                    lngAktuelleZeile2, strZeilen2(lngAktuelleZeile2)
            End If
        Else
            If bolTreffer2 = True Then
            For lng2 = lngAktuelleZeile2 To lngAktuelleZeile2 + lngAbstand2 - 1
                    Einfuegen2 db, lngAktuelleZeile2, strZeilen2(lng2)
                    lngAktuelleZeile2 = lngAktuelleZeile2 + 1
                Next lng2
                Einfuegen12 db, lngAktuelleZeile1, strZeilen1(lngAktuelleZeile1), lngAktuelleZeile2, _
                    strZeilen2(lngAktuelleZeile2)
            Else
            Einfuegen1 db, lngAktuelleZeile1, strZeilen1(lngAktuelleZeile1)
                Einfuegen2 db, lngAktuelleZeile2, strZeilen2(lngAktuelleZeile2)
            End If
        End If
        lngAktuelleZeile1 = lngAktuelleZeile1 + 1
        lngAktuelleZeile2 = lngAktuelleZeile2 + 1
        bolTreffer1 = False
        bolTreffer2 = False
        bolEnde1 = False
        bolEnde2 = False
    Loop

Listing 4: Textdateien vergleichen, Teil II

Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...

den kompletten Artikel im PDF-Format mit Beispieldatenbank

diesen und alle anderen Artikel mit dem Jahresabo

Schreibe einen Kommentar