【问题标题】:It's a good idea to use boost::program_options to parse a text file?使用 boost::program_options 解析文本文件是个好主意吗?
【发布时间】:2013-12-08 09:19:49
【问题描述】:

我必须处理大量具有明确定义的语法和语义的文件,例如:

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

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

对我来说关键的一步是我希望能够通过以下方式进行解析:

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

那么,我可以使用这个库来完成这项工作吗?还有更实用的方法吗?

【问题讨论】:

  • 你的文件是不是和ini文件类似?
  • @P0W 不,我有不同类型的文件,具有不同的语法/语义
  • 如果你有一个简单的示例输入,我可以给你一个 Spirit 示例
  • @sehe en.wikipedia.org/wiki/Wavefront_.obj_file 这是我必须处理的众多文件格式之一(我知道,我可以使用其他库来导入这些东西,但没有一个符合我的要求要求,我还必须解析其他文件类型...)这是 2 个演示文件,1 个 obj 和 1 个 mtl,这是因为 Wavefront 标准通常在 2 个单独的文件中实现 sendspace.com/filegroup/L4QTbmpmb5%2BAYsgZpY%2FO4Q
  • @user2485710 会看看。同时,这看起来很相似:stackoverflow.com/questions/17596559/…

标签: c++ boost c++11


【解决方案1】:

好的,精神语法的起点

_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 文件子集语法。

注意:这是一个相当简单的语法。但是,你知道,第一件事。实际上我可能会考虑使用the keyword list parser from the spirit repository。它具有“要求”不同“字段类型”出现一定次数的功能。

注意: Spirit Karma(以及大约 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 {"
                "\n\tNs:"    << (auto_  | "(unspecified)")
                << "\n\tKa:"    << (stream | "(unspecified)")
                << "\n\tKd:"    << (stream | "(unspecified)")
                << "\n\tKs:"    << (stream | "(unspecified)")
                << "\n\td:"     << (stream | "(unspecified)")
                << "\n\tillum:" << (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");
}

【讨论】:

  • 哇,这里面有业务逻辑,我会努力研究的,谢谢。
  • 在获得您发布的输出之前,我得到几十个类似 xml 的输出是正常的吗?我的意思是我之前得到这个加上你所说的作为这个.mtlpastebin.com/7Pnvr8zd的输出
  • @user2485710 是的。发生这种情况是因为 BOOST_SPIRIT_DEBUG 已定义 :) 如果您不想要它,只需取消注释定义
【解决方案2】:

是的,至少如果您将配置文件像键值对映射一样简单(类似于简单的 .ini)。

来自documentation

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

...

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

详情请见"multiple sources" sample

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

【讨论】:

  • 关键是在文档中没有说“不,你不能”或“不,这不是要那样做”,但我可能会选择 C++ 11 正则表达式和一些自定义代码...
【解决方案3】:

这很可能,但不一定方便。如果你想解析任何你想使用解析器的东西——你是使用现有的还是自己写的取决于你在解析什么。

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

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

【讨论】:

  • 我不使用 boost::spirit 之类的原因是,在我看来,这对于我必须做的事情来说有点矫枉过正,但由于 C++11 提供正则表达式,我会尝试用符合标准的代码来解析它。
  • 我会保证 Spirit 超过手动 regexen 9x(满分 10)。再说一次,对于重要的事情,我会手动滚动解析器。另外,您在哪里找到了一个实际(可靠)实现 c++11 正则表达式的编译器? (提示:现在是蒸汽器皿)
猜你喜欢
  • 1970-01-01
  • 2011-11-15
  • 2023-01-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多