Files
2021-05-05 15:36:28 +01:00

267 lines
12 KiB
C#

using CsvHelper;
using CsvHelper.Configuration;
using Sarand.Net.Standard.Base.Core;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
namespace Sarand.Net.Standard.Base.Helpers
{
//=======================================================================
public static class SacsCsvHelper
{
//-------------------------------------------------------------------
// Definitions.
/// <summary>
/// Encapsulate CSV Reader and the text object that it is to parse.
/// </summary>
private class ReaderClass : SacsDisposable
{
public ReaderClass() : this(DefaultCulture) { }
public ReaderClass(CultureInfo culture)
{
Reader = new StringReader("");
CsvReader = new CsvReader(Reader, culture ?? DefaultCulture);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
CsvReader.Dispose();
Reader.Dispose();
}
}
public StringReader Reader { get; }
public CsvReader CsvReader { get; }
}
/// <summary>
/// Encapsulate CSV Writer and the text stream that it is to generate.
/// </summary>
private class WriterClass : SacsDisposable
{
// Construction.
public WriterClass() : this(DefaultCulture) { }
public WriterClass(CultureInfo culture)
{
Writer = new StringWriter() { NewLine = "" };
CsvWriter = new CsvWriter(Writer, culture ?? DefaultCulture);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
CsvWriter.Dispose();
Writer.Dispose();
}
}
// Properties.
public StringWriter Writer { get; }
public CsvWriter CsvWriter { get; }
// To CSV.
public String ToCsv(IEnumerable<String> values)
{
foreach (String field in values)
CsvWriter.WriteField(field);
CsvWriter.NextRecord();
// NB Shouldn't need to strip line delimiters as the writer's NewLine is blank but CsvHelper v16 still adds the delimiter.
String record = Writer.GetStringBuilder().ToString().Replace("\r", "").Replace("\n", "");
// Clear the String Writer data. [Ref. https://stackoverflow.com/questions/12839982/clear-all-content-in-xmltextwriter-and-stringwriter]
Writer.GetStringBuilder().Clear();
return record;
}
}
//-------------------------------------------------------------------
// Properties.
public static CultureInfo DefaultCulture { get; set; } = CultureInfo.InvariantCulture;
//-------------------------------------------------------------------
// C o n f i g u r a t i o n D e f a u l t s
//-------------------------------------------------------------------
// AllowComments false bool
// BadDataFound {Method = {Void BadDataFound(CsvHelper.ReadingContext)}} System.Action<CsvHelper.ReadingContext>
// BufferSize 2048 int
// Comment 35 '#' char
// CountBytes false bool
// CultureInfo {en-GB} System.Globalization.CultureInfo
// Delimiter "," string
// DetectColumnCountChanges false bool
// DoubleQuoteString "\"\"" string
// DynamicPropertySort null System.Collections.Generic.IComparer<string>
// Encoding {System.Text.UTF8Encoding} System.Text.Encoding {System.Text.UTF8Encoding}
// Escape 34 '"' char
// GetConstructor {Method = {System.Reflection.ConstructorInfo GetConstructor(System.Type)}} System.Func<System.Type, System.Reflection.ConstructorInfo>
// HasHeaderRecord true bool
// HeaderValidated {Method = {Void HeaderValidated(Boolean, System.String[], Int32, CsvHelper.ReadingContext)}} System.Action<bool, string[], int, CsvHelper.ReadingContext>
// IgnoreBlankLines true bool
// IgnoreQuotes false bool
// IgnoreReferences false bool
// IncludePrivateMembers false bool
// InjectionCharacters {char[4]} char[]
// InjectionEscapeCharacter 9 '\t' char
// LineBreakInQuotedFieldIsBadData false bool
// Maps {CsvHelper.Configuration.ClassMapCollection} CsvHelper.Configuration.ClassMapCollection
// MemberTypes Properties CsvHelper.Configuration.MemberTypes
// MissingFieldFound {Method = {Void MissingFieldFound(System.String[], Int32, CsvHelper.ReadingContext)}} System.Action<string[], int, CsvHelper.ReadingContext>
// PrepareHeaderForMatch {Method = {System.String PrepareHeaderForMatch(System.String, Int32)}} System.Func<string, int, string>
// Quote 34 '"' char
// QuoteString "\"" string
// ReadingExceptionOccurred {Method = {Boolean ReadingExceptionOccurred(CsvHelper.CsvHelperException)}} System.Func<CsvHelper.CsvHelperException, bool>
// ReferenceHeaderPrefix null System.Func<System.Type, string, string>
// SanitizeForInjection false bool
// ShouldQuote {Method = {Boolean ShouldQuote(System.String, CsvHelper.WritingContext)}} System.Func<string, CsvHelper.WritingContext, bool>
// ShouldSkipRecord {Method = {Boolean ShouldSkipRecord(System.String[])}} System.Func<string[], bool>
// ShouldUseConstructorParameters {Method = {Boolean ShouldUseConstructorParameters(System.Type)}} System.Func<System.Type, bool>
// TrimOptions None CsvHelper.Configuration.TrimOptions
// TypeConverterCache {CsvHelper.TypeConversion.TypeConverterCache} CsvHelper.TypeConversion.TypeConverterCache
// TypeConverterOptionsCache {CsvHelper.TypeConversion.TypeConverterOptionsCache} CsvHelper.TypeConversion.TypeConverterOptionsCache
// UseNewObjectForNullReferenceMembers true bool
//-------------------------------------------------------------------
public class ParserConfig /* IParserConfiguration */
{
/// <summary>Culture</summary>
public CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture;
/// <summary>Gets or sets the size of the buffer used for reading CSV files. Default: 2048.</summary>
public int BufferSize { get; set; } = 2048;
/// <summary>
/// Gets or sets a value indicating whether the number of bytes should be counted while parsing.
/// <para>This will slow down parsing because it needs to get the byte count of every char for the given encoding.</para>
/// <para>The CsvHelper.Configuration.IParserConfiguration.Encoding needs to be set correctly for this to be accurate.</para>
/// <para>Default: false.</para>
/// </summary>
public bool CountBytes { get; set; } = false;
/// <summary>Gets or sets the encoding used when counting bytes.</summary>
public Encoding Encoding { get; set; } = Encoding.UTF8;
/// <summary>
/// Gets or sets the function that is called when bad field data is found.
/// <para>A field has bad data if it contains a quote and the field is not quoted (escaped).</para>
/// <para>You can supply your own function to do other things like logging the issue instead of throwing an exception.</para>
/// <para>Arguments: context</para>
/// </summary>
public Action<ReadingContext> BadDataFound { get; set; }
/// <summary>
/// Gets or sets a value indicating if a line break found in a quote field should be considered bad data.
/// <para>True to consider a line break bad data, otherwise false.</para>
/// <para>Defaults to false.</para>
/// </summary>
public bool LineBreakInQuotedFieldIsBadData { get; set; } = false;
/// <summary>
/// Gets or sets the character used to denote a line that is commented out.
/// <para>Default: '#'.</para>
/// </summary>
public char Comment { get; set; } = '#';
/// <summary>
/// <para>Gets or sets a value indicating if comments are allowed.</para>
/// <para>True to allow commented out lines, otherwise false (default).</para></summary>
public bool AllowComments { get; set; } = false;
/// <summary>
/// Gets or sets a value indicating if blank lines should be ignored when reading.
/// <para>True to ignore, otherwise false.</para>
/// <para>Default: true.</para>
/// </summary>
public bool IgnoreBlankLines { get; set; } = true;
/// <summary>Gets or sets a value indicating if quotes should be ingored when parsing and treated like any other character.</summary>
public bool IgnoreQuotes { get; set; } = false;
/// <summary>
/// Gets or sets the character used to quote fields.
/// <para>Default: '"'.</para>
/// </summary>
public char Quote { get; set; } = '"';
/// <summary>
/// Gets or sets the delimiter used to separate fields.
/// <para>Default: CultureInfo.CurrentCulture.TextInfo.ListSeparator (',').</para>
/// </summary>
public string Delimiter { get; set; }
/// <summary>
/// Gets or sets the escape character used to escape a quote inside a field.
/// <para>Default: '"'.</para>
/// </summary>
public char Escape { get; set; } = '"';
/// <summary>
/// Gets or sets the field trimming options.
/// <para>Default: None</para>
/// </summary>
public TrimOptions TrimOptions { get; set; } = TrimOptions.None;
}
//-------------------------------------------------------------------
// C o n v e r t T o C S V
//-------------------------------------------------------------------
public static List<String[]> ParseCsv(String data, ParserConfig parserConfig = null)
{
List<String[]> records = new List<string[]>();
using (TextReader reader = new StringReader(data))
using (CsvParser csvParser = new CsvParser(reader, parserConfig?.Culture ?? CultureInfo.InvariantCulture))
{
if (parserConfig != null)
{
if (parserConfig.BufferSize > 0)
csvParser.Configuration.BufferSize = parserConfig.BufferSize;
csvParser.Configuration.CountBytes = parserConfig.CountBytes;
csvParser.Configuration.Encoding = parserConfig.Encoding;
if (parserConfig.BadDataFound != null)
csvParser.Configuration.BadDataFound = parserConfig.BadDataFound;
csvParser.Configuration.LineBreakInQuotedFieldIsBadData = parserConfig.LineBreakInQuotedFieldIsBadData;
csvParser.Configuration.Comment = parserConfig.Comment;
csvParser.Configuration.AllowComments = parserConfig.AllowComments;
csvParser.Configuration.IgnoreBlankLines = parserConfig.IgnoreBlankLines;
csvParser.Configuration.IgnoreQuotes = parserConfig.IgnoreQuotes;
csvParser.Configuration.Quote = parserConfig.Quote;
if (parserConfig.Delimiter != null)
csvParser.Configuration.Delimiter = parserConfig.Delimiter;
csvParser.Configuration.Escape = parserConfig.Escape;
csvParser.Configuration.TrimOptions = parserConfig.TrimOptions;
}
while (true)
{
String[] record = csvParser.Read();
if (record == null) break;
records.Add(record);
}
}
return records;
}
//-------------------------------------------------------------------
// C o n v e r t T o C S V
//-------------------------------------------------------------------
// Single record.
public static String ToCsv(IEnumerable<String> values)
{
return ToCsv(values, DefaultCulture);
}
public static String ToCsv(IEnumerable<String> values, CultureInfo culture)
{
String record = "";
using (WriterClass writer = new WriterClass(culture ?? DefaultCulture))
{
record = writer.ToCsv(values);
}
return record;
}
//-------------------------------------------------------------------
// Multiple records.
public static List<String> ToCsv(IEnumerable<IEnumerable<String>> data)
{
return ToCsv(data, DefaultCulture);
}
public static List<String> ToCsv(IEnumerable<IEnumerable<String>> data, CultureInfo culture)
{
List<String> records = new List<string>();
using (WriterClass writer = new WriterClass(culture ?? DefaultCulture))
{
foreach (String[] values in data)
records.Add(writer.ToCsv(values));
}
return records;
}
//-------------------------------------------------------------------
}
//=======================================================================
}