在C++中迭代ini文件,可能使用boost::property_tree::ptree吗?

4
我的任务很简单 - 我只需要解析这样的文件:
Apple = 1
Orange = 2
XYZ = 3950

但我不知道可用的键集。使用 C# 相对容易地解析了这个文件,让我演示一下源代码:

    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;
    }

现在我需要使用C++来做同样的事情。 我想使用boost::property_tree::ptree,但是似乎我无法迭代ini文件。读取ini文件很容易:

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

但是不可能对它进行迭代,可以参考这个问题:Boost程序选项 - 获取部分中的所有条目

问题是:如何在C++上编写类似于上述C#代码的最简单方法?


1
当然可以迭代ptree,事实上这很简单,正如我的回答所示(已更新)(注意:program-options!= property-tree)。 - sehe
2个回答

19

直接回答您的问题:当然可以遍历属性树。事实上这很简单:

#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";
    }
}

这将产生类似以下的输出:

[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

我以前使用 编写了一个非常全面的 Inifile 解析器:

它支持注释(单行和块),引用,转义等。

(额外提供了所有解析元素的精确源位置记录的选项,这是那个问题的主题)。

但是,对于您的目的,我认为我会推荐 Boost Property Tree。


谢谢,我能够进行迭代。但是由于某些原因,值没有被打印出来。也就是说,我只看到了[Cat1] [Cat_2] [Cat_3]作为输出,而没有看到“name1”“name2”等等。我稍后会尝试解决这个问题,但如果您知道解决方案,我将不胜感激 :) - Oleg Vazhnev
就您的需求而言,我建议使用Boost Property Tree。我并不是建议您不要使用ini_parser,也不是建议您使用xml代替ini,而是认为Boost Property Tree更适合您的目的。 - Oleg Vazhnev
@javapowered 你知道,我停在那里是因为你要求这样做。当然,你也可以直接打印这些值。我现在已经添加了它。 - sehe
@javapowered 我推荐使用Property Tree。(注意,“boost/property_tree/ini_parser.hpp”显然是其中的一部分)。这与我在答案的第二部分中链接的另一个更全面的解析器相对应。 - sehe
1
@javapowered 除非你知道你在做什么,否则请不要告诉别人使用std::endl而不是\n。在这种情况下,这是疯狂的。 - sehe
1
我总是使用std::endl而不是\n来遵循C++的风格。我在这里阅读到:https://dev59.com/rnVC5IYBdhLWcg3wsTRi,`\n`可能更快,但使用“<<”的人可能不关心性能 :) - Oleg Vazhnev

0

目前,我已经将问题简化了一些,省略了评论的逻辑(在我看来,评论的逻辑似乎有问题)。

#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";
}

个人认为,我会将注释跳过作为一个过滤流缓冲区来编写,但对于那些不熟悉C++标准库的人来说,这可能是一个绕弯子的解决方案。另一种可能性是使用comment_iterator,它从指定的注释分隔符开始跳过该行的其余部分。我也不太喜欢这种方法,但在某些方面可能更简单。

请注意,我们真正编写的唯一代码是从文件中读取一个单一条目到pair中。从那里开始,istream_iterator几乎处理了所有事情。因此,写一个直接类似于您的函数的实际意义很小 - 我们只需从迭代器初始化映射,然后完成。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接