281 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			C#
		
	
	
	
		
		
			
		
	
	
			281 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			C#
		
	
	
	
|  | using System.Collections; | ||
|  | using System.Collections.Generic; | ||
|  | using UnityEngine; | ||
|  | using System.IO; | ||
|  | using ES3Internal; | ||
|  | 
 | ||
|  | public class ES3Spreadsheet | ||
|  | { | ||
|  | 	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; } | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public int RowCount | ||
|  | 	{ | ||
|  | 		get{ return rows; } | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public void SetCell<T>(int col, int row, T value) | ||
|  | 	{ | ||
|  |         // If we're writing a string, add it without formatting. | ||
|  |         if (value.GetType() == typeof(string)) | ||
|  | 		{ | ||
|  | 			SetCellString(col, row, (string)(object)value); | ||
|  | 			return; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		var settings = new ES3Settings (); | ||
|  | 		using(var ms = new MemoryStream()) | ||
|  | 		{ | ||
|  | 			using (var jsonWriter = new ES3JSONWriter (ms, settings, false, false)) | ||
|  | 				jsonWriter.Write(value, ES3.ReferenceMode.ByValue); | ||
|  | 
 | ||
|  | 			SetCellString(col, row, settings.encoding.GetString(ms.ToArray())); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// Expand the spreadsheet if necessary. | ||
|  | 		if(col >= cols) | ||
|  | 			cols = (col+1); | ||
|  | 		if(row >= rows) | ||
|  | 			rows = (row+1); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	private void SetCellString(int col, int row, string value) | ||
|  | 	{ | ||
|  | 		cells [new Index (col, row)] = value; | ||
|  | 
 | ||
|  | 		// Expand the spreadsheet if necessary. | ||
|  | 		if(col >= cols) | ||
|  | 			cols = (col+1); | ||
|  |         if (row >= rows) | ||
|  |             rows = (row + 1); | ||
|  |     } | ||
|  | 
 | ||
|  | 
 | ||
|  |     // 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); | ||
|  | 
 | ||
|  |         if (val == null) | ||
|  |             return default(T); | ||
|  |         return (T)val; | ||
|  | 	} | ||
|  | 
 | ||
|  |     internal object GetCell(System.Type type, int col, int row) | ||
|  |     { | ||
|  |         string value; | ||
|  | 
 | ||
|  |         if (col >= cols || row >= rows) | ||
|  |             throw new System.IndexOutOfRangeException("Cell (" + col + ", " + row + ") is out of bounds of spreadsheet (" + cols + ", " + rows + ")."); | ||
|  | 
 | ||
|  |         if (!cells.TryGetValue(new Index(col, row), out value) || string.IsNullOrEmpty(value)) | ||
|  |             return null; | ||
|  | 
 | ||
|  |         // If we're loading a string, simply return the string value. | ||
|  |         if (type == typeof(string)) | ||
|  |         { | ||
|  |             var str = (object)value; | ||
|  |             return str; | ||
|  |         } | ||
|  | 
 | ||
|  |         var settings = new ES3Settings(); | ||
|  |         using (var ms = new MemoryStream(settings.encoding.GetBytes(value))) | ||
|  |         { | ||
|  |             using (var jsonReader = new ES3JSONReader(ms, settings, false)) | ||
|  |             { | ||
|  |                 var obj = ES3TypeMgr.GetOrCreateES3Type(type, true).Read<object>(jsonReader); | ||
|  |                 return obj; | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     public void Load(string filePath) | ||
|  | 	{ | ||
|  | 		Load(new ES3Settings (filePath)); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public void Load(string filePath, ES3Settings settings) | ||
|  | 	{ | ||
|  | 		Load(new ES3Settings (filePath, settings)); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public void Load(ES3Settings settings) | ||
|  | 	{ | ||
|  | 		Load(ES3Stream.CreateStream(settings, ES3FileMode.Read), settings); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public void LoadRaw(string str) | ||
|  | 	{ | ||
|  | 		Load(new MemoryStream (((new ES3Settings ()).encoding).GetBytes(str)), new ES3Settings()); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public void LoadRaw(string str, ES3Settings settings) | ||
|  | 	{ | ||
|  | 		Load(new MemoryStream ((settings.encoding).GetBytes(str)), settings); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	private void Load(Stream stream, ES3Settings settings) | ||
|  | 	{ | ||
|  | 		using (var reader = new StreamReader(stream)) | ||
|  | 		{ | ||
|  | 			int c_int; | ||
|  | 			char c; | ||
|  | 			string value = ""; | ||
|  | 			int col = 0; | ||
|  | 			int row = 0; | ||
|  | 
 | ||
|  | 			// Read until the end of the stream. | ||
|  | 			while(true) | ||
|  | 			{ | ||
|  | 				c_int = reader.Read(); | ||
|  | 				c = (char)c_int; | ||
|  | 				if(c == QUOTE_CHAR) | ||
|  | 				{ | ||
|  | 					while (true) | ||
|  | 					{ | ||
|  | 						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; | ||
|  | 					} | ||
|  | 				} | ||
|  | 				// 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) | ||
|  | 				{ | ||
|  | 					SetCell(col, row, value); | ||
|  | 					value = ""; | ||
|  | 					if(c == COMMA_CHAR) | ||
|  | 						col++; | ||
|  | 					else if(c == NEWLINE_CHAR) | ||
|  | 					{ | ||
|  | 						col = 0; | ||
|  | 						row++; | ||
|  | 					} | ||
|  | 					else | ||
|  | 						break; | ||
|  | 				} | ||
|  | 				else | ||
|  | 					value += c; | ||
|  | 			} | ||
|  | 		} | ||
|  |     } | ||
|  | 
 | ||
|  | 	public void Save(string filePath) | ||
|  | 	{ | ||
|  | 		Save(new ES3Settings (filePath), false); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public void Save(string filePath, ES3Settings settings) | ||
|  | 	{ | ||
|  | 		Save(new ES3Settings (filePath, settings), false); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public void Save(ES3Settings settings) | ||
|  | 	{ | ||
|  | 		Save(settings, false); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public void Save(string filePath, bool append) | ||
|  | 	{ | ||
|  | 		Save(new ES3Settings (filePath), append); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public void Save(string filePath, ES3Settings settings, bool append) | ||
|  | 	{ | ||
|  | 		Save(new ES3Settings (filePath, settings), append); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public void Save(ES3Settings settings, bool append) | ||
|  | 	{ | ||
|  | 		using (var writer = new StreamWriter(ES3Stream.CreateStream(settings, append ? ES3FileMode.Append : ES3FileMode.Write))) | ||
|  | 		{ | ||
|  | 			// If data already exists and we're appending, we need to prepend a newline. | ||
|  | 			if(append && ES3.FileExists(settings)) | ||
|  | 				writer.Write(NEWLINE_CHAR); | ||
|  | 
 | ||
|  | 			var array = ToArray(); | ||
|  | 			for(int row = 0; row < rows; row++) | ||
|  | 			{ | ||
|  | 				if(row != 0) | ||
|  | 					writer.Write(NEWLINE_CHAR); | ||
|  | 
 | ||
|  | 				for(int col = 0; col < cols; col++) | ||
|  | 				{ | ||
|  | 					if(col != 0) | ||
|  | 						writer.Write(COMMA_CHAR); | ||
|  | 
 | ||
|  |                     writer.Write( Escape(array [col, row]) ); | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 		if(!append) | ||
|  | 			ES3IO.CommitBackup(settings); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	private static string Escape(string str, bool isAlreadyWrappedInQuotes=false) | ||
|  | 	{ | ||
|  | 		if(string.IsNullOrEmpty(str)) | ||
|  | 			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; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	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; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	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; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	protected struct Index | ||
|  | 	{ | ||
|  | 		public int col; | ||
|  | 		public int row; | ||
|  | 
 | ||
|  | 		public Index(int col, int row) | ||
|  | 		{ | ||
|  | 			this.col = col; | ||
|  | 			this.row = row; | ||
|  | 		} | ||
|  | 	} | ||
|  | } |