Skip to content

Commit f447015

Browse files
committed
wip
1 parent 33970e5 commit f447015

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+2642
-1206
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<ProjectConfiguration>
2+
<Settings>
3+
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
4+
</Settings>
5+
</ProjectConfiguration>

src/CsvHelper.Website/CsvHelper.Website.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
5-
<TargetFramework>net6.0</TargetFramework>
5+
<TargetFramework>net9.0</TargetFramework>
66
</PropertyGroup>
77

88
<ItemGroup>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#if !NET8_0_OR_GREATER
2+
namespace System.Numerics;
3+
4+
internal static class BitOperations
5+
{
6+
public static int TrailingZeroCount(int value)
7+
{
8+
if (value == 0)
9+
{
10+
return 32;
11+
}
12+
13+
var result = 0;
14+
while ((value & 1) == 0)
15+
{
16+
value >>= 1;
17+
result++;
18+
}
19+
20+
return result;
21+
}
22+
}
23+
#endif
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#if !NET8_0_OR_GREATER
2+
namespace System.Runtime.CompilerServices;
3+
4+
internal static class IsExternalInit { }
5+
#endif
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#if NET462 || NET47
2+
namespace System.Linq;
3+
4+
internal static class ListExtensions
5+
{
6+
public static List<T> Append<T>(this List<T> list, T item)
7+
{
8+
list.Add(item);
9+
return list;
10+
}
11+
}
12+
#endif
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#if !NET8_0_OR_GREATER && !NETSTANDARD2_1_OR_GREATER
2+
using System.Buffers;
3+
4+
namespace System.IO;
5+
6+
internal static class TextReaderExtensions
7+
{
8+
public static int ReadBlock(this TextReader textReader, Span<char> buffer)
9+
{
10+
char[] array = ArrayPool<char>.Shared.Rent(buffer.Length);
11+
12+
try
13+
{
14+
int numRead = textReader.ReadBlock(array, 0, buffer.Length);
15+
if ((uint)numRead > (uint)buffer.Length)
16+
{
17+
throw new IOException("Invalid read length.");
18+
}
19+
new Span<char>(array, 0, numRead).CopyTo(buffer);
20+
21+
return numRead;
22+
}
23+
finally
24+
{
25+
ArrayPool<char>.Shared.Return(array);
26+
}
27+
}
28+
}
29+
#endif
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#if !NET8_0_OR_GREATER && !NETSTANDARD2_1_OR_GREATER
2+
using System.Buffers;
3+
4+
namespace System.IO;
5+
6+
internal static class TextWriterExtensions
7+
{
8+
public static void Write(this TextWriter textWriter, ReadOnlySpan<char> buffer)
9+
{
10+
char[] array = ArrayPool<char>.Shared.Rent(buffer.Length);
11+
12+
try
13+
{
14+
buffer.CopyTo(new Span<char>(array));
15+
textWriter.Write(array, 0, buffer.Length);
16+
}
17+
finally
18+
{
19+
ArrayPool<char>.Shared.Return(array);
20+
}
21+
}
22+
}
23+
#endif
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
using System.Globalization;
2+
3+
namespace CsvHelper.Configuration;
4+
5+
/// <summary>
6+
/// Options common to both parsing and serialization of CSV data.
7+
/// </summary>
8+
public abstract record CsvOptions
9+
{
10+
/// <summary>
11+
/// The character used to separate fields in the CSV data.<br/>
12+
/// Defaults to <see cref="TextInfo.ListSeparator"/>.
13+
/// </summary>
14+
public char Delimiter { get; init; } = ',';
15+
16+
/// <summary>
17+
/// The character used to escape special characters in the CSV data.<br/>
18+
/// Defaults to <c>"</c> (double quote).
19+
/// </summary>
20+
public char Escape { get; init; } = '\"';
21+
22+
/// <summary>
23+
/// The character used to indicate a new line in the CSV data.<br/>
24+
/// If not set, \r\n will be used.<br/>
25+
/// Defaults to <c>null</c>.
26+
/// </summary>
27+
public char? NewLine { get; init; }
28+
29+
/// <summary>
30+
/// The mode used for parsing or serializing CSV data.<br/>
31+
/// Defaults to <see cref="CsvMode.RFC4180"/>.
32+
/// </summary>
33+
public CsvMode Mode { get; init; }
34+
35+
internal int BufferSize = 0x1000;
36+
37+
internal virtual void Validate()
38+
{
39+
if (Delimiter == Escape)
40+
{
41+
throw new ConfigurationException($"{nameof(Delimiter)} and {nameof(Escape)} cannot be the same.");
42+
}
43+
44+
if (Delimiter == NewLine)
45+
{
46+
throw new ConfigurationException($"{nameof(Delimiter)} and {nameof(NewLine)} cannot be the same.");
47+
}
48+
49+
if (Escape == NewLine)
50+
{
51+
throw new ConfigurationException($"{nameof(Escape)} and {nameof(NewLine)} cannot be the same.");
52+
}
53+
54+
if (Delimiter < 1)
55+
{
56+
throw new ConfigurationException($"{nameof(Delimiter)} must be greater than 0.");
57+
}
58+
59+
if (Escape < 1)
60+
{
61+
throw new ConfigurationException($"{nameof(Escape)} must be greater than 0.");
62+
}
63+
64+
if (NewLine < 1)
65+
{
66+
throw new ConfigurationException($"{nameof(NewLine)} must be greater than 0.");
67+
}
68+
}
69+
70+
/// <summary>
71+
/// Copies the current options to a new instance of <see cref="CsvOptions"/>.<br/>
72+
/// </summary>
73+
/// <param name="options">The new options to copy values to.</param>
74+
/// <returns>A new options with values copied to it.</returns>
75+
protected CsvOptions CopyTo(CsvOptions options)
76+
{
77+
return options with
78+
{
79+
Delimiter = Delimiter,
80+
Escape = Escape,
81+
NewLine = NewLine,
82+
Mode = Mode,
83+
BufferSize = BufferSize
84+
};
85+
}
86+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
namespace CsvHelper.Configuration;
2+
3+
/// <summary>
4+
/// Options for parsing CSV data.
5+
/// </summary>
6+
public record CsvParserOptions : CsvOptions
7+
{
8+
/// <summary>
9+
/// A value indicating whether fields should be cached for improved performance.
10+
/// </summary>
11+
public bool CacheFields { get; init; }
12+
13+
/// <summary>
14+
/// Strategy use to find special characters in the CSV data.
15+
/// </summary>
16+
public ParsingStrategy? Strategy { get; init; }
17+
18+
/// <summary>
19+
/// Creates a new instance of <see cref="CsvSerializerOptions"/> using common values from this instance.
20+
/// </summary>
21+
public CsvSerializerOptions ToSerializerOptions()
22+
{
23+
return (CsvSerializerOptions)CopyTo(new CsvSerializerOptions());
24+
}
25+
26+
internal StringCreator StringCreator = (chars, i)
27+
#if NET8_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
28+
=> new string(chars);
29+
#else
30+
=> chars.ToString();
31+
#endif
32+
33+
internal int ParsedRowsSize = 8;
34+
35+
internal int ParsedFieldsSize = 8 * 32;
36+
37+
internal CsvModeParse ModeParse = Rfc4180Mode.Parse;
38+
39+
internal ICsvParsingStrategy ParsingStrategyImplementation = new ThrowStrategy();
40+
41+
internal char[] ValidSpecialCharsForIntrinsics = Enumerable
42+
.Range(32, 127 - 32)
43+
.Select(i => (char)i)
44+
.ToList()
45+
.Append('\t')
46+
.Append('\r')
47+
.Append('\n')
48+
.ToArray();
49+
50+
internal override void Validate()
51+
{
52+
base.Validate();
53+
54+
// IndexOfAny does not use Vector256, so any char is valid for a special char.
55+
if (Strategy == ParsingStrategy.IndexOfAny)
56+
{
57+
return;
58+
}
59+
60+
if (!ValidSpecialCharsForIntrinsics.Contains(Delimiter))
61+
{
62+
ThrowInvalidSpecialCharsForIntrinsics(nameof(Delimiter));
63+
}
64+
65+
if (!ValidSpecialCharsForIntrinsics.Contains(Escape))
66+
{
67+
ThrowInvalidSpecialCharsForIntrinsics(nameof(Escape));
68+
}
69+
70+
if (NewLine.HasValue && !ValidSpecialCharsForIntrinsics.Contains(NewLine.Value))
71+
{
72+
ThrowInvalidSpecialCharsForIntrinsics(nameof(NewLine));
73+
}
74+
}
75+
76+
private void ThrowInvalidSpecialCharsForIntrinsics(string property)
77+
{
78+
var validChars = string.Join(", ", ValidSpecialCharsForIntrinsics.Select(c => $"'{c}'"));
79+
var message = $"{property} must be in the valid range of characters." +
80+
Environment.NewLine +
81+
$"You can use {nameof(ReplaceTextReader)} to replace invalid chars with valid chars." +
82+
Environment.NewLine +
83+
$"You can also use parsing strategy {nameof(ParsingStrategy.IndexOfAny)} because it does not suffer this limitation, though parsing will be slower." +
84+
Environment.NewLine +
85+
$"Valid chars: {validChars}";
86+
87+
throw new ConfigurationException(message);
88+
}
89+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace CsvHelper.Configuration;
2+
3+
/// <summary>
4+
/// Options for serializing CSV data.
5+
/// </summary>
6+
public record CsvSerializerOptions : CsvOptions
7+
{
8+
/// <summary>
9+
/// The function used to determine if a field should be escaped.<br/>
10+
/// Default is <c>null</c>, which means fields will be escaped according to the RFC 4180 standard.
11+
/// </summary>
12+
public ShouldEscape? ShouldEscape { get; init; }
13+
14+
internal CsvModeEscape ModeEscape = Rfc4180Mode.Escape;
15+
}

0 commit comments

Comments
 (0)