/// <summary> /// Save a list, or an array of, type-T objects to a file. /// </summary> /// <typeparam name="T">User's object type.</typeparam> /// <param name="list">An enumerable object providing CSV data source.</param> /// <param name="csvpath">Path to a csv file.</param> /// <param name="hc">See comments of enum HeaderCare.</param> /// <param name="encoding">Text file encoding of the csv.</param> /// <returns>CSV data lines written, not including the header line.</returns> public static int SaveCsvFile <T>(IEnumerable <T> list, string csvpath, HeaderCare hc = HeaderCare.None, Encoding encoding = null) where T : class, new() { int count = 0; if (encoding == null) { encoding = System.Text.Encoding.Default; } string correct_header_line = CsvLiner <T> .HeaderLine(); using (StreamWriter textwriter = new StreamWriter(csvpath, false, encoding)) { if ((hc & HeaderCare.Preserve) != 0) { textwriter.WriteLine(correct_header_line); } foreach (T tobj in list) { textwriter.WriteLine(CsvLiner <T> .Put(tobj)); count++; } } // using file return(count); }
/// <summary> /// Demo code. /// You see, we can use natural C# obj.field syntax to access each CSV field, /// no need to touch any ugly/verbose/redundant number for csv column index. /// What a breeze! /// </summary> static void Demo_CsvLiner() { string headerline = CsvLiner <CRecord> .HeaderLine(); Console.WriteLine(headerline); CsvLiner <CRecord> .VerifyHeaderLine("FOOD,PRICE,QTY"); string csvinput1 = "Apple,1.5,100"; CRecord rec1 = CsvLiner <CRecord> .Get(csvinput1); string csvoutput1 = CsvLiner <CRecord> .Put(rec1); if (csvoutput1 == csvinput1) { Console.WriteLine(rec1.FOOD); Console.WriteLine(rec1.PRICE); Console.WriteLine(rec1.QTY); Console.WriteLine("OK. Match."); } string[] strcols = { "QTY", "PRICE", "FOOD" }; int[] idxcols = CsvLiner <CRecord> .Idx(strcols); Debug.Assert(idxcols[0] == 2 && idxcols[1] == 1 && idxcols[2] == 0); // If you are a freak insisting on existing symbols... string[] strcols2 = new string[] { CsvLiner <CRecord> .uso.QTY, CsvLiner <CRecord> .uso.PRICE, CsvLiner <CRecord> .uso.FOOD, }; int[] idxcols2 = CsvLiner <CRecord> .Idx(strcols2); Debug.Assert(idxcols2[0] == 2 && idxcols2[1] == 1 && idxcols2[2] == 0); CRecord r2 = CsvLiner <CRecord> .Get("10,Pear", new int[] { 2, 0 }); Debug.Assert(r2.FOOD == "Pear" && r2.PRICE == "" && r2.QTY == "10"); string s2 = CsvLiner <CRecord> .Put(r2, new int[] { 2, 0 }); Debug.Assert(s2 == "10,Pear"); // // Simplify typing a bit like this: // var cc = new CsvLiner <CRecordB>(); CRecordB rec2 = cc.get(csvinput1); string csvoutput2 = cc.put(rec2); Debug.Assert(csvoutput2 == csvinput1); Debug.Assert(cc.headerLine == headerline); Debug.Assert(cc.columns == CsvLiner <CRecord> .Columns()); }
/// <summary> /// Load a csv file and return an iterator of objects of user-given type(T). /// </summary> /// <typeparam name="T">User's object type.</typeparam> /// <param name="csvpath">Path to a csv file.</param> /// <param name="hc">See comments of enum HeaderCare.</param> /// <param name="encoding">Text file encoding of the csv.</param> /// <returns></returns> public static IEnumerable <T> LoadCsvFile <T>(string csvpath, HeaderCare hc = HeaderCare.None, Encoding encoding = null) where T : class, new() { if (encoding == null) { encoding = System.Text.Encoding.Default; } string correct_header_line = CsvLiner <T> .HeaderLine(); var csvlines = File.ReadLines(csvpath, encoding); using (var enumer = csvlines.GetEnumerator()) { if (!enumer.MoveNext()) { yield break; } string line0 = enumer.Current; bool is_line0_header = (line0 == correct_header_line) ? true : false; if ((hc & HeaderCare.Verify) != 0) { // Need verify CSV header if (!is_line0_header) { string s = $"CSV file does not have required headerline.\r\n" + $"CSV file:\r\n" + $" {csvpath}\r\n" + $"Required header line:\r\n" + $" {correct_header_line}\r\n"; throw new CsvLinerException(s); } } if ((hc & HeaderCare.Preserve) != 0) // user want to preserve first line { yield return(CsvLiner <T> .Get(line0)); } if (!is_line0_header) // first line is not a csv header { yield return(CsvLiner <T> .Get(line0)); } while (enumer.MoveNext()) { yield return(CsvLiner <T> .Get(enumer.Current)); } } // using enumerator }
/// <summary> /// Check whether inputline matches correct headerline. If not throw exception. /// </summary> /// <param name="inputline">the line to verify</param> /// <param name="selected_columns">Tells which columns to care. If null, verify all columns.</param> public static void VerifyHeaderLine(string inputline, int[] selected_columns = null) { string correct; if (selected_columns == null) { correct = CsvLiner <T> .HeaderLine(); } else { correct = CsvLiner <T> .HeaderLine(selected_columns); } if (correct != inputline) { string s = $"VerifyHeaderLine() mismatch.\r\n" + $" Input : {inputline}\r\n" + $" Correct: {correct}\r\n"; throw new CsvLinerException(s); } }
/// <summary> /// See CsvLinerException in action, when we pass wrong parameters. /// </summary> static void Demo_CsvLiner_Exception() { Console.Out.WriteLine("==== Demo_CsvLiner_Exception : ERecord1 ===="); // try { string headerline = CsvLiner <ERecord1> .HeaderLine(); Debug.Assert(false); } catch (CsvLinerException ex) { // Something undesired HERE! We hope to catch CsvLinerException, but in vain. Console.Out.WriteLine(ex.Message + "\r\n"); Debug.Assert(false); } catch (TypeInitializationException ex) { // Actually, we got this: Console.Out.WriteLine("Oops! Got TypeInitializationException. \r\n" + "This means we give wrong CsvLiner type parameters at compile time.\r\n" + "So we must check InnerException to get CsvLinerException."); Type inner_exctype = ex.InnerException.GetType(); string inner_message = ex.InnerException.Message; Console.Out.WriteLine(inner_exctype.FullName); Console.Out.WriteLine(inner_message); } Console.Out.WriteLine("==== Demo_CsvLiner_Exception : ERecord2 ===="); // try { string headerline = CsvLiner <ERecord2> .HeaderLine(); Debug.Assert(false); } catch (TypeInitializationException ex) { Console.Out.WriteLine(ex.InnerException.Message); } Console.Out.WriteLine("==== Demo_CsvLiner_Exception : ERecord3 ===="); // try { string headerline = CsvLiner <ERecord3> .HeaderLine(); Debug.Assert(false); } catch (TypeInitializationException ex) { Console.WriteLine(ex.InnerException.Message); } Console.Out.WriteLine("==== Demo_CsvLiner_Exception : Too many input columns ===="); // try { CRecord rec = CsvLiner <CRecord> .Get("Apple,1.5,100,XYZ"); Debug.Assert(false); } catch (CsvLinerException ex) { Console.WriteLine(ex.Message); } Console.Out.WriteLine("==== Demo_CsvLiner_Exception : Invalid column name ===="); // try { int[] idxcols = CsvLiner <CRecord> .Idx(new string[] { "QTY", "PriZe" }); Debug.Assert(false); } catch (CsvLinerException ex) { Console.WriteLine(ex.Message); } try { CsvLiner <CRecord> .VerifyHeaderLine("--FOOD,PRICE,QTY--"); Debug.Assert(false); } catch (CsvLinerException ex) { Console.WriteLine(ex.Message); } }