267 lines
12 KiB
C#
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;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
}
|
|
//=======================================================================
|
|
}
|