using System; using System.ComponentModel; using System.Linq; using System.Reflection; using Fasterflect; namespace LeafWeb.Core.Utility { public static class ParsedObjectFactory where T : new() { static ParsedObjectFactory() { // register this for usage by TypeDescriptor.GetConverter BoolTypeConverter.Register(); } /// /// Create an object type T filling properties from the given title values /// /// Colon separated title: values public static T Create(string[] titleValues) { var properties = new ParseInfoPropertyMatcherWithCache().ParseInfoProperties; var obj = new T(); // take each of the for (var index = 0; index < titleValues.Length; index++) { PropertyInfo property = null; string value = null; var lineNumber = index + 1; var row = titleValues[index]; var split = row.Split(':'); if (split.Length > 1) { // handles case for "Title : Value" var title = split[0].Trim(); value = split[1].Trim(); property = properties .FirstOrDefault(p => p.Attribute().IsTitleMatch(title)); } if (property == null) { // handles case for row number, i.e. "Value" value = row.Trim(); property = properties .FirstOrDefault(p => p.Attribute().IsPositionMatch(lineNumber)); } if (property != null) { if (!TryConvertValue(property, value, out var convertedVal)) throw new ParseException($"Cannot convert value '{value}' for {property.Name} at line number {lineNumber}"); property.Set(obj, convertedVal); } } return obj; } public static T[] Create(string[] titles, string[][] valueArrays) { var matcher = new ParseInfoPropertyMatcherWithCache(); var objs = new T[valueArrays.Length]; for (var vIndex = 0; vIndex < valueArrays.Length; vIndex++) { var obj = new T(); var values = valueArrays[vIndex]; for (var tIndex = 0; tIndex < titles.Length; tIndex++) { var title = titles[tIndex]; var value = values[tIndex]; var position = tIndex + 1; if (IsMissingValue(value)) continue; var property = matcher.MatchProperty(title, position); if (property != null) { if (!TryConvertValue(property, value, out var convertedVal)) throw new ParseException($"Cannot convert value '{value}' for {property.Name} in position {position}"); property.Set(obj, convertedVal); } } objs[vIndex] = obj; } return objs; } public static T Create(Tuple[] titleValues) { var matcher = new ParseInfoPropertyMatcherWithCache(); var obj = new T(); for (var index = 0; index < titleValues.Length; index++) { var item = titleValues[index]; var position = index + 1; var title = item.Item1.Trim(); var value = item.Item2.Trim(); if (IsMissingValue(value)) continue; var property = matcher.MatchProperty(title, position); if (property != null) { if (!TryConvertValue(property, value, out var convertedVal)) throw new ParseException($"Cannot convert value '{value}' for {property.Name} in position {position}"); property.Set(obj, convertedVal); } } return obj; } private static bool IsMissingValue(string value) { return string.IsNullOrEmpty(value) || value == "NA" || value == "-9999"; } private static bool TryConvertValue(PropertyInfo property, object value, out object convertedValue) { try { // http://stackoverflow.com/questions/3531318/convert-changetype-fails-on-nullable-types var t = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType; value = HandleBoolEncodedProperty(property, value); // convertedValue = Convert.ChangeType(value, t); var converter = TypeDescriptor.GetConverter(t); convertedValue = converter.ConvertTo(value, t); } catch (Exception) { convertedValue = null; return false; } return true; } private static object HandleBoolEncodedProperty(PropertyInfo property, object value) { var boolEncodedPosition = property.Attribute().BoolEncodedPosition; if (boolEncodedPosition == null) return value; var v = (value as string)?.Substring(boolEncodedPosition.Value -1, 1); switch (v) { // these values are False=1, True=2 case "1": value = false; break; case "2": value = true; break; default: throw new ArgumentOutOfRangeException($"{property.Name} {boolEncodedPosition} {value} {v}"); } return value; } } }