From 0a31d40c1855c469318da6f0451fd30aa67d3865 Mon Sep 17 00:00:00 2001
From: "w.klijn" <w.klijn@fz-juelich.de>
Date: Fri, 1 Jul 2016 12:46:04 +0200
Subject: [PATCH] Add save_getline with windows EOL support

Issue 34 parse errors when opening a file created in windows.

Solution is a method that reads the lines in a windows and linux save manner.
Includes a unit/regression tests toggling between red and green after the fix is applied.
---
 src/swcio.cpp        | 37 ++++++++++++++++++++++++++++++++++++-
 src/swcio.hpp        |  3 +++
 tests/test_swcio.cpp | 34 ++++++++++++++++++++++++++++++++++
 3 files changed, 73 insertions(+), 1 deletion(-)

diff --git a/src/swcio.cpp b/src/swcio.cpp
index 203f69eb..588d969d 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 70f979cd..09c89f81 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 5ba0b991..fed88fb3 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));
+}
-- 
GitLab