String einen substring über mehrere Zeilen



  • 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?



  • Oh, schon gut. Habe mich verlesen ich dachte du meintest ich soll istream_iterator ohne typ dahinter benutzen. Habe das buf überlesen. 😃



  • Hoffentlich liefert SeppJ den INI-Parser trotzdem noch, obwohl das Problem jetzt gelöst ist. 🕶



  • cooldown schrieb:

    Hoffentlich liefert SeppJ den INI-Parser trotzdem noch, obwohl das Problem jetzt gelöst ist. 🕶

    Der von Werner ist gut. Edit: Lol, direkt darüber ist auch einer von SeppJ.

    int toInt = atoi( THEVALUE.c_str() );
    

    Geht vielleicht std::stoi?


  • Mod

    So, irgendwie bin ich ein bisschen verrückt geworden, nachdem die erste Fehlermeldung implementiert war. Da habe ich mir dann Mühe gegeben, wirklich alle Eventualitäten zu berücksichtigen und zu jeder eine aussagekräftige Meldung zu geben. Daher ist der Code doch etwas kompliziert, da viele verschachtelte if-Abfragen. Ich habe mir jedoch Mühe gegeben, möglichst alles mit (relativ) anfängerfreundlichen Mitteln zu machen (map und string sollte man als etwas fortgeschrittener Anfänger kennen und man sollte auch schon mal gehört haben, dass es eine Standardbibliothek gibt und wie man deren Funktionen nachschlägt). Wenn man also dem Programmverlauf folgen kann, dann ist jeder einzelne Ausdruck hoffentlich verständlich. Ok, Templates sind drin, aber keine komplizierten. An der Stelle mag ich einfach keinen Code schreiben, der nur mit int arbeitet.

    #include <iostream>
    #include <string>
    #include <map>
    #include <stdexcept>
    #include <algorithm>
    #include <functional>
    #include <sstream>
    
    namespace utility
    {
      // Copied from Stackoverflow:
      // trim from start
      static inline std::string &ltrim(std::string &s) 
      {
        s.erase(s.begin(), std::find_if(s.begin(), s.end(),
                std::not1(std::ptr_fun<int, int>(std::isspace))));
        return s;
      }
    
      // trim from end
      static inline std::string &rtrim(std::string &s) {
        s.erase(std::find_if(s.rbegin(), s.rend(), 
                std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
        return s;
      }
    
      // trim from both ends
      static inline std::string &trim(std::string &s) {
        return ltrim(rtrim(s));
      }
    };
    
    class SimpleIniParser
    {
      typedef std::map<std::string, std::string> Category;
      typedef std::map<std::string, Category> IniFile;
    
      IniFile data;
    
    public:
    
      SimpleIniParser(std::istream& in)
      {
        std::string line; 
        std::string current_category;
        bool category_still_open = false;
    
        while(getline(in, line))
          {
            utility::trim(line);
            if (line.size())
              {
                if (line[0] != '#')
                  {
                    if (line[0] == '[')
                      {
                        if (line.size() == 1)
                          throw std::logic_error("Error parsing ini-file: "
                                                 "Solitary '[' in file");
                        std::size_t found = line.find(']');
                        if (found == std::string::npos)
                          throw std::logic_error("Error parsing ini-file: "
                                                 "Opening '[' without a closing ']'");
    
                        if (line[1] != '/') // new category
                          {
                            if (category_still_open)
                              throw std::logic_error("Error parsing ini-file: "
                                                     "Category within category not supported.");
                            current_category = line.substr(1, found-1);
                            category_still_open = true;
                          }
                        else  // close old category
                          {
                            std::string closed_category = line.substr(2, found-2);
                            if (!category_still_open or closed_category != current_category)
                              throw std::logic_error("Error parsing ini-file: "
                                                     "Closing a category that is "
                                                     "not opened: " + closed_category);
                            category_still_open = false;
                            current_category = "";
                          }
                      }
                    else  // new entry for category
                      {
                        std::size_t found = line.find('=');
                        if (found == std::string::npos)
                          throw std::logic_error("Error parsing ini-file: Entry without a '='");
    
                        std::string name = line.substr(0, found);
                        utility::rtrim(name);
                        std::string entry = line.substr(found + 1, std::string::npos);
                        utility::ltrim(entry);
    
                        data[current_category][name] = entry;                   
                      }
                  }
              }
          }
        if (category_still_open)
          throw std::logic_error("Error parsing ini-file: Category \"" 
                                 + current_category + "\" never closed.");
      }
    
      template<typename T> void get(std::string const& category, std::string const& name, T &entry) const
      {
        IniFile::const_iterator found_category = data.find(category);
        if (found_category == data.end())
          throw std::logic_error("No category \"" + category + "\" in ini-file.");
    
        Category const& cat_data = found_category->second;
        Category::const_iterator found_entry = cat_data.find(name);
        if (found_entry == cat_data.end())
          throw std::logic_error("No entry \"" + name + "\" in category \"" + category + "\".");
    
        std::stringstream parser(found_entry->second);
        if (!(parser >> entry))
          throw std::logic_error("Could not convert \"" + found_entry->second + 
                                 "\" for entry \"" + name + "\" in category \"" +
                                 category + "\" to the desired type.");
      }
    
      template<typename T> void get(std::string const& category, std::string const& name, 
                                    T &entry, const T& default_value) const
      {
        try
          {
            get(category, name, entry);
          }
        catch (std::logic_error)
          {
            entry = default_value;
          }
      }
    
      template<typename T> void get_verbose(std::ostream &out, std::string const& category, 
                                            std::string const& name, T &entry) const
      {
        get(category, name, entry);
        out << category << "::" << name << "\t=\t" << entry << '\n';
      }
    
      template<typename T> void get_verbose(std::ostream &out, std::string const& category, 
                                            std::string const& name, T &entry, const T& default_value) const
      {
        get(category, name, entry, default_value);
        out << category << "::" << name << "\t=\t" << entry << '\n';    
      }
    };
    

    Anwendungsbeispiel:

    int main()
    {
      SimpleIniParser sip(std::cin);
    
      int a;
      sip.get_verbose(std::cout, "Krieger", "Str", a);
    }
    

    Features und Sonstiges:
    -Kommt mit allerlei Schweinereien bei der Eingabe klar oder bricht zumindest mit einem passenden Fehler ab
    -Unterstützt "freie" Einträge ohne Kategorie. Dies kann auf Wunsch entfernt werden.
    -Unterstützt bis zu einem gewissen Grad auch Namen und Einträge mit Whitespace
    -Momentanes Verhalten beim Parsen des Eintrags entspricht dem operator>>. Das heißt, was rechts vom '=' steht wird bei std::string als Typ an Whitespaces getrennt. Ich bin unentschieden, ob dies das semantisch richtige Verhalten ist. Am besten wäre wohl eine Unterstützung für Quotes einzubauen. Dies zieht aber ganz schön viel Arbeit mit sich, da man dann auch Escapesequenzen als Features möchte und wohl auch mehrzeilige Ausdrücke.
    -Nur flache Hierarchien werden unterstützt, keine Verschachtelung. Dies wäre auf Wunsch leicht einbaubar, aber momentan sehe ich es als Fehler in der Datei an, da das Format einen flachen Aufbau verlangt.



  • Bitte SeppJ ein bisschen kritisieren! Es kann ja nicht sein das er nur austeilt aber nie selbst niedergemacht wird. 😃



  • while(getline(in, line))
    

    Nein! Nein Nein Nein Nein Nein Nein Nein! Nein! 😡 So mag ich das nicht!

    Ich hau auch was raus.


  • Mod

    kleine bitte schrieb:

    Bitte SeppJ ein bisschen kritisieren! Es kann ja nicht sein das er nur austeilt aber nie selbst niedergemacht wird. 😃

    Ja, an dem Code könnte man noch vieles besser machen. Aber es ist/war Sonntagmorgen, da habe ich an einigen Stellen etwas geschlunzt. Ich hoffe, die meisten davon sind mir bewusst 😃 .



  • Also. Ich habe mal eine erste, unvollständige Zwischenversion.
    SeppJ, du solltest einen Eimer bereitstehen haben.

    #include <iostream>
    #include <algorithm>
    #include <functional>
    #include <vector>
    #include <map>
    
    // Die ist ganz nützlich.
    template<typename CharT, typename TraitsT>
    inline std::basic_string<CharT, TraitsT>& rtrim(std::basic_string<CharT, TraitsT>& s, std::locale const& loc)
    {
        s.erase( std::find_if(s.rbegin(), s.rend(), [&](CharT ch){ return !std::isspace<CharT>(ch, loc); }).base(), std::end(s) );
        return s;
    }
    
    template<typename Exception = std::logic_error>
    void Assert( bool b, std::string const& what = std::string() )
    {
        if(!b)
            throw Exception(what);
    }
    
    template<typename CharT, typename TraitsT>
    inline std::basic_istream<CharT, TraitsT>& checked_getline( std::basic_istream<CharT, TraitsT>& is, std::basic_string<CharT, TraitsT>& str, CharT delim, CharT NotAllowed )
    {
        str.clear();
    
        while( is.good() )
        {
            typename TraitsT::int_type i = is.get();
    
            if( TraitsT::eq_int_type( i, TraitsT::eof() ) )
            {
                is.setstate( std::ios_base::eofbit );
                break;
            }
    
            typename TraitsT::char_type ch;
            TraitsT::assign( ch, TraitsT::to_char_type(i) );
    
            Assert( !TraitsT::eq( ch, NotAllowed ), "Invalid character in sequence!" );
    
            if( TraitsT::eq( ch, delim ) )
                break;
    
            str.push_back(ch);
        }
    
        return is;
    }
    
    template<typename CharT,
             typename TraitsT = std::char_traits<CharT>,
             typename ValueT = std::basic_string<CharT, TraitsT> >
    struct Node
    {
        typedef CharT char_type;
        typedef TraitsT traits_type;
    
        typedef std::basic_string<CharT, TraitsT> key_type;
        typedef ValueT value_type;
    
        std::basic_string<CharT, TraitsT> const name;
    
        Node( std::basic_string<CharT, TraitsT> const& name = "Root" ):
            name(name) {}
    
        std::map<key_type, value_type> entries;
    
        std::vector<Node*> child_nodes; // Darf *nicht* verändert werden - Inhalt wird gelöscht
    
        ~Node()
        {
            std::for_each( std::begin(child_nodes), std::end(child_nodes), static_cast<void(*)(void*)>(operator delete) );
        }
    };
    
    template<typename CharT,
             typename Traits,
             typename ValueT = std::basic_string<CharT, Traits> >
    std::basic_istream<CharT, Traits>& operator>>( std::basic_istream<CharT, Traits>& is, Node<CharT, Traits, ValueT>& node )
    {
        CharT const separator = is.widen('='),
                    close_brace = is.widen(']'),
                    newline = is.widen('\n');
    
        typedef std::basic_string<CharT, Traits> string_t;
    
        while( (is >> std::ws).good() )
        {
            if( Traits::eq_int_type(is.peek(), Traits::to_int_type(is.widen('#'))) )
            {
                is.ignore( std::numeric_limits<std::streamsize>::max(), newline );
                continue;
            }
    
            if( Traits::eq_int_type(is.peek(), Traits::to_int_type(is.widen('['))) ) /// Entweder eine neue Node wird eingeleitet, oder diese wird geschlossen ( [/...] )
            {
                is.ignore() >> std::ws;
    
                if( Traits::eq_int_type(is.peek(), Traits::to_int_type(is.widen('/'))) )
                {
                    is.ignore() >> std::ws;
    
                    for( auto const& ch : node.name )
                        Assert( Traits::eq_int_type( is.get(), Traits::to_int_type(ch) ), "Invalid closing tag!" );
    
                    is >> std::ws;
    
                    Assert( Traits::eq_int_type( is.get(), Traits::to_int_type(close_brace) ), "Missing ']' character at closing tag!" );
    
                    break; /// Beenden.
                }
                else // Eine neue, verschachtelte Node
                {
                    string_t name;
    
                    Assert( !checked_getline( is, name, close_brace, newline ).eof(), "Missing ']'-character" );
    
                    rtrim(name, is.getloc());
    
                    Assert( !name.empty(), "Empty node title!" );
    
                    node.child_nodes.push_back( new Node<CharT, Traits, ValueT>{name} );
                    is >> *node.child_nodes.back();
                }
            }
            else /// Eine Key-Value zuweisung
            {
                std::pair<string_t, ValueT> pair;
    
                Assert( !checked_getline( is, pair.first, separator, is.widen('\n') ).eof(), "Invalid syntax - expected separator" );
    
                Assert( !rtrim(pair.first, is.getloc()).empty(), "Empty key!" );
    
                is >> pair.second; /// Hier wird der value eingelesen.
    
                node.entries.insert( std::move(pair) );
            }
        }
    
        return is;
    }
    
    #include <sstream>
    
    int main()
    {
        std::istringstream stream{
    R"(
    [Schurke]
    Str=12 # Hier kommt die Stärke- es gibt auch Kommentare, ja
    Dex mit      Whitespaces = 17
    Int=4
    [/Schurke] # Das ist das close-tag
    
    [Krieger]
    Str=18
    Dex=10
    Int=2
    [/Krieger]
    )" };
    
        Node<char, std::char_traits<char>, unsigned> node;
        stream >> node;
    
        for( auto sub_node : node.child_nodes )
        {
            std::cout << "Name: " << sub_node->name << '\n';
            for( auto const& pair : sub_node->entries )
                std::cout << pair.first << " : " << pair.second << '\n';
        }
    }
    

    -Kommt mit allerlei Schweinereien bei der Eingabe klar oder bricht zumindest mit einem passenden Fehler ab

    Ich glaube zumindest, dass es auch meiner tut. Hab noch nicht getestet.

    -Unterstützt "freie" Einträge ohne Kategorie. Dies kann auf Wunsch entfernt werden.

    Geht bei mir sowieso und kann angepasst werden. Ist aber ja rekursiv, daher muss da ein extra Attribut in die Klasse ( is_root o.ä.).

    -Unterstützt bis zu einem gewissen Grad auch Namen und Einträge mit Whitespace

    "Keys" bei mir ja - values müssen entsprechendes Verhalten haben. Das ist der Preis für die Flexibilität die ich haben wollte.

    -Nur flache Hierarchien werden unterstützt, keine Verschachtelung. Dies wäre auf Wunsch leicht einbaubar, aber momentan sehe ich es als Fehler in der Datei an, da das Format einen flachen Aufbau verlangt.

    :p

    Edit: Kleinen formalen Fehler behoben



  • Jetzt müssten wir ein ganz großes Beispiel-File schreiben, und dann will ich wissen wessen schneller ist.


Anmelden zum Antworten