diff --git a/src/lym/lym/lymMacro.cc b/src/lym/lym/lymMacro.cc index 1fa6d5e54..ca7ced614 100644 --- a/src/lym/lym/lymMacro.cc +++ b/src/lym/lym/lymMacro.cc @@ -196,7 +196,7 @@ void Macro::save_to (const std::string &path) tl::log << "Saving macro to " << path; } - tl::OutputStream os (path, tl::OutputStream::OM_Plain); + tl::OutputStream os (path, tl::OutputStream::OM_Plain, true /*as text*/); if (m_format == MacroFormat) { xml_struct.write (os, *this); diff --git a/src/tl/tl/tlFileUtils.cc b/src/tl/tl/tlFileUtils.cc index 303b07b4f..8428e8e40 100644 --- a/src/tl/tl/tlFileUtils.cc +++ b/src/tl/tl/tlFileUtils.cc @@ -84,6 +84,10 @@ TL_PUBLIC void file_utils_force_windows () { s_mode = OS_Windows; } TL_PUBLIC void file_utils_force_linux () { s_mode = OS_Linux; } TL_PUBLIC void file_utils_force_reset () { s_mode = OS_Auto; } +const char *line_separator () +{ + return is_win () ? "\r\n" : "\n"; +} static bool is_drive (const std::string &part) { diff --git a/src/tl/tl/tlFileUtils.h b/src/tl/tl/tlFileUtils.h index 766ffc819..32874126d 100644 --- a/src/tl/tl/tlFileUtils.h +++ b/src/tl/tl/tlFileUtils.h @@ -189,6 +189,11 @@ std::string TL_PUBLIC get_inst_path (); */ std::string TL_PUBLIC get_module_path (void *addr); +/** + * @brief Gets the line separator (CRLF on windows, LF on linux) + */ +TL_PUBLIC const char *line_separator (); + } #endif diff --git a/src/tl/tl/tlStream.cc b/src/tl/tl/tlStream.cc index 6b8be57b4..409c8cd29 100644 --- a/src/tl/tl/tlStream.cc +++ b/src/tl/tl/tlStream.cc @@ -658,8 +658,8 @@ InputZLibFile::filename () const // --------------------------------------------------------------- // OutputStream implementation -OutputStream::OutputStream (OutputStreamBase &delegate) - : m_pos (0), mp_delegate (&delegate), m_owns_delegate (false) +OutputStream::OutputStream (OutputStreamBase &delegate, bool as_text) + : m_pos (0), mp_delegate (&delegate), m_owns_delegate (false), m_as_text (as_text) { m_buffer_capacity = 16384; m_buffer_pos = 0; @@ -690,8 +690,8 @@ OutputStreamBase *create_file_stream (const std::string &path, OutputStream::Out } } -OutputStream::OutputStream (const std::string &abstract_path, OutputStreamMode om) - : m_pos (0), mp_delegate (0), m_owns_delegate (false) +OutputStream::OutputStream (const std::string &abstract_path, OutputStreamMode om, bool as_text) + : m_pos (0), mp_delegate (0), m_owns_delegate (false), m_as_text (as_text) { // Determine output mode om = output_mode_from_filename (abstract_path, om); @@ -761,6 +761,36 @@ OutputStream::flush () void OutputStream::put (const char *b, size_t n) +{ + if (m_as_text) { + // skip CR, but replace LF by CRLF -> this will normalize the line terminators to CRLF + while (n > 0) { + if (*b == '\r') { + ++b; + --n; + } else if (*b == '\n') { + const char *ls = line_separator (); + while (*ls) { + put_raw (ls++, 1); + } + ++b; + --n; + } else { + const char *b0 = b; + while (n > 0 && *b != '\r' && *b != '\n') { + ++b; + --n; + } + put_raw (b0, b - b0); + } + } + } else { + put_raw (b, n); + } +} + +void +OutputStream::put_raw (const char *b, size_t n) { m_pos += n; diff --git a/src/tl/tl/tlStream.h b/src/tl/tl/tlStream.h index d9429a35d..75127728b 100644 --- a/src/tl/tl/tlStream.h +++ b/src/tl/tl/tlStream.h @@ -1016,14 +1016,15 @@ public: * * This constructor takes a delegate object. */ - OutputStream (OutputStreamBase &delegate); + OutputStream (OutputStreamBase &delegate, bool as_text = false); /** * @brief Open an output stream with the given path and stream mode * * This will automatically create a delegate object and delete it later. + * If "as_text" is true, the output will be formatted with the system's line separator. */ - OutputStream (const std::string &abstract_path, OutputStreamMode om = OM_Auto); + OutputStream (const std::string &abstract_path, OutputStreamMode om = OM_Auto, bool as_text = false); /** * @brief Destructor @@ -1141,9 +1142,12 @@ private: size_t m_pos; OutputStreamBase *mp_delegate; bool m_owns_delegate; + bool m_as_text; char *mp_buffer; size_t m_buffer_capacity, m_buffer_pos; + void put_raw (const char *b, size_t n); + // No copying currently OutputStream (const OutputStream &); OutputStream &operator= (const OutputStream &); diff --git a/src/tl/tl/tlXMLParser.cc b/src/tl/tl/tlXMLParser.cc index 935c3a997..a9f973f9d 100644 --- a/src/tl/tl/tlXMLParser.cc +++ b/src/tl/tl/tlXMLParser.cc @@ -639,8 +639,12 @@ public: } qint64 n0 = n; - for (const char *rd = 0; n > 0 && (rd = mp_stream->get (1)) != 0; --n) { - *data++ = *rd; + for (const char *rd = 0; n > 0 && (rd = mp_stream->get (1)) != 0; ) { + // NOTE: we skip CR to compensate for Windows CRLF line terminators (issue #419). + if (*rd != '\r') { + *data++ = *rd; + --n; + } } if (n0 == n) { diff --git a/src/tl/unit_tests/tlStreamTests.cc b/src/tl/unit_tests/tlStreamTests.cc index e8d3dca15..4c398e206 100644 --- a/src/tl/unit_tests/tlStreamTests.cc +++ b/src/tl/unit_tests/tlStreamTests.cc @@ -22,6 +22,15 @@ #include "tlStream.h" #include "tlUnitTest.h" +#include "tlFileUtils.h" + +// Secret mode switchers for testing +namespace tl +{ +TL_PUBLIC void file_utils_force_windows (); +TL_PUBLIC void file_utils_force_linux (); +TL_PUBLIC void file_utils_force_reset (); +} TEST(InputPipe1) { @@ -42,3 +51,59 @@ TEST(InputPipe2) tl::info << "Process exit code: " << ret; EXPECT_NE (ret, 0); } + +TEST(TextOutputStream) +{ + std::string fn = tmp_file ("test.txt"); + + { + tl::OutputStream os (fn, tl::OutputStream::OM_Auto, false); + os << "Hello, world!\nWith another line\n\r\r\nseparated by a LFCR and CRLF."; + } + + { + tl::InputStream is (fn); + std::string s = is.read_all (); + EXPECT_EQ (s, "Hello, world!\nWith another line\n\r\r\nseparated by a LFCR and CRLF."); + } + + try { + + tl::file_utils_force_linux (); + + { + tl::OutputStream os (fn, tl::OutputStream::OM_Auto, true); + os << "Hello, world!\nWith another line\n\r\r\nseparated by a LFCR and CRLF."; + } + + tl::InputStream is (fn); + std::string s = is.read_all (); + + EXPECT_EQ (s, "Hello, world!\nWith another line\n\nseparated by a LFCR and CRLF."); + tl::file_utils_force_reset (); + + } catch (...) { + tl::file_utils_force_reset (); + throw; + } + + try { + + tl::file_utils_force_windows (); + + { + tl::OutputStream os (fn, tl::OutputStream::OM_Auto, true); + os << "Hello, world!\nWith another line\n\r\r\nseparated by a LFCR and CRLF."; + } + + tl::InputStream is (fn); + std::string s = is.read_all (); + + EXPECT_EQ (s, "Hello, world!\r\nWith another line\r\n\r\nseparated by a LFCR and CRLF."); + tl::file_utils_force_reset (); + + } catch (...) { + tl::file_utils_force_reset (); + throw; + } +}