Skip to content

Commit e4dae3a

Browse files
committed
GSF.Core: Add extension methods for parsing CSV data
1 parent 4871430 commit e4dae3a

File tree

1 file changed

+106
-0
lines changed

1 file changed

+106
-0
lines changed

Source/Libraries/GSF.Core.Shared/StringExtensions.cs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2432,5 +2432,111 @@ public static string Interpolate(this string format, IEnumerable<KeyValuePair<st
24322432

24332433
return string.Format(indexed, parameterValues);
24342434
}
2435+
2436+
/// <summary>
2437+
/// Parses the string as a CSV document.
2438+
/// </summary>
2439+
/// <param name="csv">The string to be parsed</param>
2440+
/// <returns>An array of rows parsed as CSV data.</returns>
2441+
public static string[][] ParseAsCSV(this string csv)
2442+
{
2443+
using StringReader reader = new(csv);
2444+
List<string[]> rows = [];
2445+
2446+
while (true)
2447+
{
2448+
string[] row = ReadCSVRow(reader);
2449+
if (row is null) break;
2450+
rows.Add(row);
2451+
}
2452+
2453+
return rows.ToArray();
2454+
}
2455+
2456+
/// <summary>
2457+
/// Reads characters from the text reader and returns a single row of CSV data.
2458+
/// </summary>
2459+
/// <param name="reader">The text reader providing the CSV data</param>
2460+
/// <returns>An array of fields in one row of CSV data or <c>null</c> if there is no more data available from the text reader.</returns>
2461+
public static string[] ReadCSVRow(this TextReader reader)
2462+
{
2463+
List<string> fields = new List<string>();
2464+
int c = reader.Read();
2465+
2466+
if (EOF())
2467+
return null;
2468+
2469+
while (!EOF() && !EOL())
2470+
{
2471+
if (Matches('"'))
2472+
fields.Add(ReadQuoted());
2473+
else
2474+
fields.Add(ReadToComma());
2475+
2476+
if (Matches(','))
2477+
{
2478+
Advance();
2479+
2480+
// Edge case for when the last
2481+
// field in a row is empty
2482+
if (EOF() || EOL())
2483+
fields.Add(string.Empty);
2484+
}
2485+
}
2486+
2487+
// Advance to the next line before returning
2488+
if (Matches('\r')) Advance();
2489+
if (Matches('\n')) Advance();
2490+
return fields.ToArray();
2491+
2492+
// Reads the next character into c and returns the previous value of c
2493+
char Advance() => (char)(c, c = reader.Read()).c;
2494+
2495+
bool Matches(char m) => c == m;
2496+
bool EOL() => Matches('\r') || Matches('\n');
2497+
bool EOF() => c == -1;
2498+
2499+
string ReadToComma()
2500+
{
2501+
StringBuilder token = new StringBuilder();
2502+
2503+
while (!EOF() && !EOL() && !Matches(','))
2504+
token.Append(Advance());
2505+
2506+
return token.ToString();
2507+
}
2508+
2509+
string ReadQuoted()
2510+
{
2511+
StringBuilder token = new StringBuilder();
2512+
2513+
// Skip past the opening quote
2514+
Advance();
2515+
2516+
while (true)
2517+
{
2518+
while (!EOF() && !Matches('"'))
2519+
token.Append(Advance());
2520+
2521+
// Skip past the end quote
2522+
if (!EOF())
2523+
Advance();
2524+
2525+
// Check if it's actually an end quote vs an escaped quote
2526+
if (Matches('"'))
2527+
token.Append(Advance());
2528+
else
2529+
break;
2530+
2531+
}
2532+
2533+
// Excel treats everything after the
2534+
// end quote as if it were not quoted
2535+
if (!EOF() && !EOL() && !Matches(','))
2536+
token.Append(ReadToComma());
2537+
2538+
return token.ToString();
2539+
}
2540+
}
24352541
}
24362542
}

0 commit comments

Comments
 (0)