Dominik Reichl's
Homepage
 


Information about me, how to
contact me and the imprint.
Open-Source applications, free C++
classes and other free sources.
Articles, papers, publications
and other texts.
Article Article

Print version [Print version]

Adressen in Dateien suchen

E-Mail-Adressen suchen

Bisher war es eine aufwändige Arbeit, eine E-Mail-Adresse in großen und besonders vielen Dateien zu finden. Dies ist jetzt in wenigen Augenblicken möglich!

Dominik Reichl

Sie suchen eine E-Mail-Adresse und wissen, dass diese sich in irgendeiner Datei in ihrem Dokumenten-Ordner befindet? Für Sie ist Zeit Geld und Sie haben noch viel anderes zu tun? Bisher gibt es noch keine richtig funktionierende Möglichkeit dieses Problem mit den Windows-Bordmitteln zu erledigen (zum Beispiel mit dem Suchen-Dialog von Windows). Viele Tools, die im Internet angeboten werden, haben zu wenig Möglichkeiten zum Filtern der E-Mail-Adressen. Programmieren Sie doch einfach ein Tool, das E-Mail-Adressen automatisch in Dateien sucht und übersichtlich in eine Liste einordnet! Das Tool soll verschiedene Optionen bieten, zum Beispiel nur Dateien mit bestimmten Endungen nach Adressen zu durchsuchen, oder nur E-Mail-Adressen mit einer bestimmten Länderkennung zuzulassen (.de, .com, .net, ...).

Als Entwicklungsumgebung steht Microsoft Visual Studio 5/6 mit C++ zur Verfügung. Sie setzen die MFC (Microsoft Foundation Classes) ein.

Legen Sie als Erstes ein neues Projekt an. Dazu wählen Sie im "Neu"-Menü den "MFC-Anwendungs-Assistenten" aus. In das Projektnamen-Eingabefeld tragen Sie "EGrabber" ein. In den nachfolgenden Optionsdialogen wählen Sie als Anwendungstyp "Dialogfeldbasierend" aus. Das Programm hat dann als Hauptfenster einen Dialog. Die kontextabhängige Hilfe wird im Beispielprojekt nicht verwendet. Die Windows-Sockets und die Automatisierung wird nicht benötigt. Ob Sie die optionalen Kommentare in den Quellcodedateien benötigen oder nicht, bleibt Ihnen überlassen. Es bleibt Ihnen ebenfalls überlassen, ob Sie die MFC-Bibliothek gemeinsam verwenden wollen oder ob Sie diese statisch an Ihr Programm binden. Beides hat seine Vor- und Nachteile: wenn Sie die DLL gemeinsam nutzen, müssen Sie sicher sein, dass sie auf dem Zielsystem existiert bzw. dass sie bei der Installation mit installiert wird. Der Nachteil des statischen Linkens ist, dass ihr Programm deutlich größer wird. Einen Info-Dialog sollten Sie auf jeden Fall mit einbinden.

Der Hauptdialog

Das Hauptfenster besitzt nur ein einziges Steuerelement und ein Menü. Das Steuerelement füllt das gesamte Dialogfeld und ist ein Listenelement vom Typ CListCtrl. Es wird CListCtrl anstatt einer "normalen" Listbox verwendet, da die CListCtrl-Klasse die Berichtsansicht mit verschiedenen Spalten unterstützt und so die E-Mail-Adressen viel leichter eingefügt und übersichtlicher angezeigt werden können. Das Menü ist ebenfalls recht einfach gehalten: in der Menügruppe "Datei" gibt es einen Menübefehl "E-Mail-Adressen suchen..." mit der ID IDM_GO und einen Menübefehl "Beenden" mit der ID IDM_QUIT. Die Menügruppe rechts daneben ("?") enthält den Menübefehl "Info..." (IDM_INFO). Bei Auswahl dieses Menübefehls wird der About-Dialog angezeigt, der am Anfang bei der Projekt-Generierung durch den Assistenten erzeugt wurde (normalerweise IDD_ABOUTBOX). Diesen About-Dialog können Sie frei gestalten.

Nun gilt es, das Listenelement vorzubereiten und zu initialisieren. Verwenden Sie den Klassenassistenten, um eine neue Membervariable vom Typ CListCtrl für das Listenelement zu erstellen. Danach fügen Sie dem Listenelement in der CEGrabber::OnInitDialog()-Funktion Spalten hinzu, indem Sie die Funktion InsertColumn der CListCtrl-Klasse verwenden. Es werden zwei Spalten erstellt: "Fundort (Datei)" und "E-Mail-Adresse".

Definieren Sie in der Headerdatei der CEGrabberDlg-Klasse zwei Konstanten: MAX_GRABBER_BUFFER (gibt die Anzahl der zu lesenden Bytes an, siehe Abschnitt "Such-Engine") und SAFE_SCAN_BUFFER (ebenfalls näher beschrieben im Abschnitt "Such-Engine"). Definieren Sie zusätzlich folgende Konstanten zum Filtern der Dateien: FILTER_ALL 0, FILTER_NONE 127, FILTER_DOCUMENTS 1, FILTER_EXECUTABLES 2 und FILTER_USER 3. Die Zahlen müssen den Radio-Werten der Radio-Gruppe zum Filtern bestimmter Dateien im Optionsdialog (nächster Abschnitt) entsprechen (Ausnahme FILTER_NONE).

Der Hauptklasse CEGrabberDlg müssen Sie für später noch folgende Membervariablen hinzufügen: unsigned char chBuf[MAX_GRABBER_BUFFER], unsigned char chSaver[SAFE_SCAN_BUFFER] und unsigned char chFinal[MAX_GRABBER_BUFFER + SAFE_SCAN_BUFFER]. Alle diese Variablenfelder werden später näher beschrieben.

Der Optionendialog

Der Optionendialog (IDD_OPTIONS) wird dann aufgerufen, wenn der Benutzer im Hauptmenü den Menübefehl "E-Mail-Adressen suchen..." ausgewählt hat und beginnen will. In diesem Dialog lässt sich einstellen, welches Verzeichnis durchsucht werden soll, welche Dateien nach Adressen gescannt werden sollen (Prüfung auf Dateiendungen), welche Adressen akzeptiert und in die Liste hinzugefügt werden sollen (nur E-Mail-Adressen mit den Endungen .de oder .com?), und ob doppelt gefundene Adressen aus der Liste entfernt werden sollen. Um es dem Benutzer so einfach wie möglich zu machen, ein Verzeichnis auszuwählen, wurde ein komfortabler Ordner-Suchdialog eingebaut (Button "Suchen...").

Der Dialog enthält folgende Steuerelemente:

IDC_PATH: Hier kann der Benutzer das zu durchsuchende Verzeichnis eingeben. Wenn der Benutzer auf den Button "Suchen..." mit der ID IDC_BROWSE klickt, wird ein typischer Windows-Ordner-Suchen-Dialog geöffnet. Falls der Dialog mit OK bestätigt wird, wird der neue Pfad in das Edit-Feld automatisch eingetragen.

In dem Gruppenfeld "Dateisuche" gibt es zahlreiche Optionsfelder: IDC_ALLFILES bedeutet, dass alle Dateien durchsucht werden; IDC_TEXTONLY bedeutet, dass nur Dokumente durchsucht werden; das sind in diesem Fall einfache TXT-Textdateien, formatierte Dokumente (doc und rtf), Dateien im HTML-Format (htm, html), Aufzeichnungsdateien (log), Hilfedateien (hlp), und Excel-Dateien (xls). Das nächste Optionsfeld IDC_EXEONLY bedeutet, dass nur ausführbare Dateien (exe, com, dll, bat, prf, lib) durchsucht werden. Bei Auswahl des Optionsfelds IDC_USERDEFONLY werden nur Dateien mit der Endung durchsucht, die in dem Eingabefeld IDC_DEFTYPE eingegeben wurde.

Im Gruppenfeld "E-Mail-Adressen" gibt es zwei Optionsfelder und zahlreiche Kontrollkästchen. Falls das erste Optionsfeld IDC_EMAIL_ALLOWALL ausgewählt wird, werden alle Adressen akzeptiert werden. Falls allerdings das Optionsfeld IDC_EMAIL_ALLOWDEF ausgewählt wird, werden nur E-Mail-Adressen mit bestimmten Endungen akzeptiert. Diese können mit den Kontrollkästchen IDC_EALLOW_DE, IDC_EALLOW_COM, IDC_EALLOW_NET, IDC_EALLOW_CH, IDC_EALLOW_AT sowie IDC_EALLOW_ORG eingestellt werden. Wird zusätzlich das Kontrollkästchen IDC_EALLOW_CUSTOM aktiviert, werden die Adressen auch noch nach benutzerdefinierten Eingaben in die Eingabefelder IDC_EALLOW_USERDEF1 und IDC_EALLOW_USERDEF2 gefiltert.

Das Gruppenfeld "Adressen-Liste" beinhaltet lediglich ein einziges Kontrollkästchen IDC_DELETEDOUBLES, mit Hilfe dessen der Benutzer einstellen kann, ob doppelte Adressen nach der Suche gelöscht werden sollen.

Den IDOK- und IDCANCEL-Buttons wurden Funktionen hinzugefügt. In diesen Funktionen wird eine Datenaktualisierung (UpdateData()) durchgeführt. Dies ist wichtig für den Datenaustausch mit dem Hauptdialog.

Es besteht also die Möglichkeit eigene Dateiendungen einzugeben und so nach einer anderen bestimmten Endung suchen zu lassen (zum Beispiel .fr), denn es kommt ja oft vor, dass Sie wissen in welchem Land der Besitzer der zu suchenden E-Mail-Adresse wohnt.

Fast allen Dialogelementen wurden Membervariablen hinzugefügt, um einen effektiven Datenaustausch zwischen dem Hauptdialog und dem Optionendialog zu ermöglichen. Der Optionendialog ist modal und wird auf dem üblichen Weg über DoModal() aufgerufen. Wenn der Benutzer auf OK geklickt hat, werden die Membervariablen des Optionendialogs in die Klassenvariablen des Hauptdialogs gespeichert. Die BOOL-Werte der Membervariablen des Optionendialogs zur Definition der Adressen-Endungen werden zur leichteren Verwendung für später in einem BOOL-Feld namens bAllow[7]. Ist dort ein Wert TRUE ist die entsprechende Endung erlaubt. Die Endungen werden später in der Funktion ScanBuffer überprüft. Die benutzerdefinierten Eingaben im Optionendialog (spezielle Dateiendungen und Länderkennzeichen) werden in CString-Members des Hauptdialogs gesichert. Jetzt fehlt noch ein letzter Dialog:

Der Statusdialog

Dieser soll dem Benutzer anzeigen, welche Datei gerade durchsucht wird, und ihm die Möglichkeit geben, den Vorgang abzubrechen (Button "Abbrechen"). Dafür bietet sich der standardmäßig im Visual Studio enthaltene Statusdialog an. Fügen Sie ihrem Projekt also über 'Projekt' > 'Dem Projekt hinzufügen' > 'Komponenten und Steuerelemente' die Komponente "DialogfeldFortschritt" im 'Developer Studio Components'-Ordner hinzu. Als Klassenname wird "CProgressDlg" verwendet. Das Minimum der grafischen Statusanzeige (Statusbalken) ist 0, das Maximum 100. Der Statusdialog kann dann mit der Create-Memberfunktion angelegt werden. Mit CheckCancelButton wird der Abbrechen-Button abgefragt. Nach der Verwendung des Statusdialogfelds wird es mit der DestroyWindow-Memberfunktion zerstört.

Ein Beispiel für die Anwendung des Statusdialogs:

Erstellung:

CProgressDlg progDlg;
progDlg.Create(this);

Statusanzeige aktualisieren und einstellen:

progDlg.SetStatus("Statustext");
progDlg.SetPos(nProzent);

Abbrechen-Button überprüfen:

progDlg.CheckCancelButton();

Fenster zerstören:

progDlg.DestroyWindow();

Ein Tip: verwenden Sie, falls Sie die Prozentzahl aus der Anzahl der gelesenen und der Dateigröße berechnen, immer folgendes:

progDlg.SetPos ((int)((float)100.00000000 / ((float)fileSize / (float)filePos)));

Dies sieht auf den ersten Blick etwas seltsam aus. Aber: Wenn Sie die Werte nicht zuerst in Floats und danach wieder in int konvertieren passiert folgendes: die Werte unter dem Bruchstrich (der letzte Teil des Terms) werden immer kleiner und nähern sich immer mehr der 1. Das hätte ohne Konvertierung zur Folge, dass der Statusbalken zunächst schön langsam läuft, dann die Schritte aber immer größer werden und schließlich ab der Hälfte die komplette andere Hälfte übersprungen wird. Das sieht nicht schön aus und der Benutzer könnte denken, dass sich das Programm aufgehängt hat. Also konvertiert man zuerst die Werte in das Float-Format, das Nachkommastellen mitrechnet. Dies ermöglicht sehr viel genauere Zahlen und daraus resultiert dann ein schöner, langsam, nicht springender Statusbalken.

Achten Sie jedoch auch darauf, dass fileSize und filePos niemals 0 werden dürfen, da sonst eine Division durch 0 folgt, und das ist nicht so gut. Der hintere Term darf niemals 0 werden, da sonst versucht wird, 100 durch 0 zu teilen, was in einem Ausnahmefehler enden würde. Deshalb ist es am Besten, einfach, falls fileSize den Wert 0 hat, fileSize auf 1 zu setzen. Das gleiche gilt für filePos. Die Angaben stimmen dann bei 0-Byte-Dateien dann zwar nicht mehr, aber das macht ja kaum was aus, da eine 0-Byte-Datei so schnell gescannt wird, dass man das praktisch nicht sieht.

Such-Engine

Nun kommt der interessantere Teil der Programmierung: die E-Mail-Adressen-Suchengine. Es werden drei neue Memberfunktionen der Hauptdialogklasse hinzugefügt: SearchFiles, ScanFile, und ScanBuffer. SearchFiles sucht mit Hilfe der MFC-Klasse CFileFind Dateien. Zuerst wird die Memberfunktion FindFile aufgerufen. Nach diesem Aufruf können weitere Dateien im Verzeichnis mit der Funktion FindNextFile gesucht werden. Den aktuellen Dateinamen der gesuchten Datei können Sie dann mit der GetFilePath der CFileFind-Klasse ermitteln. Des weiteren werden die Memberfunktionen IsDots und IsDirectory verwendet. Falls die IsDirectory-Funktion erfolgreich ist, ruft sich die Funktion selbst noch einmal auf (rekursiv). Damit werden alle (!) Dateien in dem am Anfang gegebenen Verzeichnis gefunden, auch in Unterordnern! Wenn die IsDirectory-Funktion allerdings fehlschlägt, handelt es sich um eine Datei. Nun wird zunächst die Endung der Datei überprüft und je nach dem, was der Benutzer im Optionendialog ausgewählt hat, übergangen oder nicht.

Die Dateisuche-Funktion:

void CEGrabberDlg::SearchFiles(CString strPath)
{
 CFileFind ff;
 BOOL bFoundFile = TRUE, bAdd = FALSE;
 CString strCurrentFile = "", strExt = "";
 if (strPath.Right(1) != "\\") strPath += "\\";
 strPath += "*.*";

 if(ff.FindFile(strPath))
 {
  while(bFoundFile)
  {
   bFoundFile = ff.FindNextFile();
   if(!ff.IsDots())
   {
    strCurrentFile = ff.GetFilePath();
    progDlg.SetStatus (strCurrentFile);
    if(ff.IsDirectory()) SearchFiles(strCurrentFile);
    else
    {
     strExt = strCurrentFile.Right(4);
     strExt.MakeLower();

     bAdd = FALSE;
     if (nFilter == FILTER_ALL) bAdd = TRUE;
     else if (nFilter == FILTER_DOCUMENTS)
     {
      if (strExt == ".txt") bAdd = TRUE;
      if (strExt == ".doc") bAdd = TRUE;
      if (strExt == ".rtf") bAdd = TRUE;
      if (strExt == ".htm") bAdd = TRUE;
      if (strExt == "html") bAdd = TRUE;
      if (strExt == ".log") bAdd = TRUE;
      if (strExt == ".hlp") bAdd = TRUE;
      if (strExt == ".xls") bAdd = TRUE;
     }
     else if (nFilter == FILTER_EXECUTABLES)
     {
      if (strExt == ".exe") bAdd = TRUE;
      if (strExt == ".com") bAdd = TRUE;
      if (strExt == ".dll") bAdd = TRUE;
      if (strExt == ".bat") bAdd = TRUE;
      if (strExt == ".prf") bAdd = TRUE;
      if (strExt == ".lib") bAdd = TRUE;
     }
     else if (nFilter == FILTER_USER)
     {
      strExt = strCurrentFile.Right(strUserFilter.GetLength());
      if (strExt == strUserFilter) bAdd = TRUE;
     }

     if (bAdd == TRUE)
     {
      strScanFile = strCurrentFile;
      ScanFile();
     }
    }
   }
   if (progDlg.CheckCancelButton() == TRUE) break;
  }
 }
}

Wenn die Datei gescannt werden soll, wird die Funktion ScanFile aufgerufen. Diese scannt die nach Adressen zu durchsuchende Datei. Dies geschieht nicht Byte für Byte. Es wird stattdessen ein ganzer Block der Größe 8000 Bytes gelesen und dieser dann im Speicher gescannt. Dieses Verfahren ist deutlich schneller als das Byte-für-Byte-Lesen, da es eine wesentlich längere Zeit braucht, um auf Dateien auf Datenträgern zuzugreifen, als auf den Speicher im RAM. Es wird also immer ein Block gelesen. Die tatsächlich gelesenen Bytes ermitteln Sie einfach, indem Sie den Rückgabewert der fread-Funktion in eine Variable speichern und später überprüfen. Ist der Wert in der Variable gleich den 8000 zu lesenden Bytes, gibt es noch Daten in der Datei. Sonderfall: genau 8000 Bytes werden gelesen und genau dann gibt es kein einziges Byte mehr. Doch auch dieser Sonderfall wird in der ScanFile-Funktion berücksichtigt. Nun werden also immer Daten gelesen und in einem Buffer gespeichert (im Projekt heißt dieser Buffer 'chBuf', ist vom Typ unsigned char, und ist als chBuf[8000] deklariert, das heisst, dass der Buffer 8000 Bytes speichern kann). Was nun mit den Daten im Buffer tun? Jetzt kommt die ScanBuffer-Funktion zum Zuge.

Die ScanBuffer-Funktion sucht im Buffer nach E-Mail-Adressen. Dabei ist zunächst einmal folgendes Problem zu beachten: es kann vorkommen, dass sich eine E-Mail-Adresse genau an der Stelle befindet, wo ein alter Block zu Ende ist und ein neuer anfängt. Die E-Mail-Adresse würde also nicht gefunden werden, da sich der erste Teil im alten Buffer befindet und der neue sich im gerade gelesenen. Was also tun? Die Lösung ist ziemlich einfach: es wird ein neuer Buffer erstellt (im Projekt chFinal), der die letzten 128 Bytes des alten Buffers enthält, gefolgt vom neuen Buffer. Dies macht also zusammen 8128 Bytes, die der neue Kombinations-Buffer halten muss. Die 128 Bytes werden im Projekt SAFE_SCAN_BUFFER genannt und sind in der Datei EGrabberDlg.h definiert. Dort finden Sie auch die Größe des Datei-Lese-Buffers (Standard 8000). Wenn Sie wollen, können Sie diese Werte anpassen.

So. Der chFinal-Buffer enthält also 8128 Bytes, die es zu scannen gilt.

Der Scan-Vorgang

Der erste Gedanke ist: wie ist eine E-Mail-Adresse aufgebaut? Zuerst sind da ein paar Zeichen oder Zahlen (auch Sonderzeichen '_' und '-'), es folgt ein @, und dann der Name der Firma oder des Providers, es kommt ein Punkt und dann folgt noch ein Länderkürzel wie de oder com. Am Einfachsten wäre es nun also, einfach mal ein @ im Buffer zu suchen. Genau das macht EGrabber als erstes. Wenn ein @ gefunden wurde, wird solange zurückgegangen, bis ein ungültiges Zeichen gefunden wurde, also Sonderzeichen oder ein Leerzeichen. Ab dieser Position wird wieder von vorne nach hinten gegangen. Einem String werden alle folgenden Bytes hinzugefügt, bis ein ungültiges Zeichen auftritt (also auch wieder Leerzeichen oder Sonderzeichen). Erst jetzt, nachdem ein ungültiges Zeichen gefunden wurde und der Scan-Vorgang abgebrochen wurde, wird die im String gespeicherte potentielle E-Mail-Adresse genauer geprüft. Ein BOOL-Wert ('bNoEMail') wird auf FALSE gesetzt. Schlägt auch nur eine einzige Überprüfung fehl, wird bNoEMail auf TRUE gesetzt und der String nicht der E-Mail-Liste im Hauptfenster hinzugefügt. Folgende Überprüfungen identifizieren den String als sichere E-Mail-Adresse:

1) der String darf nicht länger als 256 Bytes sein,

2) der String darf nicht kürzer als 5 Bytes sein (kürzeste E-Mail-Adresse: C@n.e, also 5 Zeichen)

3) es muss ein @ enthalten sein, und zwar genau eines und nicht mehr, und darf nicht am Anfang stehen

4) es muss ein Punkt enthalten sein, und dieser darf nicht am Anfang stehen,

5) bestimmte Sonderzeichen wie !, ?, ;, dürfen nicht enthalten sein,

6) es muss mindestens 1 Punkt nach dem @ stehen

7) falls der Benutzer den Endungs-Filter benutzt: ob die Adresse auf diese Endung endet.

Sind alle diese Voraussetzungen erfüllt, wird der String dem Listenelement im Hauptfenster hinzugefügt. Da dem Listenelement mit dem Dialogfeldeditor automatische Sortierung zugewiesen wurde, brauchen Sie sich nicht mehr darum zu kümmern, es geschieht automatisch beim Einfügen.

Was zum Schluss noch zu tun ist: das Entfernen der doppelten E-Mail-Adressen. Dies geschieht auf die folgende Art und Weise: jede Adresse wird herausgenommen und mit den Adressen, die sich unter (!) der aktuellen befinden, verglichen. Ist die Adresse gleich, wird das untere Element entfernt. So werden ziemlich schnell alle doppelten Adressen entfernt.

Übrigens: achten Sie auch hier darauf, die Werte für den Statusdialog zunächst in Floats und danach wieder in Ints zu konvertieren.




Copyright © 2003-2010 Dominik Reichl, [Imprint] [Disclaimer] [Acknowledgements]

Valid XHTML 1.0 Transitional Document Get Firefox Get Thunderbird Get KeePass