Leaf Input Parsing complete

This commit is contained in:
2015-11-20 11:39:23 -05:00
parent 750b2aee7b
commit 1fb73b9f76
26 changed files with 1294 additions and 1 deletions
+155
View File
@@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using System.IO;
using CsvHelper;
using CsvHelper.Configuration;
using LeafWeb.Core.Models;
namespace LeafWeb.Core.Services
{
public class LeafInputCsvParser : IDisposable
{
private readonly FileSystemInfo _csvFile;
private readonly StreamReader _reader;
private readonly CsvReader _csv;
public LeafInputCsvParser(FileSystemInfo csvFile)
{
_csvFile = csvFile;
if (!csvFile.Exists)
throw new FileNotFoundException("Cannot find file '" + csvFile.Name + "'");
_reader = File.OpenText(csvFile.FullName);
var csvConfiguration = new CsvConfiguration {HasHeaderRecord = false};
_csv = new CsvReader(_reader, csvConfiguration);
}
public LeafInput Parse()
{
// First 10 lines
var leafInput = ParseLeafInput();
leafInput.FileName = _csvFile.Name;
// Next 3 (Header, Units, and Values)
leafInput.Site = ParseLeafInputSite();
// Next 3 (Header, Units, and Values)
leafInput.Photosynthetic = ParseLeafInputPhotosynthetic();
// Remaining lines (Header, Units, and Data)
leafInput.Data = ParseLeafInputData();
return leafInput;
}
private LeafInput ParseLeafInput()
{
var items = new List<string>();
for (var i = 0; i < 10; i++)
{
if (!_csv.Read())
throw new ParseException("Could not read line number " + _csv.Row);
string field;
if (!_csv.TryGetField(0, out field))
throw new ParseException("Could not read first field on line number " + _csv.Row);
items.Add(field);
}
return ParsedObjectFactory<LeafInput>.Create(items.ToArray());
}
private string[] GetNextCsvRowValues()
{
// get values from row
if (!_csv.Read())
return null;
// put all the values from this row into an array
var values = new List<string>();
var index = 0;
string value;
while (_csv.TryGetField(index, out value))
{
values.Add(value);
index++;
}
return values.ToArray();
}
private LeafInputSite ParseLeafInputSite()
{
var titles = GetNextCsvRowValues();
if (titles == null)
throw new ParseException("Could not read site header row on line number " + _csv.Row);
var units = GetNextCsvRowValues();
if (units == null)
throw new ParseException("Could not read site units row on line number " + _csv.Row);
var values = GetNextCsvRowValues();
if (values == null)
throw new ParseException("Could not read site value row on line number " + _csv.Row);
var items = new List<Tuple<string, string>>();
for (var i = 0; i < titles.Length && i < values.Length; i++)
{
var item = Tuple.Create(titles[i], values[i]);
items.Add(item);
}
return ParsedObjectFactory<LeafInputSite>.Create(items.ToArray());
}
private LeafInputPhotosynthetic ParseLeafInputPhotosynthetic()
{
var titles = GetNextCsvRowValues();
if (titles == null)
throw new ParseException("Could not read photosynthetic header row on line number " + _csv.Row);
var units = GetNextCsvRowValues();
if (units == null)
throw new ParseException("Could not read photosynthetic units row on line number " + _csv.Row);
var values = GetNextCsvRowValues();
if (values == null)
throw new ParseException("Could not read photosynthetic value row on line number " + _csv.Row);
var items = new List<Tuple<string, string>>();
for (var i = 0; i < titles.Length && i < values.Length; i++)
{
var item = Tuple.Create(titles[i], values[i]);
items.Add(item);
}
return ParsedObjectFactory<LeafInputPhotosynthetic>.Create(items.ToArray());
}
private LeafInputData[] ParseLeafInputData()
{
var titles = GetNextCsvRowValues();
if (titles == null)
throw new ParseException("Could not read data header row on line number " + _csv.Row);
var units = GetNextCsvRowValues();
if (units == null)
throw new ParseException("Could not read data units row on line number " + _csv.Row);
var valueArrays = new List<string[]>();
while (true)
{
var values = GetNextCsvRowValues();
if (values == null)
break;
valueArrays.Add(values);
}
return ParsedObjectFactory<LeafInputData>.Create(titles, valueArrays.ToArray());
}
public void Dispose()
{
_reader.Dispose();
}
public static void ExportCsv(string filename, IEnumerable<LeafInput> leafInputs)
{
using (var textWriter = new StreamWriter(filename))
{
var csv = new CsvWriter(textWriter);
csv.WriteRecords(leafInputs);
}
}
}
}
+12
View File
@@ -0,0 +1,12 @@
using System;
namespace LeafWeb.Core.Services
{
public class ParseException : Exception
{
public ParseException(string message)
: base(message)
{
}
}
}
+46
View File
@@ -0,0 +1,46 @@
using System;
using System.Linq;
using System.Runtime.CompilerServices;
using LeafWeb.Core.Utility;
namespace LeafWeb.Core.Services
{
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class ParseInfoAttribute : Attribute
{
public int Position { get; private set; }
public string Units { get; private set; }
public string Title { get; private set; }
public string AlterateTitle { get; private set; }
public string ExampleValue { get; private set; }
public string[] FieldNames
{
get
{
var fieldNames = new[] {Title, Title.SplitCamelCase()};
if (!string.IsNullOrEmpty(AlterateTitle))
fieldNames = fieldNames.Concat(new[] { AlterateTitle }).ToArray();
return fieldNames;
}
}
public ParseInfoAttribute(int position, [CallerMemberName]string title = null, string units = null, string alternateTitle = null, string exampleValue = null)
{
AlterateTitle = alternateTitle;
ExampleValue = exampleValue;
Title = title;
Position = position;
Units = units;
}
public bool IsTitleMatch(string title)
{
return FieldNames.Any(t => string.Compare(title, t, StringComparison.InvariantCultureIgnoreCase) == 0);
}
public bool IsPositionMatch(int index)
{
return Position == index;
}
}
}
+159
View File
@@ -0,0 +1,159 @@
using System;
using System.Linq;
using System.Reflection;
using Fasterflect;
namespace LeafWeb.Core.Services
{
public static class ParsedObjectFactory<T> where T : new()
{
private static PropertyInfo[] GetProperties()
{
var propertyInfos = typeof(T).Properties();
return propertyInfos.Where(p => p.HasAttribute<ParseInfoAttribute>()).ToArray();
}
/// <summary>
/// Create an object type T filling properties from the given title values
/// </summary>
/// <param name="titleValues">Colon separated title: values</param>
public static T Create(string[] titleValues)
{
var properties = GetProperties();
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<ParseInfoAttribute>().IsTitleMatch(title));
}
if (property == null)
{
// handles case for row number, i.e. "Value"
value = row.Trim();
property =
properties
.FirstOrDefault(p => p.Attribute<ParseInfoAttribute>().IsPositionMatch(lineNumber));
}
if (property != null)
{
object convertedVal;
if (!TryConvertValue(property, value, out convertedVal))
throw new ParseException(string.Format("Cannot convert value '{0}' for {1} at line number {2}", value, property.Name, lineNumber));
property.Set(obj, convertedVal);
}
}
return obj;
}
public static T[] Create(string[] titles, string[][] valueArrays)
{
var properties = GetProperties();
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 = MatchProperty(properties, title, position);
if (property != null)
{
object convertedVal;
if (!TryConvertValue(property, value, out convertedVal))
throw new ParseException(string.Format("Cannot convert value '{0}' for {1} in position {2}", value, property.Name, position));
property.Set(obj, convertedVal);
}
}
objs[vIndex] = obj;
}
return objs;
}
public static T Create(Tuple<string, string>[] titleValues)
{
var properties = GetProperties();
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 = MatchProperty(properties, title, position);
if (property != null)
{
object convertedVal;
if (!TryConvertValue(property, value, out convertedVal))
throw new ParseException(string.Format("Cannot convert value '{0}' for {1} in position {2}", value, property.Name, position));
property.Set(obj, convertedVal);
}
}
return obj;
}
private static bool IsMissingValue(string value)
{
return string.IsNullOrEmpty(value) || value == "NA" || value == "-9999";
}
private static PropertyInfo MatchProperty(PropertyInfo[] properties, string title, int position)
{
var property =
properties
.FirstOrDefault(p => p.Attribute<ParseInfoAttribute>().IsTitleMatch(title));
if (property == null)
{
property =
properties
.FirstOrDefault(p => p.Attribute<ParseInfoAttribute>().IsPositionMatch(position));
}
return property;
}
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;
convertedValue = Convert.ChangeType(value, t);
}
catch (Exception)
{
convertedValue = null;
return false;
}
return true;
}
}
}