Как заставить мой сплит работать только на одной реальной строке и быть способным пропускать процитированные части строки? - PullRequest
3 голосов
/ 15 сентября 2011

Итак, у нас есть простое разделение :

#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 ]

11 голосов
/ 18 сентября 2011

Обновлено В качестве «спасибо» за начисление бонуса я пошел и реализовал 4 функции, которые я изначально пропустил как «Вы не будете нуждаться в этом».

  1. теперь поддерживает частично заключенные в кавычки столбцы

    Это проблема, о которой вы сообщили: например, с разделителем , только test,"one,two",three будет действительнымне test,one","two","three. Теперь оба они приняты.

  2. теперь поддерживает пользовательские выражения разделителя

    В качестве разделителей можно указывать только отдельные символы.Теперь вы можете указать любое выражение анализатора Spirit Qi в качестве разделителя rule .Например,

      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 nd пример ), если вы хотите.

  3. теперь поддерживает кавычки("") внутри значений в кавычках (вместо того, чтобы просто заставить их исчезнуть)

    См. Пример кода.Довольно просто, конечно.Обратите внимание, что последовательность "" вне конструкции в кавычках все еще представляет пустую строку (для совместимости, например, с существующими форматами вывода CSV, которые избыточно заключают в кавычки пустые строки)

  4. поддержка диапазонов повышения в дополнение к контейнерам в качестве входных данных (например, char [])

    Ну, вам это не понадобится (но это было довольно удобно для меня, чтобы просто иметь возможность писать splitInto("a char array", ...):)

Как я и ожидал наполовину, вам понадобилось понадобится частично цитируемых полей (см. Ваш комментарий 1 . Ну, здесьвы (узкое место заставляло его работать одинаково в разных версиях Boost)).

Введение

Случайные заметки и замечания для читателя:

  • splitInto шаблонная функция с радостью поддерживает все, что вы к ней добавляете:

    • вход из вектора или std :: string или std :: wstring
    • вывод в - некоторые комбинации показаны в демонстрационной версии -
      • vector<string> (все линии сплющены)
      • vector<vector<string>> (токены на строку)
      • list<list<string>> (если вы предпочитаете)
      • set<set<string>> (уникальные токенец линии)
      • ... любой контейнер, который вы придумали
  • для демоверсииoses демонстрирует генерацию вывода кармы (особенно заботясь о вложенном контейнере)
    • примечание: \n в выводе отображается как ? для понимания (safechars)
  • в комплекте с удобной сантехникой для новых пользователей Spirit (разборчивое именование правил, комментирует DEBUG, если вы хотите поиграть с вещами)
  • вы можете указать любое выражение разбора Spirit для соответствия разделителям.Это означает, что, передав +qi::lit(' ') вместо значения по умолчанию (' '), вы пропустите пустые поля (то есть повторяющиеся разделители)

Требуемые версии / проверено

Это было скомпилировано с использованием

  • gcc 4.4.5,
  • gcc 4.5.1 и
  • gcc 4.6.1.

Работает (проверено) против

  • boost 1.42.0 (возможно, и более ранних версий) на всем протяжении вплоть до
  • boost 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 ] Это очень похоже на моих конечных пользователей. Просто чтобы быть точным: сбой является неисправимой ошибкой приложения. То, с чем вы столкнулись, было обработанной ошибкой, и с вашей точки зрения было не более чем «неожиданным поведением». В любом случае, это исправлено:)

2 голосов
/ 18 сентября 2011

следующий код:

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())) {
1 голос
/ 16 сентября 2011

Если ваша грамматика содержит экранированные последовательности, я не верю, что вы сможете использовать простые методы разделения.

Вам понадобится конечный автомат.

Вот пример код, чтобы дать вам представление о том, что я имею в виду.Это решение не является ни полностью определенным, ни подразумеваемым правильным.Я вполне уверен, что в нем есть единовременные ошибки, которые могут быть обнаружены только при тщательном тестировании.

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++; 
}

Этот тип кода трудно рассуждать и поддерживать.Это то, что происходит, когда люди делают дерьмовые грамматики.Вкладки были разработаны для разграничения полей и поощрения их использования, когда это возможно.

Я был бы в восторге, если бы высказался по поводу другого, более объектно-ориентированного решения.

...