Skip to content

Commit

Permalink
Fix CSV.cs
Browse files Browse the repository at this point in the history
Now ignores blank/whitespace keys
  • Loading branch information
N95JPL committed May 26, 2024
1 parent 3edb410 commit 44471ca
Showing 1 changed file with 111 additions and 87 deletions.
198 changes: 111 additions & 87 deletions Multiplayer/Utils/Csv.cs
Original file line number Diff line number Diff line change
@@ -1,128 +1,152 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Text;

namespace Multiplayer.Utils;

public static class Csv
namespace Multiplayer.Utils
{
/// <summary>
/// Parses a CSV string into a dictionary of columns, each of which is a dictionary of rows, keyed by the first column.
/// </summary>
public static ReadOnlyDictionary<string, Dictionary<string, string>> Parse(string data)
public static class Csv
{
string[] lines = data.Split('\n');
/// <summary>
/// Parses a CSV string into a dictionary of columns, each of which is a dictionary of rows, keyed by the first column.
/// </summary>
public static ReadOnlyDictionary<string, Dictionary<string, string>> Parse(string data)
{
// Split the input data into lines
string[] separators = new string[] { "\r\n" };
string[] lines = data.Split(separators, StringSplitOptions.None);

// Dictionary<string, Dictionary<string, string>>
OrderedDictionary columns = new(lines.Length - 1);
// Use an OrderedDictionary to preserve the insertion order of keys
var columns = new OrderedDictionary();

List<string> keys = ParseLine(lines[0]);
foreach (string key in keys)
columns.Add(key, new Dictionary<string, string>());
// Parse the header line to get the column keys
List<string> keys = ParseLine(lines[0]);
foreach (string key in keys)
{
if (!string.IsNullOrWhiteSpace(key))
columns.Add(key, new Dictionary<string, string>());
}

for (int i = 1; i < lines.Length; i++)
{
string line = lines[i];
List<string> values = ParseLine(line);
if (values.Count == 0 || string.IsNullOrWhiteSpace(values[0]))
continue;
string key = values[0];
for (int j = 0; j < values.Count; j++)
((Dictionary<string, string>)columns[j]).Add(key, values[j]);
}
// Iterate through the remaining lines (rows)
for (int i = 1; i < lines.Length; i++)
{
string line = lines[i];
List<string> values = ParseLine(line);
if (values.Count == 0 || string.IsNullOrWhiteSpace(values[0]))
continue;

return new ReadOnlyDictionary<string, Dictionary<string, string>>(columns.Cast<DictionaryEntry>()
.ToDictionary(entry => (string)entry.Key, entry => (Dictionary<string, string>)entry.Value));
}
string rowKey = values[0];

private static List<string> ParseLine(string line)
{
bool inQuotes = false;
bool wasBackslash = false;
List<string> values = new();
StringBuilder builder = new();
// Add the row values to the appropriate column dictionaries
for (int j = 0; j < values.Count && j < keys.Count; j++)
{
string columnKey = keys[j];
if (!string.IsNullOrWhiteSpace(columnKey))
{
var columnDict = (Dictionary<string, string>)columns[columnKey];
columnDict[rowKey] = values[j];
}
}
}

void FinishLine()
{
values.Add(builder.ToString());
builder.Clear();
// Convert the OrderedDictionary to a ReadOnlyDictionary
return new ReadOnlyDictionary<string, Dictionary<string, string>>(
columns.Cast<DictionaryEntry>()
.ToDictionary(entry => (string)entry.Key, entry => (Dictionary<string, string>)entry.Value)
);
}

foreach (char c in line)
private static List<string> ParseLine(string line)
{
if (c == '\n' || (!inQuotes && c == ','))
{
FinishLine();
continue;
}
bool inQuotes = false;
bool wasBackslash = false;
List<string> values = new();
StringBuilder builder = new();

switch (c)
void FinishValue()
{
case '\r':
Multiplayer.LogWarning("Encountered carriage return in CSV! Please use Unix-style line endings (LF).");
continue;
case '"':
inQuotes = !inQuotes;
continue;
case '\\':
wasBackslash = true;
continue;
values.Add(builder.ToString());
builder.Clear();
}

if (wasBackslash)
foreach (char c in line)
{
wasBackslash = false;
if (c == 'n')
if (c == ',' && !inQuotes)
{
builder.Append('\n');
FinishValue();
continue;
}

// Not a special character, so just append the backslash
builder.Append('\\');
}
switch (c)
{
case '\r':
Multiplayer.LogWarning("Encountered carriage return in CSV! Please use Unix-style line endings (LF).");
continue;
case '"':
inQuotes = !inQuotes;
continue;
case '\\':
wasBackslash = true;
continue;
}

builder.Append(c);
}
if (wasBackslash)
{
wasBackslash = false;
if (c == 'n')
{
builder.Append('\n');
continue;
}

// Not a special character, so just append the backslash
builder.Append('\\');
}

if (builder.Length > 0)
FinishLine();
builder.Append(c);
}

return values;
}
if (builder.Length > 0)
FinishValue();

public static string Dump(ReadOnlyDictionary<string, Dictionary<string, string>> data)
{
StringBuilder result = new("\n");
return values;
}

foreach (KeyValuePair<string, Dictionary<string, string>> column in data)
result.Append($"{column.Key},");
public static string Dump(ReadOnlyDictionary<string, Dictionary<string, string>> data)
{
StringBuilder result = new("\n");

result.Remove(result.Length - 1, 1);
result.Append('\n');
foreach (KeyValuePair<string, Dictionary<string, string>> column in data)
result.Append($"{column.Key},");

result.Remove(result.Length - 1, 1);
result.Append('\n');

int rowCount = data.Values.FirstOrDefault()?.Count ?? 0;
int rowCount = data.Values.FirstOrDefault()?.Count ?? 0;

for (int i = 0; i < rowCount; i++)
{
foreach (KeyValuePair<string, Dictionary<string, string>> column in data)
if (column.Value.Count > i)
{
string value = column.Value.ElementAt(i).Value.Replace("\n", "\\n");
result.Append(value.Contains(',') ? $"\"{value}\"," : $"{value},");
}
else
for (int i = 0; i < rowCount; i++)
{
foreach (KeyValuePair<string, Dictionary<string, string>> column in data)
{
result.Append(',');
if (column.Value.Count > i)
{
string value = column.Value.ElementAt(i).Value.Replace("\n", "\\n");
result.Append(value.Contains(',') ? $"\"{value}\"," : $"{value},");
}
else
{
result.Append(',');
}
}

result.Remove(result.Length - 1, 1);
result.Append('\n');
}
result.Remove(result.Length - 1, 1);
result.Append('\n');
}

return result.ToString();
return result.ToString();
}
}
}

0 comments on commit 44471ca

Please sign in to comment.