Eigenes getline?



  • Hey ho,

    sauber SeppJ, vielen Dank, das hat mir schon enorm weitergeholfen. 🙂

    Ich habe jetzt folgenden Fall: Es kann sein, dass '\r' alleine vorkommt, '\n' alleine vorkommt oder '\r' nebst '\n' vorkommt. Ich muss also weg von der Einzeichen-Basis.

    So wie ich das sehe, lässt sich das folgendermaßen umsetzen:
    - '\r' nebst '\n' löse ich, indem ich nach Lesen von '\r' schaue, ob das nächste Zeichen '\n' ist und, falls ja, den get-Zeiger einfach weiterschiebe und '\n' zurückgebe
    - '\r' ohne '\n' wird gelöst, indem ich beim Lesen von '\r' einfach '\n' zurückgebe und den get-Zeiger normal lasse (einfache Konvertierung)
    - '\n' wird als normales Zeichen zurückgegeben

    Dann lässt sich das normale getline wieder anwenden. Ist folgender Code wohl in Ordnung?

    custom_streambuf::int_type custom_streambuf::underflow()
    {
    	int_type c = oldBuf.sgetc();
    
    	if(traits_type::eq_int_type(c, 13)) // c == '\r'
    	{
    		// Are characters to read and is next one '\n'?
    		if(in_avail() == 0)
    			return traits_type::int_type(10); // '\n';
    		int_type c2 = oldBuf.snextc(); // move pointer to next element
    
    		if(traits_type::eq_int_type(c2, 10))
    			return c2;
    
    		// here: c2 != '\n', so we should move back
    		oldBuf.sungetc();
    	}
    
    	// In case of '\n' or other digits, we can return just them
    	return c;
    }
    
    custom_streambuf::int_type custom_streambuf::uflow()
    {
    	int_type c = oldBuf.sbumpc();
    
    	if(traits_type::eq_int_type(c, 13)) // c == '\r'
    	{
    		// We are at the next character; is it '\n'?
    		int_type c2 = oldBuf.sgetc();
    
    		if(traits_type::eq_int_type(c2, 10)) // it is '\n', so we should move to next token but return '\n'
    		{
    			snextc();
    			return c2;
    		}
    
    		// here: c2 != '\n', so this get-pointer place is just right
    	}
    
    	// In case of '\n' or other digits, we can return just them
    	return c;
    }
    

    Ich bin nicht ganz sicher, ob dieses in_avail genau macht, was ich möchte, laut Doku liest es ja die Anzahl der noch zu lesenden Zeichen, auch falls der interne Puffer gerade leer ist, aus. Wenn das also >0 ist, kommt noch was, ansonsten ist wohl eof | fail der Fall.

    Mein initiales Beispiel "Hallo Du!" funktioniert damit übrigens. Seht ihr irgendwelche Fehler?


  • Mod

    Ich würde nicht mit in_avail() arbeiten. Das ist oftmals so implementiert, dass es einfach 0 liefert, weil der Streambuf gar nicht wissen kann, wie viel noch kommt (woher soll er z.B. wissen wie viel der Anwender noch tippen wird, wenn er an der Tastatur hängt?). Was du tun könntest, wäre, auf EOF zu prüfen (siehe z.B hier wie das geht). Aber ich würde das etwas simpler machen: Du hast eine Klasse, die kann auch einen Zustand haben. Wenn '\r' kommt, setzt du dir einen bool-Member, und wenn '\n' kommt, dann guckst du ob der bool gesetzt ist und verwirfst das Zeichen gegebenenfalls.

    Ungetestet:

    class CRLF_filter : public std::streambuf
    {
    public:
      CRLF_filter(std::istream& in)
        : in(in) 
        , buf (in.rdbuf())  
      {
        in.rdbuf(this);
      }
      ~CRLF_filter()
      {
        in.rdbuf(buf); 
      }
    
    protected:
      virtual int_type underflow() 
      {
        int_type c = buf->sgetc();
        if (traits_type::eq_int_type( c, LF ) && last_was_CR)
         {
          int_type c = buf->snextc();  // Nächstes Zeichen nehmen
          last_was_CR = false;      
         }
        if (traits_type::eq_int_type( c, CR ))
         {
           c = LF;
           last_was_CR = true;
         }
        return c; 
      }
      virtual int_type uflow()    
      {
        int_type c = buf->sbumpc();
        if (traits_type::eq_int_type( c, LF ) && last_was_CR)
         {
          int_type c = buf->sbumpc();  // Man beachte den Unterschied zu oben
          last_was_CR = false;      
         }
        if (traits_type::eq_int_type( c, CR ))
         {
           c = LF;
           last_was_CR = true;
         }
        return c; 
      }
    private:
      char_filter( const char_filter& ); 
      char_filter& operator=( const char_filter& ); 
    
      std::istream& in;
      std::streambuf* buf;
      static const int_type CR, LF;
      bool last_was_CR;
    }; 
    
    CRLF_filter::int_type CRLF_filter::LF  = CRLF_filter::traits_type::to_int_type('\n');
    CRLF_filter::int_type CRLF_filter::CR = CRLF_filter::traits_type::to_int_type('\r');
    

    So, ich hoffe, ich habe da keine Dummheiten gemacht, die man erst bei Sonderfällen wie \r\EOF oder ähnlichem bemerkt.



  • Hast Recht, das ist eigentlich viel einfacher und v.a. sicherer. 🙂

    Aber wenn \r\EOF endet, gilt last_was_CR; wenn ich die nächste Datei lese und die mit \n anfängt, wird das \n ignoriert. Können wir das noch irgendwie lösen?

    Hm... wobei ich gerade überlege, ob dieser Fall überhaupt eintritt, ohne dass wir ein neues istream-Objekt haben..


  • Mod

    Eisflamme schrieb:

    Hm... wobei ich gerade überlege, ob dieser Fall überhaupt eintritt, ohne dass wir ein neues istream-Objekt haben..

    Theoretisch könnte der Nutzer das Filterobjekt auch an einen anderen Stream binden, aber da wir keine Schnittstelle zur Verfügung stellen, kann er den Streambuf der unter unserem Stream liegt nicht ändern. Theoretisch könnte er diesen manipulieren, aber wer so etwas tut, soll auch mit den Konsequenzen leben. Das ist schließlich ein absichtlicher Hack. Und es wäre selbst dann auch konsistent: Der Streambuf filtert \r zu \n und \r\n zu \n. Woher die Zeichen kommen, sollte unserem Filter egal sein.

    Jedenfalls brauchst du keine Angst zu haben, dass dir das bei normaler Benutzung passiert.



  • Ok, prima. 🙂

    Ich habe jetzt ein paar Tests gemacht. Hier 5 Dateien:

    48 61 6C 6C 6F 0A 44 75 21
    48 61 6C 6C 6F 0D 44 75 21
    48 61 6C 6C 6F 0D 44 75 0D 0A
    48 61 6C 6C 6F 0D 44 75 0A
    48 61 6C 6C 6F 0D 44 75 0D
    

    (\r \n in der Mitte hatte ich vorher schon Mal getestet, das geht auch)

    Ich erzeuge jeweils einen neuen istream und auch Buffer. Für diese fünf Dateien funktioniert alles. 🙂👍

    Ein paar Kleinigkeiten habe ich im Code noch geändert, weil z.B. das lastCR-Flag bei normalen Zeichen nach \r nicht geändert wurde und so. Zur Vollständigkeit nochmal der Code:

    custom_streambuf::int_type custom_streambuf::underflow()
    {
    	int_type c = oldBuf.sgetc();
    
    	if(traits_type::eq_int_type(c, LF) && lastWasCR) // \r\n -> we are at \n
    	{
    		lastWasCR = false;
    		// Ignore this
    		return oldBuf.snextc();
    	}
    	if(traits_type::eq_int_type(c, CR)) // \r -> we memorize this but return \n
    	{
    		lastWasCR = true;
    		return LF;
    	}
    	lastWasCR = false;
    
    	return c;
    }
    
    custom_streambuf::int_type custom_streambuf::uflow()
    {
    	int_type c = oldBuf.sbumpc();
    
    	if(traits_type::eq_int_type(c, LF) && lastWasCR) // \r\n -> we are at \n
    	{
    		lastWasCR = false;
    		// Ignore this
    		return oldBuf.sbumpc();
    	}
    	if(traits_type::eq_int_type(c, CR)) // \r -> we memorize this but return \n
    	{
    		lastWasCR = true;
    		return LF;
    	}
    	lastWasCR = false;
    
    	return c;
    }
    


  • Hallo Eisflamme, hallo SeppJ,

    wenn man eine (Text-)Datei im sogenannten DOS-Format vorliegen hat - also mit dem "\r\n" als Zeilenende, dann ist für die Auflösung dieses Zeichenpaars IMHO die std::codecvt-Facette zuständig.

    Das geht im Prinzip so:

    #include <fstream>
    #include <iostream>
    #include <locale>
    #include <string>
    
    class ConvertCrLf2Lf : public std::codecvt< char, char, int >
    {
    protected:
        virtual bool do_always_noconv() const
        {
            return false;
        }
        // -- Einlesen
        virtual result do_in( state_type& state,
            const extern_type* from, const extern_type* from_end, const extern_type*& from_next,
            intern_type* to, intern_type* to_limit, intern_type*& to_next ) const
        {
            const char CR('\r');
            const char LF('\n');
            to_next = to;
            for( from_next = from; from_next != from_end && to_next != to_limit; ++from_next )
            {
                switch( state )
                {
                case 0: // normal
                    if( *from_next == CR )
                        state = 1; // wir merken uns nur, dass CR da war
                    else
                        *to_next++ = *from_next;
                    break;
    
                case 1: // das vorherige Zeichen war CR
                    if( *from_next != LF )
                    {
                        *to_next++ = CR; // allein stehendes CR nachliefern
                        if( to_next == to_limit )
                            return std::codecvt_base::partial;
                    }
                    if( *from_next != CR )
                    {
                        *to_next++ = *from_next;
                        state = 0;
                    }
                }
            }
            return from_next == from_end? std::codecvt_base::ok: std::codecvt_base::partial;
        }
        // -- Ausgeben
        // virtual result do_out(stateT& state,
        //   const intern_type* from, const intern_type* from_end, const intern_type*& from_next,
        //   extern_type* to, extern_type* to_limit, extern_type*& to_next) const { ...
    };
    
    int main()
    {
        using namespace std;
        ifstream in("input.txt"/*, ios_base::binary*/ );  // s.u. bei Windows
        in.imbue( locale( in.getloc(), new ConvertCrLf2Lf ) );
        for( string line; getline( in, line ); )
            cout << "> " << line << endl;
        return 0;
    }
    

    Die Facette kann mit imbue an den Stream und damit an den Streambuf übergeben werden. Jeder basic_filebuf sollte auch was damit anfangen können. Bei Windows-Systemen ist das aber nie notwendig; dort reicht es die Datei im Textmode zu öffnen - d.h. ohne das binary (s.o.). Wenn man es unter Windows ausprobieren will, so muss die Datei binär geöffnet werden und - ganz wichtig - VC10 benutzen, da die älterne Versionen einen Fehler haben - siehe codecvt<char,char>-Problem.

    Habe i.A. nicht so viel Zeit, noch mehr in die Tiefe zu gehen; mehr zum Thema hatten wir im Forum schon mal hier.

    Gruß
    Werner


Anmelden zum Antworten