【问题标题】:Handling utf-8 in Boost.Spirit with utf-32 parser在 Boost.Spirit 中使用 utf-32 解析器处理 utf-8
【发布时间】:2013-10-09 13:45:07
【问题描述】:

我有类似的问题,例如 How to use boost::spirit to parse UTF-8?How to match unicode characters with boost::spirit?,但这些都不能解决我面临的问题。我有一个带有 UTF-8 字符的std::string,我使用u8_to_u32_iterator 包装std::string 并使用unicode 终端,如下所示:

BOOST_NETWORK_INLINE void parse_headers(std::string const & input, std::vector<request_header_narrow> & container) {
        using namespace boost::spirit::qi;
        u8_to_u32_iterator<std::string::const_iterator> begin(input.begin()), end(input.end());
        std::vector<request_header_narrow_utf8_wrapper> wrapper_container;
        parse(
            begin, end,
            *(
                +(alnum|(punct-':'))
                >> lit(": ")
                >> +((unicode::alnum|space|punct) - '\r' - '\n')
                >> lit("\r\n")
            )
            >> lit("\r\n")
            , wrapper_container
            );
        BOOST_FOREACH(request_header_narrow_utf8_wrapper header_wrapper, wrapper_container)
        {
            request_header_narrow header;
            u32_to_u8_iterator<request_header_narrow_utf8_wrapper::string_type::iterator> name_begin(header_wrapper.name.begin()),
                                                                                          name_end(header_wrapper.name.end()),
                                                                                          value_begin(header_wrapper.value.begin()),
                                                                                          value_end(header_wrapper.value.end());
            for(; name_begin != name_end; ++name_begin)
                header.name += *name_begin;
            for(; value_begin != value_end; ++value_begin)
                header.value += *value_begin;
            container.push_back(header);
       }
    }

request_header_narrow_utf8_wrapper 像这样定义并映射到 Fusion(不要介意缺少命名空间声明):

struct request_header_narrow_utf8_wrapper
{
    typedef std::basic_string<boost::uint32_t> string_type;
    std::basic_string<boost::uint32_t> name, value;
};

BOOST_FUSION_ADAPT_STRUCT(
    boost::network::http::request_header_narrow_utf8_wrapper,
    (std::basic_string<boost::uint32_t>, name)
    (std::basic_string<boost::uint32_t>, value)
    )

这很好用,但我想知道我能否设法让解析器直接访问包含 std::string 成员的结构,而不是使用 u32_to_u8_iterator 进行 for-each 循环?我在想一种方法可能是为 std::string 制作一个包装器,该包装器将有一个带有 boost::uint32_t 的赋值运算符,以便解析器可以直接赋值,但还有其他解决方案吗?

编辑

在阅读了更多之后,我最终得到了这个:

namespace boost { namespace spirit { namespace traits {

    typedef std::basic_string<uint32_t> u32_string;

   /* template <>
    struct is_string<u32_string> : mpl::true_ {};*/

    template <> // <typename Attrib, typename T, typename Enable>
    struct assign_to_container_from_value<std::string, u32_string, void>
    {
        static void call(u32_string const& val, std::string& attr) {
            u32_to_u8_iterator<u32_string::const_iterator> begin(val.begin()), end(val.end());
            for(; begin != end; ++begin)
                attr += *begin;
        }
    };

} // namespace traits

} // namespace spirit

} // namespace boost

还有这个

BOOST_NETWORK_INLINE void parse_headers(std::string const & input, std::vector<request_header_narrow> & container) {
        using namespace boost::spirit::qi;
        u8_to_u32_iterator<std::string::const_iterator> begin(input.begin()), end(input.end());
        parse(
            begin, end,
            *(
                as<boost::spirit::traits::u32_string>()[+(alnum|(punct-':'))]
                >> lit(": ")
                >> as<boost::spirit::traits::u32_string>()[+((unicode::alnum|space|punct) - '\r' - '\n')]
                >> lit("\r\n")
            )
            >> lit("\r\n")
            , container
            );
    }

如果这是我能得到的最好的,有什么建议或建议吗?

【问题讨论】:

    标签: c++ boost utf-8 boost-spirit


    【解决方案1】:

    属性特征的另一个工作。出于演示目的,我已经简化了您的数据类型:

    typedef std::basic_string<uint32_t> u32_string;
    
    struct Value 
    {
        std::string value;
    };
    

    现在您可以使用以下方法“自动”进行转换:

    namespace boost { namespace spirit { namespace traits {
        template <> // <typename Attrib, typename T, typename Enable>
            struct assign_to_attribute_from_value<Value, u32_string, void>
            {
                typedef u32_to_u8_iterator<u32_string::const_iterator> Conv;
    
                static void call(u32_string const& val, Value& attr) {
                    attr.value.assign(Conv(val.begin()), Conv(val.end()));
                }
            };
    }}}
    

    考虑一个示例解析器,它解析 UTF-8 中的 JSON 样式字符串,同时还允许 32 位代码点的 Unicode 转义序列:\uXXXX。为此目的,将中间存储设置为 u32_string 很方便:

    ///////////////////////////////////////////////////////////////
    // Parser
    ///////////////////////////////////////////////////////////////
    
    namespace qi         = boost::spirit::qi;
    namespace encoding   = qi::standard_wide;
    //namespace encoding = qi::unicode;
    
    template <typename It, typename Skipper = encoding::space_type>
        struct parser : qi::grammar<It, Value(), Skipper>
    {
        parser() : parser::base_type(start)
        {
            string = qi::lexeme [ L'"' >> *char_ >> L'"' ];
    
            static qi::uint_parser<uint32_t, 16, 4, 4> _4HEXDIG;
    
            char_ = +(
                    ~encoding::char_(L"\"\\")) [ qi::_val += qi::_1 ] |
                        qi::lit(L"\x5C") >> (                    // \ (reverse solidus)
                        qi::lit(L"\x22") [ qi::_val += L'"'  ] | // "    quotation mark  U+0022
                        qi::lit(L"\x5C") [ qi::_val += L'\\' ] | // \    reverse solidus U+005C
                        qi::lit(L"\x2F") [ qi::_val += L'/'  ] | // /    solidus         U+002F
                        qi::lit(L"\x62") [ qi::_val += L'\b' ] | // b    backspace       U+0008
                        qi::lit(L"\x66") [ qi::_val += L'\f' ] | // f    form feed       U+000C
                        qi::lit(L"\x6E") [ qi::_val += L'\n' ] | // n    line feed       U+000A
                        qi::lit(L"\x72") [ qi::_val += L'\r' ] | // r    carriage return U+000D
                        qi::lit(L"\x74") [ qi::_val += L'\t' ] | // t    tab             U+0009
                        qi::lit(L"\x75")                         // uXXXX                U+XXXX
                            >> _4HEXDIG [ qi::_val += qi::_1 ]
                    );
    
            // entry point
            start = string;
        }
    
        private:
        qi::rule<It, Value(),  Skipper> start;
        qi::rule<It, u32_string()> string;
        qi::rule<It, u32_string()> char_;
    };
    

    如您所见,start 规则只是将属性值分配给 Value 结构 - 它隐式调用了我们的 assign_to_attribute_from_value 特征!

    一个简单的测试程序Live on Coliru来证明它确实有效:

    // input assumed to be utf8
    Value parse(std::string const& input) {
        auto first(begin(input)), last(end(input));
    
        typedef boost::u8_to_u32_iterator<decltype(first)> Conv2Utf32;
        Conv2Utf32 f(first), saved = f, l(last);
    
        static const parser<Conv2Utf32, encoding::space_type> p;
    
        Value parsed;
        if (!qi::phrase_parse(f, l, p, encoding::space, parsed))
        {
            std::cerr << "whoops at position #" << std::distance(saved, f) << "\n";
        }
    
        return parsed;
    }
    
    #include <iostream>
    
    int main()
    {
        Value parsed = parse("\"Footnote: ¹ serious busineş\\u1e61\n\"");
        std::cout << parsed.value;
    }
    

    现在观察输出再次以 UTF8 编码:

    $ ./test | tee &gt;(file -) &gt;(xxd)

    Footnote: ¹ serious busineşṡ
    /dev/stdin: UTF-8 Unicode text
    0000000: 466f 6f74 6e6f 7465 3a20 c2b9 2073 6572  Footnote: .. ser
    0000010: 696f 7573 2062 7573 696e 65c5 9fe1 b9a1  ious busine.....
    0000020: 0a        
    

    U+1E61 code-point 已正确编码为[0xE1,0xB9,0xA1]

    【讨论】:

    • 谢谢,如果它解决了我的问题,会检查并接受:)
    • 抱歉耽搁了这么久,但我需要额外的指导——我的解析器(如示例中)总是使用模板参数charunsigned int 调用assign_to_attribute_from_value(这很有意义),是因为语法吗?有没有办法强制它在内部使用int32_t,然后将整个u32_string 分配给std::string?我肯定错过了什么。
    • 是的,这是可能的,这就是我在回答中所做的。作为比较,如果您(想要)以另一种方式使用,请查看我的玩具 JSON 解析器 (specifically https://github.com/sehe/spirit-v2-json/blob/nowide/JSON.cpp#L48) 的 no_wide 分支。
    • 是的,我明白了,但还有一个问题 - 在没有实际创建特定解析器的情况下这可能吗?在您的回答中,您已经做出了明确的解析器声明,而在我的示例中,使用了自动解析功能。我不是懒惰或愚蠢,它只是第 3 方代码,我想了解所需的最小修改集是什么。我可以强制默认 char_parser 使用您显示的分配结构吗?感谢您的帮助。
    • 再次感谢您提供所有信息,我使用我认为可行且不涉及制作新解析器的解决方案编辑了我的问题。如果这是我能做的最好的,你能给出任何提示吗?
    猜你喜欢
    • 1970-01-01
    • 2012-11-07
    • 1970-01-01
    • 1970-01-01
    • 2015-09-25
    • 2012-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多