String einen substring über mehrere Zeilen



  • Hallo nehmen wir mal an wir haben einen Text in einer .txt Datei:

    [Peter]
    heiße Peter
    und Arbeite
    [/Peter]
    als Mechaniker

    Jetzt möchte ich alles von [Peter] bis [/Peter] in einen Substring reinschreiben. Wie kann ich das bewerkstelligen? Danke schonmal im vorraus.



  • string s = "[Peter] \
    heiße Peter \
    und Arbeite \
    [/Peter] \
    als Mechaniker";
    string sub(&s[s.find("[Peter]")], &s[s.find("[/Peter]")]);
    

    Das geht wahrscheinlich noch hübscher.



  • Das wirft bei mir aber einen fehler aus also einen fehler beim programm im Debugger steht nix ^^



  • Da müsstest du einen kleinen Parser für schreiben, wenn du das für verschiedene Tags benutzen willst.
    Ganz primitiver, erster Vorschlag:

    #include <iostream>
    #include <sstream>
    #include <limits>
    
    int main()
    {
    	std::istringstream stream{ R"(
    [Peter]
    heiße Peter
    und Arbeite
    [/Peter]
    als Mechaniker)" };
    
    	stream.exceptions( std::ios_base::failbit | std::ios_base::badbit );
    
    	std::string name, content;
    
    	try
    	{
    		stream.ignore( std::numeric_limits<std::streamsize>::max(), '[' ); /// Den Text vor dem [ weglassen
    
    		std::getline( stream, name, ']' ); // Namen einlesen
    		std::getline( stream >> std::ws, content, '[' ); // Vorgehende Leerzeichen weglassen, Inhalt lesen
    	}
    	catch( std::ios_base::failure const& f )
    	{
    		std::clog << "Parse failure!\n";
    		return -1;
    	}
    
    	std::cout << name << ":\n" << content;
    }
    


  • Helppls1234 schrieb:

    Das wirft bei mir aber einen fehler aus also einen fehler beim programm im Debugger steht nix ^^

    Bitte das komplette Programm wie du es kompiliert hast posten, und eine genauere Fehlerbeschreibung.



  • Also, um genau zu sein schreibe ich mir einen kleinen Inireader hier die Funktion:

    int IniReader::getIntager( std::string path, std::string Category, std::string keyword, int defvalue){
    
    	std::istringstream stream( path );
    
    	std::string buffer;
    	std::string value;
    
    	std::string THEVALUE;
    	int iTheValue = 0;
    
    	size_t keywordpos = 0;
    	size_t valuepos = 0;
    
    	while( !stream.eof() ){
    
    		getline( stream, buffer );
    
    		if( keywordpos = buffer.find( keyword ) != std::string::npos ){
    
    			value = buffer.substr( keywordpos - 1, buffer.size() );
    
    			valuepos = value.find( "=" );
    
    			THEVALUE = value.substr( valuepos + 1);
    
    			iTheValue = atoi( THEVALUE.c_str() );
    
    			return iTheValue;
    		}
    
    	}
    
    	std::cout << "Couldn't find Keyword: " << keyword << std::endl;
    
    	return defvalue;
    }
    

    so hier die .ini

    # This is a Comment

    [Schurke]
    Str=12
    Dex=17
    Int=4
    [/Schurke]

    [Krieger]
    Str=18
    Dex=10
    Int=2
    [/Krieger]

    std::string Category steht in dem Fall für die Tags [] Da die Keywords sich wiederholen Str usw. Deshalb möchte ich Nur die ganze gesuchte Kategorie in einen Substring stecken damit ich dann in dem String nach dem Keyword suchen kann.

    Beispiel:

    int IniReader::getIntager( std::string path, std::string Category, std::string keyword, int defvalue){
    
    	std::istringstream stream( path );
    
    	std::string buffer;
    	std::string value;
    
    	std::string subString;
    
    	std::string THEVALUE;
    	int iTheValue = 0;
    
    	size_t keywordpos = 0;
    	size_t valuepos = 0;
    
    	while( !stream.eof() ){
    
    		getline( stream, buffer );
    
    		subString = // Buffer von "[" + Category + "]" bis 	"[/" + Category + "]"
    
    		if( keywordpos = subString.find( keyword ) != std::string::npos ){
    
    			value = subString.substr( keywordpos - 1 );
    
    			valuepos = value.find( "=" );
    
    			THEVALUE = value.substr( valuepos + 1);
    
    			iTheValue = atoi( THEVALUE.c_str() );
    
    			return iTheValue;
    		}
    
    	}
    
    	std::cout << "Couldn't find Keyword: " << keyword << std::endl;
    
    	return defvalue;
    }
    

    So in der Richtung. ^^


  • Mod

    Das Format schreit doch geradezu danach, dass man es in seine Tokens zerlegt, anstatt sich den Inhalt einer Kategorie als String zu speichern. Wenn du die Datei liest, legst du eine Liste der gefundenen Kategorien an, zu jeder Kategorie speicherst du eine Liste aller Schlüsselworte und ihrer Werte. Das ist ersten deutlich leichter in der späteren Verarbeitung (da bereits alles passend zerlegt ist) und zweitens sogar deutlich leichter beim Einlesen, da du fast alles durch die Standardbibliothek erledigen lassen kannst.

    Du hast derzeit einige Fehler und Designschwächen im Code:

    • Sehr dringend: Logikfehler: Lesen, auf Fehler prüfen, dann verarbeiten ist die Reihenfolge. Du prüfst auf Fehler, liest, dann verarbeitest du. Das heißt im Falle eines Fehlers (z.B. Dateiende) verarbeitest du irgendwelchen Unsinn. In C++ sehen Leseschleifen so (oder vergleichbar) aus:
    while (leseaktion) verarbeiten;
    

    Wo auch immer du

    while(!eof) {leseaktion; verarbeiten;}
    

    her hast: Verbrennen und die Asche im Wind verstreuen!

    • Schwäche: Du nutzt ohne Not das unsichere atoi, obwohl du doch schon Streams im Code hast! Wie prüfst du, ob das Parsen erfolgreich war? Geht nicht gut mit atoi!
    • Designfehler: Wie du die Fehler behandelst ist äußerst ungeschickt. Ausgabe auf cout? Wo geht cout denn hin? Weißt du das immer? Soll dort eine Fehlermeldung hin? Rückgabe eines Defaultwertes? Wie weiß der Anwendungscode, ob es ein Fehler war und nicht wirklich ein Defaultwert gefunden wurde? Das ist das gleiche Problem wie bei dem oben genannten atoi und du baust es nach. Bei atoi hatten die Macher aber die gute Ausrede, dass es für C ist und es dort nicht wirklich besser geht. Du machst C++. Da gibt es Exceptions, die sich hier ganz wunderbar anbieten.
    • Ungeschickt: Das ganze Zerlegen des Strings könnte man durch geschicktere Nutzung der Standardbibliothek auch einfacher haben. Aber das ist bloß eine Erfahrungssache. ich empfehle dir, öfters mal in Referenzen zu stöbern (hier: Strings, Streams, Algorithms), damit du die Standardbibliothek besser kennen lernst. Auch für Funktionen, die du schon zu kennen meinst. Wusstest du zum Beispiel, dass getline einen optionalen dritten Parameter hat?

    Da ich dir ohnehin empfehle, die Datei direkt in ihre Tokens zu zerlegen, mache ich dir aber nicht vor, wie du hier den Kategoriestring geschickter zerlegen könntest. Meiner Meinung nach ist dies nämlich die falsche Vorgehensweise.



  • Okay Sepp ich werde mir deinen Rat zu Herzen nehmen und meinen Code überarbeiten. Danke für deine Kritik. 🙂



  • Ich kann nicht anders, ich musste: http://ideone.com/x4cokc

    Edit: Gut, eine Map und ein Attribut-Typ waren nicht nötig.



  • Oha Sone, das ist mir schon ein wenig zu hoch. 😕 Gibt es da keine leichtere Methode für anfänger? 😃


  • Mod

    Helppls1234 schrieb:

    Oha Sone, das ist mir schon ein wenig zu hoch. 😕 Gibt es da keine leichtere Methode für anfänger? 😃

    Nimm das nicht so ernst, es ist
    1. Absichtlich obskur
    2. Schlecht. static std::vector<std::ctype_base::mask> equals_mask; ? Wirklich, Sone?

    Da ich gerade Zeit habe, werde ich einen normalen ini-Parser schreiben. Dauert einen Moment.



  • SeppJ schrieb:

    2. Schlecht. static std::vector<std::ctype_base::mask> equals_mask; ? Wirklich, Sone?

    Sekunde! Meinst du etwa std::array<std::ctype_base::mask, std::ctype_base::table_size> ?



  • Danke für die ganze hilfe, habe jetzt das erreicht was ich wollte, vielleicht ein wenig schlecht programmiert aber ich hatte jetzt keine lust nach speziellen funktionen zu suchen etc:

    int IniReader::getIntager( std::string path, std::string Category, std::string keyword, int defvalue){
    
    	std::fstream stream(path, std::ios_base::in);
        stream << std::noskipws;
    
        if(stream.bad())
    		throw std::runtime_error("Error when opening file: " + path + ".");
    
    	std::istream_iterator<char> begin(stream), end_of_file;
    	std::string buffer(begin, end_of_file);
    	stream.close();
    
    	size_t categoryPos1 = buffer.find( "[" + Category + "]" );
    	size_t categoryPos2 = buffer.find( "[/" + Category + "]" );
    
    	size_t len = categoryPos2 - categoryPos1;
    
    	std::string subCategory = buffer.substr( categoryPos1, len );
    
    	size_t keywordPos = 0;
    
    	if( keywordPos = subCategory.find( keyword ) != std::string::npos ){
    
    			std::string value = subCategory.substr( keywordPos - 1 );
    
    			size_t valuepos = value.find( "=" );
    
    			std::string THEVALUE = value.substr( valuepos + 1);
    
    			int toInt = atoi( THEVALUE.c_str() );
    
    			return toInt;
    		}
    
    	return defvalue;
    }
    

    Es gibt mir perfekt das aus was ich suche.


  • Mod

    Sone schrieb:

    SeppJ schrieb:

    2. Schlecht. static std::vector<std::ctype_base::mask> equals_mask; ? Wirklich, Sone?

    Sekunde! Meinst du etwa std::array<std::ctype_base::mask, std::ctype_base::table_size> ?

    Nein, ich würde es sowieso ganz anders machen, aber so find ich das nicht gut. Wahnsinnig umsandlich, um was zu erreichen? Dass wir später Operator>> benutzen können? Dafür willst du ein dynamisches Objekt static in der Funktion hinterlegen? Das ist doch Wahnsinn! Du weißt, welches Zeichen '=' ist, da braucht man keine Masken für, das suchen wir direkt. Mag der Code auch zwei bis drei Zeilen länger werden, dafür ist er reentrant und flott!



  • Sone ist doch dafür bekannt über-komplexen Code zu posten um von Anfängern als 'Guru' angesehen zu werden.



  • ini-p schrieb:

    Sone ist doch dafür bekannt über-komplexen Code zu posten um von Anfängern als 'Guru' angesehen zu werden.

    Blödsinn. Ich schreibe über-komplexen Code für die Lolz. Das war ein wenig Spaß, nicht mehr. (Außerdem weiß ich genau, dass mich niemand als Guru sieht nur weil ich überkomplexen Code schreibe. Den Titel verdient man sich ganz anders. )

    SeppJ schrieb:

    Sone schrieb:

    SeppJ schrieb:

    2. Schlecht. static std::vector<std::ctype_base::mask> equals_mask; ? Wirklich, Sone?

    Sekunde! Meinst du etwa std::array<std::ctype_base::mask, std::ctype_base::table_size> ?

    Nein, ich würde es sowieso ganz anders machen, aber so find ich das nicht gut. Wahnsinnig umsandlich, um was zu erreichen? Dass wir später Operator>> benutzen können? Dafür willst du ein dynamisches Objekt static in der Funktion hinterlegen? Das ist doch Wahnsinn! Du weißt, welches Zeichen '=' ist, da braucht man keine Masken für, das suchen wir direkt. Mag der Code auch zwei bis drei Zeilen länger werden, dafür ist er reentrant und flott!

    Klar, wir können auch getline() benutzen. Aber das ist dann weniger Flexibel. Außerdem würde Werner das auch missbilligen - aber natürlich in jedem Fall weniger als meine Lösung. (Mir ist klar dass meine Lösung purer Overkill ist, das war wie gesagt eher Spaßig gemeint)

    std::string attribute;
    unsigned value;
    
    std::getline( stream >> std::ws, attribute, '=' );
    stream >> value;
    

    Oder wie würdest du das machen?



  • Kleine Verbesserung an deinem Code:

    std::ifstream stream(path);  // Du willst nur lesen, daher ifstream. Das in-Flag ist dann hier unnötig.
    
        if(!stream.is_open()) // Meinst du is_open? Außerdem müsstest du eigentlich auf failbit prüfen....
            throw std::runtime_error("Error when opening file: " + path + "."); 
    
        // stream << std::noskipws; // Sowas eher nach dem Prüfen, oder? Edit: Das ist hier sogar völlig überflüssig, daher kommentiere ich es aus.
        std::istreambuf_iterator<char> begin(stream), end_of_file; // Hier wäre ein istreambuf_iterator schneller!
        std::string buffer(begin, end_of_file); 
        stream.close();
    


  • if( keywordPos = subCategory.find( keyword ) != std::string::npos )
    

    Ganz schlecht! Der Zuweisungsoperator wird schwächer als der Ungleich-Operator gebunden. Daher ist das äquivalent zu

    if( keywordPos = (subCategory.find( keyword ) != std::string::npos) )
    

    Das funktioniert sogar zufälligerweise irgendwie, da keywordPos dann entweder 1 oder 0 wird, je nach dem. Du willst das aber natürlich nicht.
    Mach also

    if( (keywordPos = subCategory.find( keyword )) != std::string::npos )
    // oder noch besser:
    std::size_t keywordPos = subCategory.find( keyword ); // Initialisierung, ein Geschenk an die Menschheit das einige dankend ablehnen...
    if( keywordPos != std::string::npos )
    

    daraus.



  • Okay Sone, habe deine Tipps befolgt danke, wobei das istream_iterator ohne typ angabe bei mir einen Fehler auswirft und std::ifstream auch. Das mit der if Abfrage ist aber gut danke.



  • Helppls1234 schrieb:

    wobei das istream_iterator ohne typ angabe bei mir einen Fehler auswirft und std::ifstream auch.

    Was meinst du, ohne Typangabe? Hast du vergessen istreambuf_iterator mit <char> zu qualifizieren, dann könnte vielleicht so eine Fehlermeldung kommen...?
    Und natürlich alle Header einbinden:

    #include <fstream> // ifstream
    #include <iterator> // istream(buf)_iterator
    

    Also was ist jetzt die genaue Fehlermeldung?


Anmelden zum Antworten