it-swarm.dev

Tworzenie prostego pliku konfiguracyjnego i parsera w C++

Próbuję utworzyć prosty plik konfiguracyjny, który wygląda tak

url = http://mysite.com
file = main.exe
true = 0

gdy program działa, chciałbym, aby załadował ustawienia konfiguracji do wymienionych poniżej zmiennych programów.

string url, file;
bool true_false;

Zrobiłem kilka badań i this link wydawał się pomagać (post nukleonowi), ale nie wydaje mi się, aby działało i jest zbyt skomplikowane, aby zrozumieć z mojej strony. Czy jest to prosty sposób? Mogę załadować plik za pomocą ifstream, ale to tak daleko, jak tylko mogę. Dzięki!

45
llk

Ogólnie najłatwiej jest parsować takie typowe pliki konfiguracyjne w dwóch etapach: najpierw przeczytaj linie, a następnie parsuj je jeden po drugim.
W C++ wiersze można odczytać ze strumienia przy użyciu std::getline(). Podczas gdy domyślnie będzie czytał do następnego '\n' (który zużyje, ale nie zwraca), możesz przekazać mu także jakiś inny ogranicznik, co czyni go dobrym kandydatem do czytania char, jak = w twoim przykładzie.

Dla uproszczenia poniżej zakłada się, że =not otoczone białymi spacjami. Jeśli chcesz zezwolić na białe znaki w tych pozycjach, musisz strategicznie umieścić is >> std::ws przed odczytaniem wartości i usunąć końcowe białe znaki z klawiszy. Jednak IMO niewielka dodatkowa elastyczność w składni nie jest warta kłopotów dla czytnika plików konfiguracyjnych.

const char config[] = "url=http://example.com\n"
                      "file=main.exe\n"
                      "true=0";

std::istringstream is_file(config);

std::string line;
while( std::getline(is_file, line) )
{
  std::istringstream is_line(line);
  std::string key;
  if( std::getline(is_line, key, '=') )
  {
    std::string value;
    if( std::getline(is_line, value) ) 
      store_line(key, value);
  }
}

(Dodanie obsługi błędów pozostawia się czytnikowi jako ćwiczenie.)

46
sbi

Jak zauważyli inni, korzystanie z istniejącej biblioteki parsera plików konfiguracyjnych będzie prawdopodobnie mniej skuteczne niż ponowne wymyślenie koła.

Na przykład, jeśli zdecydujesz się użyć biblioteki Config4Cpp (którą utrzymuję), wtedy składnia twojego pliku konfiguracyjnego będzie nieco inna (wstaw podwójne cudzysłowy wokół wartości i zakończ instrukcje przypisania za pomocą średnika), jak pokazano w przykładzie poniżej :

# File: someFile.cfg
url = "http://mysite.com";
file = "main.exe";
true_false = "true";

Poniższy program analizuje powyższy plik konfiguracyjny, kopiuje żądane wartości do zmiennych i drukuje je:

#include <config4cpp/Configuration.h>
#include <iostream>
using namespace config4cpp;
using namespace std;

int main(int argc, char ** argv)
{
    Configuration *  cfg = Configuration::create();
    const char *     scope = "";
    const char *     configFile = "someFile.cfg";
    const char *     url;
    const char *     file;
    bool             true_false;

    try {
        cfg->parse(configFile);
        url        = cfg->lookupString(scope, "url");
        file       = cfg->lookupString(scope, "file");
        true_false = cfg->lookupBoolean(scope, "true_false");
    } catch(const ConfigurationException & ex) {
        cerr << ex.c_str() << endl;
        cfg->destroy();
        return 1;
    }
    cout << "url=" << url << "; file=" << file
         << "; true_false=" << true_false
         << endl;
    cfg->destroy();
    return 0;
}

Witryna Config4Cpp website zapewnia kompleksową dokumentację, ale przeczytanie tylko Rozdziałów 2 i 3 „Przewodnika dla początkujących” powinno być więcej niż wystarczające dla Twoich potrzeb.

32
Ciaran McHale

Naiwne podejście może wyglądać tak:

#include <map>
#include <sstream>
#include <stdexcept>
#include <string>

std::map<std::string, std::string> options; // global?

void parse(std::istream & cfgfile)
{
    for (std::string line; std::getline(cfgfile, line); )
    {
        std::istringstream iss(line);
        std::string id, eq, val;

        bool error = false;

        if (!(iss >> id))
        {
            error = true;
        }
        else if (id[0] == '#')
        {
            continue;
        }
        else if (!(iss >> eq >> val >> std::ws) || eq != "=" || iss.get() != EOF)
        {
            error = true;
        }

        if (error)
        {
            // do something appropriate: throw, skip, warn, etc.
        }
        else
        {
            options[id] = val;
        }
    }
}

Teraz możesz uzyskać dostęp do każdej wartości opcji z globalnej mapy options w dowolnym miejscu programu. Jeśli chcesz castability, możesz uczynić mapowany typ boost::variant.

13
Kerrek SB

libconfig jest bardzo łatwy, a co więcej, używa notacji pseudo json dla lepszej czytelności.

Łatwa instalacja na Ubuntu: Sudo apt-get install libconfig++8-dev

i link: -lconfig++

11
user1382306

Ostatnio przeszukałem biblioteki parsowania konfiguracji dla mojego projektu i znalazłem te biblioteki:

3
Ivan Samygin

Co powiesz na formatowanie swojej konfiguracji jako JSON i używanie biblioteki takiej jak jsoncpp

na przykład.

{"url": "http://mysite dot com",
"file": "main.exe",
"true": 0}

Następnie możesz odczytać je w nazwanych zmiennych, a nawet zapisać je w std :: map itd. Oznacza to, że możesz dodawać opcje bez konieczności zmiany i ponownej kompilacji analizatora składni.

3
patmanpato

Dlaczego nie wypróbować czegoś prostego i czytelnego dla człowieka, takiego jak JSON (lub XML)? 

Istnieje wiele gotowych implementacji JSON (lub XML) z otwartym kodem źródłowym dla C++ - używałbym jednego z nich.

A jeśli chcesz czegoś bardziej „binarnego” - spróbuj BJSON lub BSON :)

3
yosh kemu

Oto prosty sposób na białe spacje między znakiem „=” a danymi w pliku konfiguracyjnym. Przypisz do strumienia istringstream z lokalizacji po znaku „=” i podczas odczytu z niego każda biała spacja jest ignorowana.

Uwaga: podczas używania strumienia istring w pętli, upewnij się, że wywołujesz clear () przed przypisaniem do niego nowego łańcucha.

//config.txt
//Input name = image1.png
//Num. of rows = 100
//Num. of cols = 150

std::string ipName;
int nR, nC;

std::ifstream fin("config.txt");
std::string line;
std::istringstream sin;

while (std::getline(fin, line)) {
 sin.str(line.substr(line.find("=")+1));
 if (line.find("Input name") != std::string::npos) {
  std::cout<<"Input name "<<sin.str()<<std::endl;
  sin >> ipName;
 }
 else if (line.find("Num. of rows") != std::string::npos) {
  sin >> nR;
 }
 else if (line.find("Num. of cols") != std::string::npos) {
  sin >> nC;
 }
 sin.clear();
}
1
haripkannan

Szukałem czegoś, co działałoby jak moduł python ConfigParser i znalazłem to: https://github.com/jtilly/inih

Jest to nagłówek inih w wersji C++.

inih (INI Not Invented Here) to prosty parser plików .INI napisany w C. To tylko kilka stron kodu i został zaprojektowany jako małe i proste, więc jest dobre dla systemów wbudowanych. To także więcej lub mniej kompatybilny ze stylem Python ConfigParser plików .INI w tym wieloliniowa składnia i nazwa: wartość RFC 822.

1
AaronS