使用 boost::p rogram_options 来解析文本文件是个好主意吗?

It's a good idea to use boost::program_options to parse a text file?

本文关键字:文件 文本 好主意 boost rogram options 使用      更新时间:2023-10-16

我必须处理许多语法和语义定义良好的文件,例如:

  • 第一行是一个带有特殊信息的标题
  • 其他行在行的开头包含一个键值,告诉您如何解析和处理该行的内容
  • 如果有注释,则以给定的标记开头
  • 等等

现在,据我所知,boost::program_options做了几乎相同的工作,但我只关心导入这些文本文件的内容,而不需要任何额外的工作,只需解析它并将其存储在我的数据结构中。

对我来说,关键的一步是我希望能够用进行解析

  • 正则表达式,因为我需要检测不同的语义,而且我真的无法想象其他方法来做到这一点
  • 错误检查(损坏的文件,即使在解析整个文件后也不匹配的密钥,等等…)

那么,我可以用这个图书馆做这份工作吗?还有更实用的方法吗?

好的,Spirit语法的起点

_Name    = "newmtl" >> lexeme [ +graph ];
_Ns      = "Ns"     >> double_;
_Ka      = "Ka"     >> double_ >> double_ >> double_;
_Kd      = "Kd"     >> double_ >> double_ >> double_;
_Ks      = "Ks"     >> double_ >> double_ >> double_;
_d       = "d"      >> double_;
_illum  %= "illum"  >> qi::int_ [ _pass = (_1>=0) && (_1<=10) ];
comment  = '#' >> *(char_ - eol);
statement=
         comment
       | _Ns    [ bind(&material::_Ns, _r1) = _1 ]
       | _Ka    [ bind(&material::_Ka, _r1) = _1 ]
       | _Kd    [ bind(&material::_Kd, _r1) = _1 ]
       | _Ks    [ bind(&material::_Ks, _r1) = _1 ]
       | _d     [ bind(&material::_d,  _r1) = _1 ]
       | _illum [ bind(&material::_illum, _r1) = _1 ]
       ;
_material = -comment % eol
        >> _Name [ bind(&material::_Name, _val) = _1 ] >> eol
        >> -statement(_val) % eol;
start = _material % -eol;

我只从您的示例文件中实现了MTL文件子集语法。

注意:这是一个相当简单的语法。但是,你知道,第一件事是第一位的。实际上,我可能会考虑使用spirit存储库中的关键字列表解析器。它具有"要求"不同"字段类型"出现一定次数的功能。

注意:灵因果报应(以及大约50行其他代码)仅用于演示目的。

untitled.mtl 包含以下内容

# Blender MTL File: 'None'
# Material Count: 2
newmtl None
Ns 0
Ka 0.000000 0.000000 0.000000
Kd 0.8 0.8 0.8
Ks 0.8 0.8 0.8
d 1
illum 2
# Added just for testing:
newmtl Demo
Ns 1
Ks 0.9 0.9 0.9
d 42
illum 7

输出读取

phrase_parse -> true
remaining input: ''
void dump(const T&) [with T = std::vector<blender::mtl::material>]
-----
material {
    Ns:0
    Ka:{r:0,g:0,b:0}
    Kd:{r:0.8,g:0.8,b:0.8}
    Ks:{r:0.8,g:0.8,b:0.8}
    d:1
    illum:2(Highlight on)
}
material {
    Ns:1
    Ka:(unspecified)
    Kd:(unspecified)
    Ks:{r:0.9,g:0.9,b:0.9}
    d:42
    illum:7(Transparency: Refraction on/Reflection: Fresnel on and Ray trace on)
}
-----

这是的列表

#define BOOST_SPIRIT_USE_PHOENIX_V3
#define BOOST_SPIRIT_DEBUG
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/karma.hpp> // for debug output/streaming
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
namespace qi = boost::spirit::qi;
namespace phx= boost::phoenix;
namespace wavefront { namespace obj
{
} }
namespace blender { namespace mtl // material?
{
    struct Ns { int exponent; }; // specular exponent
    struct Reflectivity { double r, g, b; };
    using Name = std::string;
    using Ka   = Reflectivity;
    using Kd   = Reflectivity;
    using Ks   = Reflectivity;
    using dissolve_factor = double;
    enum class illumination_model {
            color,          // 0     Color on and Ambient off
            color_ambient,  // 1     Color on and Ambient on
            highlight,      // 2     Highlight on
            reflection_ray, // 3     Reflection on and Ray trace on
            glass_ray,      // 4     Transparency: Glass on
                            //       Reflection: Ray trace on
            fresnel_ray,    // 5     Reflection: Fresnel on and Ray trace on
            refract_ray,    // 6     Transparency: Refraction on
                            //       Reflection: Fresnel off and Ray trace on
            refract_ray_fresnel,// 7 Transparency: Refraction on
                            //       Reflection: Fresnel on and Ray trace on
            reflection,     // 8     Reflection on and Ray trace off
            glass,          // 9     Transparency: Glass on
                            //       Reflection: Ray trace off
            shadow_invis,   // 10    Casts shadows onto invisible surfaces
    };
    struct material
    {
        Name                                _Name;
        boost::optional<Ns>                 _Ns;
        boost::optional<Reflectivity>       _Ka;
        boost::optional<Reflectivity>       _Kd;
        boost::optional<Reflectivity>       _Ks;
        boost::optional<dissolve_factor>    _d;
        boost::optional<illumination_model> _illum;
    };
    using mtl_file = std::vector<material>;
    ///////////////////////////////////////////////////////////////////////
    // Debug output helpers
    std::ostream& operator<<(std::ostream& os, blender::mtl::illumination_model o)
    {
        using blender::mtl::illumination_model;
        switch(o)
        {
            case illumination_model::color:               return os << "0(Color on and Ambient off)";
            case illumination_model::color_ambient:       return os << "1(Color on and Ambient on)";
            case illumination_model::highlight:           return os << "2(Highlight on)";
            case illumination_model::reflection_ray:      return os << "3(Reflection on and Ray trace on)";
            case illumination_model::glass_ray:           return os << "4(Transparency: Glass on/Reflection: Ray trace on)";
            case illumination_model::fresnel_ray:         return os << "5(Reflection: Fresnel on and Ray trace on)";
            case illumination_model::refract_ray:         return os << "6(Transparency: Refraction on/Reflection: Fresnel off and Ray trace on)";
            case illumination_model::refract_ray_fresnel: return os << "7(Transparency: Refraction on/Reflection: Fresnel on and Ray trace on)";
            case illumination_model::reflection:          return os << "8(Reflection on and Ray trace off)";
            case illumination_model::glass:               return os << "9(Transparency: Glass on/Reflection: Ray trace off)";
            case illumination_model::shadow_invis:        return os << "10(Casts shadows onto invisible surfaces)";
            default: return os << "ILLEGAL VALUE";
        }
    }
    std::ostream& operator<<(std::ostream& os, blender::mtl::Reflectivity const& o)
    {
        return os << "{r:" << o.r << ",g:" << o.g << ",b:" << o.b << "}";
    }
    std::ostream& operator<<(std::ostream& os, blender::mtl::material const& o)
    {
        using namespace boost::spirit::karma;
        return os << format("material {"
                "ntNs:"    << (auto_  | "(unspecified)")
                << "ntKa:"    << (stream | "(unspecified)")
                << "ntKd:"    << (stream | "(unspecified)")
                << "ntKs:"    << (stream | "(unspecified)")
                << "ntd:"     << (stream | "(unspecified)")
                << "ntillum:" << (stream | "(unspecified)")
                << "n}", o);
    }
} }
BOOST_FUSION_ADAPT_STRUCT(blender::mtl::Reflectivity,(double, r)(double, g)(double, b))
BOOST_FUSION_ADAPT_STRUCT(blender::mtl::Ns, (int, exponent))
BOOST_FUSION_ADAPT_STRUCT(blender::mtl::material,
        (boost::optional<blender::mtl::Ns>, _Ns)
        (boost::optional<blender::mtl::Ka>, _Ka)
        (boost::optional<blender::mtl::Kd>, _Kd)
        (boost::optional<blender::mtl::Ks>, _Ks)
        (boost::optional<blender::mtl::dissolve_factor>, _d)
        (boost::optional<blender::mtl::illumination_model>, _illum))
namespace blender { namespace mtl { namespace parsing
{
    template <typename It>
        struct grammar : qi::grammar<It, qi::blank_type, mtl_file()>
    {
        template <typename T=qi::unused_type> using rule = qi::rule<It, qi::blank_type, T>;
        rule<Name()>               _Name;
        rule<Ns()>                 _Ns;
        rule<Reflectivity()>       _Ka;
        rule<Reflectivity()>       _Kd;
        rule<Reflectivity()>       _Ks;
        rule<dissolve_factor()>    _d;
        rule<illumination_model()> _illum;
        rule<mtl_file()> start;
        rule<material()> _material;
        rule<void(material&)> statement;
        rule<> comment;
        grammar() : grammar::base_type(start)
        {
            using namespace qi;
            using phx::bind;
            using blender::mtl::material;
            _Name    = "newmtl" >> lexeme [ +graph ];
            _Ns      = "Ns"     >> double_;
            _Ka      = "Ka"     >> double_ >> double_ >> double_;
            _Kd      = "Kd"     >> double_ >> double_ >> double_;
            _Ks      = "Ks"     >> double_ >> double_ >> double_;
            _d       = "d"      >> double_;
            _illum  %= "illum"  >> qi::int_ [ _pass = (_1>=0) && (_1<=10) ];
            comment  = '#' >> *(char_ - eol);
            statement=
                    comment
                | _Ns    [ bind(&material::_Ns, _r1) = _1 ]
                | _Ka    [ bind(&material::_Ka, _r1) = _1 ]
                | _Kd    [ bind(&material::_Kd, _r1) = _1 ]
                | _Ks    [ bind(&material::_Ks, _r1) = _1 ]
                | _d     [ bind(&material::_d,  _r1) = _1 ]
                | _illum [ bind(&material::_illum, _r1) = _1 ]
                ;
            _material = -comment % eol
                    >> _Name [ bind(&material::_Name, _val) = _1 ] >> eol
                    >> -statement(_val) % eol;
            start = _material % -eol;
            BOOST_SPIRIT_DEBUG_NODES(
                    (start)
                    (statement)
                    (_material)
                    (_Name) (_Ns) (_Ka) (_Kd) (_Ks) (_d) (_illum)
                    (comment))
        }
};
} } }
#include <fstream>
template <typename T>
void dump(T const& data)
{
    using namespace boost::spirit::karma;
    std::cout << __PRETTY_FUNCTION__
        << "n-----n"
        << format(stream % eol, data)
        << "n-----n";
}
void testMtl(const char* const fname)
{
    std::ifstream mtl(fname, std::ios::binary);
    mtl.unsetf(std::ios::skipws);
    boost::spirit::istream_iterator f(mtl), l;
    using namespace blender::mtl::parsing;
    static const grammar<decltype(f)> p;
    blender::mtl::mtl_file data;
    bool ok = qi::phrase_parse(f, l, p, qi::blank, data);
    std::cout << "phrase_parse -> " << std::boolalpha << ok << "n";
    std::cout << "remaining input: '" << std::string(f,l) << "'n";
    dump(data);
}
int main()
{
    testMtl("untitled.mtl");
}

是的,至少如果您配置的文件像键值对映射一样简单(比如simple.ini)

来自文件:

program_options库允许程序开发人员获得程序选项,即来自用户的(名称、值)对,通过常规方法,如命令行和配置文件

选项可以从任何地方读取。迟早命令行对您的用户来说还不够,您需要配置文件或甚至可能环境变量。可以添加这些您付出了巨大的努力

有关详细信息,请参阅"多源"示例。

但是,如果您需要(或者将来可能需要)更复杂的配置文件(例如XML、JSON或二进制),那么使用独立库是值得的。

这很有可能,但不一定方便。如果您想解析任何您想使用的解析器——是使用现有的解析器还是自己编写解析器取决于您正在解析的内容。

如果无法使用任何现有工具解析您的格式,那么只需编写自己的解析器即可。您可以将lex/flex/flex++与yacc/bison/bison++或boost::spirit一起使用。

我认为,从长远来看,学习维护自己的解析器将比强制调整boost::program_options配置更有用,但不如使用已经符合您需求的现有解析器更方便。