2013-12-07 2 views
2

мне приходится иметь дело с большим количеством файлов с хорошо определенным синтаксисом и семантической, например:Рекомендуется использовать boost :: program_options для разбора текстового файла?

  • первая строка это заголовок со специальной информацией
  • другие строки, содержащие ключевое значение на начало строки, которые говорят вам, как анализировать и иметь дело с содержанием этой линии
  • если есть комментарий начинается с заданной лексема
  • и т.д. и т.п ...

сейчас boost::program_options, насколько я могу судить, выполняет почти ту же работу, но мне только интересно импортировать содержимое этого текстового файла, без какой-либо дополнительной работы, просто проанализировать его и сохранить в моей структуре данных.

ключевой шаг для меня является то, что я хотел бы быть в состоянии сделать это синтаксический с:

  • регулярных выражений, так как мне нужно, чтобы обнаружить различную семантику, и я не могу представить себе другой способ сделать это
  • проверки ошибок (поврежденные файлы, непарные ключи даже после разбора всего файла, и т.д. и т.п. ...)

так, я могу использовать эту библиотеку для этой работы? Существует более функциональный подход?

+0

Ваш файл похож на 'ini' файл? – P0W

+0

@ P0W нет, и у меня есть разные типы файлов с различным синтаксисом/семантикой – user2485710

+0

Если у вас есть простой пример ввода, я могу бросить вам образец духа – sehe

ответ

3

Хорошо, отправная точка для грамматики духа

_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. Он имеет возможности «требовать» определенного количества случаев для разных «типов полей».

Примечание: Духовная карма (и около ~ 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"); 
} 
+0

Вау, это что-то с бизнес-логикой, я попытаюсь изучить это, спасибо. – user2485710

+0

Это нормально, что я получаю как десятки и десятки xml-подобных выходных данных до получения вывода, который вы опубликовали? Я имею в виду, что я делаю это раньше, плюс то, что вы говорите, вы получаете как результат для этого '.mtl' http://pastebin.com/7Pnvr8zd – user2485710

+0

@ user2485710 Да. Это происходит потому, что определяется 'BOOST_SPIRIT_DEBUG' :) Просто раскомментируйте определение, если вы этого не хотите – sehe

2

Это, скорее всего, возможно, но не обязательно удобно. Если вы хотите анализировать все, что хотите использовать синтаксический анализатор - независимо от того, используете ли вы существующий или записываете самостоятельно, зависит от того, что вы анализируете.

Если нет способа разобрать ваш формат с помощью любого существующего инструмента, просто напишите свой собственный синтаксический анализатор. Вы можете использовать lex/flex/flex ++ с yacc/bison/bison ++ или boost :: spirit.

Я думаю, что в долгосрочной перспективе обучение содержанию собственного анализатора будет более полезным, если вы решительно настроите boost :: program_options config, но не так удобно, как с использованием уже существующего парсера, который уже соответствует вашим потребностям.

+0

Причина, по которой я не использую что-то вроде boost :: spirit, заключается в том, что, на мой взгляд, это слишком много для того, что я должен делать, но поскольку C++ 11 предлагает регулярное выражение, я попытаюсь разобрать это с помощью стандартного кода. – user2485710

+1

Я ручаюсь за Духа над ручным регулярным выражением 9x из 10. Затем снова для важных вещей я вручную рулон парсер. Кроме того, где вы нашли компилятор, который на самом деле (надежно) реализует регулярное выражение C++ 11?(Подсказка: это паровая посуда на данный момент) – sehe

2

Да, по крайней мере, если вы конфигурируете файл так же просто, как карта пар ключ-значение (что-то вроде простого .ini).

От documentation:

Библиотека program_options позволяет разработчикам программ для получения опции программы, то есть (имя, значение) пара от пользователя, с помощью обычных способов, таких как командная строка и конфигурационного файл ,

...

Опции могут быть прочитаны из любого места. Рано или поздно командной строке будет недостаточно для ваших пользователей, и вам понадобятся файлы конфигурации или , возможно, даже переменные окружения. Эти могут быть добавлены без значительных усилий с вашей стороны.

Для получения более подробной информации см. "multiple sources" sample.

Но, если вам нужно (или, возможно, понадобится в будущем) более сложные файлы конфигурации (например, XML, JSON или двоичные), стоит использовать автономную библиотеку.

+0

Дело в том, что в доке нет ничего, что говорит «нет, ты не можешь» или «нет, это не предназначено для этого», но я, вероятно, поеду на С ++ 11 regex и некоторый индивидуальный код ... – user2485710

Смежные вопросы