Search code examples
c++stringbufferline

How can I read a line from a stringstream only if it contains any newline?


I'm reading some network data into a stringstream as an input_buffer.

The data is ASCII lines separated by a LF char.

The input_buffer may be in a state where there is only a partial line in it.

I'm trying to call getline (), but only when there actually is a new newline char in the stringstream. In other words it should extract completed lines, but leave a partial line in the buffer.

Here is a MVCE:

#include <string>
#include <sstream>
#include <iostream>

int
main (void)
{
  std::stringstream input_buffer;
  input_buffer << "test123\nOK\n";
  while (input_buffer.str ().find ('\n') != std::string::npos)
    {
      std::string line;
      std::getline (input_buffer, line, '\n');
      std::cout << "input_buffer.str ().size: " << input_buffer.str ().size () << "\n";
      std::cout << "line: " << line << "\n";
    }
  return 0;
}

It currently does not terminate, here is a fragment of the output:

input_buffer.str ().size: 11
line: test123
input_buffer.str ().size: 11
line: OK
input_buffer.str ().size: 11
line: 
input_buffer.str ().size: 11
...

How can I read a line from a stringstream only if it contains any newline?

Edit: For clarification here is another code sample with partial input:

#include <string>
#include <sstream>
#include <iostream>
#include <vector>

void
extract_complete_lines_1 (std::stringstream &input_buffer, std::vector<std::string> &lines)
{
  while (input_buffer.str ().find ('\n') != std::string::npos)
    {
      std::string line;
      std::getline (input_buffer, line, '\n');
      lines.push_back (line);
    }
}

void
print_lines (const std::vector<std::string> &v)
{
  for (auto l : v)
    {
      std::cout << l << '\n';
    }
}

int
main (void)
{
  std::vector<std::string> lines;
  std::stringstream input_buffer {"test123\nOK\npartial line"};
  extract_complete_lines_1 (input_buffer, lines);
  print_lines (lines);
  return 0;
}

This should print "test123" and "OK", but not "partial line".


Solution

  • I think that it's not easily possible with std::stringstream. I tried to manipulate the stream position with tellg () and seekg (), but they don't behave like I expected.

    I have found a solution using a std::vector<char> as a buffer:

    #include <string>
    #include <sstream>
    #include <iostream>
    #include <vector>
    #include <iterator>
    #include <algorithm>
    
    void
    extract_complete_lines (std::vector<char> &buf, std::vector<std::string> &lines)
    {
      auto pos = std::end (buf);
      while ((pos = std::find (std::begin (buf), std::end (buf), '\n')) != std::end (buf))
        {
          std::string line (std::begin (buf), pos);
          buf.erase (std::begin(buf), pos + 1);
          lines.push_back (line);
        }
    }
    
    void
    print_lines (const std::vector<std::string> &v)
    {
      for (auto l : v)
        {
          std::cout << l << '\n';
        }
    }
    
    int
    main (void)
    {
      std::vector<std::string> lines;
      const std::string test_input = "test123\nOK\npartial line";
      std::vector<char> input_buffer {std::begin (test_input), std::end (test_input)};
      extract_complete_lines_1 (input_buffer, lines);
      print_lines (lines);
      return 0;
    }
    

    It prints the first two lines as expected and the "partial line" is left in the vector.


    Or even better, a std::vector<char> is not too different from a std::string:

    #include <string>
    #include <sstream>
    #include <iostream>
    #include <vector>
    #include <iterator>
    #include <algorithm>
    
    void
    extract_complete_lines (std::string &buf, std::vector<std::string> &lines)
    {
      std::string::size_type pos;
      while ((pos = buf.find ('\n')) != std::string::npos)
        {
          lines.push_back (buf.substr (0, pos));
          buf.erase (0, pos + 1);
        }
    }
    
    void
    print_lines (const std::vector<std::string> &v)
    {
      for (auto l : v)
        {
          std::cout << l << '\n';
        }
    }
    
    int
    main (void)
    {
      std::vector<std::string> lines;
      std::string input_buffer = "test123\nOK\npartial line";
      extract_complete_lines (input_buffer, lines);
      print_lines (lines);
      return 0;
    }