Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions Csv/CsvBufferWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ namespace Csv
public sealed class CsvBufferWriter : IBufferWriter<char>, IDisposable
{
// The separator is per-call so it can't be baked into a single cached SearchValues.
// Keep the fixed escape chars cached and check the separator with a separate Contains.
// Keep the fixed quote-trigger chars cached and check the separator with a separate Contains.
// Without this caching, MemoryExtensions.IndexOfAny(ReadOnlySpan, ReadOnlySpan) builds
// a fresh SearchValues<char> on the heap every call (~72 bytes per WriteCell).
private static readonly SearchValues<char> FixedEscapeChars = SearchValues.Create("'\n\r");
private static readonly SearchValues<char> QuoteTriggerChars = SearchValues.Create("'\n\r");

private readonly CsvMemoryOptions _options;
private readonly List<(char[] buffer, int written, bool isPooled)> _buffers;
Expand Down Expand Up @@ -96,17 +96,17 @@ public void WriteRow(ReadOnlySpan<ReadOnlyMemory<char>> cells, char separator =
}

/// <summary>
/// Writes a single cell with proper CSV escaping.
/// Writes a single cell with proper CSV quoting and escaping.
/// </summary>
/// <param name="cell">The cell content.</param>
/// <param name="separator">The column separator character.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteCell(ReadOnlySpan<char> cell, char separator = ',')
{
var needsQuoteEscape = cell.Contains('"');
var needsGeneralEscape = cell.Contains(separator) || cell.IndexOfAny(FixedEscapeChars) >= 0;
var needsQuoting = cell.Contains(separator) || cell.IndexOfAny(QuoteTriggerChars) >= 0;

if (needsQuoteEscape || needsGeneralEscape)
if (needsQuoteEscape || needsQuoting)
{
Write('"');

Expand Down
4 changes: 2 additions & 2 deletions Csv/CsvLineSplitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
namespace Csv
{
/// <summary>
/// Splits a single line (multiline handling is done independently) into multiple parts
/// Splits a single record's text (multiline handling is done independently) into its fields
/// </summary>
internal sealed class CsvLineSplitter
{
Expand Down Expand Up @@ -69,7 +69,7 @@ public static bool IsUnterminatedQuotedValue(SpanText value, CsvOptions options)
// A quote may open a quoted field either at the literal field start or, when
// TrimData is set, after any run of leading whitespace. This matches the
// user-visible promise of TrimData: surrounding whitespace doesn't break the
// structure of a quoted cell (issue #71).
// structure of a quoted field (issue #71).
private static bool IsAtFieldOpen(SpanText span, int start, int i, bool trimData)
{
if (i == start)
Expand Down
4 changes: 2 additions & 2 deletions Csv/CsvOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public sealed class CsvOptions
public HeaderMode HeaderMode { get; set; } = HeaderMode.HeaderPresent;

/// <summary>
/// Gets or sets whether a row should be validated immediately that the column count matches the header count, defaults to <c>false</c>.
/// Gets or sets whether each row is validated immediately so that its field count matches the header count, defaults to <c>false</c>.
/// </summary>
public bool ValidateColumnCount { get; set; }

Expand All @@ -56,7 +56,7 @@ public sealed class CsvOptions
public bool ReturnEmptyForMissingColumn { get; set; }

/// <summary>
/// Can be used to use multiple names for a single column. (e.g. to allow "CategoryName", "Category Name", "Category-Name")
/// Can be used to map multiple names to a single header/column. (e.g. to allow "CategoryName", "Category Name", "Category-Name")
/// </summary>
/// <remarks>
/// A group with no matches is ignored.
Expand Down
8 changes: 4 additions & 4 deletions Csv/CsvReader.Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@
var row = new ReadLine(headers, headerLookup, index, rawString ?? raw, options);
#endif
if (rawSplit != null)
row.rawSplitLine = rawSplit;
row.rawFields = rawSplit;
return row;
}
}
Expand All @@ -208,7 +208,7 @@
{
var row = new ReadLineSpan(headers, headerLookup, index, rawString ?? raw.ToString(), options);
if (rawSplit != null)
row.rawSplitLine = rawSplit;
row.rawFields = rawSplit;
return row;
}
}
Expand All @@ -226,7 +226,7 @@
{
var row = new ReadLineSpanOptimized(headers, headerLookup, index, raw, options, memoryOptions);
if (rawSplit != null)
row.rawSplitLine = rawSplit;
row.rawFields = rawSplit;
return row;
}
}
Expand All @@ -237,7 +237,7 @@
{
var row = new ReadLineFromMemory(headers, headerLookup, index, raw, options);
if (rawSplit != null)
row.rawSplitLine = rawSplit;
row.rawFields = rawSplit;
return row;
}
}
Expand Down Expand Up @@ -273,7 +273,7 @@
// case via index == RowsToSkip + 1 and skips its own multiline pass to avoid double-reading.
if (!skipInitialLine && options.AllowNewLineInEnclosedFieldValues)
{
rawSplit = options.Splitter.Split(line, options);

Check warning on line 276 in Csv/CsvReader.Engine.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Dereference of a possibly null reference.

Check warning on line 276 in Csv/CsvReader.Engine.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Dereference of a possibly null reference.

Check warning on line 276 in Csv/CsvReader.Engine.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Dereference of a possibly null reference.

while (rawSplit.Count > 0 && CsvLineSplitter.IsUnterminatedQuotedValue(rawSplit[rawSplit.Count - 1].AsSpan(), options))
{
Expand Down Expand Up @@ -328,7 +328,7 @@
var isFirstDataLineInHeaderAbsentMode = options.HeaderMode == HeaderMode.HeaderAbsent && index == (options.RowsToSkip + 1);
if (options.AllowNewLineInEnclosedFieldValues && !isFirstDataLineInHeaderAbsentMode)
{
rawSplit = options.Splitter.Split(line, options, headers!.Length);

Check warning on line 331 in Csv/CsvReader.Engine.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Dereference of a possibly null reference.

Check warning on line 331 in Csv/CsvReader.Engine.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Dereference of a possibly null reference.

Check warning on line 331 in Csv/CsvReader.Engine.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Dereference of a possibly null reference.
while (rawSplit.Count > 0 && CsvLineSplitter.IsUnterminatedQuotedValue(rawSplit[rawSplit.Count - 1].AsSpan(), options))
{
if (!source.TryReadLine(out var nextLine, out _))
Expand Down Expand Up @@ -378,7 +378,7 @@
// case via index == RowsToSkip + 1 and skips its own multiline pass to avoid double-reading.
if (!skipInitialLine && options.AllowNewLineInEnclosedFieldValues)
{
rawSplit = options.Splitter.Split(line, options);

Check warning on line 381 in Csv/CsvReader.Engine.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Dereference of a possibly null reference.

Check warning on line 381 in Csv/CsvReader.Engine.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Dereference of a possibly null reference.

while (rawSplit.Count > 0 && CsvLineSplitter.IsUnterminatedQuotedValue(rawSplit[rawSplit.Count - 1].AsSpan(), options))
{
Expand Down Expand Up @@ -434,7 +434,7 @@
var isFirstDataLineInHeaderAbsentMode = options.HeaderMode == HeaderMode.HeaderAbsent && index == (options.RowsToSkip + 1);
if (options.AllowNewLineInEnclosedFieldValues && !isFirstDataLineInHeaderAbsentMode)
{
rawSplit = options.Splitter.Split(line, options, headers!.Length);

Check warning on line 437 in Csv/CsvReader.Engine.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Dereference of a possibly null reference.

Check warning on line 437 in Csv/CsvReader.Engine.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Dereference of a possibly null reference.
while (rawSplit.Count > 0 && CsvLineSplitter.IsUnterminatedQuotedValue(rawSplit[rawSplit.Count - 1].AsSpan(), options))
{
var (nextOk, nextLine, _) = await source.TryReadLineAsync(ct).ConfigureAwait(false);
Expand Down
30 changes: 15 additions & 15 deletions Csv/CsvReader.FromMemory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Csv
partial class CsvReader
{
/// <summary>
/// Reads the lines from the csv string.
/// Reads the records from the csv string.
/// </summary>
/// <param name="csv">The csv string to read the data from.</param>
/// <param name="options">The optional options to use when reading.</param>
Expand All @@ -23,8 +23,8 @@ internal sealed class ReadLineFromMemory : ICsvLineFromMemory
{
private readonly Dictionary<string, int> headerLookup;
private readonly CsvOptions options;
internal IList<MemoryText>? rawSplitLine;
private MemoryText[]? parsedLine;
internal IList<MemoryText>? rawFields;
private MemoryText[]? parsedValues;

public ReadLineFromMemory(MemoryText[] headers, Dictionary<string, int> headerLookup, int index, MemoryText raw, CsvOptions options)
{
Expand All @@ -41,7 +41,7 @@ public ReadLineFromMemory(MemoryText[] headers, Dictionary<string, int> headerLo

public int Index { get; }

public int ColumnCount => Line.Length;
public int ColumnCount => ParsedValues.Length;

public bool HasColumn(string name) => headerLookup.ContainsKey(name);

Expand All @@ -50,28 +50,28 @@ public bool LineHasColumn(string name)
if (!headerLookup.TryGetValue(name, out var index))
return false;

return RawSplitLine.Count > index;
return RawFields.Count > index;
}

internal IList<MemoryText> RawSplitLine => rawSplitLine ??= SplitLine(Raw, options);
internal IList<MemoryText> RawFields => rawFields ??= SplitLine(Raw, options);

public MemoryText[] Values => Line;
public MemoryText[] Values => ParsedValues;

private MemoryText[] Line
private MemoryText[] ParsedValues
{
get
{
if (parsedLine == null)
if (parsedValues == null)
{
var raw = RawSplitLine;
var raw = RawFields;

if (options.ValidateColumnCount && raw.Count != Headers.Length)
throw new InvalidOperationException($"Expected {Headers.Length}, got {raw.Count} columns.");

parsedLine = Trim(raw, options);
parsedValues = Trim(raw, options);
}

return parsedLine;
return parsedValues;
}
}

Expand All @@ -89,16 +89,16 @@ MemoryText ICsvLineFromMemory.this[string name]

try
{
return Line[index];
return ParsedValues[index];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException($"Invalid row, missing {name} header, expected {Headers.Length} columns, got {Line.Length} columns.");
throw new InvalidOperationException($"Invalid row, missing {name} header, expected {Headers.Length} columns, got {ParsedValues.Length} columns.");
}
}
}

MemoryText ICsvLineFromMemory.this[int index] => Line[index];
MemoryText ICsvLineFromMemory.this[int index] => ParsedValues[index];

public override string ToString()
{
Expand Down
Loading
Loading