diff --git a/src/swcio.cpp b/src/swcio.cpp index 203f69eb1bdca8905145edb4edbe86d564682e9b..588d969d5d443d2edbacccc2d49a83b125e56c25 100644 --- a/src/swcio.cpp +++ b/src/swcio.cpp @@ -123,11 +123,46 @@ swc_record::kind parse_value_strict(std::istream &is, const swc_parser &parser) // swc_parser implementation // +std::istream& swc_parser::safe_getline(std::istream& is, std::string& t) +{ + // Based on http://stackoverflow.com/questions/6089231/getting-std-ifstream-to-handle-lf-cr-and-crlf + t.clear(); + + // The characters in the stream are read one-by-one using a std::streambuf. + // That is faster than reading them one-by-one using the std::istream. + // Code that uses streambuf this way must be guarded by a sentry object. + // The sentry object performs various tasks, + // such as thread synchronization and updating the stream state. + + std::istream::sentry se(is, true); + std::streambuf* sb = is.rdbuf(); + + for (;;) { + int c = sb->sbumpc(); + switch (c) { + case '\n': + return is; + case '\r': + if (sb->sgetc() == '\n') + sb->sbumpc(); + return is; + case EOF: + // Also handle the case when the last line has no line ending + if (t.empty()) + is.setstate(std::ios::eofbit); + return is; + default: + t += (char)c; + } + } +} + std::istream &swc_parser::parse_record(std::istream &is, swc_record &record) { while (!is.eof() && !is.bad()) { // consume empty and comment lines first - std::getline(is, linebuff_); + safe_getline(is, linebuff_); + ++lineno_; if (!linebuff_.empty() && !starts_with(linebuff_, comment_prefix_)) break; diff --git a/src/swcio.hpp b/src/swcio.hpp index 70f979cda362f503a81965c274a5cdeb23ea5056..09c89f8112de9379ee3248a82935a410f3a94a3c 100644 --- a/src/swcio.hpp +++ b/src/swcio.hpp @@ -211,6 +211,9 @@ public: std::istream &parse_record(std::istream &is, swc_record &record); + // Getline version supporting both windows and linux line endings + std::istream &safe_getline(std::istream& is, std::string& t); + private: // Read the record from a string stream; will be treated like a single line swc_record parse_record(std::istringstream &is); diff --git a/tests/test_swcio.cpp b/tests/test_swcio.cpp index 5ba0b991a865dfb397e5c1dbbb1e8813e71b628c..fed88fb33909ac6557e843c4dfce96394a5bd6cb 100644 --- a/tests/test_swcio.cpp +++ b/tests/test_swcio.cpp @@ -521,3 +521,37 @@ TEST(swc_parser, from_file_ball_and_stick) EXPECT_TRUE(nest::mc::cell_basic_equality(local_cell, cell)); } + +// check that windows EOL are supported in linux. +// This test is based on the ball_and_stick.swc with windows endings inserted +// manually in a file stream, regression test for issue_34 +TEST(swc_parser, windows_eol) +{ + std::string datadir{ DATADIR }; + auto fname = datadir + "/ball_and_stick.swc"; + std::ifstream fid(fname); + + // Check valid usage + std::stringstream is; + is << "# ball and stick model with\r\n"; + is << "# - soma with radius 12.6157/2\r\n"; + is << "# - dendrite with length 200 and radius 0.5\r\n"; + is << "\r\n"; + is << "1 1 0.0 0.0 0.0 6.30785 - 1\r\n"; + is << "2 2 6.30785 0.0 0.0 0.5 1\r\n"; + is << "3 2 206.30785 0.0 0.0 0.5 2\r\n"; + + // read the file into a cell object + auto cell = nest::mc::io::swc_read_cell(fid); + + // verify that the correct number of nodes was read + EXPECT_EQ(cell.num_segments(), 2); + EXPECT_EQ(cell.num_compartments(), 2u); + + // make an equivalent cell via C++ interface + nest::mc::cell local_cell; + local_cell.add_soma(6.30785); + local_cell.add_cable(0, nest::mc::segmentKind::dendrite, 0.5, 0.5, 200); + + EXPECT_TRUE(nest::mc::cell_basic_equality(local_cell, cell)); +}