Konfigurationsdatei elegant auslesen
-
Hi, ich habe ein Programm und dazu eine Konfigurationsdatei. Diese soll vom Benutzer bei Bedarf geändert werden und ist deshalb mit Text versehen, ungefähr so:
Blablabla
blablablabla. Bla
Blabla.screen-resolution: 1024x768
blabla: 892
blubb: 5Blablablubb.
In einer alten Version meines Programmes, habe ich mit:
pKonfiguration -> read(Text, 290);Die komplette Datei in ein Array gepackt und dann meine Funktionen darauf losgelassen. Die haben dann angefangen und z.B. von Position 0-5 geschaut, ob da "Hallo:" steht, weil es das Schlüsselwort vor einer gesuchten Variable ist. Steht es nicht da, springen diese Funktionen auf 1-6, dann 2-7 usw. Finden sie ihr jeweiliges Schlüsselwort dann, habe ich mangels Fachwissens (
) die Zahl mit:ScreenX = atoi (AktuellesTextstueck);In eine Integer umgewandelt. Dann hat die nächste Funktion angefangen, den Puffer von vorne nach hinten abzugrasen usw.
Seeehr unelegant finde ich. Und auch nicht Standardkonform.
In meinem C++-Buch steht der Zugriff auf Dateistreams nur so kurz beschrieben, dass ich daraus nciht schlau werde, wie ich das anders löse.
Ich habe jetzt soweit begonnen, dass ich eine Funktion geschrieben habe, die einen Zeiger auf den Dateistream zur Konfigurationsdatei zurückliefert. Aber wie kann ich nun elegant im Text die passenden Stellen suchen und dort die Zahlenwerte auslesen? Ich hoffe mir hilft jemand, damit ich nicht immer dumm bleibe. Denn ich hab nichts Brauchbares dazu gefunden.
-
Schau dir mal ifstreams und mit ifstreams die find() syntax an
-
#include <iostream> #include <fstream> #include <string> #include <map> class ConfigValue { private: std::string myValue; public: ConfigValue(const std::string& value = "") : myValue(value) { } int toInt() const { return atoi(myValue.c_str()); } double toDouble() const { return atof(myValue.c_str()); } float toFloat() const { return (float) toDouble(); } const std::string& toString() const { return myValue; } }; typedef std::map<std::string, ConfigValue> ConfigValues; class ConfigParser { private: ConfigValues myValues; public: bool loadFile(const std::string& file) { myValues.clear(); std::ifstream inFile(file.c_str()); if (!inFile.good()) { return false; } std::string buf; while (!inFile.eof()) { std::getline(inFile, buf); parseLine(buf); } inFile.close(); return true; } bool existValue(const std::string& key) { return myValues.find(key) != myValues.end(); } ConfigValue getValue(const std::string& key) { return myValues[key]; } private: void parseLine(const std::string& line) { size_t pos; if (line.empty() || (pos = line.find(":")) == std::string::npos) { return; } std::string left = line.substr(0, pos); std::string right = line.substr(pos + 1); trim(left); trim(right); myValues[left] = ConfigValue(right); } void trim(std::string& str) { while (str[0] == ' ' || str[0] == '\t') { str.erase(0, 1); } while (str[str.length() - 1] == ' ' || str[str.length() - 1] == '\t') { str.erase(str.length() - 1, 1); } } }; int main() { ConfigParser configParser; if (!configParser.loadFile("Config.cfg")) { std::cerr << "Konnte die Config nicht laden." << std::endl; return 1; } if (configParser.existValue("zahlInt")) { std::cout << configParser.getValue("zahlInt").toInt() << std::endl; } else { return 1; } if (configParser.existValue("zahlFloat")) { std::cout << configParser.getValue("zahlFloat").toFloat() << std::endl; } else { return 1; } if (configParser.existValue("String")) { std::cout << configParser.getValue("String").toString() << std::endl; } else { return 1; } return 0; }Eingabedatei:
asdas das d asd asd asd asd zahlInt: 10 zahlFloat: 10.2 String: Test asdas das d asd asd asd asd
-
Als Alternative dafür kannst du auch inis verwenden, unetr Windows via WinApi, für die anderen betriebssysteme gibts Libs die das können. Das Format ist einfacher und der Aufwand zum lesen und erweitern sehr viel geringer.
-
Wenn selber machen dann würd ich das so machen wie es in etwa bei der INI abläuft. Funktion mit Section und Key. Als erstes stumpf nach der Section suchen, dann innerhalb dieser Section den Key. Das ergebnis dann einfach als string zurück.
Gut, bei jedem aufruf würde die Datei erneut geöffnet werden usw. aber man kann das ganze ja entsprechend ergänzen das beim Instanzieren der Klasse die Datei schon geöffnet wird und kurz ein Inhaltsverzeichnis erstellt wird was den Zugriff dann beschleunigt.
-
zjt5hn5tr schrieb:
Wenn selber machen dann würd ich das so machen wie es in etwa bei der INI abläuft. Funktion mit Section und Key. Als erstes stumpf nach der Section suchen, dann innerhalb dieser Section den Key. Das ergebnis dann einfach als string zurück.
Gut, bei jedem aufruf würde die Datei erneut geöffnet werden usw. aber man kann das ganze ja entsprechend ergänzen das beim Instanzieren der Klasse die Datei schon geöffnet wird und kurz ein Inhaltsverzeichnis erstellt wird was den Zugriff dann beschleunigt.
Einen wirklichen Vorteil hast du dadurch aber nicht. Man könnte auch Kommentare erlauben und damit die Eintragungen gliedern.
-
Was mich ja eher stört, ist atoi. Mir wurde hier im Forum gesagt, dass wäre absolut ncicht Standardkonform und ich sollte es nicht verwenden. Man kann ja auch einen Dateistream direkt in eine Integer auslesen lassen. Aber dann müsste ich direkt im Stream und nicht in einem Puffer die passende Stelle finden. Ist das irgendwie möglich? Ich würde das gerne standardkonform lösen.
P.S. @ asdasdasd, danke für die ganze Arbeit, die du dir gemacht hast. Meine Sorge sind halt nur die atoi usw. Aufrufe.
-
Little Programmer schrieb:
Was mich ja eher stört, ist atoi. Mir wurde hier im Forum gesagt, dass wäre absolut ncicht Standardkonform und ich sollte es nicht verwenden. Man kann ja auch einen Dateistream direkt in eine Integer auslesen lassen. Aber dann müsste ich direkt im Stream und nicht in einem Puffer die passende Stelle finden. Ist das irgendwie möglich? Ich würde das gerne standardkonform lösen.
P.S. @ asdasdasd, danke für die ganze Arbeit, die du dir gemacht hast. Meine Sorge sind halt nur die atoi usw. Aufrufe.
atoi() ist standardkonform. Das Problem mit der Funktion ist aber die Fehleranalyse. Falls ein String nicht komplett als int interpretierbar ist (z.B. "123foo"), liefert dir atoi() einfach den Teil, der Ziffern enthält und hat sein Tagwerk beendet.
Du kannst strtol() verwenden, das eine Fehleranalyse gestattet:
#include <cstdlib> int getInt() { char *endptr = 0; int result = std::strtol(myString.c_str(), &endptr, 10); if(*endptr) throw InvalidIntegerValueError(); return result; }Der Punkt ist, dass strtol() den Teil des Strings, der nicht interpretiert werden konnte nach endptr schreibt. Konnte der gesamte String interpretiert werden ist *endptr == 0.
Stefan.
-
Wieso soll atoi nicht zum Standard gehören, C ist eine Untermenge von C++.
http://www.cplusplus.com/reference/
Ich hätte da keine Bedenken, diese Funktion zu benutzen. Man kann leicht eine Zeichenkette vor der Umwandlung auf 'illegale Zeichen' durchsuchen.
Es schadet sicherlich nicht, sich auch mal andere Möglichkeiten anzusehen:#include <sstream> #include <string> #include <iostream> using namespace std; int main() { string strval("123abc"); stringstream sstr; sstr << strval; int num; sstr >> num; cout << num << endl; }In der FAQ findest du mehrere Beispiele:
http://www.c-plusplus.net/forum/viewtopic-var-t-is-39488.htmlIm Dateistream zu wühlen, ist keine gute Idee. Am einfachsten und effektivsten ist es, wenn du die Datei in den Speicher einliest, in Key-/Value Paare deinem Format entsprechend aufteilst und dann bearbeitest.
Gruß,
B.B.
-
Little Programmer schrieb:
Was mich ja eher stört, ist atoi. Mir wurde hier im Forum gesagt, dass wäre absolut ncicht Standardkonform und ich sollte es nicht verwenden. Man kann ja auch einen Dateistream direkt in eine Integer auslesen lassen. Aber dann müsste ich direkt im Stream und nicht in einem Puffer die passende Stelle finden. Ist das irgendwie möglich? Ich würde das gerne standardkonform lösen.
P.S. @ asdasdasd, danke für die ganze Arbeit, die du dir gemacht hast. Meine Sorge sind halt nur die atoi usw. Aufrufe.
Zu atoi wurde ja nun alles gesagt. Im Prinzip wollte ich dir mit meinem Code nur eine Möglichkeit aufzeigen, wie man eine Konfigurationsdatei auslesen könnte. Der von dir im ersten Post gezeigte Ansatz schien mir nämlich vollkommen unpraktisch.
Wenn du meinen Code etwas ausbaust, hast du einen sehr robusten und einfach zu benutzenden "Config-Parser". Zum Beispiel könntest du noch einige Exceptions werfen, wenn eine Konvertierung fehlgeschlagen ist oder die Datei nicht geöffnet werden konnte, oder auf einen Eintrag zugegriffen wird, der nicht existiert.
Du könntest ja atoi, usw. durch std::stringstream ersetzen und dann nach der Umwandlung die Flags überprüfen. Sollte es einen Fehler gegeben haben => Exception. Alles kein Problem.
-
Ok, danke für eure Hilfe. Dann werde ich weiterhin atoi usw. verwenden, aber in einer Form, wie asdasdasd es vorgeschlagen hat. Danke euch allen
