Wie kann ich meinen Split nur an einer reellen Linie arbeiten lassen und in der Lage sein, Teile der Saite zu überspringen?

Also haben wir eine einfache Trennung :

#include  #include  #include  #include  #include  using namespace std; vector split(const string& s, const string& delim, const bool keep_empty = true) { vector 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; } 

oder Boost-Split . Und wir haben einfache Haupt mögen:

 int main() { const vector words = split("close no \"\n matter\" how \n far", " "); copy(words.begin(), words.end(), ostream_iterator(cout, "\n")); } 

wie man es zu etwas macht wie etwas

 close no "\n matter" how end symbol found. 

Wir wollen Split- structures einführen, die ungesplittet bleiben sollen und Character, die den Parsing-process beenden sollen. Wie macht man so etwas?

Der folgende Code:

 vector::const_iterator matchSymbol(const string & s, string::const_iterator i, const vector & symbols) { vector::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 split(const string& s, const vector & delims, const vector & terms, const bool keep_empty = true) { vector 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::const_iterator testTerm=terms.end(); vector::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 delims; delims.push_back(" "); delims.push_back("\t"); delims.push_back("\n"); delims.push_back("split_here"); vector terms; terms.push_back(">"); terms.push_back("end_here"); const vector 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(cout, "\n")); } 

erzeugt:

 close no "\n end_here matter" how far test test "another split_here test" with some"mo 

Basierend auf den von Ihnen angegebenen Beispielen wollten Sie, dass Zeilenumbrüche als Trennzeichen gezählt werden, wenn sie außerhalb von Anführungszeichen stehen und durch das Literal \n innerhalb von Anführungszeichen dargestellt werden. Es fügt auch die Möglichkeit hinzu, mehrere Trennzeichen zu haben, wie zum Beispiel split_here während ich den Test verwendete.

Ich war mir nicht sicher, ob Sie möchten, dass nicht übereinstimmende Anführungszeichen wie übereinstimmende Anführungszeichen aufgeteilt werden, da das Beispiel, das Sie angegeben haben, das unübertroffene Anführungszeichen durch Leerzeichen getrennt hat. Dieser Code behandelt nicht angepasste Anführungszeichen wie jedes andere Zeichen. Es sollte jedoch leicht zu ändern sein, wenn dies nicht das gewünschte Verhalten ist.

Die Linie:

 if (0==testSymbol->compare(0,testSymbol->size(),&(*i),testSymbol->size())) { 

wird an den meisten, wenn nicht allen, Implementierungen der STL arbeiten, aber es ist nicht garantiert, zu arbeiten. Es kann durch die sicherere, aber langsamere Version ersetzt werden:

 if (*testSymbol==s.substr(is.begin(),testSymbol->size())) { 

Aktualisiert Als Dankeschön für die Bonusprämie habe ich 4 Features implementiert, die ich anfangs als “Du wirst es nicht nötig haben” übersprungen habe.

  1. unterstützt jetzt teilweise zitierte Spalten

    Dies ist das Problem, das Sie gemeldet haben: zB mit einem Begrenzer , nur test,"one,two",three wären gültig, nicht test,one","two","three . Jetzt sind beide akzeptiert

  2. unterstützt jetzt benutzerdefinierte Begrenzerausdrücke

    Sie konnten nur einzelne Zeichen als Trennzeichen angeben. Jetzt können Sie einen beliebigen Spirit Qi Parser-Ausdruck als Begrenzerregel angeben. Z.B

      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 

    Beachten Sie, dass sich das Verhalten für die Standardüberladung ändert

    In der alten Version wurden wiederholte Leerzeichen standardmäßig als einzelnes Trennzeichen behandelt. Sie müssen dies nun explizit angeben ( 2. Beispiel ), wenn Sie es möchten.

  3. unterstützt jetzt Zitate (“”) in zitierten Werten (anstatt sie einfach verschwinden zu lassen)

    Siehe das Codebeispiel. Ganz einfach natürlich. Beachten Sie, dass die Sequenz "" außerhalb eines in Anführungszeichen gesetzten Konstrukts immer noch den leeren String darstellt (zur Kompatibilität mit zB vorhandenen CSV-Ausgabeformaten, die leere Strings redundant angeben).

  4. Unterstützungsbereiche zusätzlich zu Containern als Eingabe (zB char [])

    Nun, du wirst es nicht brauchen (aber es war ziemlich praktisch für mich, um einfach in der Lage zu sein, splitInto("a char array", ...) zu schreiben splitInto("a char array", ...) 🙂

Wie ich halb erwartet hatte, würden Sie teilweise zitierte Felder benötigen (siehe Ihren Kommentar 1) . Nun, hier sind Sie (der Flaschenhals hat es durchgängig über verschiedene Versionen von Boost funktionieren)).

Einführung

Zufällige Anmerkungen und Beobachtungen für den Leser:

  • splitInto Template-function unterstützt glücklich, was auch immer Sie darauf casting:

    • Eingabe von einem Vektor oder Std :: String oder Std :: Wstring
    • Ausgabe an – einige Kombinationen in Demo gezeigt
      • vector (alle Zeilen abgeflacht)
      • vector> (Token pro Zeile)
      • list> (wenn Sie bevorzugen)
      • set> (eindeutige zeilenweise toksensets)
      • … jeden Container, den du dir ausgedacht hast
  • zu Demonstrationszwecken, die die Ausgabe von Karma-Ausgaben anzeigen (insbesondere die Pflege von verschachtelten Containern)
    • Hinweis: \n in der Ausgabe wird als angezeigt ? zum Verständnis ( safechars )
  • komplett mit praktischen Installationsmöglichkeiten für neue Spirit Benutzer (lesbare Regeln benennen, kommentiert DEBUG definiert, falls du mit den Dingen spielen willst)
  • Sie können einen beliebigen Spirit-Syntaxausdruck angeben, um mit Trennzeichen übereinzustimmen. Dies bedeutet, dass Sie durch das Übergeben von +qi::lit(' ') anstelle des Standardwerts ( ' ' ) leere Felder (dh wiederholte Trennzeichen) überspringen.

Versionen erforderlich / getestet

Dies wurde mit verwendet

  • gcc 4.4.5,
  • gcc 4.5.1 und
  • gcc 4.6.1.

Es funktioniert (getestet) gegen

  • Boost 1.42.0 (möglicherweise auch frühere Versionen) den ganzen Weg
  • Boost 1.47.0.

Hinweis : Die Abflachung von Ausgabebehältern scheint nur für Spirit V2.5 (Boost 1.47.0) zu funktionieren.
(Dies könnte etwas Einfaches sein, da für ältere Versionen ein zusätzliches Include benötigt wird.)

Der Code!

 //#define BOOST_SPIRIT_DEBUG #define BOOST_SPIRIT_DEBUG_PRINT_SOME 80 // YAGNI #4 - support boost ranges in addition to containers as input (eg char[]) #define SUPPORT_BOOST_RANGE // our own define for splitInto #include  #include  #include  #include  // for pre 1.47.0 boost only #include  #include  namespace /*anon*/ { namespace phx=boost::phoenix; namespace qi =boost::spirit::qi; namespace karma=boost::spirit::karma; template  struct my_grammar : qi::grammar { typedef qi::rule 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 delim; qi::rule noquote; #if SPIRIT_VERSION >= 0x2050 // boost 1.47.0 qi::rule plain, quoted, mixed; #else qi::rule plain, quoted; qi::rule > mixed; #endif qi::rule rule; }; } template  bool splitInto(const Input& input, Container& result, Delim delim) { #ifdef SUPPORT_BOOST_RANGE typedef typename boost::range_const_iterator::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 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& e) { std::cerr < < "FIXME: expected " << e.what_ << ", got '"; std::cerr << std::string(e.first, e.last) << "'" << std::endl; return false; } } template  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 flattened; input = "actually on\ntwo lines"; if (splitInto(input, flattened)) std::cout < < format(*char_[safechars] % '|', flattened) << std::endl; #endif std::list > 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; } 

Die Ausgabe

Ausgabe von der Probe wie gezeigt:

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

Aktualisieren Sie die Ausgabe für Ihren zuvor fehlgeschlagenen Testfall:

 --server=127.0.0.1:4774/|--username=robota|--userdescr=robot A ? I am cool robot ||--robot|>|echo.txt 

Ich muss zugeben, dass ich gut gelacht habe, als ich gelesen habe, dass es "abgestürzt" ist. Das klingt sehr nach meinen Endnutzern. Um genau zu sein: Ein Absturz ist ein nicht behebbarer Anwendungserrors. In was Sie hineingeraten sind, war ein behandelter Fehler und war aus Ihrer Sicht nichts anderes als "unerwartetes Verhalten". Wie auch immer, das ist jetzt behoben 🙂

Wenn Ihre Grammatik ausgelassene Sequenzen enthält, glaube ich nicht, dass Sie einfache Split-Techniken verwenden können.

Sie benötigen eine Zustandsmaschine.

Hier ist ein Beispielcode , um Ihnen eine Vorstellung davon zu geben, was ich meine. Diese Lösung ist weder vollständig spezifiziert noch implizit korrekt. Ich bin ziemlich sicher, dass es einmalige Fehler gibt, die nur bei gründlichen Tests gefunden werden können.

 std::vector 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++; } 

Diese Art von Code ist schwer zu verstehen und zu pflegen. Das passiert, wenn Leute schlechte Grammatiken machen. Tabs wurden entworfen, um Felder zu begrenzen, ihre Verwendung wenn möglich zu fördern.

Ich wäre begeistert, eine weitere objektorientierte Lösung zu finden.