Files
Convention-CSharp/Convention/Runtime/EasySave/EasySaveSpreadsheet.cs

305 lines
6.9 KiB
C#
Raw Normal View History

2025-06-27 19:51:53 +08:00
using System.Collections;
using System.Collections.Generic;
using System.IO;
using Convention.EasySave.Internal;
2025-06-29 01:46:32 +08:00
namespace Convention.EasySave
2025-06-27 19:51:53 +08:00
{
2025-06-29 01:46:32 +08:00
public class EasySaveSpreadsheet
2025-06-27 19:51:53 +08:00
{
2025-06-29 01:46:32 +08:00
private int cols = 0;
private int rows = 0;
private Dictionary<Index, string> cells = new Dictionary<Index, string>();
private const string QUOTE = "\"";
private const char QUOTE_CHAR = '"';
private const char COMMA_CHAR = ',';
private const char NEWLINE_CHAR = '\n';
private const string ESCAPED_QUOTE = "\"\"";
private static char[] CHARS_TO_ESCAPE = { ',', '"', '\n', ' ' };
public int ColumnCount
{
get { return cols; }
}
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
public int RowCount
{
get { return rows; }
}
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
public int GetColumnLength(int col)
{
if (col >= cols)
return 0;
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
int maxRow = -1;
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
foreach (var index in cells.Keys)
if (index.col == col && index.row > maxRow)
maxRow = index.row;
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
return maxRow + 1;
}
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
public int GetRowLength(int row)
{
if (row >= rows)
return 0;
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
int maxCol = -1;
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
foreach (var index in cells.Keys)
if (index.row == row && index.col > maxCol)
maxCol = index.col;
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
return maxCol + 1;
}
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
public void SetCell(int col, int row, object value)
{
var type = value.GetType();
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
// If we're writing a string, add it without formatting.
if (type == typeof(string))
{
SetCellString(col, row, (string)value);
return;
}
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
var settings = new EasySaveSettings();
if (EasySaveReflection.IsPrimitive(type))
SetCellString(col, row, value.ToString());
else
SetCellString(col, row, settings.encoding.GetString(EasySave.Serialize(value, EasySaveTypeMgr.GetOrCreateEasySaveType(type))));
// Expand the spreadsheet if necessary.
if (col >= cols)
cols = (col + 1);
if (row >= rows)
rows = (row + 1);
}
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
private void SetCellString(int col, int row, string value)
{
cells[new Index(col, row)] = value;
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
// Expand the spreadsheet if necessary.
if (col >= cols)
cols = (col + 1);
if (row >= rows)
rows = (row + 1);
}
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
// Don't create non-generic version of this. Generic parameter is necessary as no type data is stored in the CSV file.
public T GetCell<T>(int col, int row)
{
var val = GetCell(typeof(T), col, row);
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
if (val == null)
return default(T);
return (T)val;
}
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
public object GetCell(System.Type type, int col, int row)
{
string value;
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
if (col >= cols || row >= rows)
throw new System.IndexOutOfRangeException("Cell (" + col + ", " + row + ") is out of bounds of spreadsheet (" + cols + ", " + rows + ").");
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
if (!cells.TryGetValue(new Index(col, row), out value) || value == null)
return null;
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
// If we're loading a string, simply return the string value.
if (type == typeof(string))
{
var str = (object)value;
return str;
}
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
var settings = new EasySaveSettings();
return EasySave.Deserialize(EasySaveTypeMgr.GetOrCreateEasySaveType(type, true), settings.encoding.GetBytes(value), settings);
}
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
public void Load(string filePath)
{
Load(new EasySaveSettings(filePath));
}
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
public void Load(string filePath, EasySaveSettings settings)
{
Load(new EasySaveSettings(filePath, settings));
}
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
public void Load(EasySaveSettings settings)
{
Load(EasySaveStream.CreateStream(settings, EasySaveFileMode.Read), settings);
}
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
public void LoadRaw(string str)
{
Load(new MemoryStream(((new EasySaveSettings()).encoding).GetBytes(str)), new EasySaveSettings());
}
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
public void LoadRaw(string str, EasySaveSettings settings)
{
Load(new MemoryStream((settings.encoding).GetBytes(str)), settings);
}
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
private void Load(Stream stream, EasySaveSettings settings)
2025-06-27 19:51:53 +08:00
{
2025-06-29 01:46:32 +08:00
using (var reader = new StreamReader(stream))
2025-06-27 19:51:53 +08:00
{
2025-06-29 01:46:32 +08:00
int c_int;
char c;
string value = "";
int col = 0;
int row = 0;
// Read until the end of the stream.
while (true)
2025-06-27 19:51:53 +08:00
{
2025-06-29 01:46:32 +08:00
c_int = reader.Read();
c = (char)c_int;
if (c == QUOTE_CHAR)
2025-06-27 19:51:53 +08:00
{
2025-06-29 01:46:32 +08:00
while (true)
2025-06-27 19:51:53 +08:00
{
2025-06-29 01:46:32 +08:00
c = (char)reader.Read();
if (c == QUOTE_CHAR)
{
// If this quote isn't escaped by another, it is the last quote, so we should stop parsing this value.
if (((char)reader.Peek()) != QUOTE_CHAR)
break;
else
c = (char)reader.Read();
}
value += c;
2025-06-27 19:51:53 +08:00
}
}
2025-06-29 01:46:32 +08:00
// If this is the end of a column, row, or the stream, add the value to the spreadsheet.
else if (c == COMMA_CHAR || c == NEWLINE_CHAR || c_int == -1)
2025-06-27 19:51:53 +08:00
{
2025-06-29 01:46:32 +08:00
SetCell(col, row, value);
value = "";
if (c == COMMA_CHAR)
col++;
else if (c == NEWLINE_CHAR)
{
col = 0;
row++;
}
else
break;
2025-06-27 19:51:53 +08:00
}
else
2025-06-29 01:46:32 +08:00
value += c;
2025-06-27 19:51:53 +08:00
}
}
}
2025-06-29 01:46:32 +08:00
public void Save(string filePath)
{
Save(new EasySaveSettings(filePath), false);
}
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
public void Save(string filePath, EasySaveSettings settings)
{
Save(new EasySaveSettings(filePath, settings), false);
}
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
public void Save(EasySaveSettings settings)
{
Save(settings, false);
}
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
public void Save(string filePath, bool append)
{
Save(new EasySaveSettings(filePath), append);
}
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
public void Save(string filePath, EasySaveSettings settings, bool append)
2025-06-27 19:51:53 +08:00
{
2025-06-29 01:46:32 +08:00
Save(new EasySaveSettings(filePath, settings), append);
}
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
public void Save(EasySaveSettings settings, bool append)
{
using (var writer = new StreamWriter(EasySaveStream.CreateStream(settings, append ? EasySaveFileMode.Append : EasySaveFileMode.Write)))
2025-06-27 19:51:53 +08:00
{
2025-06-29 01:46:32 +08:00
// If data already exists and we're appending, we need to prepend a newline.
if (append && EasySave.FileExists(settings))
2025-06-27 19:51:53 +08:00
writer.Write(NEWLINE_CHAR);
2025-06-29 01:46:32 +08:00
var array = ToArray();
for (int row = 0; row < rows; row++)
2025-06-27 19:51:53 +08:00
{
2025-06-29 01:46:32 +08:00
if (row != 0)
writer.Write(NEWLINE_CHAR);
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
for (int col = 0; col < cols; col++)
{
if (col != 0)
writer.Write(COMMA_CHAR);
writer.Write(Escape(array[col, row]));
}
2025-06-27 19:51:53 +08:00
}
}
2025-06-29 01:46:32 +08:00
if (!append)
EasySaveIO.CommitBackup(settings);
2025-06-27 19:51:53 +08:00
}
2025-06-29 01:46:32 +08:00
private static string Escape(string str, bool isAlreadyWrappedInQuotes = false)
2025-06-27 19:51:53 +08:00
{
2025-06-29 01:46:32 +08:00
if (str == "")
return "\"\"";
else if (str == null)
return null;
// Now escape any other quotes.
if (str.Contains(QUOTE))
str = str.Replace(QUOTE, ESCAPED_QUOTE);
// If there's chars to escape, wrap the value in quotes.
if (str.IndexOfAny(CHARS_TO_ESCAPE) > -1)
str = QUOTE + str + QUOTE;
return str;
2025-06-27 19:51:53 +08:00
}
2025-06-29 01:46:32 +08:00
private static string Unescape(string str)
{
if (str.StartsWith(QUOTE) && str.EndsWith(QUOTE))
{
str = str.Substring(1, str.Length - 2);
if (str.Contains(ESCAPED_QUOTE))
str = str.Replace(ESCAPED_QUOTE, QUOTE);
}
return str;
}
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
private string[,] ToArray()
{
var array = new string[cols, rows];
foreach (var cell in cells)
array[cell.Key.col, cell.Key.row] = cell.Value;
return array;
}
2025-06-27 19:51:53 +08:00
2025-06-29 01:46:32 +08:00
protected struct Index
2025-06-27 19:51:53 +08:00
{
2025-06-29 01:46:32 +08:00
public int col;
public int row;
public Index(int col, int row)
{
this.col = col;
this.row = row;
}
2025-06-27 19:51:53 +08:00
}
}
}