Programmierung von ConfigReader/Parser



  • Hi,
    ich würde gerne in Teamarbeit mit einigen schon fortgeschrittenen(!) Leuten eine Bibliothek schreiben die es ermöglicht Konfigurationsdatein auszulesen. Die Werte sollen dann im Programm verwendbar sein.

    Ich habe das ganze schon soweit fertig programmiert, suche nun aber noch Leute die mir helfen noch folgendes zu erledigen:
    - Optimierung damit die Geschwindigkeit des Ladens einer Datei noch geringer wird und der Speicherverbrauch auch
    - Das Interface flexibler machen um damit angenehmer und effektiver arbeiten zu können
    - Bessere Fehlerbehandlung...
    - Das ganze in eine Bibliothek verpacken
    - Evtl. noch weitere Funktionen die uns einfallen einbauen...

    Jetzt zur eigentlichen Konfigurationsdatei. Eine Beispieldatei würde z.B. so aussehen wobei die Tiefe der Blöcke - intern Tabs genannt - nur durch den Speicher begrenz ist:

    "Block1"
    {
       "Wert1" = "Test";
       "Wert2" = 1234;
    
       "Block2"
       {
          "Wert1" = "Test";
          "Wert2" = 1234;
    
          "Block3"
          {
             /*
               Und so weiter...
               Kommentare sind also möglich sowohl Mehrzeilig als auch Einzeilig(//)
             */
          };
       }
    };
    
    "Block2"
    {
       "Wert1" = "Test";
       "Wert2" = 1234;
    
       "Block3"
       {
          "Wert1" = "Test";
          "Wert2" = 1234;
    
          "Block4"
          {
             /*
               Und so weiter...
               Kommentare sind also möglich sowohl Mehrzeilig als auch Einzeilig(//)
             */
          };
       }
    };
    

    Der Zugriff aus einem C++ Programm gefällt mir jedenfalls noch nicht, aber das würde z.Z. so aussehen:

    PC0 P( "Datei.pc0" );
    PC0Tab *Main = P.GetMainTab();
    PC0Tab *Block1 = Main->Search( "Block1" );
    PC0Tab *Block2 = Block1->Search( "Block2" );
    PC0Tab *Wert1 = Block2->Search( "Wert1" );
    std::cout << Wert1->GetValues().Name << " = " << Wert1->GetValues().Value << std::endl;
    

    Nun fragt man sich natürlich: Warum nutzt man nicht XML? Also ich persönlich mag XML nicht und finde es irgendwie sehr unübersichtlich.

    Okay, ich hoffe ich habe nun alles erwähnt was ich erwähnen wollte, falls noch Fragen bestehen bitte melden 😃

    Gruß



  • Schau dir mal boost::spirit an, damit müsstest du das recht gut parsen können.

    Pseudo spiritcode:

    using namespace boost::spirit;
    rule<> rvar= *alnum_p >> "=" >> *alnum_p;
    rule<> rblock = *alnum_p >> "{" >> *(rblock|rvar) >> "};";
    rule<> rfile = +rblock;
    


  • Hi,
    das mag sein, dass sich das damit gut parsen lässt, aber darum gehts nicht ich habs schon ohne geschafft ich suche nurnoch Leute die mir helfen die Oben genannten Punkte abzuarbeiten.

    Das Interesse scheint jetzt aber noch nciht so groß zu sein

    Gruß



  • Dieser Thread wurde von Moderator/in Korbinian aus dem Forum Projekte in das Forum C++ verschoben.

    Im Zweifelsfall bitte auch folgende Hinweise beachten:
    C/C++ Forum :: FAQ - Sonstiges :: Wohin mit meiner Frage?

    Dieses Posting wurde automatisch erzeugt.



  • Hallo Dummie,

    also ich wäre interessiert an dem Projekt. Wollte so etwas auch mal schreiben/nutzen habe dann aber keine Zeit dafür gehabt.
    Normalerweiße programmie ich in c, anscheinend ist es in cpp geschrieben, sollte aber kein allzu großes Problem darstellen.

    Schreib mir doch einfach mal an blubb email Addresse entfernt.

    Gruß
    yogle



  • Hi,
    sowas in der Art hatte ich damals auch mal angefangen allerdings entspricht dein Ansatz eher dem was bei XML DOM entsprechen würde und dass was ich da damals zusammengeflickt hatte würde eher dem Eventbasierten SAX entsprechen(basiert auch auf nem Artikel über nen einfachen Sax Parser 😉 ).
    Insgesamt ist der Aufbau deiner Config Datei allerdings nem XML File doch sehr ähnlich und ich sehe da momentan auch keinen grossen Grund warum du deshalb auf die Annehmlichkeiten fertiger XML Parser wie XercesC oder MSXML verzichtest und dich statt dessen lieber selber auf die Suche nach geschweiften Klammern machst.
    Naja der Punkt steht ja anscheinend auch nicht zur Debatte.
    Ich hab meinen damaligen Code mal etwas umgestrickt dass er bei leicht abgewandelter Syntax deines Config Formats nen halbwegs brauchbares Ergebnis liefert.
    Unterm Strich muss man für nen spezifisches Configfile nur ne Klasse von configuration_base ableiten und in der init Methode die Assoziation zwischen dem Key(welcher dem kompletten Pfad bis zum Wert entspricht) und der dazugehörigen Variablen herstellen.

    Also:
    config_element.h

    #ifndef __CONFIG_ELEMENT_H
    #define __CONFIG_ELEMENT_H
    
    #include <string>
    
    class config_element
    {
    public:
      virtual void put(const std::string& s) const=0;
      virtual ~config_element() {}
    };
    #endif
    

    config_element_data.h

    #ifndef __CONFIG_ELEMENT_DATA_H
    #define __CONFIG_ELEMENT_DATA_H
    
    #include <sstream>
    #include <string>
    #include "config_element.h"
    
    template<typename T>
    class config_element_data : public config_element
    {
    public:
        config_element_data(T& item) : ref_(item){}
        virtual void put(const std::string& s)const
        {
            std::istringstream stream(s);
            stream >> ref_;
        }
    private:
        T& ref_;
    };
    
    #endif
    

    Und noch configuration_base.h(Implementierung mit in die Klassendeklaration gestopft).

    #ifndef __CONFIGURATION_BASE_H
    #define __CONFIGURATION_BASE_H
    
    #include <fstream>
    #include <string>
    #include <map>
    #include <stack>
    #include <sstream>
    #include "config_element.h"
    
    class configuration_base
    {
        typedef std::map<std::string,config_element* > config_map_t;
    public:
    	configuration_base(){_path.push("");}
        virtual ~configuration_base()
        {
            config_map_t::iterator it = config_map.begin();
            while(it != config_map.end())
    		{
                delete (*it).second;
    			it++;
    		}
        }
    
        void loadConfiguration(const char* filename)
        {
            std::ifstream ifstr(filename);
            if(ifstr)
            {
                std::string buf;
                while(std::getline(ifstr,buf))
    			{
    				static std::string _last_buf="";
    
    				std::string::size_type pos = buf.find_first_not_of(' ');
    				if(pos != std::string::npos)
    					buf = buf.substr(pos);
    
    				if(buf=="{")
    				{
    					std::string temp = _path.top();
    					temp += _last_buf;
    					temp.insert(temp.end(),'/');
    					_path.push(temp);
    				}
    				else 
    					if(buf=="}")
    						_path.pop();
    					else
    						setValue(buf);
    				_last_buf=buf;
    			}
                ifstr.close();
            }
        }
    protected:
        config_map_t config_map;
    	std::stack<std::string> _path;
    
        bool setValue(const std::string &s)
        {
            std::istringstream isstr(s);
            std::string valueName;
            std::string value;
            isstr>>valueName;
            isstr>>value;
    		std::string str = _path.top();
    		str += valueName;
            config_map_t::iterator it;
            it = config_map.find(str);
            if(it!=config_map.end())
            {
                (*it).second->put(value);
                return true;
            }
            else
                return false;
        }
    private:
        virtual void init() = 0;
    };
    
    #define CONFIG_ENTRY(Keyname,DataType,VarName) config_map.insert(std::make_pair(std::string(Keyname),new config_element_data<DataType>(VarName)));
    
    #endif
    

    Und dann noch was zum Testen:
    demo_configuration.h(die einzige Klasse die man neu schreiben müsste)

    #ifndef __DEMO_CONFIG_H
    #define __DEMO_CONFIG_H
    
    #include "config_element_data.h"
    #include "configuration_base.h"
    
    class demo_configuration : public configuration_base
    {
    public:
        int block1_wert1;
        double block2_wert2;
        std::string name;
    	std::string wert45;
    	int dummy;
    	float dummy2;
    
        demo_configuration()
        {
    		//eventuell mit Standardwerten belegen
            block1_wert1=0;
            block2_wert2=0.0;
    		dummy=12;
            name="Default";
    		wert45="Default";
    		dummy2=12.34;
    
            init();
        }
    private:
        void init()
        {
            config_map.insert(std::make_pair(std::string("Block1/Wert1"),new config_element_data<int>(block1_wert1)));
            config_map.insert(std::make_pair(std::string("Block2/Wert2"),new config_element_data<double>(block2_wert2)));
            config_map.insert(std::make_pair(std::string("Name"),new config_element_data<std::string>(name)));
    		//oder für die Schreibfaulen
    		CONFIG_ENTRY("dummy2",float,dummy2)
    		CONFIG_ENTRY("dummy",int,dummy)
    		CONFIG_ENTRY("Block2/Block3/Block4/Wert45",std::string,wert45)
        }
    };
    
    #endif
    

    main.cpp

    #include <iostream>
    #include "demo_configuration.h"
    
    int main()
    {
        demo_configuration config;
    
    	std::cout<<"Vor dem laden:"<<std::endl;
        std::cout<<config.name<<std::endl;
    	std::cout<<config.block1_wert1<<std::endl;
    	std::cout<<config.block2_wert2<<std::endl;
    	std::cout<<config.dummy<<std::endl;
    	std::cout<<config.wert45<<std::endl;
    	std::cout<<config.dummy2<<std::endl;
    
    	config.loadConfiguration("conf.txt");
    
    	std::cout<<"\nNach dem laden:"<<std::endl;
        std::cout<<config.name<<std::endl;
    	std::cout<<config.block1_wert1<<std::endl;
    	std::cout<<config.block2_wert2<<std::endl;
    	std::cout<<config.dummy<<std::endl;
    	std::cout<<config.wert45<<std::endl;
    	std::cout<<config.dummy2<<std::endl;
    
    	return 0;
    }
    

    und die Config Datei:

    Name	Teststring
    Block1
    {
       Wert1 567
       Wert2 1234
    
       Block2
       {
          Wert1 Test
          Wert2 
    
          Block3
          {
             /*
               Und so weiter...
               Kommentare sind also möglich sowohl Mehrzeilig als auch Einzeilig(//)
             */
          }
       }
    }
    
    dummy 7
    
    Block2
    {
       Wert1 Test
       Wert2 3.14
    
       Block3
       {
          Wert1 Test
          Wert2 1234
    
          Block4
          {
    	 Wert45 Telefonmann
             /*
               Und so weiter...
               Kommentare sind also möglich sowohl Mehrzeilig als auch Einzeilig(//)
             */
          }
       }
    }
    
    dummy2 34.38
    

    Die Fehlerbehandlung fehlt fast vollständig und gescheit getestet ist das Ganze auch nicht aber im Gegensatz zu dir möchte ich ja auch keine Lib schreiben 😃 .
    Also schau dir das Ganze mal an.Eventuell findest du da ja die eine oder andere Anregung.

    Gruss Spacelord



  • phlox81 schrieb:

    Schau dir mal boost::spirit an, damit müsstest du das recht gut parsen können.

    Pseudo spiritcode:

    using namespace boost::spirit;
    rule<> rvar= *alnum_p >> "=" >> *alnum_p;
    rule<> rblock = *alnum_p >> "{" >> *(rblock|rvar) >> "};";
    rule<> rfile = +rblock;
    

    Ich habe spirit mittlerweile wieder verbannt. Es ist zwar recht angenehm die Syntax runter zu schreiben, aber den Aufwand den ich dort spare muss ich dann meist doppelt aufwenden um überhaupt an die geparsten Werte zu kommen. Für komplexe Sachen ist ohnehin flex/bison besser geeignet und für einfache Sachen ist ein handgeschriebener Parser auch kein Beinbruch.


Anmelden zum Antworten