.txt-datei parsen - Problem bei Ausgabe



  • @Werner Salomon:

    Stimmt. Deine Lösung ist noch einfacher. Allerdings funktioniert sie nicht, wenn der Text nach dem Doppelpunkt ein Leerzeichen enthält. Außerdem wird bei deiner Lösung der Index nicht in einem String gespeichert, es fallen also führende Nullen weg, was eventuell nicht erwünscht ist.

    Sollten allerdings keine Leerzeichen vorkommen, würde ich allerdings auch deine Lösung bevorzugen.

    MfG, Jochen



  • gab es dafür nicht was von eatwhite?

    if( datei >> index >> doppelpunkt >> eatwhite >> inhalt )
    

    Und der index darf auch ein string sein.



  • Jochen S. schrieb:

    @Werner Salomon:

    Stimmt. Deine Lösung ist noch einfacher. Allerdings funktioniert sie nicht, wenn der Text nach dem Doppelpunkt ein Leerzeichen enthält. Außerdem wird bei deiner Lösung der Index nicht in einem String gespeichert, es fallen also führende Nullen weg, was eventuell nicht erwünscht ist.

    Nun wir sollten Milhouse_111 fragen, was er denn gerne hätte. Ich ging davon aus, dass der Index eine Zahl sein soll und der Rest dahinter ein Wort ist, wie in dem Beispiel.
    Lesen bis zum Zeilenende ginge mit

    getline( datei >> index >> doppelpunkt, inhalt )
    

    volkard schrieb:

    gab es dafür nicht was von eatwhite?

    if( datei >> index >> doppelpunkt >> eatwhite >> inhalt )
    

    Und der index darf auch ein string sein.

    genau - eatwhite heißt std::ws; also in diesem Fall:

    getline( datei >> index >> doppelpunkt >> ws, inhalt )
    

    .. und falls index doch ein String sein soll:

    string index;
        getline( datei, index, ':' );
        getline( datei >> ws, inhalt ); // 'inhalt' ohne führendes Leerzeichen, aber die nächste Zeile wird gelesen, falls hinter ':' ein Zeilenende kommt!
    

    es gilt immer: lies das (den Typ) ein, was(den) Du im Programm haben willst.

    Gruß
    Werner



  • Es könnte, denke ich, Sinn machen, das ganze in einen operator>> zu packen, um das ganze mit Standardalgorithmen verwenden zu können. Etwa so:

    #include <algorithm>
    #include <fstream>
    #include <iostream>
    #include <iterator>
    #include <string>
    
    struct titelsatz {
      std::string kategorie;
      std::string inhalt;
    };
    
    std::istream &operator>>(std::istream &in, titelsatz &ts) {
      std::getline(in, ts.kategorie, ':');
      std::getline(in, ts.inhalt);
    
      return in;
    }
    
    void print_titelsatz(titelsatz const &ts) {
      std::cout << "Kategorie: " << ts.kategorie << " Inhalt: " << ts.inhalt << std::endl;
    }
    
    int main() {
      std::ifstream in("datei.txt");
    
      std::for_each(std::istream_iterator<titelsatz>(in),
                    std::istream_iterator<titelsatz>(),
                    print_titelsatz);
    }
    

    Und bevor jemand fragt: Ich benutze hier eine Funktion print_titelsatz statt operator<<, weil ich der Ansicht bin, dass operator<< und operator>> symmetrisch sein sollten, also operator<< etwas ausgeben sollte, was operator>> wieder einlesen kann. Das ist aber eine Stilfrage.



  • Erstmal vielen Dank für die zahlreichen Antworten und Lösungswege. Erstmal ja, die Kategorie (Index) darf und sollte als string gespeichert werden. Ich persönlich finde die Lösung von seldon ziemlich schick, auch wenn dort libraries benutzt werden die ich noch nicht kenne. Bin noch im Studium und noch nicht sehr weit. Habe meinen Fehler inzwischen herausgefunden. Das Array für Kategorie in dem struct is zu klein bemessen mit [3], sollte man eigentlich wissen, stand da einfach aufm Schlauch. Habe das Array jetzt auf [5] vergrössert und es werden für Kategorie bzw. Index immer nur alle Zeichen ausgegeben bis zu dem ':'.

    Allerdings stehe ich jetzt vor dem Problem das es in der .txt-Datei auch Kategorien mit Unterkategorien gibt. das sieht dann so aus:

    003.001:bineininhalt55884
    003.002:bineininhaltdergleichenunterkategorie8874764

    Dieser Fall muss in der Ausgabe auch irgendwie beachtet werden. Naja, werde gleich mal weiter probieren.



  • Das Problem mit den Unterketegorien könnte man so lösen:

    Man liest die ersten drei Zeichen ein, dies ist dann der erste Index.
    Dann liest man das nächste Zeichen ein, sollte es sich bei dem Zeichen um ein '.' handeln, muss man weitere drei Zeichen einlesen, den zweiten Index.

    Hier ein Beispielcode:

    char index[4] = ""; 
    char index2[4] = "";
    char str[300] = ""; 
    bool bExisting = false;
    
    ifstream dat_ein; 
    dat_ein.open("Datei.txt",ios_base::in); 
    
    char x; 
    int loop=0; 
    
    for(loop=0;x!=':'&&x!='.';loop++) 
    { 
    dat_ein.get(x); 
    if(x!=':'&&x!='.')index[loop]=x; 
    else index[loop] = '\0'; 
    } 
    
    if(x=='.')
    {
        for(loop=0;x!=':';loop++) 
        { 
        dat_ein.get(x); 
        if(x!=':')index2[loop]=x; 
        else index2[loop] = '\0'; 
        }
    bExisting = true;
    }
    
    for(loop=0;x!='\n';loop++) 
    { 
    dat_ein.get(x); 
    if(x!='\n')str[loop]=x;
    else index[loop]='\0'; 
    } 
    
    dat_ein.close();
    

    Die Indizes werden in index und index2 gespeichet, der String in str.
    sollte es keine unterkategorie geben, bleibt index2 leer. Anhand der boolschen Variablen bExisting kann man sehen, ob es Unterkategorien gibt oder nicht.

    MfG, Jochen



  • An dieser Stelle würde ich bereits auf eine regex-Bibliothek zurückgreifen, beispielsweise Boost.Xpressive. Habt ihr formale Sprachen schon behandelt?

    Naja, auf die Gefahr hin, damit in komplexere Materie einzusteigen, als momentan für dich geeignet ist (ggf. kannst du dir das später immer noch genauer ankucken, es ist auf jeden Fall praktisch zu kennen), ich finge das wohl etwa so an:

    #include <fstream>
    #include <iostream>
    #include <string>
    
    #include <boost/xpressive/xpressive.hpp>
    
    int main() {
      using namespace boost::xpressive;
    
      std::string line;
      std::ifstream in("datei.txt");
    
      sregex rex = (s1 = +_d) >> !('.' >> (s2 = +_d)) >> ':' >> (s3 = *_);
    
      while(std::getline(in, line)) {
        smatch m;
    
        if(regex_match(line, m, rex)) {
          std::cout << "K: " << m[1];
          if(m[2]) std::cout << " UK: " << m[2];
          std::cout << " I: " << m[3] << '\n';
        } else {
          std::cout << "Fehler: " << line << '\n';
        }
      }
    }
    

    Die Syntax ist ein bisschen gewöhnungsbedürftig (Operatorüberladung kann einen nur etwa so weit bringen), dafür kann der Compiler aber so eine Menge Arbeit schon erledigen, die zur Laufzeit nicht mehr getan werden muss.

    Kurz erklärt:

    _d matcht Ziffern,
    +d matcht eine Folge von ein oder mehreren Ziffern.
    _ matcht irgendein Zeichen,
    *
    matcht eine Folge von null oder mehr Zeichen.
    Der !-Operator kennzeichnet einen Ausdruck als optional.
    s1 = foo merkt sich den betreffenden submatch.

    In Perl gesprochen entspricht der Ausdruck oben

    (\d+)(?:\.(\d+))?:(.*)
    

    Oder, ohne die Submatcherei,

    \d+(\.\d+)?:.*
    

    das nur der Übersichtlichkeit halber. Der große Vorteil gegenüber Fummelparsern ist, dass du dir um fehlerhafte Eingaben (so gut wie) keine Sorgen mehr machen musst - was die regex nicht matcht, wird schlicht als fehlerhaft verworfen.



  • seldons Vorschläge gehen in die richtige Richtung. boost.Xpressive habe ich noch nie benutzt. Es gibt ja auch noch boost.regex und boost.spirit mit den Varianten Spirit Classic und Spirit V2.0 - irgendwann kann man da auch den Überblick verlieren ...

    Der Vollständigkeit halber poste ich hier die Variante, die mit Standardmitteln auskommt und die Unterkategorien mit berücksichtigt. Man muss dazu noch wissen, dass der Manipulator setw() beim Einlesen von std::strings die maximale Stringlänge festlegt. Ich mache mir zu Nutze, dass die Kategorien immer aus 3Zeichen bestehen.

    #include <fstream>
    #include <iostream>
    #include <iomanip> // setw
    #include <iterator> // istream_iterator
    #include <string>
    #include <vector>
    
    struct titelsatz 
    {
        std::string kategorie;
        std::string unterKategorie;
        std::string inhalt;
    };
    
    std::istream& operator>>( std::istream& in, titelsatz& ts ) 
    {
        using namespace std;
        in >> std::setw(3) >> ts.kategorie;
        char c;
        string unterKategorie;
        if( in >> c && c == '.' )
        {   // Unterkategorie folgt ...
            in >> std::setw(3) >> unterKategorie >> c;
        }
        if( in && c == ':' )
        {
            swap( unterKategorie, ts.unterKategorie ); // garantiert, dass ts.unterKategorie leer ist, wenn keine gelesen wurde
            std::getline( in >> ws, ts.inhalt );
        }
        else
            in.setstate( ios_base::failbit ); // anderes Zeichen außer '.' oder ':'
        return in;
    }
    
    int main() 
    {
        using namespace std;
        ifstream in("***.txt");
        vector< titelsatz > inhalte( (istream_iterator<titelsatz>(in)), istream_iterator<titelsatz>() );
        if( in.eof() ) // bis End-Of-File gelesen?
        {
            cout << inhalte.size() << " Eintraege gelesen" << endl;
            // usw. ...
        }
        else
            cerr << "Lesefehler!" << endl;
        return 0;
    }
    

    Der Inhalt der Datei befindet sich danach im std::vector<titelsatz > 'inhalte'.

    Gruß
    Werner



  • Boost.Spirit ist eine geniale Bibliothek, ich hielt es aber in diesem Zusammenhang für Overkill. Ich habe Boost.Xpressive Boost.Regex vor allem deshalb vorgezogen, weil ich damit mehr Erfahrung habe; zwar ist Boost.Xpressive mächtiger als Boost.Regex und erlaubt mir, Teile der Arbeit zur Compilezeit zu erledigen, aber die zusätzlichen Features wurden hier nicht gebraucht, und da die Regex nur einmal ausgewertet werden muss, dürfte der Laufzeitverlust vernachlässigbar sein. Auch ist die Regex-Bibliothek in TR1 stark von Boost.Regex inspiriert (sprich: nahezu identisch), also macht es wohl durchaus Sinn, sich damit auseinanderzusetzen - auch wenn dieser Teil von TR1 in der gcc noch nicht implementiert ist.

    Letztendlich führen viele Wege ans Ziel.



  • Hallo zusammen,

    ich wollte jetzt kein neues Topic eröffnen, da der Titel nach wie vor passend ist 😉

    Die Ausgabe funktioniert soweit. Allerdings stellt sich gerade ein viel allgemeineres Problem.

    Ich arbeite mit Visual Studio unter Vista. Das Problem liegt jetzt darin, dass der Datensatz der ausgelesen wird sehr lang ist -> Die Ausgabe aber nur gut 1 Drittel des gewünschten Datensatzes widergibt/anzeigt.

    Jetzt meine Frage: Gibt es in Visual-Studio vielleicht irgendwo eine Einstellung, wodurch sich das "Ausgabe-Volumen" vergrößern lässt?(Um Fehler im Prog. selber ausszuschließen, habe ich es bereits auf anderen Systemen und Rechnern testen lassen - dort wird die txt. VOLLSTÄNDIG ausgegeben! Es muss also eine Konfi-Sache in Visual sein...)

    Wenn jetzt jemand verstanden hat, was ich meine, wäre das schon mal ein Anfang..;)

    PS: um es vielleicht doch nochmal zu veranschaulichen:

    -----> die auszulesene txt:

    001: inhalt
    004: inhalt
    005: inhalt
    
    006: inhalt
    007: inhalt
    008: inhalt
    
    009: inhalt
    012: inhalt
    018: inhalt
    
    001: inhalt
    004: inhalt
    005: inhalt
    
    020: inhalt
    022: inhalt
    036: inhalt
    

    -------> die ausgabe unter Visual Studio:

    018: inhalt
    
    001: inhalt
    004: inhalt
    005: inhalt
    
    020: inhalt
    022: inhalt
    036: inhalt
    

    mfg


Anmelden zum Antworten