ASCII Dateien

Vielleicht haben Sie es schon bemerkt. Über die Serialisierung können Sie nur binäre Dateien verarbeiten. Oftmals ist es jedoch notwendig Daten zu verarbeiten die in Textform (ASCII-Dateien) vorliegen. Wie Sie solche ASCII-Daten innerhalb des Doc/View Modells einlesen und wieder abspeichern erfahren Sie hier.

Fangen wir mit dem Einlesen von ASCII-Daten an. Der MFC-Rahmen ruft zum Einlesen von Daten die CDocument-Methode OnOpenDocument(...) auf. In der Standard-Implementierung ruft die Methode OnOpenDocument(...) zuerst die Methode DeleteContents(...) auf um die bisherigen Dokumentdaten zu löschen. Anschließend wird die Serialize(...) Methode aufgerufen um die neuen Daten einzulesen. Durch Überschreiben der OnOpenDocument(...) Methode kann nun das Verhalten beim Einlesen der Daten verändert werden. Als Parameter erhält die Methode den kompletten Dateinamen der Eingabedatei, d.h. einschließlich Pfad, übergeben. Wurden die Daten erfolgreich eingelesen, so muss die Methode TRUE zurückliefern und im Fehlerfall FALSE. Wenn Sie diese Methode überladen um das Einlesen der Daten selbst durchzuführen, so erstellen Sie zunächst über den Klassen-Assistenten die entsprechende Methode (unter Virtuelle Funktion hinzufügen....). Anschließend entfernen Sie den vom Assistenten eingefügten Code und fügen Ihren eigenen ein. Sehen wir uns nun eine typische überladene OnOpenDocument(...) Methode einmal an:

// OnOpenDocument()
// Wird aufgerufen wenn Daten fuer ein Dokument eingelesen werden.
// Der vom Wizard generierte Code wurde kompl. entfernt
BOOL CSerialAscDoc::OnOpenDocument(LPCTSTR lpszPathName)
{

    // Alte Dok-Daten loeschen
    DeleteContents();
    // Eingabedatei oeffnen
    ifstream InFile(lpszPathName);
    // Fehler abfangen
    if (InFile.fail())
    {
        AfxGetMainWnd()->MessageBox("Fehler beim Öffnen der Datei!");
        return FALSE;
    }
    // neues Datenobjekt erstellen
    m_pCData = new CData;
    // Daten einlesen und Datei danach schliessen
    m_pCData->ReadData(InFile);
    InFile.close();
    // Modified-Flag loeschen da Daten noch nicht geaendert wurden
    SetModifiedFlag(FALSE);

    return TRUE;
}

Als erstes muss die überladene Methode die Methode DeleteContents(...) aufrufen um die bisherigen Dokumentdaten zu löschen. Anschließend kann die Eingabedatei geöffnet werden. Im obigen Beispiel wird für die Eingabe eine Stream-Datei (ASCII-Datei) geöffnet. War das Öffnen der Datei erfolgreich können die Daten eingelesen werden. Im Beispiel liegen die Dokumentdaten in einem CData-Objekt, weswegen zuerst das entsprechende Objekt erstellt werden muss. Anschließend wird die Methode ReadData(...) des Datenobjektes aufgerufen um die Daten einzulesen. Die ReadData(...) Methode erhält als Parameter eine Referenz auf das Stream-Objekt der Eingabedatei. Nachdem die Daten eingelesen wurden wird die Eingabedatei wieder geschlossen. Zum Schluss wird noch das Modified-Flag des Dokuments zurückgesetzt da die eingelesenen Daten noch nicht verändert wurden. Selbstverständlich sind auch andere Verfahren möglich um die Daten letztendlich einzulesen; es soll hier ja nur ums Prinzip gehen.

Kommen wir jetzt zum Speichern der Daten. Das MFC-Rahmenprogramm ruft zum Speichern der Daten die CDocument-Methode OnSaveDocument(...) auf. Der Standard-Rahmen ruft nur die Methode Serialize(...) auf um die Daten abzuspeichern. Durch Überschreiben der Methode OnSaveDocument(...) kann das Format der zu speichernden Daten selbst definiert werden. Als Parameter erhält die Methode wiederum den vollständigen Namen der Datei, in der die Daten abzulegen sind. Wurden die Daten erfolgreich abgespeichert, so muss die Methode ebenfalls den Wert TRUE zurückliefern und ansonsten FALSE.

Auch diese Methode ist zunächst wieder durch den Klassen-Assistenten zu erstellen und danach der vom Assistenten erstellte Code durch den eignen zu ersetzen. Nachfolgend finden Sie die zum obigen Listing gehörende OnSaveDocument(...) Methode:

// OnSaveDocument()
// Wird aufgerufen wenn Daten fuer ein Dokument geschrieben werden.
// Der vom Wizard generierte Methoden-Code wurde kompl. entfernt
BOOL CSerialAscDoc::OnSaveDocument(LPCTSTR lpszPathName)
{

    // Ausgabedatei oeffnen
    ofstream OutFile(lpszPathName);
    // Fehler abfangen
    if (OutFile.fail())
    {
        AfxGetMainWnd()->MessageBox("Fehler beim Öffnen der Datei!");
        return FALSE;
    }
    // Daten schreiben und Datei danach schliessen
    m_pCData->WriteData(OutFile);
    OutFile.close();
    // Modified-Flag loeschen da Daten gesichert wurden
    SetModifiedFlag(FALSE);

    return TRUE;
}

Und auch hier wird wieder zuerst die Ausgabedatei geöffnet. War dies erfolgreich, so werden die Daten geschrieben und anschließend die Datei wieder geschlossen. Zum Schluss wird erneut das Modified-Flag des Dokuments zurückgesetzt.

Damit wären wir eigentlich am Ende dieses Tipps, wenn nicht beim Einlesen der Daten eine Stolperfalle lauern würde. Im Beispiel soll die Datenklasse (hier CData) Daten vom Typ String (char-Feld), einen short-Wert und ein CString-Objekt in die Ausgabedatei schreiben. Sehen wir uns zunächst das Schreiben der Daten an.

// Daten schreiben
void CData::WriteData(ofstream & OutFile)
{
    // Textlaenge und Text schreiben
    OutFile << strlen(m_pszText) << " " << m_pszText << endl;
    // short-Variable schreiben
    OutFile << m_nVar << endl;
    // CString muss auf char* konvertiert werden da CString den
    // Operator << nur fuer CArchive ueberlaedt und nicht fuer Dateistreams
    // Stringlaenge und String schreiben
    OutFile << m_CText.GetLength() << " " << (LPCTSTR)m_CText << endl;
}

Die obige Methode WriteData(...) dürfte nicht Unbekanntes enthalten. Das einzige was Sie beim Schreiben beachten müssen ist, dass zwischen dem Schreiben der Textlänge und den ASCII-String ein Trennzeichen eingefügt werden muss damit später diese Daten getrennt eingelesen werden können. Sehen wir uns die geschriebenen Daten einmal an:

Damit diese Daten wieder einzulesen können, ist die ReadData(...) Methode wie folgt zu implementieren:

// Daten lesen
void CData::ReadData(ifstream & InFile)
{
    int nLength;

    // Zuerst alten Speicherbereich freigeben
    delete [] m_pszText;
    // Textlaenge einlesen und char-Feld allokieren
    InFile >> nLength;
    m_pszText = new char[nLength+1];
    // Blank ueberspringen und Text einlesen
    InFile.ignore(1);
    InFile.get(m_pszText,nLength+1);
    // int-Variable einlesen
    InFile >> m_nVar;
    // Stringlaenge in temp. char-Feld einlesen
    InFile >> nLength;
    char* pszTemp = new char[nLength+1];
    // Blank ueberspringen und Text einlesen
    InFile.ignore(1);
    InFile.get(pszTemp,nLength+1);
    // Text dem CString-Objekt zuweisen
    m_CText = pszTemp;
    // temp. Speicherbereich freigeben
    delete [] pszTemp;
}

Worauf es in dieser Methode ankommt sind die kursiv und rot hervorgehobenen Anweisungen Infile.ignore(1). Sie dienen dazu, das Trennzeichen zwischen den Textlängen und den ASCII-Strings zu überspringen. Ohne diese Anweisungen würde die Datei nicht richtig eingelesen werden.

Ein Programm zu diesem Thema finden Sie unter 04DocView\SerialAsc.



Copyright © 2004

Senden Sie Emails mit Fragen oder Kommentaren zu dieser Website an: mailto:info@cpp-tutor.de
 Wolfgang Schröder, Lerchenweg 23, D-72805 Lichtenstein. Tel: +49 7129 6470