boost::spirit::qi是一个简单的解释器开发库。可以用来解析文本,构建解释器等。
笔者花了两天时间看完了README文档,并且照着Demo代码写了一遍。感觉语法很复杂。特别是最后的一个XML解析器,很容易就写错了。好在错误信息还是很好理解的。
现在把代码贴出来和大家共享一下。
代码结构如下,
test/CMakeLists.txt
cmake_minimum_required(VERSION 2.6)
if(APPLE)
message(STATUS "This is Apple, do nothing.")
set(CMAKE_MACOSX_RPATH 1)
set(CMAKE_PREFIX_PATH /Users/aabjfzhu/software/vcpkg/ports/cppwork/vcpkg_installed/x64-osx/share )
elseif(UNIX)
message(STATUS "This is linux, set CMAKE_PREFIX_PATH.")
set(CMAKE_PREFIX_PATH /vcpkg/ports/cppwork/vcpkg_installed/x64-linux/share)
endif(APPLE)
project(spirit_xml_error_handling)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-narrowing")
add_definitions(-g)
find_package(ZLIB)
find_package(OpenCV REQUIRED )
find_package(Arrow CONFIG REQUIRED)
find_package(unofficial-brotli REQUIRED)
find_package(unofficial-utf8proc CONFIG REQUIRED)
find_package(Thrift CONFIG REQUIRED)
find_package(glog REQUIRED)
find_package(OpenSSL REQUIRED)
find_package(Boost REQUIRED COMPONENTS
system
filesystem
serialization
program_options
thread
)
find_package(DataFrame REQUIRED)
if(APPLE)
MESSAGE(STATUS "This is APPLE, set INCLUDE_DIRS")
set(INCLUDE_DIRS ${Boost_INCLUDE_DIRS} /usr/local/include /usr/local/iODBC/include /opt/snowflake/snowflakeodbc/include/ ${CMAKE_CURRENT_SOURCE_DIR}/../include/ ${CMAKE_CURRENT_SOURCE_DIR}/../../../include)
elseif(UNIX)
MESSAGE(STATUS "This is linux, set INCLUDE_DIRS")
set(INCLUDE_DIRS ${Boost_INCLUDE_DIRS} /usr/local/include ${CMAKE_CURRENT_SOURCE_DIR}/../include/ ${CMAKE_CURRENT_SOURCE_DIR}/../../../include/)
endif(APPLE)
if(APPLE)
MESSAGE(STATUS "This is APPLE, set LINK_DIRS")
set(LINK_DIRS /usr/local/lib /usr/local/iODBC/lib /opt/snowflake/snowflakeodbc/lib/universal)
elseif(UNIX)
MESSAGE(STATUS "This is linux, set LINK_DIRS")
set(LINK_DIRS ${Boost_INCLUDE_DIRS} /usr/local/lib /vcpkg/ports/cppwork/vcpkg_installed/x64-linux/lib)
endif(APPLE)
if(APPLE)
MESSAGE(STATUS "This is APPLE, set ODBC_LIBS")
set(ODBC_LIBS iodbc iodbcinst)
elseif(UNIX)
MESSAGE(STATUS "This is linux, set LINK_DIRS")
set(ODBC_LIBS odbc odbcinst ltdl)
endif(APPLE)
include_directories(${INCLUDE_DIRS})
LINK_DIRECTORIES(${LINK_DIRS})
file( GLOB test_file_list ${CMAKE_CURRENT_SOURCE_DIR}
struct mini_xml;
// 要么是 mini_xml的递归结构,要么是std::string
// 这个节点的两种属性
using mini_xml_node =
boost::variant, std::string>;
struct mini_xml {
std::string name; // Tag name
std::vector children; // children
};
}; // namespace client
BOOST_FUSION_ADAPT_STRUCT(client::mini_xml,
(std::string,
name)(std::vector, children))
namespace client {
// print out the mini xml tree
int const tabsize = 4;
void tab(int indent) {
for (int i = 0; i < indent; ++i) {
std::cout << ' ';
}
}
struct mini_xml_printer {
mini_xml_printer(int indent_ = 0) : indent{indent_} {}
void operator()(mini_xml const& xml) const;
int indent;
};
struct mini_xml_node_printer : boost::static_visitor<> {
mini_xml_node_printer(int indent_ = 0) : indent{indent_} {}
void operator()(mini_xml const& xml) const {
mini_xml_printer(indent + tabsize)(xml);
}
void operator()(std::string const& text) const {
tab(indent + tabsize);
std::cout << "text: "" << text << '"' << std::endl;
}
int indent;
};
void mini_xml_printer::operator()(mini_xml const& xml) const {
tab(indent);
std::cout << "tag: " << xml.name << std::endl;
tab(indent);
std::cout << '{' << std::endl;
for (auto&& node : xml.children) {
boost::apply_visitor(mini_xml_node_printer(indent), node);
}
tab(indent);
std::cout << '}' << std::endl;
}
// 使用本地变量保存中间临时值, qi::locals
// Refers to: https://www.boost.org/doc/libs/1_66_0/libs/spirit/doc/html/spirit/qi/tutorials/mini_xml___error_handling.html
// Our mini xml grammar definition
template
struct mini_xml_grammar : qi::grammar, ascii::space_type> {
mini_xml_grammar() : mini_xml_grammar::base_type(xml, "xml_grammar") {
using ascii::char_;
using ascii::string;
using qi::on_error;
using qi::fail;
using qi::lexeme;
using qi::lit;
using namespace qi::labels;
using phoenix::at_c;
using phoenix::push_back;
using phoenix::val;
using phoenix::construct;
text %= lexeme[+(char_ - '<')];
node %= (xml | text);
start_tag %=
'<' >> !lit('/') >> lexeme[+(char_ - '>')] >> '>';
// r1表示继承 start_tag中的属性
end_tag = "" >> string(_r1) >> '>';
// 复用start_tag的内容是通过 这里实现的
// 这里其实实现的是一个boost::fusion::tuple
// 0 = start_tag content
// ..... 若干 nodes
// 最后验证end_tag == start_tag,但是不push_back
xml %= start_tag[_a = _1] >>
*node >> end_tag(_a);
// 给各个rule命名
xml.name("xml");
node.name("node");
text.name("text");
start_tag.name("start_tag");
end_tag.name("end_tag");
on_error(
xml,
std::cout <<
val("Error! Expecting ")
<< _4 // What failed
<< val(" here"")
<< construct(_3, _2) // Iterators to error-pos, end
<< val(""")
<< std::endl
);
}
qi::rule text;
qi::rule node;
qi::rule start_tag;
// 继承属性,需要验证start_tag中的字符串
qi::rule end_tag;
qi::rule, ascii::space_type> xml;
};
}; // namespace client
#endif
test/data/1.xml
Tove Jani Reminder Don't forget me this weekend!
test/data/2.xml
程序输出如下,



