¿Cómo hacer que mi división funcione solo en una línea real y ser capaz de saltear partes citadas de la cadena?

Entonces tenemos una división simple :

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

o boost la división . Y tenemos main simple como:

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

cómo hacerlo funcionar como algo así

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

queremos introducir structures divididas que se mantendrán sin particiones y caracteres que finalizarán el proceso de análisis sintáctico. ¿Cómo hacer tal cosa?

El siguiente código:

 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")); } 

genera:

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

Según los ejemplos que proporcionó, parecía querer que las nuevas líneas cuenten como delimitadores cuando aparecen fuera de las comillas y se representan por el literal \n cuando están dentro de las comillas, así que eso es lo que hace. También agrega la capacidad de tener múltiples delimitadores, como split_here cuando utilicé la prueba.

No estaba seguro de si quieres que las comillas sin igual se dividan de la misma manera que las comillas coincidentes, ya que el ejemplo que diste tiene la cita sin parangón separada por espacios. Este código trata las comillas sin igual como cualquier otro carácter, pero debería ser fácil de modificar si este no es el comportamiento que desea.

La línea:

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

funcionará en la mayoría, si no en todas, las implementaciones del STL, pero no está garantizado para funcionar. Se puede reemplazar por la versión más segura, pero más lenta:

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

Actualizado A modo de ‘gracias’ por otorgar el bono, fui e implementé 4 funciones que inicialmente omití como “No lo vas a necesitar”.

  1. ahora admite columnas parcialmente citadas

    Este es el problema que informó: por ejemplo, con un delimitador , solo test,"one,two",three serían válidos, no de test,one","two","three . Ahora ambos son aceptados

  2. ahora es compatible con expresiones delimitadoras personalizadas

    Solo puede especificar caracteres individuales como delimitadores. Ahora puede especificar cualquier expresión del analizador de Spirit Qi como regla del delimitador. P.ej

      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 

    Tenga en cuenta que esto cambia el comportamiento de la sobrecarga predeterminada

    La versión anterior trataba espacios repetidos como un delimitador único de forma predeterminada. Ahora debe especificarlo explícitamente ( ejemplo ) si lo desea.

  3. ahora admite comillas (“”) dentro de los valores cotizados (en lugar de hacerlos desaparecer)

    Ver el ejemplo de código. Muy simple por supuesto. Tenga en cuenta que la secuencia "" fuera de una construcción entrecomillada todavía representa la cadena vacía (por compatibilidad con, por ejemplo, los formatos de salida CSV existentes que citan cadenas vacías de forma redundante)

  4. Soporta rangos de impulso adicionales a los contenedores como entrada (por ejemplo, char [])

    Bueno, no vas a necesitarlo (pero fue bastante útil para mí solo para poder escribir splitInto("a char array", ...) 🙂

Como esperaba a medias, necesitabas campos parcialmente cotizados (mira tu comentario 1. Bueno, aquí estás (el cuello de botella lo estaba haciendo funcionar consistentemente en diferentes versiones de Boost)).

Introducción

Notas aleatorias y observaciones para el lector:

  • splitInto función de plantilla splitInto admite felizmente lo que le splitInto :

    • entrada desde un vector o std :: string o std :: wstring
    • salida a – algunas combinaciones se muestran en demostración
      • vector (todas las líneas aplanadas)
      • vector> (fichas por línea)
      • list> (si lo prefiere)
      • set> (tokensets en línea exclusivos)
      • … cualquier contenedor que soñas
  • con fines de demostración mostrando la generación de salida de karma (especialmente el cuidado del contenedor nested)
    • nota: \n en la salida se muestra como ? para la comprensión ( safechars )
  • completar con una fontanería práctica para los nuevos usuarios de Spirit (denominación de reglas legibles, define DEBUG definido en caso de que quiera jugar con cosas)
  • puede especificar cualquier expresión de análisis de espíritu para hacer coincidir los delimitadores. Esto significa que al pasar +qi::lit(' ') lugar del valor predeterminado ( ' ' ) saltará los campos vacíos (es decir, los delimitadores repetidos)

Versiones requeridas / probadas

Esto fue comstackdo usando

  • gcc 4.4.5,
  • gcc 4.5.1 y
  • gcc 4.6.1.

Funciona (probado) contra

  • impulso 1.42.0 (posiblemente versiones anteriores también) todo el camino
  • impulsar 1.47.0.

Nota : El aplanamiento de los contenedores de salida solo parece funcionar para Spirit V2.5 (boost 1.47.0).
(Esto podría ser algo tan simple como necesitar una inclusión extra para versiones anteriores?)

¡El código!

 //#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; } 

La salida

Salida de la muestra como se muestra:

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

Actualizar salida para su caso de prueba que falló anteriormente:

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

1 Debo admitir que me reí mucho al leer que 'se estrelló' [ sic ]. Eso se parece mucho a mis usuarios finales. Solo para ser precisos: un locking es un error de aplicación irrecuperable. Lo que se encontró fue un error manejado, y no fue más que un "comportamiento inesperado" desde su punto de vista. De todos modos, eso está arreglado ahora 🙂

Si tu gramática contiene secuencias escapadas, no creo que puedas usar técnicas simples de división.

Necesitarás una máquina de estado.

Aquí hay un código de ejemplo para darle una idea de lo que quiero decir. Esta solución no está totalmente especificada ni implica implícita. Estoy bastante seguro de que tiene errores puntuales que solo se pueden encontrar con pruebas exhaustivas.

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

Este tipo de código es difícil de razonar y mantener. Sin embargo, eso es lo que sucede cuando las personas hacen gramática de mierda. Las tabs fueron diseñadas para delimitar los campos, fomentar su uso cuando sea posible.

Estaría encantado de recomendar otra solución más orientada a objetos.