From c79f167209595eaa8440e0fc0b5af6f359888a78 Mon Sep 17 00:00:00 2001 From: Gediminas Backevicius Date: Mon, 17 May 2021 12:02:34 +0300 Subject: [PATCH 1/2] Version has been updated --- CHANGES.md | 4 ++++ LibCSV/LibCSV.csproj | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c0aa7d0..b2d4427 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## 2.1.1.0 + + * Async/Await operations has been introduced + ## 2.1.0.0 * Ported to .NETStandart 2.0 diff --git a/LibCSV/LibCSV.csproj b/LibCSV/LibCSV.csproj index 9c87073..cf9c012 100644 --- a/LibCSV/LibCSV.csproj +++ b/LibCSV/LibCSV.csproj @@ -6,9 +6,9 @@ LibCSV Library for reading and writing tabular (CSV) files. Copyright © 2011-2021, Darius Kucinskas and Contributors - 2.1.0.0 - 2.1.0.0 - 2.1.0.0 + 2.1.1.0 + 2.1.1.0 + 2.1.1.0 true \ No newline at end of file From 62d138d67f94a04e1d96be8745cce0f03e53723f Mon Sep 17 00:00:00 2001 From: Gediminas Backevicius Date: Mon, 17 May 2021 15:50:11 +0300 Subject: [PATCH 2/2] Open method has been introduced for csv reader and writer --- CHANGES.md | 4 + LibCSV.Tests.NetFramework/ReaderBaseTests.cs | 4 + LibCSV.Tests.NetFramework/ReaderTests.cs | 10 + LibCSV.Tests.NetFramework/WriterTests.cs | 13 +- LibCSV/CSVAdapter.cs | 7 + LibCSV/CSVReader.cs | 253 +++++++++++-------- LibCSV/CSVWriter.cs | 23 ++ LibCSV/ICSVReader.cs | 22 +- LibCSV/ICSVWriter.cs | 4 + LibCSV/LibCSV.csproj | 6 +- README.md | 2 + 11 files changed, 225 insertions(+), 123 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b2d4427..974d8b4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## 2.2.0.0 + + * BREAKING CHANGE: Open method has been introduced for csv reader and writer + ## 2.1.1.0 * Async/Await operations has been introduced diff --git a/LibCSV.Tests.NetFramework/ReaderBaseTests.cs b/LibCSV.Tests.NetFramework/ReaderBaseTests.cs index 7076d29..af6f0d8 100644 --- a/LibCSV.Tests.NetFramework/ReaderBaseTests.cs +++ b/LibCSV.Tests.NetFramework/ReaderBaseTests.cs @@ -41,6 +41,8 @@ public static void ReadTest(string input, IList> expect, Dialect d IList> results = new List>(); using (var reader = new CSVReader(dialect, new StringReader(input))) { + reader.Open(); + while (reader.Next()) { var record = reader.Current; @@ -62,6 +64,8 @@ public static async Task ReadTestAsync(string input, IList> expect IList> results = new List>(); using (var reader = new CSVReader(dialect, new StringReader(input))) { + await reader.OpenAsync(); + while (await reader.NextAsync()) { var record = reader.Current; diff --git a/LibCSV.Tests.NetFramework/ReaderTests.cs b/LibCSV.Tests.NetFramework/ReaderTests.cs index 2804a89..68a36a4 100644 --- a/LibCSV.Tests.NetFramework/ReaderTests.cs +++ b/LibCSV.Tests.NetFramework/ReaderTests.cs @@ -38,6 +38,8 @@ public void Next_FirstLine_HeadersArePopulated() { using (var reader = new CSVReader(dialect, new StringReader("a,b,c"))) { + reader.Open(); + reader.Next(); var current = reader.Current; @@ -54,6 +56,8 @@ public async Task NextAsync_FirstLine_HeadersArePopulated() { using (var reader = new CSVReader(dialect, new StringReader("a,b,c"))) { + reader.Open(); + await reader.NextAsync(); var current = reader.Current; @@ -379,6 +383,8 @@ public void Next_WithHeaders_HeadersAreInitialized() { using (var reader = new CSVReader(dialect, new StringReader(input))) { + reader.Open(); + IsNotNull(reader.Headers); } } @@ -392,6 +398,8 @@ public void Next_EmptyLine_NextReturnsFalse() { using (var reader = new CSVReader(dialect, new StringReader(input))) { + reader.Open(); + IsTrue(reader.Next()); IsFalse(reader.Next()); } @@ -406,6 +414,8 @@ public async Task NextAsync_EmptyLine_NextReturnsFalse() { using (var reader = new CSVReader(dialect, new StringReader(input))) { + await reader.OpenAsync(); + IsTrue(await reader.NextAsync()); IsFalse(await reader.NextAsync()); } diff --git a/LibCSV.Tests.NetFramework/WriterTests.cs b/LibCSV.Tests.NetFramework/WriterTests.cs index 3d231d8..8840b5e 100644 --- a/LibCSV.Tests.NetFramework/WriterTests.cs +++ b/LibCSV.Tests.NetFramework/WriterTests.cs @@ -98,6 +98,7 @@ public void WriteRow_QuoteAll_Quoted() var dialect = new Dialect(true, ';', '\"', '\\', true, "\r\n", QuoteStyle.QuoteAll, false, false); using (var csvWriter = new CSVWriter(dialect, writer)) { + csvWriter.Open(); csvWriter.WriteRow(row); } @@ -121,6 +122,7 @@ public async Task WriteRowAsync_QuoteAll_Quoted() var dialect = new Dialect(true, ';', '\"', '\\', true, "\r\n", QuoteStyle.QuoteAll, false, false); using (var csvWriter = new CSVWriter(dialect, writer)) { + await csvWriter.OpenAsync(); await csvWriter.WriteRowAsync(row); } @@ -141,6 +143,7 @@ public void WriteRow_EscapeStrings_Escaped() var dialect = new Dialect(true, ';', '\"', '\\', true, "\r\n", QuoteStyle.QuoteAll, false, false); using (var csvWriter = new CSVWriter(dialect, writer)) { + csvWriter.Open(); csvWriter.WriteRow(row); } @@ -161,6 +164,7 @@ public async Task WriteRowAsync_EscapeStrings_Escaped() var dialect = new Dialect(true, ';', '\"', '\\', true, "\r\n", QuoteStyle.QuoteAll, false, false); using (var csvWriter = new CSVWriter(dialect, writer)) { + await csvWriter.OpenAsync(); await csvWriter.WriteRowAsync(row); } @@ -181,6 +185,7 @@ public void WriteRow_DoNotEscapeStrings_NotEscaped() var dialect = new Dialect(false, ';', '\"', '\\', true, "\r\n", QuoteStyle.QuoteAll, false, false); using (var csvWriter = new CSVWriter(dialect, writer)) { + csvWriter.Open(); csvWriter.WriteRow(row); } @@ -201,6 +206,7 @@ public async Task WriteRowAsync_DoNotEscapeStrings_NotEscaped() var dialect = new Dialect(false, ';', '\"', '\\', true, "\r\n", QuoteStyle.QuoteAll, false, false); using (var csvWriter = new CSVWriter(dialect, writer)) { + await csvWriter.OpenAsync(); await csvWriter.WriteRowAsync(row); } @@ -294,7 +300,9 @@ public void WriteRow_RowIsNull_ThrowsRowIsNullOrEmptyException() { using (var writer = new CSVWriter(dialect, stringWriter)) { - writer.WriteRow(null); + writer.Open(); + + writer.WriteRow(null); } } } @@ -312,6 +320,7 @@ public void WriteRowAsync_RowIsNull_ThrowsRowIsNullOrEmptyException() { using (var writer = new CSVWriter(dialect, stringWriter)) { + await writer.OpenAsync(); await writer.WriteRowAsync(null); } } @@ -370,6 +379,7 @@ private void WriteAndTestRow(object[] input, string output, Dialect dialect, Cul { using (var csvWriter = new CSVWriter(dialect, writer, culture)) { + csvWriter.Open(); csvWriter.WriteRow(input); } @@ -400,6 +410,7 @@ private async Task WriteAndTestRowAsync(object[] input, string output, Dialect d { using (var csvWriter = new CSVWriter(dialect, writer, culture)) { + await csvWriter.OpenAsync(); await csvWriter.WriteRowAsync(input); } diff --git a/LibCSV/CSVAdapter.cs b/LibCSV/CSVAdapter.cs index 6fd8622..28934b4 100644 --- a/LibCSV/CSVAdapter.cs +++ b/LibCSV/CSVAdapter.cs @@ -80,6 +80,8 @@ public IEnumerable ReadAll(IDataTransformer transformer) try { + reader.Open(); + string[] aliases = null; if (_dialect.HasHeader) { @@ -111,6 +113,8 @@ public async Task ReadAllAsync(IDataTransformer transformer) try { + await reader.OpenAsync(); + string[] aliases = null; if (_dialect.HasHeader) { @@ -145,6 +149,7 @@ public void WriteAll(IEnumerable data, IDataTransformer transformer) try { + writer.Open(); var cellCount = -1; @@ -189,6 +194,8 @@ public async Task WriteAllAsync(IEnumerable data, IDataTransformer transformer) try { + await writer.OpenAsync(); + var cellCount = -1; if (_dialect.HasHeader && _headers != null && _headers.Length > 0) { diff --git a/LibCSV/CSVReader.cs b/LibCSV/CSVReader.cs index e86a6a0..bfd960d 100644 --- a/LibCSV/CSVReader.cs +++ b/LibCSV/CSVReader.cs @@ -37,14 +37,48 @@ public class CSVReader : ICSVReader private int _fieldLength; - private long _index; - + private bool _opened = false; + private string[] _headers; private bool _ownsReader = false; public bool IsDisposed { get; private set; } + /// + /// Returns the headers as string array. + /// + public string[] Headers + { + get + { + ThrowIfClosed(); + + return _headers; + } + } + + /// + /// Returns the current record as string array. + /// + public string[] Current + { + get + { + ThrowIfClosed(); + + string[] results = null; + + if (_fields != null) + { + results = new string[_fields.Count]; + _fields.CopyTo(results, 0); + } + + return results; + } + } + public CSVReader(Dialect dialect, string filename, string encoding) { if (dialect == null) @@ -77,8 +111,6 @@ public CSVReader(Dialect dialect, string filename, string encoding) throw new CannotReadFromFileException(string.Format("Can't read from file: '{0}'!", filename), exp); } } - - InitializeHeaders(); } public CSVReader(Dialect dialect, TextReader reader) @@ -96,24 +128,112 @@ public CSVReader(Dialect dialect, TextReader reader) throw new TextReaderIsNullException(); } _reader = reader; - - InitializeHeaders(); } - - protected void InitializeHeaders() + + public void Open() { - if (_dialect != null && _dialect.HasHeader) + if (!_opened) { - Next(); - - if (_headers == null && _fields != null && _fields.Count > 0) + _opened = true; + + if (_dialect != null && _dialect.HasHeader && _headers == null) { - _headers = new string[_fields.Count]; - _fields.CopyTo(_headers, 0); + Next(); + + if (_fields?.Count > 0) + { + _headers = new string[_fields.Count]; + _fields.CopyTo(_headers, 0); + } } } } - + + public async Task OpenAsync() + { + if (!_opened) + { + _opened = true; + + if (_dialect != null && _dialect.HasHeader && _headers == null) + { + await NextAsync(); + + if (_fields?.Count > 0) + { + _headers = new string[_fields.Count]; + _fields.CopyTo(_headers, 0); + } + } + } + } + + /// + /// Reads and parses next record. + /// + /// true on success otherwise false. + public bool Next() + { + ThrowIfClosed(); + + Reset(); + + var line = ReadLine(); + if (string.IsNullOrEmpty(line) || line.Trim().Length < 1) + { + return false; + } + + var length = line.Length; + if (line != new string(_dialect.Delimiter, length)) + { + for (var i = 0; i < length; i++) + { + if (IsNull(line[i])) + { + throw new BadFormatException("Line contains NULL byte!"); + } + + ProcessChar(line[i]); + } + + SaveField(); + } + + return true; + } + + public async Task NextAsync() + { + ThrowIfClosed(); + + Reset(); + + var line = await ReadLineAsync(); + if (string.IsNullOrEmpty(line) || line.Trim().Length < 1) + { + return false; + } + + var length = line.Length; + if (line != new string(_dialect.Delimiter, length)) + { + for (var i = 0; i < length; i++) + { + if (IsNull(line[i])) + { + throw new BadFormatException("Line contains NULL byte!"); + } + + ProcessChar(line[i]); + } + + SaveField(); + } + + return true; + } + protected void SaveField() { _fields.Add(new string(_buffer, 0, _fieldLength)); @@ -338,76 +458,11 @@ protected void Reset() _fieldLength = 0; _state = ParserState.StartOfRecord; } - - /// - /// Reads and parses next record. - /// - /// true on success otherwise false. - public bool Next() - { - Reset(); - - var line = ReadLine(); - if (string.IsNullOrEmpty(line) || line.Trim().Length < 1) - { - return false; - } - - var length = line.Length; - if (line != new string(_dialect.Delimiter, length)) - { - for (var i = 0; i < length; i++) - { - if (IsNull(line[i])) - { - throw new BadFormatException("Line contains NULL byte!"); - } - - ProcessChar(line[i]); - } - - SaveField(); - } - - _index++; - - return true; - } - - public async Task NextAsync() - { - Reset(); - - var line = await ReadLineAsync(); - if (string.IsNullOrEmpty(line) || line.Trim().Length < 1) - { - return false; - } - - var length = line.Length; - if (line != new string(_dialect.Delimiter, length)) - { - for (var i = 0; i < length; i++) - { - if (IsNull(line[i])) - { - throw new BadFormatException("Line contains NULL byte!"); - } - - ProcessChar(line[i]); - } - - SaveField(); - } - - _index++; - return true; - } /// /// Returns the next line. /// - public virtual String ReadLine() + protected virtual String ReadLine() { StringBuilder sb = new StringBuilder(); bool inQuotes = false; @@ -427,7 +482,7 @@ public virtual String ReadLine() return null; } - public virtual async Task ReadLineAsync() + protected virtual async Task ReadLineAsync() { var sb = new StringBuilder(); var inQuotes = false; @@ -503,35 +558,13 @@ private async Task PeekAsync() return _nextChar; } - /// - /// Returns the headers as string array. - /// - public string[] Headers - { - get - { - return _headers; - } - } - - /// - /// Returns the current record as string array. - /// - public string[] Current - { - get - { - string[] results = null; - - if (_fields != null) - { - results = new string[_fields.Count]; - _fields.CopyTo(results, 0); - } - - return results; + protected void ThrowIfClosed() + { + if (!_opened || IsDisposed) + { + throw new CsvException("CSV reader is closed"); } - } + } protected virtual void Dispose(bool disposing) { diff --git a/LibCSV/CSVWriter.cs b/LibCSV/CSVWriter.cs index 3262c10..ad13452 100644 --- a/LibCSV/CSVWriter.cs +++ b/LibCSV/CSVWriter.cs @@ -15,6 +15,8 @@ namespace LibCSV /// public class CSVWriter : ICSVWriter { + private bool _opened = false; + private TextWriter _writer; private Dialect _dialect; @@ -77,8 +79,19 @@ public CSVWriter(Dialect dialect, TextWriter writer, CultureInfo culture = null) _culture = culture ?? Thread.CurrentThread.CurrentCulture; } + public void Open() { _opened = true; } + + public async Task OpenAsync() + { + _opened = true; + + await Task.CompletedTask; + } + public void WriteRow(IList row) { + ThrowIfClosed(); + if (row == null || row.Count < 1) { throw new RowIsNullOrEmptyException(); @@ -100,6 +113,8 @@ public void WriteRow(IList row) public async Task WriteRowAsync(IList row) { + ThrowIfClosed(); + if (row == null || row.Count < 1) { throw new RowIsNullOrEmptyException(); @@ -297,6 +312,14 @@ protected virtual async Task WriteEscapedStringAsync(string field) } } + protected void ThrowIfClosed() + { + if (!_opened || IsDisposed) + { + throw new CsvException("CSV writer is closed"); + } + } + protected virtual void Dispose(bool disposing) { if (!IsDisposed) diff --git a/LibCSV/ICSVReader.cs b/LibCSV/ICSVReader.cs index 4ffdd9c..94b9dd2 100644 --- a/LibCSV/ICSVReader.cs +++ b/LibCSV/ICSVReader.cs @@ -5,15 +5,6 @@ namespace LibCSV { public interface ICSVReader : IDisposable { - /// - /// Reads and parses next record. - /// - /// true on success otherwise false. - /// - bool Next(); - - Task NextAsync(); - /// /// Returns the headers as string array. /// @@ -23,5 +14,18 @@ public interface ICSVReader : IDisposable /// Returns the current record as string array. /// string[] Current { get; } + + void Open(); + + Task OpenAsync(); + + /// + /// Reads and parses next record. + /// + /// true on success otherwise false. + /// + bool Next(); + + Task NextAsync(); } } diff --git a/LibCSV/ICSVWriter.cs b/LibCSV/ICSVWriter.cs index 5d126de..f30e45e 100644 --- a/LibCSV/ICSVWriter.cs +++ b/LibCSV/ICSVWriter.cs @@ -6,6 +6,10 @@ namespace LibCSV { public interface ICSVWriter : IDisposable { + void Open(); + + Task OpenAsync(); + void WriteRow(IList row); Task WriteRowAsync(IList row); diff --git a/LibCSV/LibCSV.csproj b/LibCSV/LibCSV.csproj index cf9c012..5b3d65e 100644 --- a/LibCSV/LibCSV.csproj +++ b/LibCSV/LibCSV.csproj @@ -6,9 +6,9 @@ LibCSV Library for reading and writing tabular (CSV) files. Copyright © 2011-2021, Darius Kucinskas and Contributors - 2.1.1.0 - 2.1.1.0 - 2.1.1.0 + 2.2.0.0 + 2.2.0.0 + 2.2.0.0 true \ No newline at end of file diff --git a/README.md b/README.md index 36f0454..e3e7716 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,8 @@ var dialect = new Dialect using (var reader = new CSVReader(dialect, new StringReader(input))) { + reader.Open(); + foreach (var item in reader.Headers) { Console.Write(item + "| ");