/// <summary> /// Get enumerable text objects beginning at the current position in the stream. /// </summary> /// <param name="textReader">Stream to read</param> /// <param name="t">Type of class objects to read into.</param> /// <returns> /// Enumerable list of class object. DO NOT close stream until after the /// enumerable list has been evaluated. /// </returns> private IEnumerable Deserialize(TextReader textReader, Type t) { var sp = new SerializerProperties(t, null, Culture); using (var reader = new CsvReader(textReader)) { var list = (IList)Activator.CreateInstance(typeof(List <>).MakeGenericType(t)); var headers = reader.ReadRecord(); var properties = sp.ReOrderPropertiesByHeaders(headers); while (!reader.EndOfFile) { var index = 0; var nuClass = Activator.CreateInstance(t); foreach (var field in reader.ReadField()) { var pa = properties[index]; // CSV cannot detect difference between "" and null, so we opt for null to support nullable types. pa.SetValue(nuClass, field.Length == 0 ? null : field); index++; } if (index > 0) { yield return(nuClass); } } yield break; } }
/// <summary> /// Serializes a single enumerable object into a single CSV document into the /// specified Stream. There are NO limits to the number of records written to the /// stream. There is no caching or buffering. Individual values are formatted and /// written to the stream immediately. /// </summary> /// <param name="textwriter"> /// Open stream to write to. Note: stream is not closed and stream pointer is not /// reset to beginning in order to potentially perform further processing. /// </param> /// <param name="items">Enumerable list of items to write.</param> public void Serialize(TextWriter textwriter, IEnumerable items) { var sp = new SerializerProperties(items, null, Culture); using (var writer = new CsvWriter(textwriter)) { // Write header record foreach (var p in sp.Properties) { writer.WriteField(p.Header); } writer.WriteEOL(); // Write records while (sp.ItemEnumerator.MoveNext()) { foreach (var p in sp.Properties) { writer.WriteField(p.GetValue(sp.ItemEnumerator.Current)); } writer.WriteEOL(); } } }
/// <summary> /// Create new Property Attribute object with all necessary values pre-computed for fast usability. /// </summary> /// <param name="p">Property info to use to set values.</param> /// <param name="ci">Culture to use for localization of header</param> private PropertyAttribute(PropertyInfo p, CultureInfo ci) { Name = p.Name; PropertyType = p.PropertyType; CellType = p.PropertyType.IsGenericType ? p.PropertyType.GenericTypeArguments[0] : p.PropertyType; IsNullable = p.PropertyType.IsGenericType; GetValue = (o) => p.GetValue(o); SetValue = (o, v) => p.SetValue(o, Cast.To(p.PropertyType, v)); if (CellType == typeof(bool)) { GetValue = (o) => p.GetValue(o)?.ToString(); // Excel uses TRUE/FALSE. We like True/False } else if (CellType == typeof(string)) { GetValue = (o) => p.GetValue(o)?.ToString().AppendSp(); SetValue = (o, v) => p.SetValue(o, v.ToString().TrimSp()); } else if (CellType == typeof(char)) // EPPlus assumes primitive types are always numbers! { GetValue = (o) => p.GetValue(o)?.ToString().AppendSp(); SetValue = (o, v) => p.SetValue(o, v?.ToString()?[0]); } XlColumnAttribute a = p.GetCustomAttribute <XlColumnAttribute>(true); if (a == null) { Header = LocalizedStrings.GetString(p.Name, null, ci); } else { if (ci.Name != string.Empty && a.TranslateData) { if (CellType == typeof(string) || CellType == typeof(bool)) { GetValue = (o) => { var v = p.GetValue(o); if (v == null) { return(null); } return(LocalizedStrings.GetString(v.ToString(), ci)); }; SetValue = (o, v) => { if (!string.IsNullOrWhiteSpace(v as string)) { p.SetValue(o, Cast.To(p.PropertyType, LocalizedStrings.ReverseLookup(v.ToString(), ci))); } }; } else if (CellType.IsEnum) { // Note: ExcelSerializer restricts values to a limited set of choices. var vals = Enum.GetValues(CellType); var elookup = new Dictionary <Enum, string>(vals.Length); var erevlookup = new Dictionary <string, Enum>(vals.Length); foreach (Enum e in vals) { var s = SerializerProperties.LocalizedEnumName(e, ci); elookup.Add(e, s); erevlookup.Add(s, e); } GetValue = (o) => { var v = p.GetValue(o); if (v == null) { return(null); } return(elookup[(Enum)v]); }; SetValue = (o, v) => { if (!string.IsNullOrWhiteSpace(v as string)) { p.SetValue(o, erevlookup[(string)v]); } }; } } Header = LocalizedStrings.GetString(a.Id, p.Name, ci); Format = a.Format; Frozen = a.Frozen; HasFilter = a.HasFilter; Justification = a.Justification; Hidden = a.Hidden; TranslateData = a.TranslateData; MaxWidth = a.MaxWidth; // Format styling may contain 2 comma delimited fields. The first field contains code char + int // where the int represents the number of digits after the decimal (aka precision). The optional 2nd // field contains the units string(including leading and trailing whitespace). However if the 2nd // field contains an integer, it is used as a relative column index to the column value containing the units. if (!string.IsNullOrWhiteSpace(Format) && (Format[0] == 'f' || Format[0] == 'F' || Format[0] == 'n' || Format[0] == 'N' || Format[0] == 'c' || Format[0] == 'C')) { var e = Format.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); if (e.Length > 1 && int.TryParse(e[1].Trim(), out var index)) { RelUnitsIndex = index; Format = e[0]; } } } }