Skip to content

Commit

Permalink
Added CsvDataWriterOptions.QuoteNonEmptyStrings (#245)
Browse files Browse the repository at this point in the history
* Added CsvDataWriterOptions.QuoteNonEmptyStrings to force quoting of strings

* Introduced CsvStringQuoting enum for CsvDataWriterOptions.QuoteStrings

Deprecated CsvDataWriterOptions.QuoteEmptyStrings
  • Loading branch information
benru authored Feb 9, 2024
1 parent adfeed9 commit 811ca6b
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 11 deletions.
14 changes: 10 additions & 4 deletions source/Sylvan.Data.Csv.Tests/CsvDataWriterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -471,12 +471,18 @@ public void CsvWriteBatches()
}

[Theory]
[InlineData(true, "Name\n\"\"\n\n")]
[InlineData(false, "Name\n\n\n")]
public void QuoteEmptyStrings(bool mode, string result)
[InlineData(CsvStringQuoting.Default, "Name\n1\n\n\n")]
[InlineData(CsvStringQuoting.AlwaysQuoteEmpty, "Name\n1\n\"\"\n\n")]
[InlineData(CsvStringQuoting.AlwaysQuoteNonEmpty, "\"Name\"\n\"1\"\n\n\n")]
[InlineData(CsvStringQuoting.AlwaysQuote, "\"Name\"\n\"1\"\n\"\"\n\n")]
public void QuoteStringsOptions(CsvStringQuoting mode, string result)
{
var data = new[]
{
new
{
Name = "1"
},
new
{
Name = ""
Expand All @@ -487,7 +493,7 @@ public void QuoteEmptyStrings(bool mode, string result)
},
};
var reader = data.AsDataReader();
var opts = new CsvDataWriterOptions { QuoteEmptyStrings = mode, NewLine = "\n" };
var opts = new CsvDataWriterOptions { QuoteStrings = mode, NewLine = "\n" };
var sw = new StringWriter();
var writer = CsvDataWriter.Create(sw, opts);
writer.Write(reader);
Expand Down
4 changes: 2 additions & 2 deletions source/Sylvan.Data.Csv/CsvDataWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ bool IsFastTimeOnly
readonly CsvWriter csvWriter;

readonly bool writeHeaders;
readonly bool quoteEmptyStrings;
readonly CsvStringQuoting quoteStrings;
readonly char delimiter;
readonly char quote;
readonly char escape;
Expand Down Expand Up @@ -530,7 +530,7 @@ public static CsvDataWriter Create(TextWriter writer, char[] buffer, CsvDataWrit
this.dateOnlyFormat = options.DateOnlyFormat;
#endif
this.writeHeaders = options.WriteHeaders;
this.quoteEmptyStrings = options.QuoteEmptyStrings;
this.quoteStrings = options.QuoteStrings;
this.delimiter = options.Delimiter;
this.quote = options.Quote;
this.escape = options.Escape;
Expand Down
50 changes: 47 additions & 3 deletions source/Sylvan.Data.Csv/CsvDataWriterOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,33 @@

namespace Sylvan.Data.Csv;

/// <summary>
/// Specifies how strings are quoted
/// </summary>
[Flags]
public enum CsvStringQuoting
{
/// <summary>
/// Strings are only quoted when it is required.
/// </summary>
Default = 0,

/// <summary>
/// Empty strings are always quoted. This distinguishes them from null.
/// </summary>
AlwaysQuoteEmpty = 1,

/// <summary>
/// Non-empty strings are always quoted. This helps prevent confusion with numbers and dates.
/// </summary>
AlwaysQuoteNonEmpty = 2,

/// <summary>
/// All strings are always quoted.
/// </summary>
AlwaysQuote = 3,
}

/// <summary>
/// Options for configuring a CsvWriter.
/// </summary>
Expand Down Expand Up @@ -30,6 +57,7 @@ public CsvDataWriterOptions()
this.BinaryEncoding = BinaryEncoding.Base64;
this.Style = CsvStyle.Standard;
this.Delimiter = DefaultDelimiter;
this.QuoteStrings = CsvStringQuoting.Default;
this.Quote = DefaultQuote;
this.Escape = DefaultEscape;
this.Comment = DefaultComment;
Expand Down Expand Up @@ -121,10 +149,26 @@ public string? TimeFormat {
public char Delimiter { get; set; }

/// <summary>
/// Empty strings will be written as empty quotes in the CSV.
/// The rules to use when quoting strings, defaults to <see cref="CsvStringQuoting.Default"/>
/// </summary>
public CsvStringQuoting QuoteStrings { get; set; }

/// <summary>
/// Empty strings will be written as empty quotes in the CSV.
/// This allows distinguishing empty strings from null.
/// </summary>
public bool QuoteEmptyStrings { get; set; }
[Obsolete("Use QuoteStrings instead.")]
public bool QuoteEmptyStrings
{
get => QuoteStrings.HasFlag(CsvStringQuoting.AlwaysQuoteEmpty);
set
{
if (value)
QuoteStrings |= CsvStringQuoting.AlwaysQuoteEmpty;
else
QuoteStrings &= ~CsvStringQuoting.AlwaysQuoteEmpty;
}
}

/// <summary>
/// The character to use for quoting fields. The default is '"'.
Expand Down Expand Up @@ -184,7 +228,7 @@ internal void Validate()
Quote >= 128 ||
Escape >= 128 ||
Comment >= 128 ||
(QuoteEmptyStrings && Style == CsvStyle.Escaped)
(QuoteStrings != CsvStringQuoting.Default && Style == CsvStyle.Escaped)
;
if (invalid)
throw new CsvConfigurationException();
Expand Down
9 changes: 7 additions & 2 deletions source/Sylvan.Data.Csv/CsvWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,15 @@ static int WriteValueOptimistic(WriterContext context, string value, char[] buff
{
var pos = offset;
var needsEscape = context.writer.needsEscape;
if (value.Length == 0 && context.writer.quoteEmptyStrings)
if (value.Length == 0)
{
return NeedsQuoting;
if (context.writer.quoteStrings.HasFlag(CsvStringQuoting.AlwaysQuoteEmpty))
return NeedsQuoting;
else
return 0;
}
else if (context.writer.quoteStrings.HasFlag(CsvStringQuoting.AlwaysQuoteNonEmpty))
return NeedsQuoting;

if (pos + value.Length >= buffer.Length)
return InsufficientSpace;
Expand Down

0 comments on commit 811ca6b

Please sign in to comment.