如何将 boost tokenizer 定义为 return boost::iterator_range<const char*>
How to define boost tokenizer to return boost::iterator_range<const char*>
我正在尝试解析一个文件,其中每一行都由 ;
分隔的属性组成。每个属性定义为 key value
或 key=value
,其中键和值可以用双引号括起来 "
以允许键和值包含特殊字符,例如空格
,等号 =
或分号 ;
.
为此,我首先使用 boost::algorithm::make_split_iterator, and then, to allow for double quotes, I use boost::tokenizer。
我需要将每个键和值解析为 boost::iterator_range<const char*>
。我尝试按照下面的代码进行编码,但无法构建它。可能是 tokenizer 的定义是正确的,但错误来自 iterator_range
的打印。
如有必要,我可以提供更多信息。
#include <boost/algorithm/string.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/tokenizer.hpp>
boost::iterator_range<const char*> line;
const auto topDelim = boost::token_finder(
[](const char c) { return (c == ';'); },
boost::token_compress_on);
for (auto attrIt = make_split_iterator(line, topDelim); !attrIt.eof() && !attrIt->empty(); attrIt++) {
std::string escape("\");
std::string delim(" =");
std::string quote("\"");
boost::escaped_list_separator<char> els(escape, delim, quote);
boost::tokenizer<
boost::escaped_list_separator<char>,
boost::iterator_range<const char*>::iterator, // how to define iterator for iterator_range?
boost::iterator_range<const char*>
> tok(*attrIt, els);
for (auto t : tok) {
std::cout << t << std::endl;
}
构建错误:
/third_party/boost/boost-1_58_0/include/boost/token_functions.hpp: In instantiation of 'bool boost::escaped_list_separator<Char, Traits>::operator()(InputIterator&, InputIterator, Token&) [with InputIterator = const char*; Token = boost::iterator_range<const char*>; Char = char; Traits = std::char_traits<char>]':
/third_party/boost/boost-1_58_0/include/boost/token_iterator.hpp:70:36: required from 'void boost::token_iterator<TokenizerFunc, Iterator, Type>::initialize() [with TokenizerFunc = boost::escaped_list_separator<char>; Iterator = const char*; Type = boost::iterator_range<const char*>]'
/third_party/boost/boost-1_58_0/include/boost/token_iterator.hpp:77:63: required from 'boost::token_iterator<TokenizerFunc, Iterator, Type>::token_iterator(TokenizerFunc, Iterator, Iterator) [with TokenizerFunc = boost::escaped_list_separator<char>; Iterator = const char*; Type = boost::iterator_range<const char*>]'
/third_party/boost/boost-1_58_0/include/boost/tokenizer.hpp:86:33: required from 'boost::tokenizer<TokenizerFunc, Iterator, Type>::iter boost::tokenizer<TokenizerFunc, Iterator, Type>::begin() const [with TokenizerFunc = boost::escaped_list_separator<char>; Iterator = const char*; Type = boost::iterator_range<const char*>; boost::tokenizer<TokenizerFunc, Iterator, Type>::iter = boost::token_iterator<boost::escaped_list_separator<char>, const char*, boost::iterator_range<const char*> >]'
test.cpp:21:23: required from here
/third_party/boost/boost-1_58_0/include/boost/token_functions.hpp:188:19: error: no match for 'operator+=' (operand types are 'boost::iterator_range<const char*>' and 'const char')
188 | else tok+=*next;
| ~~~^~~~~~~
正如我所说,您想要解析,而不是拆分。具体来说,如果您要将输入拆分为迭代器范围,则必须重复解析工作,例如引用构造以获得预期的(未引用的)值。
我会按照您对 Boost Spirit 的规格进行操作:
using Attribute = std::pair<std::string /*key*/, //
std::string /*value*/>;
using Line = std::vector<Attribute>;
using File = std::vector<Line>;
语法
现在使用 X3 我们可以编写表达式来定义语法:
auto file = x3::skip(x3::blank)[ line % x3::eol ];
在文件中,通常会跳过空白 space (std::isblank
)。
内容由换行符分隔的一行或多行组成。
auto line = attribute % ';';
一行由一个或多个由';'
分隔的属性组成
auto attribute = field >> -x3::lit('=') >> field;
auto field = quoted | unquoted;
一个属性是两个字段,可以选择用 =
分隔。请注意,每个字段都是带引号或未带引号的值。
现在,事情变得有点棘手了:在定义字段规则时,我们希望它们是“词位”,即任何白色space都不会被跳过。
auto unquoted = x3::lexeme[+(x3::graph - ';' - '=')];
请注意 graph
已经排除了白色 space(请参阅
std::isgraph
)。另外我们禁止裸';'
或'='
这样我们就不会运行变成下一个attribute/field.
对于可能包含白色space和/或那些特殊字符的字段,我们定义引用的词素:
auto quoted = x3::lexeme['"' >> *quoted_char >> '"'];
所以,这只是 ""
,中间有任意数量的引号字符,其中
auto quoted_char = '\' >> x3::char_ | ~x3::char_('"');
字符可以是用 \
转义的任何字符,也可以是结束引号以外的任何字符。
测试时间
一起运动吧*Live On Compiler Explorer
for (std::string const& str :
{
R"(a 1)",
R"(b = 2 )",
R"("c"="3")",
R"(a=1;two 222;three "3 3 3")",
R"(b=2;three 333;four "4 4 4"
c=3;four 444;five "5 5 5")",
// special cases
R"("e=" "5")",
R"("f=""7")",
R"("g="="8")",
R"("\"Hello\ World\!\"" '8')",
R"("h=10;i=11;" bogus;yup "nope")",
// not ok?
R"(h i j)",
// allowing empty lines/attributes?
"",
"a 1;",
";",
";;",
R"(a=1;two 222;three "3 3 3"
n=1;gjb 222;guerr "3 3 3"
)",
}) //
{
File contents;
if (parse(begin(str), end(str), parser::file, contents))
fmt::print("Parsed:\n\t- {}\n", fmt::join(contents, "\n\t- "));
else
fmt::print("Not Parsed\n");
}
版画
Parsed:
- {("a", "1")}
Parsed:
- {("b", "2")}
Parsed:
- {("c", "3")}
Parsed:
- {("a", "1"), ("two", "222"), ("three", "3 3 3")}
Parsed:
- {("b", "2"), ("three", "333"), ("four", "4 4 4")}
- {("c", "3"), ("four", "444"), ("five", "5 5 5")}
Parsed:
- {("e=", "5")}
Parsed:
- {("f=", "7")}
Parsed:
- {("g=", "8")}
Parsed:
- {(""Hello\ World\!"", "'8'")}
Parsed:
- {("h=10;i=11;", "bogus"), ("yup", "nope")}
Not Parsed
Not Parsed
Not Parsed
Not Parsed
Not Parsed
Not Parsed
允许空元素
就像用
替换 line
一样简单
auto line = -(attribute % ';');
也允许冗余分隔符:
auto line = -(attribute % +x3::lit(';')) >> *x3::lit(';');
坚持迭代器范围
我在上面解释了为什么我认为这是个坏主意。考虑如何正确解释此行中的 key/value:
"\"Hello\ World\!\"" '8'
您根本不想在 解析器之外处理语法。但是,也许您的数据是一个 10 GB 的内存映射文件:
using Field = boost::iterator_range<std::string::const_iterator>;
using Attribute = std::pair<Field /*key*/, //
Field /*value*/>;
然后将x3::raw[]
添加到词素中:
auto quoted = x3::lexeme[x3::raw['"' >> *quoted_char >> '"']];
auto unquoted = x3::lexeme[x3::raw[+(x3::graph - ';' - '=')]];
我正在尝试解析一个文件,其中每一行都由 ;
分隔的属性组成。每个属性定义为 key value
或 key=value
,其中键和值可以用双引号括起来 "
以允许键和值包含特殊字符,例如空格
,等号 =
或分号 ;
.
为此,我首先使用 boost::algorithm::make_split_iterator, and then, to allow for double quotes, I use boost::tokenizer。
我需要将每个键和值解析为 boost::iterator_range<const char*>
。我尝试按照下面的代码进行编码,但无法构建它。可能是 tokenizer 的定义是正确的,但错误来自 iterator_range
的打印。
如有必要,我可以提供更多信息。
#include <boost/algorithm/string.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/tokenizer.hpp>
boost::iterator_range<const char*> line;
const auto topDelim = boost::token_finder(
[](const char c) { return (c == ';'); },
boost::token_compress_on);
for (auto attrIt = make_split_iterator(line, topDelim); !attrIt.eof() && !attrIt->empty(); attrIt++) {
std::string escape("\");
std::string delim(" =");
std::string quote("\"");
boost::escaped_list_separator<char> els(escape, delim, quote);
boost::tokenizer<
boost::escaped_list_separator<char>,
boost::iterator_range<const char*>::iterator, // how to define iterator for iterator_range?
boost::iterator_range<const char*>
> tok(*attrIt, els);
for (auto t : tok) {
std::cout << t << std::endl;
}
构建错误:
/third_party/boost/boost-1_58_0/include/boost/token_functions.hpp: In instantiation of 'bool boost::escaped_list_separator<Char, Traits>::operator()(InputIterator&, InputIterator, Token&) [with InputIterator = const char*; Token = boost::iterator_range<const char*>; Char = char; Traits = std::char_traits<char>]':
/third_party/boost/boost-1_58_0/include/boost/token_iterator.hpp:70:36: required from 'void boost::token_iterator<TokenizerFunc, Iterator, Type>::initialize() [with TokenizerFunc = boost::escaped_list_separator<char>; Iterator = const char*; Type = boost::iterator_range<const char*>]'
/third_party/boost/boost-1_58_0/include/boost/token_iterator.hpp:77:63: required from 'boost::token_iterator<TokenizerFunc, Iterator, Type>::token_iterator(TokenizerFunc, Iterator, Iterator) [with TokenizerFunc = boost::escaped_list_separator<char>; Iterator = const char*; Type = boost::iterator_range<const char*>]'
/third_party/boost/boost-1_58_0/include/boost/tokenizer.hpp:86:33: required from 'boost::tokenizer<TokenizerFunc, Iterator, Type>::iter boost::tokenizer<TokenizerFunc, Iterator, Type>::begin() const [with TokenizerFunc = boost::escaped_list_separator<char>; Iterator = const char*; Type = boost::iterator_range<const char*>; boost::tokenizer<TokenizerFunc, Iterator, Type>::iter = boost::token_iterator<boost::escaped_list_separator<char>, const char*, boost::iterator_range<const char*> >]'
test.cpp:21:23: required from here
/third_party/boost/boost-1_58_0/include/boost/token_functions.hpp:188:19: error: no match for 'operator+=' (operand types are 'boost::iterator_range<const char*>' and 'const char')
188 | else tok+=*next;
| ~~~^~~~~~~
正如我所说,您想要解析,而不是拆分。具体来说,如果您要将输入拆分为迭代器范围,则必须重复解析工作,例如引用构造以获得预期的(未引用的)值。
我会按照您对 Boost Spirit 的规格进行操作:
using Attribute = std::pair<std::string /*key*/, //
std::string /*value*/>;
using Line = std::vector<Attribute>;
using File = std::vector<Line>;
语法
现在使用 X3 我们可以编写表达式来定义语法:
auto file = x3::skip(x3::blank)[ line % x3::eol ];
在文件中,通常会跳过空白 space (std::isblank
)。
内容由换行符分隔的一行或多行组成。
auto line = attribute % ';';
一行由一个或多个由';'
auto attribute = field >> -x3::lit('=') >> field;
auto field = quoted | unquoted;
一个属性是两个字段,可以选择用 =
分隔。请注意,每个字段都是带引号或未带引号的值。
现在,事情变得有点棘手了:在定义字段规则时,我们希望它们是“词位”,即任何白色space都不会被跳过。
auto unquoted = x3::lexeme[+(x3::graph - ';' - '=')];
请注意 graph
已经排除了白色 space(请参阅
std::isgraph
)。另外我们禁止裸';'
或'='
这样我们就不会运行变成下一个attribute/field.
对于可能包含白色space和/或那些特殊字符的字段,我们定义引用的词素:
auto quoted = x3::lexeme['"' >> *quoted_char >> '"'];
所以,这只是 ""
,中间有任意数量的引号字符,其中
auto quoted_char = '\' >> x3::char_ | ~x3::char_('"');
字符可以是用 \
转义的任何字符,也可以是结束引号以外的任何字符。
测试时间
一起运动吧*Live On Compiler Explorer
for (std::string const& str :
{
R"(a 1)",
R"(b = 2 )",
R"("c"="3")",
R"(a=1;two 222;three "3 3 3")",
R"(b=2;three 333;four "4 4 4"
c=3;four 444;five "5 5 5")",
// special cases
R"("e=" "5")",
R"("f=""7")",
R"("g="="8")",
R"("\"Hello\ World\!\"" '8')",
R"("h=10;i=11;" bogus;yup "nope")",
// not ok?
R"(h i j)",
// allowing empty lines/attributes?
"",
"a 1;",
";",
";;",
R"(a=1;two 222;three "3 3 3"
n=1;gjb 222;guerr "3 3 3"
)",
}) //
{
File contents;
if (parse(begin(str), end(str), parser::file, contents))
fmt::print("Parsed:\n\t- {}\n", fmt::join(contents, "\n\t- "));
else
fmt::print("Not Parsed\n");
}
版画
Parsed:
- {("a", "1")}
Parsed:
- {("b", "2")}
Parsed:
- {("c", "3")}
Parsed:
- {("a", "1"), ("two", "222"), ("three", "3 3 3")}
Parsed:
- {("b", "2"), ("three", "333"), ("four", "4 4 4")}
- {("c", "3"), ("four", "444"), ("five", "5 5 5")}
Parsed:
- {("e=", "5")}
Parsed:
- {("f=", "7")}
Parsed:
- {("g=", "8")}
Parsed:
- {(""Hello\ World\!"", "'8'")}
Parsed:
- {("h=10;i=11;", "bogus"), ("yup", "nope")}
Not Parsed
Not Parsed
Not Parsed
Not Parsed
Not Parsed
Not Parsed
允许空元素
就像用
替换line
一样简单
auto line = -(attribute % ';');
也允许冗余分隔符:
auto line = -(attribute % +x3::lit(';')) >> *x3::lit(';');
坚持迭代器范围
我在上面解释了为什么我认为这是个坏主意。考虑如何正确解释此行中的 key/value:
"\"Hello\ World\!\"" '8'
您根本不想在 解析器之外处理语法。但是,也许您的数据是一个 10 GB 的内存映射文件:
using Field = boost::iterator_range<std::string::const_iterator>;
using Attribute = std::pair<Field /*key*/, //
Field /*value*/>;
然后将x3::raw[]
添加到词素中:
auto quoted = x3::lexeme[x3::raw['"' >> *quoted_char >> '"']];
auto unquoted = x3::lexeme[x3::raw[+(x3::graph - ';' - '=')]];