/*
* 2006 - 2018 Ted Spence, http://tedspence.com
* License: http://www.apache.org/licenses/LICENSE-2.0
* Home page: https://github.com/tspence/csharp-csv-reader
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
#if HAS_ASYNC
using System.Threading.Tasks;
#endif
// These suggestions from Resharper apply because we don't want it to recommend fixing things needed for Net20:
// ReSharper disable LoopCanBeConvertedToQuery
// ReSharper disable ConvertIfStatementToNullCoalescingAssignment
// ReSharper disable ReplaceSubstringWithRangeIndexer
// ReSharper disable InvertIf
// ReSharper disable ConvertIfStatementToSwitchExpression
// ReSharper disable ConvertIfStatementToSwitchStatement
namespace CSVNET
{
///
/// Root class that contains static functions for straightforward CSV parsing
///
public static class CSV
{
///
/// Use this to determine what version of DotNet was used to build this library
///
#if NET2_0
public const string VERSION = "NET20";
#elif NET4_0
public const string VERSION = "NET40";
#elif NET4_5
public const string VERSION = "NET45";
#elif NET5_0
public const string VERSION = "NET50";
#elif NET6_0
public const string VERSION = "NET60";
#elif NETSTANDARD1_0
public const string VERSION = "NETSTANDARD10";
#elif NETSTANDARD2_0
public const string VERSION = "NETSTANDARD20";
#else
public const string VERSION = "UNKNOWN";
#endif
///
/// Parse a CSV stream into ]]>, while permitting embedded newlines
///
/// The stream to read
/// The CSV settings to use for this parsing operation (Default: CSV)
/// An enumerable object that can be examined to retrieve rows from the stream.
public static IEnumerable ParseStream(StreamReader inStream, CSVSettings settings = null)
{
int bufferSize = settings?.BufferSize ?? CSVSettings.DEFAULT_BUFFER_SIZE;
var buffer = new char[bufferSize];
var machine = new CSVStateMachine(settings);
while (machine.State == CSVState.CanKeepGoing)
{
var line = string.Empty;
if (machine.NeedsMoreText() && !inStream.EndOfStream)
{
var readChars = inStream.ReadBlock(buffer, 0, bufferSize);
line = new string(buffer, 0, readChars);
}
var row = machine.ParseChunk(line, inStream.EndOfStream);
if (row != null)
{
yield return row;
}
else if (inStream.EndOfStream)
{
break;
}
}
}
#if HAS_ASYNC_IENUM
///
/// Parse a CSV stream into ]]> asynchronously, while permitting embedded newlines
///
/// The stream to read
/// The CSV settings to use for this parsing operation (Default: CSV)
/// An enumerable object that can be examined to retrieve rows from the stream.
public static async IAsyncEnumerable ParseStreamAsync(StreamReader inStream, CSVSettings settings = null)
{
int bufferSize = settings?.BufferSize ?? CSVSettings.DEFAULT_BUFFER_SIZE;
var buffer = new char[bufferSize];
var machine = new CSVStateMachine(settings);
while (machine.State == CSVState.CanKeepGoing)
{
var line = string.Empty;
if (machine.NeedsMoreText() && !inStream.EndOfStream)
{
var readChars = await inStream.ReadBlockAsync(buffer, 0, bufferSize);
line = new string(buffer, 0, readChars);
}
var row = machine.ParseChunk(line, inStream.EndOfStream);
if (row != null)
{
yield return row;
}
else if (inStream.EndOfStream)
{
break;
}
}
}
#endif
///
/// Parse a line from a CSV file and return an array of fields, or null if it fails
///
/// One line of text from a CSV file
/// The CSV settings to use for this parsing operation (Default: CSV)
/// If this value is true, throws an exception if parsing fails
/// An array containing all fields in the next row of data, or null if it could not be parsed.
public static string[] ParseLine(string line, CSVSettings settings = null, bool? throwOnFailure = null)
{
string[] row = null;
var machine = new CSVStateMachine(settings);
while (machine.State == CSVState.CanKeepGoing)
{
row = machine.ParseChunk(line, true);
line = string.Empty;
}
// Anything other than success throws an error here
if (machine.State != CSVState.Done)
{
throw new Exception($"Malformed CSV structure: {machine.State}");
}
return row;
}
///
/// Try to parse a line of CSV data. Can only return false if an unterminated text qualifier is encountered.
///
/// This function cannot recognize 'sep=' lines because it does not know whether it is parsing the first line
/// in the overall CSV stream.
///
/// False if there was an unterminated text qualifier in the
/// The line of text to parse
/// The CSV settings to use for this parsing operation (Default: CSV)
/// The array of fields found in the line
public static bool TryParseLine(string line, out string[] row, CSVSettings settings = null)
{
row = null;
var machine = new CSVStateMachine(settings);
while (machine.State == CSVState.CanKeepGoing)
{
row = machine.ParseChunk(line, true);
line = string.Empty;
}
return machine.State == CSVState.Done;
}
///
/// Deserialize a CSV string into a list of typed objects
///
/// The type of objects to deserialize
/// The CSV settings to use when parsing the source (Default: CSV)
/// The source CSV to deserialize
///
public static IEnumerable Deserialize(string source, CSVSettings settings = null) where T : class, new()
{
return CSVReader.FromString(source, settings).Deserialize();
}
#if HAS_ASYNC_IENUM
///
/// Deserialize a CSV string into a list of typed objects
///
/// The type of objects to deserialize
/// The CSV settings to use when parsing the source (Default: CSV)
/// The source CSV to deserialize
///
public static IAsyncEnumerable DeserializeAsync(string source, CSVSettings settings = null) where T : class, new()
{
return CSVReader.FromString(source, settings).DeserializeAsync();
}
#endif
///
/// Serialize a sequence of objects into a CSV string
///
/// A single line of CSV encoded data containing these values
/// A list or array of objects to serialize
/// The field delimiter character (Default: comma)
#if NET2_0
public static string ToCSVString(IEnumerable