diff --git a/source/Sylvan.Data.Csv.Tests/CsvDataWriterTests.cs b/source/Sylvan.Data.Csv.Tests/CsvDataWriterTests.cs
index da27bdf..680a8e6 100644
--- a/source/Sylvan.Data.Csv.Tests/CsvDataWriterTests.cs
+++ b/source/Sylvan.Data.Csv.Tests/CsvDataWriterTests.cs
@@ -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 = ""
@@ -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);
diff --git a/source/Sylvan.Data.Csv/CsvDataWriter.cs b/source/Sylvan.Data.Csv/CsvDataWriter.cs
index 22e1631..c2d46f3 100644
--- a/source/Sylvan.Data.Csv/CsvDataWriter.cs
+++ b/source/Sylvan.Data.Csv/CsvDataWriter.cs
@@ -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;
@@ -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;
diff --git a/source/Sylvan.Data.Csv/CsvDataWriterOptions.cs b/source/Sylvan.Data.Csv/CsvDataWriterOptions.cs
index 459b3db..c32036e 100644
--- a/source/Sylvan.Data.Csv/CsvDataWriterOptions.cs
+++ b/source/Sylvan.Data.Csv/CsvDataWriterOptions.cs
@@ -3,6 +3,33 @@
namespace Sylvan.Data.Csv;
+///
+/// Specifies how strings are quoted
+///
+[Flags]
+public enum CsvStringQuoting
+{
+ ///
+ /// Strings are only quoted when it is required.
+ ///
+ Default = 0,
+
+ ///
+ /// Empty strings are always quoted. This distinguishes them from null.
+ ///
+ AlwaysQuoteEmpty = 1,
+
+ ///
+ /// Non-empty strings are always quoted. This helps prevent confusion with numbers and dates.
+ ///
+ AlwaysQuoteNonEmpty = 2,
+
+ ///
+ /// All strings are always quoted.
+ ///
+ AlwaysQuote = 3,
+}
+
///
/// Options for configuring a CsvWriter.
///
@@ -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;
@@ -121,10 +149,26 @@ public string? TimeFormat {
public char Delimiter { get; set; }
///
- /// Empty strings will be written as empty quotes in the CSV.
+ /// The rules to use when quoting strings, defaults to
+ ///
+ public CsvStringQuoting QuoteStrings { get; set; }
+
+ ///
+ /// Empty strings will be written as empty quotes in the CSV.
/// This allows distinguishing empty strings from null.
///
- 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;
+ }
+ }
///
/// The character to use for quoting fields. The default is '"'.
@@ -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();
diff --git a/source/Sylvan.Data.Csv/CsvWriter.cs b/source/Sylvan.Data.Csv/CsvWriter.cs
index 4480c4a..b81031b 100644
--- a/source/Sylvan.Data.Csv/CsvWriter.cs
+++ b/source/Sylvan.Data.Csv/CsvWriter.cs
@@ -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;