【问题标题】:Parsing heterogeneous data using Boost::Spirit使用 Boost::Spirit 解析异构数据
【发布时间】:2015-01-26 13:43:43
【问题描述】:

我正在尝试弄清楚如何解决以下问题。

我有以下格式的结构:

struct Data
{
     time_t timestamp;
     string id;
     boost::optional<int> data1;
     boost::optional<string> data2;
     // etc...
};

这应该被解析为以下格式的单行字符串:

human_readable_timestamp;id;key1=value1 key2=value2.....

当然,键的顺序不必与结构中元素的顺序相匹配。

Boost::Spirit 是否适合这种类型的数据?我该如何处理?我已经浏览了这些示例,但我无法从示例中获得符合我要求的代码。

【问题讨论】:

    标签: c++ boost boost-spirit boost-spirit-qi


    【解决方案1】:

    您可以使用排列解析器。我在这里做了一个非常相似的例子:

    如果您有重复键,那么使用Kleene* 可能更有意义,也许

    1. 用语义动作来分配属性/或/
    2. 使用属性自定义点来分配结果
    3. PS。另请查看 Spirit Repository (Boost Qi Composing rules using Functions) 中的关键字解析器

    如果您不想使用语义操作 (Boost Spirit: "Semantic actions are evil"?),您可以稍微调整结构,以便在使用 data 元素的排列时匹配自动合成的属性类型:

    struct Data
    {
        boost::posix_time::ptime timestamp;
        std::string id;
        struct Fields {
            boost::optional<int> data1;
            boost::optional<std::string> data2;
        } fields;
    };
    

    现在解析器可以是:

        timestamp = stream;
    
        text  = lexeme [ '"' >> *~char_('"') >> '"' ];
        data1 = "key1" >> lit('=') >> int_;
        data2 = "key2" >> lit('=') >> text;
        id    = lexeme [ *~char_(';') ];
    
        start = timestamp >> ';' >> id >> ';' >> (data1 ^ data2);
    

    更新

    对于评论,使其“有弹性”。我最终放弃了排列解析器,并采用了第一种编号方法(Kleene star with semantic actions 方法)。

        id     = lexeme [ *~char_(';') ];
    
        auto data1 = bind(&Data::Fields::data1, _val);
        auto data2 = bind(&Data::Fields::data2, _val);
    
        other  = lexeme [ +(graph-'=') ] >> '=' >> (real_|int_|text);
    
        fields = *(
                    ("key1" >> lit('=') >> int_) [ data1 = _1 ]
                  | ("key2" >> lit('=') >> text) [ data2 = _1 ]
                  | other
                  );
    
        start  = timestamp >> ';' >> id >> -(';' >> fields);
    

    这会改变以下几个方面:

    • 为了能够跳过“其他”字段,我需要为“其他”字段提出合理的语法:

      other  = lexeme [ +(graph-'=') ] >> '=' >> (real_|int_|text);
      

      (允许键由除= 之外的任何非空格组成,后跟=,后跟数字(渴望)或文本)。

    • 我扩展了文本的概念以支持流行的引用/转义方案:

      text   = lexeme [ 
                  '"' >> *('\\' >> char_ | ~char_('"')) >> '"'
                | "'" >> *('\\' >> char_ | ~char_("'")) >> "'"
                | *graph 
             ];
      
    • 它允许重复相同的键(在这种情况下,它保留上次看到的有效值)。

    • 如果您想禁止无效值,请将&gt;&gt; int_&gt;&gt; text 替换为&gt; int_&gt; textexpectation parser)。

    我用一些具有挑战性的案例扩展了测试用例:

        2015-Jan-26 00:00:00;id
        2015-Jan-26 14:59:24;id;key2="value"
        2015-Jan-26 14:59:24;id;key2="value" key1=42
        2015-Jan-26 14:59:24;id;key2="value" key1=42 something=awful __=4.74e-10 blarg;{blo;bloop='whatever \'ignor\'ed' key2="new} \"value\""
        2015-Jan-26 14:59:24.123;id;key1=42 key2="value" 
    

    现在打印出来了

    ----------------------------------------
    Parsing '2015-Jan-26 00:00:00;id'
    Parsing success
    2015-Jan-26 00:00:00    id
    data1: --
    data2: --
    ----------------------------------------
    Parsing '2015-Jan-26 14:59:24;id;key2="value"'
    Parsing success
    2015-Jan-26 14:59:24    id
    data1: --
    data2:  value
    ----------------------------------------
    Parsing '2015-Jan-26 14:59:24;id;key2="value" key1=42'
    Parsing success
    2015-Jan-26 14:59:24    id
    data1:  42
    data2:  value
    ----------------------------------------
    Parsing '2015-Jan-26 14:59:24;id;key2="value" key1=42 something=awful __=4.74e-10 blarg;{blo;bloop='whatever \'ignor\'ed' key2="new} \"value\""'
    Parsing success
    2015-Jan-26 14:59:24    id
    data1:  42
    data2:  new} "value"
    ----------------------------------------
    Parsing '2015-Jan-26 14:59:24.123;id;key1=42 key2="value" '
    Parsing success
    2015-Jan-26 14:59:24.123000 id
    data1:  42
    data2:  value
    

    Live On Coliru

    //#define BOOST_SPIRIT_DEBUG
    #include <boost/optional/optional_io.hpp>
    #include <boost/date_time/posix_time/posix_time.hpp>
    #include <boost/date_time/posix_time/posix_time_io.hpp>
    #include <boost/fusion/adapted/struct.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    
    namespace qi = boost::spirit::qi;
    namespace phx = boost::phoenix;
    
    struct Data
    {
        boost::posix_time::ptime timestamp;
        std::string id;
        struct Fields {
            boost::optional<int> data1;
            boost::optional<std::string> data2;
        } fields;
    };
    
    BOOST_FUSION_ADAPT_STRUCT(Data::Fields,
            (boost::optional<int>, data1)
            (boost::optional<std::string>, data2)
        )
    
    BOOST_FUSION_ADAPT_STRUCT(Data,
            (boost::posix_time::ptime, timestamp)
            (std::string, id)
            (Data::Fields, fields)
        )
    
    template <typename It, typename Skipper = qi::space_type>
    struct grammar : qi::grammar<It, Data(), Skipper> {
        grammar() : grammar::base_type(start) {
            using namespace qi;
            timestamp = stream;
    
            real_parser<double, strict_real_policies<double> > real_;
    
            text   = lexeme [ 
                        '"' >> *('\\' >> char_ | ~char_('"')) >> '"'
                      | "'" >> *('\\' >> char_ | ~char_("'")) >> "'"
                      | *graph 
                   ];
    
            id     = lexeme [ *~char_(';') ];
    
            auto data1 = bind(&Data::Fields::data1, _val);
            auto data2 = bind(&Data::Fields::data2, _val);
    
            other  = lexeme [ +(graph-'=') ] >> '=' >> (real_|int_|text);
    
            fields = *(
                        ("key1" >> lit('=') >> int_) [ data1 = _1 ]
                      | ("key2" >> lit('=') >> text) [ data2 = _1 ]
                      | other
                      );
    
            start  = timestamp >> ';' >> id >> -(';' >> fields);
    
            BOOST_SPIRIT_DEBUG_NODES((timestamp)(id)(start)(text)(other)(fields))
        }
      private:
        qi::rule<It,                                 Skipper> other;
        qi::rule<It, std::string(),                  Skipper> text, id;
        qi::rule<It, boost::posix_time::ptime(),     Skipper> timestamp;
        qi::rule<It, Data::Fields(),                 Skipper> fields;
        qi::rule<It, Data(),                         Skipper> start;
    };
    
    int main() {
        using It = std::string::const_iterator;
        for (std::string const input : {
                "2015-Jan-26 00:00:00;id",
                "2015-Jan-26 14:59:24;id;key2=\"value\"",
                "2015-Jan-26 14:59:24;id;key2=\"value\" key1=42",
                "2015-Jan-26 14:59:24;id;key2=\"value\" key1=42 something=awful __=4.74e-10 blarg;{blo;bloop='whatever \\'ignor\\'ed' key2=\"new} \\\"value\\\"\"",
                "2015-Jan-26 14:59:24.123;id;key1=42 key2=\"value\" ",
                })
        {
            std::cout << "----------------------------------------\nParsing '" << input << "'\n";
            It f(input.begin()), l(input.end());
            Data parsed;
            bool ok = qi::phrase_parse(f,l,grammar<It>(),qi::space,parsed);
    
            if (ok) {
                std::cout << "Parsing success\n";
                std::cout << parsed.timestamp << "\t" << parsed.id << "\n";
                std::cout << "data1: " << parsed.fields.data1 << "\n";
                std::cout << "data2: " << parsed.fields.data2 << "\n";
            } else {
                std::cout << "Parsing failed\n";
            }
    
            if (f!=l)
                std::cout << "Remaining unparsed: '" << std::string(f,l) << "'\n";
        }
    }
    

    【讨论】:

    • 真的很酷的答案。我有一个简短的问题。有没有办法让它对出现的未知键有弹性?如果它在最后没关系,它只会保持未解析,但如果它出现在开头它会​​阻止解析。
    • 是的。不过,您将不得不稍等片刻。我在晚上工作
    • @Let_Me_Be 更新了语法(见下文更新),带来了一些有趣的变化/效果。还有 Live On Coliru
    • 只是一个简短的澄清请求。为什么other = lexeme [ +(graph-'=') ] &gt;&gt; '=' &gt;&gt; (real_|int_|text); 而不仅仅是other = lexeme [ +(graph-'=') ] &gt;&gt; '=' &gt;&gt; text;
    • @Let_Me_Be 就像我说的,我必须编造一些东西。我没有理由假设值中不会出现空格,因此引用的值是给定的。然后,(出于习惯)拼出支持的其他类型是有意义的(毕竟这是语法)。是的,你可以简化它,但经验告诉我它很少值得(除非分析器告诉你,在这种情况下分支重新排序可能更合适)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-05
    • 2019-07-16
    • 1970-01-01
    • 1970-01-01
    • 2013-08-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多