OK, here's one way to do it. Basically I've made an implementation of std::getline
which accepts a predicate instead of a character. This gets you 2/3's of the way there:
template <class Ch, class Tr, class A, class Pred>
std::basic_istream<Ch, Tr> &getline(std::basic_istream<Ch, Tr> &is, std::basic_string<Ch, Tr, A>& str, Pred p) {
typename std::string::size_type nread = 0;
if(typename std::istream::sentry(is, true)) {
std::streambuf *sbuf = is.rdbuf();
str.clear();
while (nread < str.max_size()) {
int c1 = sbuf->sbumpc();
if (Tr::eq_int_type(c1, Tr::eof())) {
is.setstate(std::istream::eofbit);
break;
} else {
++nread;
const Ch ch = Tr::to_char_type(c1);
if (!p(ch)) {
str.push_back(ch);
} else {
break;
}
}
}
}
if (nread == 0 || nread >= str.max_size()) {
is.setstate(std::istream::failbit);
}
return is;
}
with a functor similar to this:
struct is_newline {
bool operator()(char ch) const {
return ch == '\n' || ch == '\r';
}
};
Now, the only thing left is to determine if you ended on a '\r'
or not..., if you did, then if the next character is a '\n'
, just consume it and ignore it.
EDIT: So to put this all into a functional solution, here's an example:
#include <string>
#include <sstream>
#include <iostream>
namespace util {
struct is_newline {
bool operator()(char ch) {
ch_ = ch;
return ch_ == '\n' || ch_ == '\r';
}
char ch_;
};
template <class Ch, class Tr, class A, class Pred>
std::basic_istream<Ch, Tr> &getline(std::basic_istream<Ch, Tr> &is, std::basic_string<Ch, Tr, A>& str, Pred &p) {
typename std::string::size_type nread = 0;
if(typename std::istream::sentry(is, true)) {
std::streambuf *const sbuf = is.rdbuf();
str.clear();
while (nread < str.max_size()) {
int c1 = sbuf->sbumpc();
if (Tr::eq_int_type(c1, Tr::eof())) {
is.setstate(std::istream::eofbit);
break;
} else {
++nread;
const Ch ch = Tr::to_char_type(c1);
if (!p(ch)) {
str.push_back(ch);
} else {
break;
}
}
}
}
if (nread == 0 || nread >= str.max_size()) {
is.setstate(std::istream::failbit);
}
return is;
}
}
int main() {
std::stringstream ss("this\ris a\ntest\r\nyay");
std::string item;
util::is_newline is_newline;
while(util::getline(ss, item, is_newline)) {
if(is_newline.ch_ == '\r' && ss.peek() == '\n') {
ss.ignore(1);
}
std::cout << '[' << item << ']' << std::endl;
}
}
I've made a couple minor changes to my original example. The Pred p
parameter is now a reference so that the predicate can store some data (specifically the last char
tested). And likewise I made the predicate operator()
non-const so it can store that character.
The in main, I have a string in a std::stringstream
which has all 3 versions of line breaks. I use my util::getline
, and if the predicate object says that the last char
was a '\r'
, then I peek()
ahead and ignore 1
character if it happens to be '\n'
.