0

I'm trying to make configuration file for my application, for which I'm using yaml-cpp library to generate a configuration and modify when user changes changes some setting or something in the application. I have a separate class set up for this, the constructor generates the yaml file which looks like this,

Serializer::Serializer(const std::string& filepath)
{
    std::ifstream ifin(filepath);

    if (!ifin)
    {
        emitter << YAML::Comment("Hello");
        emitter << YAML::BeginDoc;
        emitter << "This is the configuration file for the Sample Browser,"
                << YAML::Newline;
        emitter << "feel free to edit this file as needed";
        emitter << YAML::EndDoc;

        emitter << YAML::BeginMap;

        emitter << YAML::Newline << YAML::Key << "Window";
        emitter << YAML::BeginMap;
        emitter << YAML::Key << "SizeW" << YAML::Value << "1280";
        emitter << YAML::Key << "SizeH" << YAML::Value << "720";
        emitter << YAML::EndMap << YAML::Newline;

        emitter << YAML::Newline << YAML::Key << "Media";
        emitter << YAML::BeginMap;
        emitter << YAML::Key << "Autoplay" << YAML::Value << "false";
        emitter << YAML::Key << "Loop" << YAML::Value << "false";
        emitter << YAML::Key << "Muted" << YAML::Value << "false";
        emitter << YAML::EndMap << YAML::Newline;

        emitter << YAML::Newline << YAML::Key << "Display";
        emitter << YAML::BeginMap;
        emitter << YAML::Key << "Font";
        emitter << YAML::BeginMap;
        emitter << YAML::Key << "Family" << YAML::Value << "Sans";
        emitter << YAML::Key << "Size" << YAML::Value << "10";
        emitter << YAML::EndMap;
        emitter << YAML::EndMap << YAML::Newline;

        emitter << YAML::Newline << YAML::Key << "Import_dir";
        emitter << YAML::BeginMap;
        emitter << YAML::Key << "AutoImport" << YAML::Value << "false";
        emitter << YAML::Key << "Directory" << YAML::Value << "/home/apoorv";
        emitter << YAML::EndMap << YAML::Newline;

        emitter << YAML::EndMap;

        std::ofstream ofout(filepath);
        ofout << emitter.c_str();
    }
    else
    {
        wxLogDebug("Config file already exists! Skipping..");
    }
}

and the output of this file looks like,

# Hello
---
This is the configuration file for the Sample Browser,
feel free to edit this file as needed
...

Window:
  SizeW: 1280
  SizeH: 720

Media:
  Autoplay: false
  Loop: false
  Muted: false

Display:
  Font:
    Family: "Sans"
    Size: "10"

Import_dir:
  AutoImport: false
  Directory: "/home/apoorv/"

I want that when user changes say font, the related key value also changes with the selected option. I tried making a function that should do this like,

void Serializer::SerializeDisplaySettings(const std::string& filepath, wxFont& font)
{
    YAML::Emitter out;

    std::string fontFace = font.GetFaceName().ToStdString();
    int fontSize = font.GetPointSize();

    std::ifstream ifin(filepath);

    try
    {
        YAML::Node config = YAML::LoadAllFromFile(filepath)[1];

        auto display = config["Display"];

        if (auto fontSetting = display["Font"])
        {
            wxLogDebug("Changing font settings");
            wxLogDebug("Font face: %s", fontFace);
            wxLogDebug("Font size: %d", fontSize);

            out << YAML::Key << fontSetting["Family"] << YAML::Value << fontFace;
            out << YAML::Key << fontSetting["Size"] << YAML::Value << fontSize;

            std::ofstream ofout(filepath);
            ofout << out.c_str();
        }
        else
        {
            wxLogDebug("Error! Cannot fetch values.");
        }
    }

    catch(const YAML::ParserException& ex)
    {
        std::cout << ex.what() << std::endl;
    }
}

but this deletes the whole and fills just font name and size. How do I modify those values without changing/deleting whole file?

apoorv569
  • 143
  • 1
  • 12

1 Answers1

1

You override the file, of course it only contains the items you gave to the emitter out. If you want to modify the loaded file, you should update the values in config and write all of it back out:

fontSetting["Family"] = fontFace;
fontSetting["Size"] = fontSize;
out << config;
std::ofstream ofout(filepath);
ofout << out.c_str();

To get the other content as well, do this when loading:

auto docs = YAML::LoadAllFromFile(filepath);
out << YAML::Comment("Hello") << YAML::BeginDoc << docs[0] << YAML::EndDoc;
YAMl::Node config = docs[1];
flyx
  • 35,506
  • 7
  • 89
  • 126
  • Thanks this works, but it removes my comments and `YAML` Doc as well. – apoorv569 Mar 03 '21 at 11:15
  • 1
    @apoorv569 Added code to preserve other document. – flyx Mar 03 '21 at 11:37
  • I appreciate the code you provided. It fixes the problem, but its also messing with my spacing and new lines, in the original post you see there are new lines after all parent nodes, i.e before `Window`, `Media`, `Display` and `Import_Dir`. Do I have to manually edit the file as such it adds those new lines again when editing. Because as I add more items to the file, it will get very tedious to add back those comments and all every time I edit the file. – apoorv569 Mar 03 '21 at 11:51
  • 1
    @apoorv569 You can't preserve all formatting when loading and modifying a YAML file, see [this question](https://stackoverflow.com/q/60891174/347964) for details. It's just not how YAML works. As you can see in [EventHandler](https://github.com/jbeder/yaml-cpp/blob/master/include/yaml-cpp/eventhandler.h#L18), the parser does not supply events for comments, non-content newlines and so on, so they are unrecoverably lost. – flyx Mar 03 '21 at 12:01
  • I see, do you recommend any other data serialization language that is good alternative to `yaml` in case I change my mind in the future to switch to another language ? – apoorv569 Mar 03 '21 at 12:25
  • 1
    @apoorv569 XML is specified to preserve everything on loading, if that's what you're after. Of course, there is a reason why lots of devs favour JSON/YAML/TOML etc. Those languages are written for user-friendliness, which includes using whitespace for formatting that is ignored by the parser and is the main reason why you can't replicate a loaded file exactly. – flyx Mar 03 '21 at 12:32
  • I see, thank you, I will look into other languages, but for now I'm sticking with `yaml`. – apoorv569 Mar 03 '21 at 13:41