4

My task is trivial - i just need to parse such file:

Apple = 1
Orange = 2
XYZ = 3950

But i do not know the set of available keys. I was parsing this file relatively easy using C#, let me demonstrate source code:

    public static Dictionary<string, string> ReadParametersFromFile(string path)
    {
        string[] linesDirty = File.ReadAllLines(path);
        string[] lines = linesDirty.Where(
            str => !String.IsNullOrWhiteSpace(str) && !str.StartsWith("//")).ToArray();

        var dict = lines.Select(s => s.Split(new char[] { '=' }))
                        .ToDictionary(s => s[0].Trim(), s => s[1].Trim());
        return dict;
    }

Now I just need to do the same thing using c++. I was thinking to use boost::property_tree::ptree however it seems I just can not iterate over ini file. It's easy to read ini file:

boost::property_tree::ptree pt;
boost::property_tree::ini_parser::read_ini(path, pt);

But it is not possible to iterate over it, refer to this question Boost program options - get all entries in section

The question is - what is the easiest way to write analog of C# code above on C++ ?

Community
  • 1
  • 1
Oleg Vazhnev
  • 23,239
  • 54
  • 171
  • 305
  • 1
    _Of course_ it's possible to iterate over a ptree, in fact it is trivial, as my answer now shows (updated) (note: program-options != property-tree) – sehe Apr 21 '13 at 20:13

2 Answers2

19

To answer your question directly: of course iterating a property tree is possible. In fact it's trivial:

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/ini_parser.hpp>

int main()
{
    using boost::property_tree::ptree;
    ptree pt;

    read_ini("input.txt", pt);

    for (auto& section : pt)
    {
        std::cout << '[' << section.first << "]\n";
        for (auto& key : section.second)
            std::cout << key.first << "=" << key.second.get_value<std::string>() << "\n";
    }
}

This results in output like:

[Cat1]
name1=100 #skipped
name2=200 \#not \\skipped
name3=dhfj dhjgfd
[Cat_2]
UsagePage=9
Usage=19
Offset=0x1204
[Cat_3]
UsagePage=12
Usage=39
Offset=0x12304

I've written a very full-featured Inifile parser using before:

It supports comments (single line and block), quotes, escapes etc.

(as a bonus, it optionally records the exact source locations of all the parsed elements, which was the subject of that question).

For your purpose, though, I think I'd recomment Boost Property Tree.

Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
  • thanks, i able to iterate. However by some reason values is not printed. I.e. i do see only [Cat1] [Cat_2] [Cat_3] as output and no "name1" "name2" etc. I will try to solve this problem a little bit later but if you know the solution I would appreciate your help :) – Oleg Vazhnev Apr 21 '13 at 20:51
  • "For your purpose, though, I think I'd recomment Boost Property Tree." - i don't understand what exactly do you recommend. are you recommending not to use `ini_parser`? are you recomending using `xml` instead of `ini` or what? – Oleg Vazhnev Apr 21 '13 at 20:55
  • @javapowered you know, I stopped there because you asked for that. You could, of course, just print the values too. I've added it now – sehe Apr 21 '13 at 20:56
  • @javapowered I'm recommending Property Tree. (Note that "boost/property_tree/ini_parser.hpp" is obviously a part of that). This is as opposed to my (more full featured) other parser I linked to in the second part of the answer. – sehe Apr 21 '13 at 20:58
  • 1
    @javapowered Please, don't [tell people to use `std::endl`](http://stackoverflow.com/revisions/16135573/6) instead of `\n` unless you know what you're doing. In this case, it's in[s]ane. – sehe Apr 21 '14 at 21:00
  • 1
    i'm always using `std::endl` instead of \n to follow c++ style. I read here http://stackoverflow.com/questions/213907/c-stdendl-vs-n that \n must be faster, but people who use "<<" likely not care about performance :) – Oleg Vazhnev Apr 22 '14 at 04:11
0

For the moment, I've simplified the problem a bit, leaving out the logic for comments (which looks broken to me anyway).

#include <map>
#include <fstream>
#include <iostream>
#include <string>

typedef std::pair<std::string, std::string> entry;

// This isn't officially allowed (it's an overload, not a specialization) but is
// fine with every compiler of which I'm aware.
namespace std {
std::istream &operator>>(std::istream &is,  entry &d) { 
    std::getline(is, d.first, '=');
    std::getline(is, d.second);
    return is;
}
}

int main() {
    // open an input file.
    std::ifstream in("myfile.ini");

    // read the file into our map:
    std::map<std::string, std::string> dict((std::istream_iterator<entry>(in)),
                                            std::istream_iterator<entry>());

    // Show what we read:
    for (entry const &e : dict) 
        std::cout << "Key: " << e.first << "\tvalue: " << e.second << "\n";
}

Personally, I think I'd write the comment skipping as a filtering stream buffer, but for those unfamiliar with the C++ standard library, it's open to argument that would be a somewhat roundabout solution. Another possibility would be a comment_iterator that skips the remainder of a line, starting from a designated comment delimiter. I don't like that as well, but it's probably simpler in some ways.

Note that the only code we really write here is to read one, single entry from the file into a pair. The istream_iterator handles pretty much everything from there. As such, there's little real point in writing a direct analog of your function -- we just initialize the map from the iterators, and we're done.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111