为了熟悉它,我正在使用Spirit X3编写一个解析器,尽管我非常熟悉Qi,但我仍然在X3中遇到了一些绊脚石。

例如,Qi示例包括一个基本的XML解析器,您应该如何使用Phoenix占位符来匹配先前匹配的值。但是,我只能在X3中弄清楚它:

#include <iostream>
#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/include/adapt_struct.hpp>

namespace x3 = boost::spirit::x3;

namespace mytest
{

struct SimpleElement
{
    std::string tag;
    std::string content;
};

} // namespace bbspirit

BOOST_FUSION_ADAPT_STRUCT
(
    mytest::SimpleElement, tag, content
)

namespace mytest
{

namespace x3 = boost::spirit::x3;
namespace ascii = boost::spirit::x3::ascii;

using x3::lit;
using x3::lexeme;
using ascii::char_;

const x3::rule<class SimpleElementID, SimpleElement> simpleTag = "simpleTag";

auto assignTag = [](auto& ctx)
{
    x3::_val(ctx).tag = x3::_attr(ctx);
};

auto testTag = [](auto& ctx)
{
    x3::_pass(ctx) =
        (x3::_val(ctx).tag == x3::_attr(ctx));
};

auto assignContent = [](auto& ctx)
{
    x3::_val(ctx).content = x3::_attr(ctx);
};

auto const simpleTag_def
    = '['
    >> x3::lexeme[+(char_ - ']')][assignTag]
    >> ']'
    >> x3::lexeme[
        +(char_ - x3::lit("[/"))]
            [assignContent]
    >> "[/"
    >> x3::lexeme[+(char_ - ']')][testTag]
    >> ']'
    ;

BOOST_SPIRIT_DEFINE(simpleTag);

} // namespace bbspirit


int main()
{

const std::string text = "[test]Hello World![/test]";
std::string::const_iterator start = std::begin(text);
const std::string::const_iterator stop = std::end(text);

mytest::SimpleElement element{};

bool result =
    phrase_parse(start, stop, mytest::simpleTag, x3::ascii::space, element);

if (!result)
{
    std::cout << "failed to parse!\n";
}
else
{
    std::cout << "tag    : " << element.tag << '\n';
    std::cout << "content: " << element.content << '\n';
}

}

(链接:https://wandbox.org/permlink/xLZN9plcOwkSKCrD)

这行得通,但是,如果我尝试解析类似[test]Hello [/World[/test]的内容,则该行不通,因为我没有在此处指定正确的省略项:
    >> x3::lexeme[
        +(char_ - x3::lit("[/"))]
            [assignContent]

本质上,我想告诉解析器类似:
    >> x3::lexeme[
        +(char_ - (x3::lit("[/")  << *the start tag* << ']') )]
            [assignContent]

我该怎么做呢?另外,在X3中引用开始标记并稍后对其进行匹配的方式是“最佳”方法吗?还是有更好/更优选的方式?

谢谢!

最佳答案

好问题。

最好的答案是准确地完成XML的工作:在标签数据中禁止[/。实际上,XML禁止使用<(因为它可能正在打开一个嵌套标签,并且您不必潜在地预先读取整个流以查找它是否是有效的子标签)。



接下来,您当然可以像以前一样使用!closeTag属性成员执行否定的超前断言(-closeTagtag)。

改写乱七八糟的规则,这还没有那么糟糕


const x3::rule<class SimpleElementID, SimpleElement, true> simpleTag = "simpleTag";
auto testTag = [](auto& ctx) { _pass(ctx) = (_val(ctx).tag == _attr(ctx)); };

auto openTag     = '[' >> x3::lexeme[+(char_ - ']')] >> ']';
auto closeTag    = "[/" >> x3::lexeme[+(char_ - ']')] [testTag] >> ']';
auto tagContents = x3::lexeme[ +(char_ - closeTag) ];

auto const simpleTag_def
    =  openTag
    >> tagContents
    >> x3::omit [ closeTag ]
    ;

看到它 Live On Coliru

背景

这行得通,但最终变得很笨拙,因为这意味着到处使用语义 Action ,并且还违背了属性引用的自然绑定(bind)。

在盒子外面思考一个垃圾:

在Qi中,您可以为此使用 qi::locals inherited attributes(请参阅docs:MiniXML中的一个非常相似的示例)。

两者都会产生扩展解析器上下文与您的信息的最终效果。

X3没有这种“高级”功能。但是它确实具有扩展上下文的构造块:x3::witt<>(data) [ p ]

x3::with

在这个简单的示例中,这似乎有些矫kill过正,但是在某些时候,您会欣赏如何在规则中使用额外的上下文而不用将属性类型作为人质:
struct TagName{};
auto openTag
    = x3::rule<struct openTagID, std::string, true> {"openTag"}
    = ('[' >> x3::lexeme[+(char_ - ']')] >> ']')
        [([](auto& ctx) { x3::get<TagName>(ctx) = _attr(ctx); })]
    ;
auto closeTag
    = x3::rule<struct closeTagID, std::string, true> {"closeTag"}
    = ("[/" >> x3::lexeme[+(char_ - ']')] >> ']')
        [([](auto& ctx) { _pass(ctx) = (x3::get<TagName>(ctx) == _attr(ctx)); })]
    ;
auto tagContents
    = x3::rule<struct openTagID, std::string> {"tagContents"}
    = x3::lexeme[ +(char_ - closeTag) ];

auto const simpleTag
    = x3::rule<class SimpleElementID, SimpleElement, true> {"simpleTag"}
    = x3::with<TagName>(std::string()) [
        openTag
        >> tagContents
        >> x3::omit [ closeTag ]
    ];

看到它 Live On Coliru

关于c++ - Spirit X3,指的是先前匹配的值,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/62143841/

10-13 04:58