Как заставить мой сплит работать только на одной реальной строке и быть способным пропускать процитированные части строки?
Итак, у нас есть простой сплит:
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;
vector<string> split(const string& s, const string& delim, const bool keep_empty = true) {
vector<string> result;
if (delim.empty()) {
result.push_back(s);
return result;
}
string::const_iterator substart = s.begin(), subend;
while (true) {
subend = search(substart, s.end(), delim.begin(), delim.end());
string temp(substart, subend);
if (keep_empty || !temp.empty()) {
result.push_back(temp);
}
if (subend == s.end()) {
break;
}
substart = subend + delim.size();
}
return result;
}
или увеличить сплит. И у нас есть простой основной, как:
int main() {
const vector<string> words = split("close no \"\n matter\" how \n far", " ");
copy(words.begin(), words.end(), ostream_iterator<string>(cout, "\n"));
}
как сделать так
close
no
"\n matter"
how
end symbol found.
мы хотим представить разделить structures
это должно быть неразделенным и признаки, которые должны закончить процесс синтаксического анализа. как сделать такую вещь?
3 ответа
Следующий код:
vector<string>::const_iterator matchSymbol(const string & s, string::const_iterator i, const vector<string> & symbols)
{
vector<string>::const_iterator testSymbol;
for (testSymbol=symbols.begin();testSymbol!=symbols.end();++testSymbol) {
if (!testSymbol->empty()) {
if (0==testSymbol->compare(0,testSymbol->size(),&(*i),testSymbol->size())) {
return testSymbol;
}
}
}
assert(testSymbol==symbols.end());
return testSymbol;
}
vector<string> split(const string& s, const vector<string> & delims, const vector<string> & terms, const bool keep_empty = true)
{
vector<string> result;
if (delims.empty()) {
result.push_back(s);
return result;
}
bool checkForDelim=true;
string temp;
string::const_iterator i=s.begin();
while (i!=s.end()) {
vector<string>::const_iterator testTerm=terms.end();
vector<string>::const_iterator testDelim=delims.end();
if (checkForDelim) {
testTerm=matchSymbol(s,i,terms);
testDelim=matchSymbol(s,i,delims);
}
if (testTerm!=terms.end()) {
i=s.end();
} else if (testDelim!=delims.end()) {
if (!temp.empty() || keep_empty) {
result.push_back(temp);
temp.clear();
}
string::const_iterator j=testDelim->begin();
while (i!=s.end() && j!=testDelim->end()) {
++i;
++j;
}
} else if ('"'==*i) {
if (checkForDelim) {
string::const_iterator j=i;
do {
++j;
} while (j!=s.end() && '"'!=*j);
checkForDelim=(j==s.end());
if (!checkForDelim && !temp.empty() || keep_empty) {
result.push_back(temp);
temp.clear();
}
temp.push_back('"');
++i;
} else {
//matched end quote
checkForDelim=true;
temp.push_back('"');
++i;
result.push_back(temp);
temp.clear();
}
} else if ('\n'==*i) {
temp+="\\n";
++i;
} else {
temp.push_back(*i);
++i;
}
}
if (!temp.empty() || keep_empty) {
result.push_back(temp);
}
return result;
}
int runTest()
{
vector<string> delims;
delims.push_back(" ");
delims.push_back("\t");
delims.push_back("\n");
delims.push_back("split_here");
vector<string> terms;
terms.push_back(">");
terms.push_back("end_here");
const vector<string> words = split("close no \"\n end_here matter\" how \n far testsplit_heretest\"another split_here test\"with some\"mo>re", delims, terms, false);
copy(words.begin(), words.end(), ostream_iterator<string>(cout, "\n"));
}
генерирует:
close
no
"\n end_here matter"
how
far
test
test
"another split_here test"
with
some"mo
Основываясь на приведенных вами примерах, вы, похоже, хотели, чтобы символы новой строки считались разделителями, когда они появляются вне кавычек и были представлены литералом \n
когда внутри кавычек, вот что это делает. Это также добавляет возможность иметь несколько разделителей, таких как split_here
как я использовал тест.
Я не был уверен, что вы хотите, чтобы несопоставленные кавычки были разбиты так, как это делают сопоставленные кавычки, поскольку в приведенном вами примере непревзойденная цитата разделена пробелами. Этот код обрабатывает несогласованные кавычки как любой другой символ, но его легко изменить, если это не то поведение, которое вам нужно.
Линия:
if (0==testSymbol->compare(0,testSymbol->size(),&(*i),testSymbol->size())) {
будет работать на большинстве, если не на всех реализациях STL, но это не гарантируется. Его можно заменить на более безопасную, но более медленную версию:
if (*testSymbol==s.substr(i-s.begin(),testSymbol->size())) {
Обновленный в порядке "спасибо" за присуждение бонуса, я пошел и реализовал 4 функции, которые я изначально пропустил как "Вы не будете нуждаться в этом".
теперь поддерживает частично заключенные в кавычки столбцы
Это проблема, о которой вы сообщили: например, с разделителем
,
толькоtest,"one,two",three
будет действительным, а неtest,one","two","three
, Теперь оба принимаютсятеперь поддерживает пользовательские выражения разделителя
Вы можете указать только отдельные символы в качестве разделителей. Теперь вы можете указать любое выражение анализатора Spirit Qi в качестве правила разделителя. Например
splitInto(input, output, ' '); // single space splitInto(input, output, +qi.lit(' ')); // one or more spaces splitInto(input, output, +qi.lit(" \t")); // one or more spaces or tabs splitInto(input, output, (qi::double_ >> !'#') // -- any parse expression
Обратите внимание, что это меняет поведение для перегрузки по умолчанию
Старая версия по умолчанию считала повторяющиеся пробелы одним разделителем. Теперь вы должны явно указать это (2- й пример), если хотите.
теперь поддерживает кавычки ("") внутри значений в кавычках (вместо того, чтобы просто исчезать)
Смотрите пример кода. Довольно просто, конечно. Обратите внимание, что последовательность
""
вне цитируемой конструкции все еще представляет пустую строку (для совместимости, например, с существующими выходными форматами CSV, которые избыточно заключают в кавычки пустые строки)поддержка расширенных диапазонов в дополнение к контейнерам в качестве входных данных (например, char[])
Ну, тебе это не понадобится (но это было довольно удобно для меня, чтобы просто иметь возможность писать
splitInto("a char array", ...)
:)
Как я и ожидал наполовину, вам понадобятся поля с частичными кавычками (см. Ваш комментарий 1. Ну, вот и все (узкое место заставляло его работать одинаково в разных версиях Boost)).
Вступление
Случайные заметки и замечания для читателя:
splitInto
Функция шаблона с радостью поддерживает все, что вы бросаете в нее:- входные данные из вектора или std::string или std::wstring
- вывод на - некоторые комбинации показаны в демо -
vector<string>
(все линии сплющены)vector<vector<string>>
(токены на строку)list<list<string>>
(Если вы предпочитаете)set<set<string>>
(уникальные линукс токенец)- ... любой контейнер, который вы придумали
- для демонстрационных целей, демонстрирующих генерацию выходных данных кармы (особенно заботясь о вложенном контейнере)
- нота:
\n
в выводе отображается как?
для понимания (safechars
)
- нота:
- в комплекте с удобной сантехникой для новых пользователей Spirit (разборчивое наименование правил, комментирует DEBUG, если вы хотите поиграть с вещами)
- Вы можете указать любое выражение разбора Spirit для соответствия разделителям. Это означает, что, передавая
+qi::lit(' ')
вместо значения по умолчанию (' '
) вы пропустите пустые поля (т.е. повторяющиеся разделители)
Требуются / проверены версии
Это было скомпилировано с использованием
- gcc 4.4.5,
- gcc 4.5.1 и
- gcc 4.6.1.
Работает (проверено) против
- Boost 1.42.0 (возможно, и более ранние версии тоже)
- повысить 1.47.0.
Примечание. Сглаживание выходных контейнеров работает только для Spirit V2.5 (повышение 1.47.0).
(это может быть что-то простое, например, необходимость дополнительного включения для более старых версий?)
Код!
//#define BOOST_SPIRIT_DEBUG
#define BOOST_SPIRIT_DEBUG_PRINT_SOME 80
// YAGNI #4 - support boost ranges in addition to containers as input (e.g. char[])
#define SUPPORT_BOOST_RANGE // our own define for splitInto
#include <boost/spirit/include/lex_lexertl.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/karma.hpp>
#include <boost/spirit/include/phoenix.hpp> // for pre 1.47.0 boost only
#include <boost/spirit/version.hpp>
#include <sstream>
namespace /*anon*/
{
namespace phx=boost::phoenix;
namespace qi =boost::spirit::qi;
namespace karma=boost::spirit::karma;
template <typename Iterator, typename Output>
struct my_grammar : qi::grammar<Iterator, Output()>
{
typedef qi::rule<Iterator> delim_t;
//my_grammar(delim_t const& _delim) : delim(_delim),
my_grammar(delim_t _delim) : delim(_delim),
my_grammar::base_type(rule, "quoted_delimited")
{
using namespace qi;
noquote = char_ - '"';
plain = +((!delim) >> (noquote - eol));
quoted = lit('"') > *(noquote | '"' >> char_('"')) > '"';
#if SPIRIT_VERSION >= 0x2050 // boost 1.47.0
mixed = *(quoted|plain);
#else
// manual folding
mixed = *( (quoted|plain) [_a << _1]) [_val=_a.str()];
#endif
// you gotta love simple truths:
rule = mixed % delim % eol;
BOOST_SPIRIT_DEBUG_NODE(rule);
BOOST_SPIRIT_DEBUG_NODE(plain);
BOOST_SPIRIT_DEBUG_NODE(quoted);
BOOST_SPIRIT_DEBUG_NODE(noquote);
BOOST_SPIRIT_DEBUG_NODE(delim);
}
private:
qi::rule<Iterator> delim;
qi::rule<Iterator, char()> noquote;
#if SPIRIT_VERSION >= 0x2050 // boost 1.47.0
qi::rule<Iterator, std::string()> plain, quoted, mixed;
#else
qi::rule<Iterator, std::string()> plain, quoted;
qi::rule<Iterator, std::string(), qi::locals<std::ostringstream> > mixed;
#endif
qi::rule<Iterator, Output()> rule;
};
}
template <typename Input, typename Container, typename Delim>
bool splitInto(const Input& input, Container& result, Delim delim)
{
#ifdef SUPPORT_BOOST_RANGE
typedef typename boost::range_const_iterator<Input>::type It;
It first(boost::begin(input)), last(boost::end(input));
#else
typedef typename Input::const_iterator It;
It first(input.begin()), last(input.end());
#endif
try
{
my_grammar<It, Container> parser(delim);
bool r = qi::parse(first, last, parser, result);
r = r && (first == last);
if (!r)
std::cerr << "parsing failed at: \"" << std::string(first, last) << "\"\n";
return r;
}
catch (const qi::expectation_failure<It>& e)
{
std::cerr << "FIXME: expected " << e.what_ << ", got '";
std::cerr << std::string(e.first, e.last) << "'" << std::endl;
return false;
}
}
template <typename Input, typename Container>
bool splitInto(const Input& input, Container& result)
{
return splitInto(input, result, ' '); // default space delimited
}
/********************************************************************
* replaces '\n' character by '?' so that the demo output is more *
* comprehensible (see when a \n was parsed and when one was output *
* deliberately) *
********************************************************************/
void safechars(char& ch)
{
switch (ch) { case '\r': case '\n': ch = '?'; break; }
}
int main()
{
using namespace karma; // demo output generators only :)
std::string input;
#if SPIRIT_VERSION >= 0x2050 // boost 1.47.0
// sample invocation: simple vector of elements in order - flattened across lines
std::vector<std::string> flattened;
input = "actually on\ntwo lines";
if (splitInto(input, flattened))
std::cout << format(*char_[safechars] % '|', flattened) << std::endl;
#endif
std::list<std::set<std::string> > linewise, custom;
// YAGNI #1 - now supports partially quoted columns
input = "partially q\"oute\"d columns";
if (splitInto(input, linewise))
std::cout << format(( "set[" << ("'" << *char_[safechars] << "'") % ", " << "]") % '\n', linewise) << std::endl;
// YAGNI #2 - now supports custom delimiter expressions
input="custom delimiters: 1997-03-14 10:13am";
if (splitInto(input, custom, +qi::char_("- 0-9:"))
&& splitInto(input, custom, +(qi::char_ - qi::char_("0-9"))))
std::cout << format(( "set[" << ("'" << *char_[safechars] << "'") % ", " << "]") % '\n', custom) << std::endl;
// YAGNI #3 - now supports quotes ("") inside quoted values (instead of just making them disappear)
input = "would like ne\"\"sted \"quotes like \"\"\n\"\" that\"";
custom.clear();
if (splitInto(input, custom, qi::char_("() ")))
std::cout << format(( "set[" << ("'" << *char_[safechars] << "'") % ", " << "]") % '\n', custom) << std::endl;
return 0;
}
Выход
Вывод из образца, как показано:
actually|on|two|lines
set['columns', 'partially', 'qouted']
set['am', 'custom', 'delimiters']
set['', '03', '10', '13', '14', '1997']
set['like', 'nested', 'quotes like "?" that', 'would']
Обновите вывод для ранее неудачного теста:
--server=127.0.0.1:4774/|--username=robota|--userdescr=robot A ? I am cool robot ||--robot|>|echo.txt
1 Должен признаться, я посмеялся, когда прочитал, что "он разбился" [ sic ]. Это очень похоже на моих конечных пользователей. Просто чтобы быть точным: сбой является неисправимой ошибкой приложения. То, с чем вы столкнулись, было обработанной ошибкой, и с вашей точки зрения было не более чем "неожиданным поведением". В любом случае, это исправлено сейчас:)
Если ваша грамматика содержит экранированные последовательности, я не верю, что вы сможете использовать простые методы разделения.
Вам понадобится конечный автомат.
Вот пример кода, чтобы дать вам представление о том, что я имею в виду. Это решение не является ни полностью определенным, ни подразумеваемым правильным. Я совершенно уверен, что в ней есть разовые ошибки, которые можно найти только при тщательном тестировании.
std::vector<std::string> result;
std::string str;
size_t i = 0, last = 0;
for (;;) {
next_token:
last = i;
for (;;) {
switch (str.at(i)) {
case '"': goto handle_quote;
case ' ': goto handle_token;
}
i++;
if (i >= str.size())
goto handle_token;
}
handle_quote:
for (;;) {
switch (str.at(i)) {
case '"': goto handle_token;
}
i++;
if (i >= str.size())
std::runtime_error("invalid format, mismatched quotes");
}
handle_token:
results.push_back(std::string.substr(last, i - last));
if (i >= str.size())
break;
i++;
}
Этот вид кода трудно рассуждать и поддерживать. Это то, что происходит, когда люди делают дерьмовые грамматики. Вкладки предназначены для разграничения полей, поощрения их использования, когда это возможно.
Я был бы в восторге, чтобы объявить еще одно объектно-ориентированное решение.