c++ - 获取找到给定选项的 INI 文件行号的跨平台方法

标签 c++ parsing boost ini

正在寻找一些能够返回 INI 文件行号的 C++ 库(如 boost::program_options),其中找到了给定的选项或部分。

用例:

  1. 我要求该库在“[SSS]”部分中找到值“vvv”。库返回在“[SSS]”节中找到“vvv”的行号,或 -1。它使我能够说“第 55 行:vvv 必须 < 256”。

  2. 我为部分迭代 INI 文件并验证它们的名称。当发现一些野生的部分时,我告诉:“第 55 行:部分 [Hahaha] 未知”。

更新:我知道“INI 比猛犸象还老”,但目前我必须将大型 Windows 项目移植到跨平台并且不能很快摆脱 .ini 文件。

最佳答案

又一次借机玩了Boost Spirit。这次我要玩 line_pos_iterator .

这是我的劳动成果:https://gist.github.com/1425972

  • 何时POSITIONINFO == 0
    • 输入流
    • 输出是原始字符串(嗯,map<string, map<string, string> > 用于部分)
  • POSITIONINFO == 1

    • 输入缓冲
    • 输出为 textnode_t :

      struct textnode_t {
          int sline, eline, scol, ecol;
          string_t text;
      };
      

      这意味着生成的 map<textnode_t, map<textnode_t, textnode_t> >能够准确报告 (line,col) 开始和结束点标记的各个文本节点。 查看演示的测试输出

    • 评论( # , /* ... */ 风格)已经实现

    • 空格是“容忍的”

      name = value # 使用注释强制包含尾随空格 alternative = escape\with slash\

    • 复制代码
    • 转义 slashes留作练习

    • 如果启用,也会报告带有完整位置信息的错误

注意 C++11 支持不需要,但我用它来转储解析结果。我懒得用 C++03 冗长的迭代器风格来写它。 :)

所有代码、makefile、example.ini 都可以在这里找到:https://gist.github.com/1425972

代码

/* inireader.h */
#pragma once

#define POSITIONINFO 0

#include <map>
#include <string>
#include <iterator>
#include <boost/tuple/tuple_comparison.hpp>

template <typename S=std::string, typename Cmp=std::less<S> >
    class IniFile
{
    public:
    IniFile(Cmp cmp=Cmp()) : _cmp(cmp) 
        {}

    IniFile(const std::string& filename, Cmp cmp=Cmp()) : _cmp(cmp)
        { open(filename); }

    void open(const std::string& filename);

    typedef S string_t;
#if POSITIONINFO
    struct textnode_t
    {
        int sline, eline,
            scol, ecol;
        string_t text;

        operator const string_t&() const { return text; }
        friend std::ostream& operator<<(std::ostream& os, const textnode_t& t)
        { 
            os << "[L:" << t.sline << ",C" << t.scol << " .. L" << t.eline << ",C" << t.ecol << ":";
            for (typename string_t::const_iterator it=t.text.begin(); it!=t.text.end(); ++it)
            switch (*it)
            {
                case '\r' : os << "\\r"; break;
                case '\n' : os << "\\n"; break;
                case '\t' : os << "\\t"; break;
                case '\0' : os << "\\0"; break;
                default:    os << *it  ; break;
            }
            return os << "]"; 
        }

        bool operator<(const textnode_t& o) const 
            { return boost::tie(text/*, sline, eline, scol, ecol*/) <
                     boost::tie(o.text/*, o.sline, o.eline, o.scol, o.ecol*/); }

        textnode_t() : sline(0), eline(0), scol(0), ecol(0) { }
    };
#else
    typedef string_t textnode_t;
#endif

    typedef std::pair<textnode_t, textnode_t>   keyvalue_t;
    typedef std::map<textnode_t, textnode_t>    section_t;
    typedef std::map<textnode_t, section_t> sections_t;

  private:
    Cmp _cmp;
};

///////////////////////////////////////
// template implementation
//#define BOOST_SPIRIT_DEBUG

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/support_istream_iterator.hpp>
#include <boost/spirit/include/support_line_pos_iterator.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/fusion/adapted/std_pair.hpp>
#include <fstream>

namespace qi = boost::spirit::qi;
namespace phx= boost::phoenix;

namespace inireader
{
    struct printer
    {
        printer(std::ostream& os) : _os(os) {}
        std::ostream& _os;

        typedef boost::spirit::utf8_string string;

        void element(string const& tag, string const& value, int depth) const
        {
            for (int i = 0; i < (depth*4); ++i) // indent to depth
                _os << ' ';

            _os << "tag: " << tag;
            if (value != "")
                _os << ", value: " << value;
            _os << std::endl;
        }
    };

    void print_info(std::ostream& os, boost::spirit::info const& what)
    {
        using boost::spirit::basic_info_walker;

        printer pr(os);
        basic_info_walker<printer> walker(pr, what.tag, 0);
        boost::apply_visitor(walker, what.value);
    }

    template <typename It, typename Skipper, typename Ini>
        struct Grammar : qi::grammar<It, typename Ini::sections_t(), Skipper>
    {
        typedef typename Ini::string_t string_t;
        typedef typename Ini::textnode_t textnode_t;

        struct textbuilder
        {
            template <typename> struct result { typedef textnode_t type; };

            textbuilder(It begin) : _begin(begin) { }

            textnode_t operator()(const boost::iterator_range<It>& iters) const
            {
#if !POSITIONINFO
                return textnode_t(std::begin(iters), std::end(iters));
#else
                using boost::spirit::get_line;
                using boost::spirit::get_line_start;
                using boost::spirit::get_column;

                textnode_t element;
                element.text  = string_t       (std::begin(iters)  , std::end(iters));
                element.sline = get_line       (std::begin(iters));
                element.eline = get_line       (std::end(iters));
                It sol        = get_line_start (_begin             , std::begin(iters));
                element.scol  = get_column     (sol                , std::begin(iters));
                element.ecol  = get_column     (sol                , std::end(iters));

                return element;
#endif
            }

          private: 
            const It _begin;
        } makenode;

        Grammar(It begin) : Grammar::base_type(inifile), makenode(begin)
        {
            using namespace qi;
            txt_ch = (lit('\\') > char_) | (char_ - (eol | '#' | "/*"));

            key     = raw [ lexeme [ +(txt_ch - char_("=")) ] ] [ _val = phx::bind(makenode, _1) ];
            value   = raw [ lexeme [ +txt_ch ] ]                [ _val = phx::bind(makenode, _1) ];
            pair   %= key > '=' > value;

            heading  = ('[' > raw [ +~char_(']') ] > ']') [ _val = phx::bind(makenode, _1) ];
            section %= heading >> +eol >> -((pair-heading) % +eol);
            inifile %= -(section % +eol) >> *eol > eoi;

            comment = 
                  ('#' >> *(char_ - eol))
                | ("/*" > *(char_ - "*/") > "*/");

            //BOOST_SPIRIT_DEBUG_NODE(comment);
            //BOOST_SPIRIT_DEBUG_NODE(txt_ch);
            BOOST_SPIRIT_DEBUG_NODE(heading);
            BOOST_SPIRIT_DEBUG_NODE(section);
            BOOST_SPIRIT_DEBUG_NODE(key);
            BOOST_SPIRIT_DEBUG_NODE(value);
            BOOST_SPIRIT_DEBUG_NODE(pair);
            BOOST_SPIRIT_DEBUG_NODE(inifile);
        }

        typedef typename Ini::keyvalue_t keyvalue_t;
        typedef typename Ini::section_t  section_t;
        typedef typename Ini::sections_t sections_t;
        typedef typename string_t::value_type Char;
        qi::rule<It>                        comment;
        qi::rule<It, Char()>                txt_ch;
        qi::rule<It, textnode_t(), Skipper> key, value, heading;
        qi::rule<It, keyvalue_t(), Skipper> pair;
        qi::rule<It, std::pair<textnode_t, section_t>(), Skipper> section;
        qi::rule<It, sections_t(), Skipper> inifile;
    };

    template <typename It, typename Builder>
        typename Builder::template result<void>::type
            fragment(const It& first, const It& last, const Builder& builder)
            {
                size_t len = std::distance(first, last);
                It frag_end = first;
                std::advance(frag_end, std::min(10ul, len));
                return builder(boost::iterator_range<It>(first, frag_end));
            }
}

template <typename S, typename Cmp>
void IniFile<S, Cmp>::open(const std::string& filename)
{
    using namespace qi;

    std::ifstream ifs(filename.c_str());
    ifs.unsetf(std::ios::skipws);

#if POSITIONINFO
    typedef std::string::const_iterator RawIt;
    typedef boost::spirit::line_pos_iterator<RawIt> It;

    typedef rule<It> Skipper;

    std::string buffer(std::istreambuf_iterator<char>(ifs), (std::istreambuf_iterator<char>()));
    It f(buffer.begin()), l(buffer.end());
#else
    typedef boost::spirit::istream_iterator It;
    typedef rule<It> Skipper;

    It f(ifs), l;
#endif

    inireader::Grammar<It, Skipper, IniFile<S, Cmp> > grammar(f);
    Skipper skip = char_(" \t") | grammar.comment;

    try
    {
        sections_t data;
        bool ok = phrase_parse(f, l, grammar, skip, data);
        if (ok)
        {
            std::cout << "Parse success!" << std::endl;

///////// C++11 specific features for quick display //////////
            for (auto& section : data)
            {
                std::cout << "[" << section.first << "]" << std::endl;
                for (auto& pair : section.second)
                    std::cout << pair.first << " = " << pair.second << std::endl;
///////// End C++11 specific /////////////////////////////////
            }
        } else
        {
            std::cerr << "Parse failed" << std::endl;
        }
    } catch (const qi::expectation_failure<It>& e)
    {
        std::cerr << "Exception: " << e.what() << 
              " " << inireader::fragment(e.first, e.last, grammar.makenode) << "... ";
        inireader::print_info(std::cerr, e.what_);
    }
    if (f!=l)
    {
        std::cerr << "Stopped at: '" << inireader::fragment(f, l, grammar.makenode) << "'" << std::endl;
    }
}

演示输入

[Cat1]
name1=100 #skipped

name2=200 \#not \\skipped
name3=   dhfj dhjgfd/* skipped

*/

[Cat_2]
UsagePage=9
Usage=19
Offset=0x1204

/*
[Cat_2_bak]
UsagePage=9
Usage=19
Offset=0x1204
*/

[Cat_3]
UsagePage=12
Usage=39
#Usage4=39
Offset=0x12304

演示输出(POSITIONINFO == 0)

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

演示输出(POSITIONINFO == 1)

Parse success!
[[L:1,C2 .. L1,C6:Cat1]]
[L:2,C2 .. L2,C7:name1] = [L:2,C8 .. L2,C12:100 ]
[L:6,C2 .. L6,C7:name2] = [L:6,C8 .. L6,C27:200 \#not \\skipped]
[L:7,C2 .. L7,C7:name3] = [L:7,C11 .. L7,C22:dhfj dhjgfd]
[[L:13,C3 .. L13,C8:Cat_2]]
[L:16,C2 .. L16,C8:Offset] = [L:16,C9 .. L16,C15:0x1204]
[L:15,C2 .. L15,C7:Usage] = [L:15,C8 .. L15,C10:19]
[L:14,C2 .. L14,C11:UsagePage] = [L:14,C12 .. L14,C13:9]
[[L:25,C3 .. L25,C8:Cat_3]]
[L:29,C2 .. L29,C8:Offset] = [L:29,C9 .. L29,C16:0x12304]
[L:27,C2 .. L27,C7:Usage] = [L:27,C8 .. L27,C10:39]
[L:26,C2 .. L26,C11:UsagePage] = [L:26,C12 .. L26,C14:12]

关于c++ - 获取找到给定选项的 INI 文件行号的跨平台方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8358975/

相关文章:

c++ - Halide 可变域减少

c++ - boost 信号有问题

c++ - 与 boost::condition_variable 的线程同步

c++ - boost::算法::字符串::finder.hpp

c++ - 堆分配的对象构造函数

用于读取写入数组的 csv 的 C++ 程序;然后操作并打印到文本文件中(已经用 matlab 编写)

c++ - OpenGl - 如何重复纹理?

ruby - 我如何检测 Ruby 中的文件结尾?

php - 如何通过输出数组的每一行的特定一个属性来运行 htmlentities()

C#:如何简化这个数字字符串到各种日期部分的代码