Hearty
Hearty

Reputation: 543

Parse an INI file

I am building a program using C++/CLI in Visual Studio 2008, I've been trying to save my default users directory setting in a txt file. So far I managed to save it so it look like this inside the txt file.

Input Directory=C:\Input
Output Directory=C:\Output

The question is, how can my program detect the word "Input Directory=" and takes only the words after it?

And is there any way to search for a specific word in text file and replace it in C++/CLI? So I can just search for "C:\Input" and replace it with "D:\Input", for example.

Upvotes: 0

Views: 1023

Answers (3)

Hearty
Hearty

Reputation: 543

After struggling for the whole day finding simple solution, I've decided to omit the "Input Directory = " as suggested by @Hans Passant and do a crude workaround by reading the first line as input and second line as output folder setting, using techniques similar to this.

Thanks to all who have responded.

Upvotes: 0

Adriano Repetti
Adriano Repetti

Reputation: 67128

I suppose it's an INI-like syntax but without sections, to parse it do the following.

In the constructor (for example) of the class you'll use to parse the file:

  • Create a Dictionary<string, string>, you'll keep parsed values there (specify a case-insensitive comparer for keys).
  • Read each line of the file, skip blank lines (or comment lines if you want to support it).
  • Search each string line for the first "=" character. The part on its left is the name of the option and the part on the right is the value.
  • Save them in the dictionary.

In the GetValue() method do this:

  • Search the dictionary for the given key and return it.
  • If there is not any key with the given value simply return the default value.

This is a possible implementation for a simple INI parser (if you do not need the section name simply pass an empty string for it).

using namespace System;
using namespace System::Text;
using namespace System::IO;
using namespace System::Globalization;
using namespace System::Collections::Generic;

namespace Testy
{
    ref class IniParser
    {
    public:
        IniParser()
        {
            _values = gcnew Dictionary<String^, Dictionary<String^, String^>^>(
                StringComparer::InvariantCultureIgnoreCase);
        }

        IniParser(String^ path) 
        {
            _values = gcnew Dictionary<String^, Dictionary<String^, String^>^>(
                StringComparer::InvariantCultureIgnoreCase);

            Load(path);
        }

        void Load(String^ path)
        {
            String^ currentSection = "";
            for each (String^ line in File::ReadAllLines(path))
            {
                if (String::IsNullOrWhiteSpace(line))
                    continue;

                if (line->StartsWith(L";", StringComparison::InvariantCultureIgnoreCase))
                    continue;

                if (line->StartsWith(L"[", StringComparison::InvariantCultureIgnoreCase) && line->EndsWith(L"]", StringComparison::InvariantCultureIgnoreCase))
                {
                    String^ sectionName = line->Substring(1, line->Length - 2);
                    if (String::IsNullOrWhiteSpace(sectionName))
                        continue;

                    currentSection = sectionName;
                }

                array<String^>^ parts = line->Split(gcnew array<wchar_t>(2) { '=' }, 2);
                if (parts->Length == 1)
                    SetString(currentSection, parts[0]->Trim(), "");
                else
                    SetString(currentSection, parts[0]->Trim(), parts[1]->Trim());
            }
        }

        bool ContainsSection(String^ sectionName)
        {
            return _values->ContainsKey(sectionName);
        }

        bool ContainsValue(String^ sectionName, String^ valueName)
        {
            Dictionary<String^, String^>^ values = GetSection(sectionName);
            if (values == nullptr)
                return false;

            return values->ContainsKey(valueName);
        }

        void Clear()
        {
            _values->Clear();
        }

        void SetString(String^ sectionName, String^ valueName, String^ value)
        {
            Dictionary<String^, String^>^ values = GetSection(sectionName);
            if (values == nullptr)
            {
                values = gcnew Dictionary<String^, String^>(StringComparer::InvariantCultureIgnoreCase);
                _values->Add(sectionName, values);
            }

            if (values->ContainsKey(valueName))
                values[valueName] = value;
            else
                values->Add(valueName, value);
        }

        String^ GetString(String^ sectionName, String^ valueName, String^ defaultValue)
        {
            Dictionary<String^, String^>^ values = GetSection(sectionName);
            if (values == nullptr)
                return defaultValue;

            if (values->ContainsKey(valueName))
                return values[valueName];

            return defaultValue;
        }

    private:
        Dictionary<String^, Dictionary<String^, String^>^>^ _values;

        Dictionary<String^, String^>^ GetSection(String^ sectionName)
        {
            if (_values->ContainsKey(sectionName))
                return _values[sectionName];

            return nullptr;
        }
    };
}

You should add error checking and you can omit what you do not need (sections and comments, for example). To "read" different value types you can implement more GetXYZ() methods to parse the result from GetString() to desired value (a float, for example).

Another solution could be to...simply import the native functions to parse INIs. They works quite well!

Upvotes: 1

rerun
rerun

Reputation: 25505

You can read from a file in many ways. One of the benfits of using c++\cli is you can utilize the very convent .net interface.

So you could use File::ReadAllLines and then scan each line for the string "Input Directory"

Upvotes: 1

Related Questions