.txt-datei parsen - Problem bei Ausgabe
-
Hallo liebes Forum,
ich versuche gerade eine etwas komplexere .txt-Datei einzulesen und möchte diese dann in einem bestimmten Format wieder ausgeben. Ich habe quasi 2 Fälle in der Datei: einen Index mit 3 Ziffern und eine darauffolgende Zeichenkette als Inhalt. Getrennt werden diese beiden Teile durch ein ':'. Die switch-Anweisung die ich gebaut habe funktionier aber nur im Falle des Inhalts richtig und nicht im Falle des 3-stelligen Index.
Die .txt hat dieses Format
001:binambahnhof56748383
hier der Code:
#include <iostream> using namespace std; #include <string> #include <stdlib.h> #include <fstream> struct titelsatz { char kategorie[3]; char inhalt[200]; }; enum zustand {inKategorie, inInhalt}; int main() { struct titelsatz titel; char puffer[300], zeichen; int zaehler; enum zustand zustand; ifstream datei; datei.open("***.txt"); zustand=inKategorie; zaehler=0; for (;;) { datei.get(zeichen); if (datei.eof()) break; switch (zeichen) { case ':': puffer[zaehler]='\0'; zaehler=0; strncpy(titel.kategorie,puffer,3); zustand=inKategorie; break; case '\n': puffer[zaehler]='\0'; zaehler=0; strcpy(titel.inhalt,puffer); cout << "Kategorie: " << titel.kategorie << " Inhalt: " << titel.inhalt << endl; break; default: puffer[zaehler]=zeichen; zaehler++; break; } } }Vielleicht kann mir ja jemand helfen. Ich glaube das es was mit der strcpy-Funktion zu tun hat und die das Array von [3] überschreibt. Wäre für jede Antwort sehr dankbar.
-
Hallo,
Ich glaube, du machst das viel zu kompliziert. Ich habe das mal so gemacht:
char index[4] = ""; char str[300] = ""; ifstream dat_ein; dat_ein.open("Datei.txt",ios_base::in); char x; int loop=0; for(loop=0;x!=':';loop++) { dat_ein.get(x); if(x!=':')index[loop]=x; } index[loop] = '\0'; for(loop=0;x!='\n';loop++) { dat_ein.get(x); if(x!='\n')str[loop]=x; } index[loop]='\0'; dat_ein.close();So wird der Index in dem Array index gespeichert und der Text in dem Array str. Das Array index hat vier Elemente, damit noch Platz für das Nullterminierungszeichen bleibt.
MfG, Jochen
-
.. das ist immer noch zu kompliziert. Lese einfach den Index und den Inhalt und fertig:
#include <fstream> #include <iostream> #include <string> int main() { using namespace std; ifstream datei("***.txt"); int index; char doppelpunkt; string inhalt; if( datei >> index >> doppelpunkt >> inhalt ) { // alles richtig gelesen // .... } return 0; }Gruß
Werner
-
@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 mitgetline( 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:bineininhaltdergleichenunterkategorie8874764Dieser 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: inhaltmfg