diff --git a/Core.Tests/Core.Tests.csproj b/Core.Tests/Core.Tests.csproj index bfaaf0f..2b39d64 100644 --- a/Core.Tests/Core.Tests.csproj +++ b/Core.Tests/Core.Tests.csproj @@ -43,24 +43,35 @@ + - + + - + PreserveNewest - + PreserveNewest - + PreserveNewest - + + PreserveNewest + + + + PreserveNewest + + + PreserveNewest + + PreserveNewest - diff --git a/Core.Tests/Core.Tests.csproj.DotSettings b/Core.Tests/Core.Tests.csproj.DotSettings index c1b9bd1..fa43f95 100644 --- a/Core.Tests/Core.Tests.csproj.DotSettings +++ b/Core.Tests/Core.Tests.csproj.DotSettings @@ -1,2 +1,3 @@  + True True \ No newline at end of file diff --git a/Core.Tests/Parsers/CntrlComparisonParserTests.cs b/Core.Tests/Parsers/CntrlComparisonParserTests.cs new file mode 100644 index 0000000..6277361 --- /dev/null +++ b/Core.Tests/Parsers/CntrlComparisonParserTests.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using LeafWeb.Core.Models; +using LeafWeb.Core.Parsers; +using NUnit.Framework; + +namespace LeafWeb.Core.Tests.Parsers +{ + [TestFixture] + public class CntrlComparisonParserTests + { + protected const string ContentDirectory = @"Parsers\LeafOutputData\"; + + private static FileInfo GetContentFile(string fileName) + { + var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ContentDirectory); + return new FileInfo(path + fileName); + } + + [Test] + public void Parse_Valid() + { + var fileInfo = GetContentFile("cntrlcomparison_Wild Capsicum.csv"); + + CntrlComparison[] cntrlComparison; + using (var parser = new CntrlComparisonParser(fileInfo)) + cntrlComparison = parser.Parse(); + + Assert.That(cntrlComparison.Length, Is.EqualTo(7 * 4)); + } + } +} diff --git a/Core.Tests/Parsers/CurveDataListTests.cs b/Core.Tests/Parsers/CurveDataListTests.cs new file mode 100644 index 0000000..0955333 --- /dev/null +++ b/Core.Tests/Parsers/CurveDataListTests.cs @@ -0,0 +1,31 @@ +using System; +using System.IO; +using LeafWeb.Core.Charter; +using NUnit.Framework; + +namespace LeafWeb.Core.Tests.Parsers +{ + [TestFixture] + public class CurveDataListTests + { + protected const string ContentDirectory = @"Services\LeafOutputData\"; + + private static FileInfo GetContentFile(string fileName) + { + var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ContentDirectory); + return new FileInfo(path + fileName); + } + + [Test] + public void Parse_Valid() + { + var fileInfo = GetContentFile("cntrlcomparison_Wild Capsicum.csv"); + var cntrlComparison = new CurveDataList(); + using (var reader = fileInfo.OpenText()) + { + cntrlComparison.ReadFromStream(reader); + } + Assert.That(cntrlComparison.CurveData.Count, Is.EqualTo(7)); + } + } +} diff --git a/Core.Tests/Services/LeafInputCsvParserTests.cs b/Core.Tests/Parsers/LeafInputCsvParserTests.cs similarity index 97% rename from Core.Tests/Services/LeafInputCsvParserTests.cs rename to Core.Tests/Parsers/LeafInputCsvParserTests.cs index 48ba5ba..003668f 100644 --- a/Core.Tests/Services/LeafInputCsvParserTests.cs +++ b/Core.Tests/Parsers/LeafInputCsvParserTests.cs @@ -3,15 +3,16 @@ using System.Collections.Generic; using System.IO; using System.Linq; using LeafWeb.Core.Models; -using LeafWeb.Core.Services; +using LeafWeb.Core.Parsers; +using LeafWeb.Core.Utility; using NUnit.Framework; -namespace LeafWeb.Core.Tests.Services +namespace LeafWeb.Core.Tests.Parsers { [TestFixture] public class LeafInputCsvParserTests { - protected const string ContentDirectory = @"Services\LeafInputData\"; + protected const string ContentDirectory = @"Parsers\LeafInputData\"; private static FileInfo GetContentFile(string fileName) { diff --git a/Core.Tests/Services/LeafInputData/LeafInput-incompleteRows.csv b/Core.Tests/Parsers/LeafInputData/LeafInput-incompleteRows.csv similarity index 100% rename from Core.Tests/Services/LeafInputData/LeafInput-incompleteRows.csv rename to Core.Tests/Parsers/LeafInputData/LeafInput-incompleteRows.csv diff --git a/Core.Tests/Services/LeafInputData/LeafInput-noData.csv b/Core.Tests/Parsers/LeafInputData/LeafInput-noData.csv similarity index 100% rename from Core.Tests/Services/LeafInputData/LeafInput-noData.csv rename to Core.Tests/Parsers/LeafInputData/LeafInput-noData.csv diff --git a/Core.Tests/Services/LeafInputData/LeafInput-tabSeparated.csv b/Core.Tests/Parsers/LeafInputData/LeafInput-tabSeparated.csv similarity index 100% rename from Core.Tests/Services/LeafInputData/LeafInput-tabSeparated.csv rename to Core.Tests/Parsers/LeafInputData/LeafInput-tabSeparated.csv diff --git a/Core.Tests/Services/LeafInputData/LeafInput-titlesRemoved.csv b/Core.Tests/Parsers/LeafInputData/LeafInput-titlesRemoved.csv similarity index 100% rename from Core.Tests/Services/LeafInputData/LeafInput-titlesRemoved.csv rename to Core.Tests/Parsers/LeafInputData/LeafInput-titlesRemoved.csv diff --git a/Core.Tests/Services/LeafInputData/LeafInput-valid.csv b/Core.Tests/Parsers/LeafInputData/LeafInput-valid.csv similarity index 100% rename from Core.Tests/Services/LeafInputData/LeafInput-valid.csv rename to Core.Tests/Parsers/LeafInputData/LeafInput-valid.csv diff --git a/Core.Tests/Services/LeafOutputData/cntrlbestparameters_Wild Capsicum.csv b/Core.Tests/Parsers/LeafOutputData/cntrlbestparameters_Wild Capsicum.csv similarity index 100% rename from Core.Tests/Services/LeafOutputData/cntrlbestparameters_Wild Capsicum.csv rename to Core.Tests/Parsers/LeafOutputData/cntrlbestparameters_Wild Capsicum.csv diff --git a/Core.Tests/Services/LeafOutputData/cntrlcomparison_Wild Capsicum.csv b/Core.Tests/Parsers/LeafOutputData/cntrlcomparison_Wild Capsicum.csv similarity index 100% rename from Core.Tests/Services/LeafOutputData/cntrlcomparison_Wild Capsicum.csv rename to Core.Tests/Parsers/LeafOutputData/cntrlcomparison_Wild Capsicum.csv diff --git a/Core.Tests/Services/LeafOutputData/cntrlparameters_Wild Capsicum.csv b/Core.Tests/Parsers/LeafOutputData/cntrlparameters_Wild Capsicum.csv similarity index 100% rename from Core.Tests/Services/LeafOutputData/cntrlparameters_Wild Capsicum.csv rename to Core.Tests/Parsers/LeafOutputData/cntrlparameters_Wild Capsicum.csv diff --git a/Core.Tests/Services/CntrlComparisonCsvParserTests.cs b/Core.Tests/Services/CntrlComparisonCsvParserTests.cs deleted file mode 100644 index 9c9a6f1..0000000 --- a/Core.Tests/Services/CntrlComparisonCsvParserTests.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using NUnit.Framework; - -namespace LeafWeb.Core.Tests.Services -{ - [TestFixture] - public class CntrlComparisonCsvParserTests - { - protected const string ContentDirectory = @"Services\LeafOutputData\"; - - private static FileInfo GetContentFile(string fileName) - { - var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ContentDirectory); - return new FileInfo(path + fileName); - } - - } -} diff --git a/Core/Charter/CurveData.cs b/Core/Charter/CurveData.cs new file mode 100644 index 0000000..9c47287 --- /dev/null +++ b/Core/Charter/CurveData.cs @@ -0,0 +1,37 @@ +using System.IO; + +namespace LeafWeb.Core.Charter +{ + public class CurveData + { + private readonly string _curveId; + + public string CurveId => _curveId; + + // 1 + public CurveParamSet FixedCndFixedCmp { get; } + + // 2 + public CurveParamSet FixedCndEstimatedCmp { get; } + + // 3 + public CurveParamSet EstimatedCndFixedCmp { get; } + + // 4 + public CurveParamSet EstimatedCndEstimatedCmp { get; } + + + public CurveData(TextReader sr, ref int lineNbr) + { + // For each curve in the output file there are four sets of data. + + FixedCndFixedCmp = new CurveParamSet(sr, ref lineNbr, ref _curveId); + + FixedCndEstimatedCmp = new CurveParamSet(sr, ref lineNbr, ref _curveId); + + EstimatedCndFixedCmp = new CurveParamSet(sr, ref lineNbr, ref _curveId); + + EstimatedCndEstimatedCmp = new CurveParamSet(sr, ref lineNbr, ref _curveId); + } + } +} \ No newline at end of file diff --git a/Core/Charter/CurveDataList.cs b/Core/Charter/CurveDataList.cs new file mode 100644 index 0000000..3852d31 --- /dev/null +++ b/Core/Charter/CurveDataList.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.IO; + +namespace LeafWeb.Core.Charter +{ + public class CurveDataList + { + // Each element will be a PiscalCurve element that contains + // all of the output data for one curve. + public List CurveData { get; } + + public CurveDataList() + { + CurveData = new List(); + } + + public bool ReadFromStream(StreamReader sr) + { + // Skip the first two lines. + sr.ReadLine(); + sr.ReadLine(); + var lineNbr = 2; + + // Now, there should be one or more rows, delimited by a row that has + // CO2i in the first field. Read in all of these rows. The first field + // in each row is the curve ID (input filename). + + var more = true; + while (more) + { + var curve = new CurveData(sr, ref lineNbr); + + CurveData.Add(curve); + + if (sr.EndOfStream) + more = false; + } + return true; + } + } + +} \ No newline at end of file diff --git a/Core/Charter/CurveParamSet.cs b/Core/Charter/CurveParamSet.cs new file mode 100644 index 0000000..2c00a04 --- /dev/null +++ b/Core/Charter/CurveParamSet.cs @@ -0,0 +1,166 @@ +using System.Collections.Generic; +using System.IO; +using LeafWeb.Core.Utility; + +namespace LeafWeb.Core.Charter +{ + public class CurveParamSet + { + public List AnetMeasChloro1Data { get; } = new List(); // y=AnetMeas column, x=PCO2c, for PointLimitType=1 + public List AnetMeasChloro2Data { get; } = new List(); // y=AnetMeas column, x=PCO2c, for PointLimitType=2 + public List AnetMeasChloro3Data { get; } = new List(); // y=AnetMeas column, x=PCO2c, for PointLimitType=3 + public List AnetMeasInter1Data { get; } = new List(); // y=AnetMeas column, x=PCO2i, for PointLimitType=1 + public List AnetMeasInter2Data { get; } = new List(); // y=AnetMeas column, x=PCO2i, for PointLimitType=2 + public List AnetMeasInter3Data { get; } = new List(); // y=AnetMeas column, x=PCO2i, for PointLimitType=3 + + public List AcChloroData { get; } = new List(); + public List AjChloroData { get; } = new List(); + public List AtChloroData { get; } = new List(); + public List AcInterData { get; } = new List(); + public List AjInterData { get; } = new List(); + public List AtInterData { get; } = new List(); + + public CurveParamSet(TextReader sr, ref int lineNbr, ref string curveId) + { + bool curveIdSet = false, doneWithAnet = false; + string line; + List phrases; + + while (!doneWithAnet) + { + lineNbr++; + line = sr.ReadLine(); + if (line == null) + { + throw new ParseException("Unexpected end-of-file at line " + lineNbr); + } + + phrases = SplitCsvLine(line); + + var firstField = phrases[0]; + if (firstField.Equals("CO2i")) + { + doneWithAnet = true; + } + else + { + // The fields on the line: + // Column Name + // 0 CurveID + // 1 ChlFlUse + // 2 FitGi + // 3 FitGamma + // 4 FitKco + // 5 FitRd + // 6 FitAlpha + // 7 LimitCombina + // 8 PCO2i + // 9 PCO2c + // 10 AnetMeas + // 11 AnetCal + // 12 weitedrms + // 13 PointLimitType + + if (!curveIdSet) + { + curveId = firstField; + curveIdSet = true; + } + var xyPoint1 = new XyPoint(phrases[9], phrases[10]); // AnetMeas(y), PCO2c(x) + var xyPoint2 = new XyPoint(phrases[8], phrases[10]); + var pointLimitType = int.Parse(phrases[13]); + switch (pointLimitType) + { + case 1: + AnetMeasChloro1Data.Add(xyPoint1); + AnetMeasInter1Data.Add(xyPoint2); + break; + case 2: + AnetMeasChloro2Data.Add(xyPoint1); + AnetMeasInter2Data.Add(xyPoint2); + break; + case 3: + AnetMeasChloro3Data.Add(xyPoint1); + AnetMeasInter3Data.Add(xyPoint2); + break; + } + } + } + + // The next set of lines will have three pairs of x,y-coordinates to save. + // A blank line signals the end of the data. + + var moreData = true; + while (moreData) + { + // The fields on the line: + // Column Name + // 0 CO2i + // 1 CO2cc + // 2 Ac + // 3 CO2cj + // 4 Aj + // 5 CO2ct + // 6 At + + lineNbr++; + line = sr.ReadLine(); + if (line == null) + { + throw new ParseException("Unexpected end-of-file at line " + lineNbr); + } + + if (line.Length == 0) + moreData = false; + else + { + phrases = SplitCsvLine(line); + var xyPoint1 = new XyPoint(phrases[1],phrases[2]); // Ac(y),CO2cc(x) + if (xyPoint1.YIsInRange(-20.0, 50.0)) + AcChloroData.Add(xyPoint1); + xyPoint1 = new XyPoint(phrases[3], phrases[4]); // Aj(y),CO2cj(x) + if (xyPoint1.YIsInRange(-20.0, 50.0)) + AjChloroData.Add(xyPoint1); + xyPoint1 = new XyPoint(phrases[5], phrases[6]); // At(y),CO2ct(x) + if (xyPoint1.YIsInRange(-20.0, 50.0)) + AtChloroData.Add(xyPoint1); + + xyPoint1 = new XyPoint(phrases[0], phrases[2]); // Ac(y),CO2i(x) + if (xyPoint1.YIsInRange(-20.0, 50.0)) + AcInterData.Add(xyPoint1); + xyPoint1 = new XyPoint(phrases[0], phrases[4]); // Aj(y),CO2i(x) + if (xyPoint1.YIsInRange(-20.0, 50.0)) + AjInterData.Add(xyPoint1); + xyPoint1 = new XyPoint(phrases[0], phrases[6]); // At(y),CO2i(x) + if (xyPoint1.YIsInRange(-20.0, 50.0)) + AtInterData.Add(xyPoint1); + } + } + } + + /// + /// This method assumes that the argument is a comma-, blank-, or + /// tab-separated set of strings. It returns an ArrayList of those + /// quantities. Consecutive commas will be returned as an empty string, + /// but all empty strings at the end of the line will be thrown away. + /// + /// + /// + private static List SplitCsvLine(string line) + { + int i; + var separator = new [] {',', ' ', '\t'}; + var phrases = line.Split(separator); + + var retPhrases = new List(); + for (i = 0; i < phrases.Length; i++) + { + var phrase = phrases[i].Trim(); + if (phrase.Length > 0) + retPhrases.Add(phrase); + } + + return retPhrases; + } + } +} \ No newline at end of file diff --git a/Core/Models/XYPoint.cs b/Core/Charter/XYPoint.cs similarity index 60% rename from Core/Models/XYPoint.cs rename to Core/Charter/XYPoint.cs index 1226f3d..b562267 100644 --- a/Core/Models/XYPoint.cs +++ b/Core/Charter/XYPoint.cs @@ -1,14 +1,11 @@ -using System; - -namespace LeafWeb.Core.Models +namespace LeafWeb.Core.Charter { - [Serializable] public class XyPoint { - public XyPoint(String x, String y) + public XyPoint(string x, string y) { - X = Double.Parse(x); - Y = Double.Parse(y); + X = double.Parse(x); + Y = double.Parse(y); } public double X { get; private set; } diff --git a/Core/Core.csproj b/Core/Core.csproj index f9c0f43..3a76622 100644 --- a/Core/Core.csproj +++ b/Core/Core.csproj @@ -56,16 +56,26 @@ + + + + + + + + + + - - - - + + + + diff --git a/Core/Models/CntrlComparison.cs b/Core/Models/CntrlComparison.cs index f96b6e4..85022c4 100644 --- a/Core/Models/CntrlComparison.cs +++ b/Core/Models/CntrlComparison.cs @@ -1,49 +1,15 @@ -using System; using System.Collections.Generic; -using System.IO; +using System.ComponentModel.DataAnnotations; namespace LeafWeb.Core.Models { - [Serializable] + /// + /// The file 'cntrlcomparison.csv', which is in comma-separated-value format, contains outputs from PISCAL that + /// facilitates examination of how well the fitting is. + /// public class CntrlComparison { - // Each element will be a PiscalCurve element that contains - // all of the output data for one curve. - private readonly List _curveData; - - public CntrlComparison() - { - _curveData = new List(); - } - - public bool ReadFromStream(StreamReader sr, ref String errorMsg) - { - // Skip the first two lines. - sr.ReadLine(); - sr.ReadLine(); - var lineNbr = 2; - - // Now, there should be one or more rows, delimited by a row that has - // CO2i in the first field. Read in all of these rows. The first field - // in each row is the curve ID (input filename). - - var more = true; - while (more) - { - var curve = new CurveData(sr, ref errorMsg, ref lineNbr); - if (errorMsg.Length > 0) - return false; - - _curveData.Add(curve); - if (sr.EndOfStream) - more = false; - } - return true; - } - - public List GetCurveData() - { - return _curveData; - } + public virtual IEnumerable FittingInfo { get; set; } + public virtual IEnumerable PhotosyntheticInfo { get; set; } } } \ No newline at end of file diff --git a/Core/Models/CntrlComparisonFittingInfo.cs b/Core/Models/CntrlComparisonFittingInfo.cs new file mode 100644 index 0000000..4e866fa --- /dev/null +++ b/Core/Models/CntrlComparisonFittingInfo.cs @@ -0,0 +1,118 @@ +using LeafWeb.Core.Parsers; +using LeafWeb.Core.Utility; + +namespace LeafWeb.Core.Models +{ + /// + /// First model prediction is calculated at each sampling point. + /// Then model prediction is calculated at many selected levels of intercelluar CO2 partial pressure + /// levels under three limitation states to enable plotting curves.The same structure is then repeated for + /// each fitting of the same curve with different parameters to be estimated and with different curves. Note + /// that some common information about a curve is repeated for each data point to make the structure of the + /// file as regular as possible. + /// + public class CntrlComparisonFittingInfo + { + /// + /// the curve identifier, repeated for each point + /// + [ParseInfo(1)] + public string CurveID { get; set; } + + /// + /// whether or not chlorophyll fluorescence data are used for identifying the limitation states + /// 0 = not used, 1 = used. (Currently this choice is still under evaluation and therefore ChlFlUse? = 0). + /// + [ParseInfo(2, alternateTitle: "ChlFlUse?")] + public bool ChlFlUse {get; set; } + + /// + /// whether or not the internal conductance (gi) is fitted for. + /// 0 = not fitted and gi is either infinite or fixed at the value provided by the user. + /// 1 = gi is estiamted, repeated for each point + /// + [ParseInfo(3, alternateTitle: "FitGi?")] + public bool FitGi {get; set;} + + /// + /// whether or not the chloroplastic CO2 partial pressure photocompensation point is + /// fitted for. 0= not fitted, a prescribed value is used, repeated for each point + /// + [ParseInfo(4, alternateTitle: "FitGamma*?")] + public bool FitGammaStar {get; set; } + + /// + /// whether or not the apparent Michaelis - Menten constant Kco = Kc(1+O/Ko) is fitted for. + /// 0= not fitted, a prescribed value is calculated from Kc, Ko and the oxygen partial pressure. + /// 1= Fitted for, repeated for each point + /// + [ParseInfo(5, alternateTitle: "FitKco?")] + public bool FitKco {get; set; } + + /// + /// whether the dark respiration is fitted for. 0= not fitted for, a prescribed value is used. + /// 1= fitted for. repeated for each point + /// + [ParseInfo(6, alternateTitle: "FitRd?")] + public bool FitRd {get; set;} + + /// + /// whether alpha (the non-returned fraction of the glycolate carbon + /// recycled in the photorespiratory cycle) is fitted for. + /// 0= not fitted for and alpha = 0 + /// 1= fitted for, repeated for each point + /// + [ParseInfo(7, alternateTitle: "FitAlpha?")] + public bool FitAlpha {get; set;} + + /// + /// the combination of limitation states present in the A/Ci dataset. Parameters + /// are estimated for those limitation states that occur in the measured curve, repeated for each point + /// Rubisco: Rubisco limitation(Vcmax, Kco) + /// RuBP: RuBP regeneration limitation(J) + /// Tpu: export limitation(TPU, alpha) + /// + [ParseInfo(8)] + public string LimitCombina { get; set; } + + /// + /// intercellular CO2 partial pressure + /// + [ParseInfo(9, units:"Pa")] + public double PCO2i { get; set; } + + /// + /// chloroplastic CO2 partial pressure corresponding to the PCO2i of a point. + /// PCO2c=PCO2i-AnetCal/internal conductance + /// + [ParseInfo(10, units:"Pa")] + public double PCO2c { get; set; } + + /// + /// measured net assimilation rate + /// + [ParseInfo(11, units: "umol/m2/s")] + public double AnetMeas { get; set; } + + /// + /// calculated net assimilation rate + /// + [ParseInfo(12, units: "umol/m2/s")] + public double AnetCal { get; set; } + + // TODO: readme has weitedrms, data file has PCOiCal + // weitedrms - (umol/m2/s), root mean square error of the fitting (weiting coefficient =1) + [ParseInfo(13)] + public double PCOiCal { get; set; } + + /// + /// 1 = point limited by rubisco + /// 2 = point limited by RuBP regeneration + /// 3 = point limited by TPU + /// + [ParseInfo(14, units:"1,2,3")] + public int PointLimitType { get; set; } + + public virtual CntrlComparison CntrlComparison { get; set; } + } +} \ No newline at end of file diff --git a/Core/Models/CntrlComparisonPhotosyntheticInfo.cs b/Core/Models/CntrlComparisonPhotosyntheticInfo.cs new file mode 100644 index 0000000..26abc6b --- /dev/null +++ b/Core/Models/CntrlComparisonPhotosyntheticInfo.cs @@ -0,0 +1,56 @@ +using LeafWeb.Core.Parsers; +using LeafWeb.Core.Utility; + +namespace LeafWeb.Core.Models +{ + /// + /// The second section gives photosynthesis for each of the three limitation states at selected values of intercellular + /// partial pressure.Note that the corresponding values of chloraplastic CO2 partial pressure depend on the limitation state. + /// + public class CntrlComparisonPhotosyntheticInfo + { + /// + /// intercellular CO2 partial pressure + /// + [ParseInfo(1, units:"Pa")] + public double CO2i { get; set; } + + /// + /// Chloraplastic CO2 partial pressure for Rubisco limited photosynthesis + /// + [ParseInfo(2, units: "Pa")] + public double CO2cc { get; set; } + + /// + /// Rubisco-limited net assimilation rate calculated with the optimized parameters + /// + [ParseInfo(3, units: "umol/m2/s")] + public double Ac { get; set; } + + /// + /// Chloraplastic CO2 partial pressure for RuBP regeneration limited photosynthesis + /// + [ParseInfo(4, units:"Pa")] + public double CO2cj { get; set; } + + /// + /// RuBP regeneration-limited net assimilation rate calculated with estimated parameters + /// + [ParseInfo(5, units: "umol/m2/s")] + public double Aj { get; set; } + + /// + /// Chloraplastic CO2 partial pressure for export limited photosynthesis + /// + [ParseInfo(6, units: "Pa")] + public double CO2ct { get; set; } + + /// + /// TPU-limited net assimilation rate calculated with estimated parameters + /// + [ParseInfo(7, units: "umol/m2/s")] + public double At { get; set; } + + public virtual CntrlComparison CntrlComparison { get; set; } + } +} \ No newline at end of file diff --git a/Core/Models/CurveData.cs b/Core/Models/CurveData.cs deleted file mode 100644 index e0f75db..0000000 --- a/Core/Models/CurveData.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.IO; - -namespace LeafWeb.Core.Models -{ - [Serializable] - public class CurveData - { - private readonly string _curveId; - private readonly CurveParamSet _paramSet1; - private readonly CurveParamSet _paramSet2; - private readonly CurveParamSet _paramSet3; - private readonly CurveParamSet _paramSet4; - - public CurveData(StreamReader sr, ref string errMsg, ref int lineNbr) - { - // For each curve in the output file there are four sets of data. - - _paramSet1 = new CurveParamSet(sr, ref errMsg, ref lineNbr, ref _curveId); - if (errMsg.Length > 0) - return; - - _paramSet2 = new CurveParamSet(sr, ref errMsg, ref lineNbr, ref _curveId); - if (errMsg.Length > 0) - return; - - _paramSet3 = new CurveParamSet(sr, ref errMsg, ref lineNbr, ref _curveId); - if (errMsg.Length > 0) - return; - - _paramSet4 = new CurveParamSet(sr, ref errMsg, ref lineNbr, ref _curveId); - } - - public string GetCurveId() - { - return _curveId; - } - - public CurveParamSet CndctFixedCmpPntFixedParams() - { - return _paramSet1; - } - - public CurveParamSet CndctFixedCmpPntEstimatedParams() - { - return _paramSet2; - } - - public CurveParamSet CndctEstimatedCmpPntFixedParams() - { - return _paramSet3; - } - - public CurveParamSet CndctEstimatedCmpPntEstimatedParams() - { - return _paramSet4; - } - } -} \ No newline at end of file diff --git a/Core/Models/CurveParamSet.cs b/Core/Models/CurveParamSet.cs deleted file mode 100644 index 6470d32..0000000 --- a/Core/Models/CurveParamSet.cs +++ /dev/null @@ -1,238 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace LeafWeb.Core.Models -{ - [Serializable] - public class CurveParamSet - { - private readonly List _anetMeasChloro1Data; // y=AnetMeas column, x=PCO2c, for PointLimitType=1 - private readonly List _anetMeasChloro2Data; // y=AnetMeas column, x=PCO2c, for PointLimitType=2 - private readonly List _anetMeasChloro3Data; // y=AnetMeas column, x=PCO2c, for PointLimitType=3 - private readonly List _anetMeasInter1Data; // y=AnetMeas column, x=PCO2i, for PointLimitType=1 - private readonly List _anetMeasInter2Data; // y=AnetMeas column, x=PCO2i, for PointLimitType=2 - private readonly List _anetMeasInter3Data; // y=AnetMeas column, x=PCO2i, for PointLimitType=3 - private readonly List _acChloroData; // y=Ac column, x=CO2cc column - private readonly List _ajChloroData; // y=Aj column, x=CO2cj column - private readonly List _atChloroData; // y=At column, x=CO2ct column - private readonly List _acInterData; // y=Ac column, x=CO2i column - private readonly List _ajInterData; // y=Aj column, x=CO2i column - private readonly List _atInterData; // y=At column, x=CO2i column - - public CurveParamSet() - { - } - - public CurveParamSet(StreamReader sr, ref String errMsg, ref int lineNbr, ref String curveId) - { - bool curveIdSet = false, doneWithAnet = false; - String line; - var errorMsg = new StringBuilder(""); - List phrases; - XyPoint xyPoint1; - _anetMeasChloro1Data = new List(); - _anetMeasChloro2Data = new List(); - _anetMeasChloro3Data = new List(); - _anetMeasInter1Data = new List(); - _anetMeasInter2Data = new List(); - _anetMeasInter3Data = new List(); - _acChloroData = new List(); - _ajChloroData = new List(); - _atChloroData = new List(); - _acInterData = new List(); - _ajInterData = new List(); - _atInterData = new List(); - - while (!doneWithAnet) - { - lineNbr++; - line = sr.ReadLine(); - if (line == null) - { - errorMsg.Append("Unexpected end-of-file at line " + lineNbr); - sr.Close(); - errMsg = errorMsg.ToString(); - return; - } - - phrases = SplitCsvLine(line); - - String firstField = phrases[0]; - if (firstField.Equals("CO2i")) - { - doneWithAnet = true; - } - else - { - // The fields on the line: - // Column Name - // 0 CurveID - // 1 ChlFlUse - // 2 FitGi - // 3 FitGamma - // 4 FitKco - // 5 FitRd - // 6 FitAlpha - // 7 LimitCombina - // 8 PCO2i - // 9 PCO2c - // 10 AnetMeas - // 11 AnetCal - // 12 weitedrms - // 13 PointLimitType - - if (!curveIdSet) - { - curveId = firstField; - curveIdSet = true; - } - xyPoint1 = new XyPoint(phrases[9], phrases[10]); // AnetMeas(y), PCO2c(x) - var xyPoint2 = new XyPoint(phrases[8], phrases[10]); - var pointLimitType = Int32.Parse(phrases[13]); - switch (pointLimitType) - { - case 1: - _anetMeasChloro1Data.Add(xyPoint1); - _anetMeasInter1Data.Add(xyPoint2); - break; - case 2: - _anetMeasChloro2Data.Add(xyPoint1); - _anetMeasInter2Data.Add(xyPoint2); - break; - case 3: - _anetMeasChloro3Data.Add(xyPoint1); - _anetMeasInter3Data.Add(xyPoint2); - break; - } - } - } - - // The next set of lines will have three pairs of x,y-coordinates to save. - // A blank line signals the end of the data. - - var moreData = true; - while (moreData) - { - // The fields on the line: - // Column Name - // 0 CO2i - // 1 CO2cc - // 2 Ac - // 3 CO2cj - // 4 Aj - // 5 CO2ct - // 6 At - - lineNbr++; - line = sr.ReadLine(); - if (line == null) - { - errorMsg.Append("Unexpected end-of-file at line " + lineNbr); - sr.Close(); - errMsg = errorMsg.ToString(); - return; - } - - if (line.Length == 0) - moreData = false; - else - { - phrases = SplitCsvLine(line); - xyPoint1 = new XyPoint(phrases[1],phrases[2]); // Ac(y),CO2cc(x) - if (xyPoint1.YIsInRange(-20.0, 50.0)) - _acChloroData.Add(xyPoint1); - xyPoint1 = new XyPoint(phrases[3], phrases[4]); // Aj(y),CO2cj(x) - if (xyPoint1.YIsInRange(-20.0, 50.0)) - _ajChloroData.Add(xyPoint1); - xyPoint1 = new XyPoint(phrases[5], phrases[6]); // At(y),CO2ct(x) - if (xyPoint1.YIsInRange(-20.0, 50.0)) - _atChloroData.Add(xyPoint1); - - xyPoint1 = new XyPoint(phrases[0], phrases[2]); // Ac(y),CO2i(x) - if (xyPoint1.YIsInRange(-20.0, 50.0)) - _acInterData.Add(xyPoint1); - xyPoint1 = new XyPoint(phrases[0], phrases[4]); // Aj(y),CO2i(x) - if (xyPoint1.YIsInRange(-20.0, 50.0)) - _ajInterData.Add(xyPoint1); - xyPoint1 = new XyPoint(phrases[0], phrases[6]); // At(y),CO2i(x) - if (xyPoint1.YIsInRange(-20.0, 50.0)) - _atInterData.Add(xyPoint1); - } - } - } - - /// - /// This method assumes that the argument is a comma-, blank-, or - /// tab-separated set of strings. It returns an ArrayList of those - /// quantities. Consecutive commas will be returned as an empty string, - /// but all empty strings at the end of the line will be thrown away. - /// - /// - /// - private static List SplitCsvLine(String line) - { - int i; - var separator = new [] {',', ' ', '\t'}; - var phrases = line.Split(separator); - - //int lastNonBlank = -1; - var retPhrases = new List(); - for (i = 0; i < phrases.Length; i++) - { - var phrase = phrases[i].Trim(); - if (phrase.Length > 0) - // lastNonBlank = i; - retPhrases.Add(phrase); - } - - return retPhrases; - } - - public List> GetAnetMeasData() - { - var list = new List> - { - _anetMeasChloro1Data, - _anetMeasChloro2Data, - _anetMeasChloro3Data, - _anetMeasInter1Data, - _anetMeasInter2Data, - _anetMeasInter3Data - }; - - return list; - } - - public List GetAcChloroData() - { - return _acChloroData; - } - - public List GetAjChloroData() - { - return _ajChloroData; - } - - public List GetAtChloroData() - { - return _atChloroData; - } - - public List GetAcInterData() - { - return _acInterData; - } - - public List GetAjInterData() - { - return _ajInterData; - } - - public List GetAtInterData() - { - return _atInterData; - } - } -} \ No newline at end of file diff --git a/Core/Models/LeafInput.cs b/Core/Models/LeafInput.cs index b253744..51359c1 100644 --- a/Core/Models/LeafInput.cs +++ b/Core/Models/LeafInput.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using LeafWeb.Core.Services; +using LeafWeb.Core.Parsers; +using LeafWeb.Core.Utility; namespace LeafWeb.Core.Models { diff --git a/Core/Models/LeafInputData.cs b/Core/Models/LeafInputData.cs index a7b5a7c..396d9f9 100644 --- a/Core/Models/LeafInputData.cs +++ b/Core/Models/LeafInputData.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; -using LeafWeb.Core.Services; +using LeafWeb.Core.Parsers; +using LeafWeb.Core.Utility; namespace LeafWeb.Core.Models { diff --git a/Core/Models/LeafInputPhotosynthetic.cs b/Core/Models/LeafInputPhotosynthetic.cs index 0b61462..0408930 100644 --- a/Core/Models/LeafInputPhotosynthetic.cs +++ b/Core/Models/LeafInputPhotosynthetic.cs @@ -1,4 +1,5 @@ -using LeafWeb.Core.Services; +using LeafWeb.Core.Parsers; +using LeafWeb.Core.Utility; namespace LeafWeb.Core.Models { diff --git a/Core/Models/LeafInputSite.cs b/Core/Models/LeafInputSite.cs index 5a93aab..0237623 100644 --- a/Core/Models/LeafInputSite.cs +++ b/Core/Models/LeafInputSite.cs @@ -1,4 +1,5 @@ -using LeafWeb.Core.Services; +using LeafWeb.Core.Parsers; +using LeafWeb.Core.Utility; namespace LeafWeb.Core.Models { diff --git a/Core/Parsers/CntrlComparisonParser.cs b/Core/Parsers/CntrlComparisonParser.cs new file mode 100644 index 0000000..355399d --- /dev/null +++ b/Core/Parsers/CntrlComparisonParser.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using LeafWeb.Core.Models; +using LeafWeb.Core.Utility; + +namespace LeafWeb.Core.Parsers +{ + public class CntrlComparisonParser : CsvParserBase + { + public CntrlComparisonParser(FileSystemInfo csvFile) : base(csvFile) + { + } + + public CntrlComparison[] Parse() + { + var fittingTitles = GetNextCsvRowValues(); + if (fittingTitles == null) + throw new ParseException($"Could not read data header row on line number {CsvReader.Row}"); + var fitUnits = GetNextCsvRowValues(); + if (fitUnits == null) + throw new ParseException($"Could not read data units row on line number {CsvReader.Row}"); + + return ParseCntrlComparisonSet(fittingTitles).ToArray(); + } + + private IEnumerable ParseCntrlComparisonSet(string[] fittingTitles) + { + var endOfFile = false; + while (!endOfFile) + { + // First Section + var fittingValues = new List(); + string[] photosyntheticTitles; + while (true) + { + var values = GetNextCsvRowValues(); + if (CsvReader.IsRecordEmpty()) + throw new ParseException($"Encountered empty line while readding fitting info on line {CsvReader.Row}"); + if (values == null) // end of file + yield break; + if (ParsedObjectFactory.IsPropertiesTitlesMatch(values)) + { + photosyntheticTitles = values; + break; + } + fittingValues.Add(values); + } + + var photosyntheticValues = new List(); + while (true) + { + var values = GetNextCsvRowValues(); + if (CsvReader.IsRecordEmpty()) // end of set + break; + if (values == null) + { + endOfFile = true; + break; + } + photosyntheticValues.Add(values); + } + + var fittingInfo = + ParsedObjectFactory + .Create(fittingTitles, fittingValues.ToArray()); + var photosyntheticInfo = + ParsedObjectFactory + .Create(photosyntheticTitles, photosyntheticValues.ToArray()); + + yield return new CntrlComparison + { + FittingInfo = fittingInfo, + PhotosyntheticInfo = photosyntheticInfo + }; + } + } + } +} diff --git a/Core/Parsers/CsvParserBase.cs b/Core/Parsers/CsvParserBase.cs new file mode 100644 index 0000000..5fd31e9 --- /dev/null +++ b/Core/Parsers/CsvParserBase.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.IO; +using CsvHelper; +using CsvHelper.Configuration; + +namespace LeafWeb.Core.Parsers +{ + public class CsvParserBase : IDisposable + { + protected readonly FileSystemInfo CsvFile; + private readonly StreamReader _reader; + protected readonly CsvReader CsvReader; + + protected CsvParserBase(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, IgnoreBlankLines = false}; + CsvReader = new CsvReader(_reader, csvConfiguration); + } + + protected string[] GetNextCsvRowValues() + { + // get values from row + if (!CsvReader.Read()) + return null; + + // put all the values from this row into an array + var values = new List(); + var index = 0; + string value; + while (CsvReader.TryGetField(index, out value)) + { + values.Add(value); + index++; + } + return values.ToArray(); + } + + public void Dispose() + { + _reader.Dispose(); + } + } +} \ No newline at end of file diff --git a/Core/Services/LeafInputCsvParser.cs b/Core/Parsers/LeafInputCsvParser.cs similarity index 67% rename from Core/Services/LeafInputCsvParser.cs rename to Core/Parsers/LeafInputCsvParser.cs index 18f5263..3740659 100644 --- a/Core/Services/LeafInputCsvParser.cs +++ b/Core/Parsers/LeafInputCsvParser.cs @@ -2,33 +2,22 @@ using System.Collections.Generic; using System.IO; using CsvHelper; -using CsvHelper.Configuration; using LeafWeb.Core.Models; +using LeafWeb.Core.Utility; -namespace LeafWeb.Core.Services +namespace LeafWeb.Core.Parsers { - public class LeafInputCsvParser : IDisposable + public class LeafInputCsvParser : CsvParserBase { - private readonly FileSystemInfo _csvFile; - private readonly StreamReader _reader; - private readonly CsvReader _csv; - - public LeafInputCsvParser(FileSystemInfo csvFile) + public LeafInputCsvParser(FileSystemInfo csvFile) : base(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; + leafInput.FileName = CsvFile.Name; // Next 3 (Header, Units, and Values) leafInput.Site = ParseLeafInputSite(); @@ -47,46 +36,28 @@ namespace LeafWeb.Core.Services var items = new List(); for (var i = 0; i < 10; i++) { - if (!_csv.Read()) - throw new ParseException("Could not read line number " + _csv.Row); + if (!CsvReader.Read()) + throw new ParseException("Could not read line number " + CsvReader.Row); string field; - if (!_csv.TryGetField(0, out field)) - throw new ParseException("Could not read first field on line number " + _csv.Row); + if (!CsvReader.TryGetField(0, out field)) + throw new ParseException("Could not read first field on line number " + CsvReader.Row); items.Add(field); } return ParsedObjectFactory.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(); - 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); + throw new ParseException("Could not read site header row on line number " + CsvReader.Row); var units = GetNextCsvRowValues(); if (units == null) - throw new ParseException("Could not read site units row on line number " + _csv.Row); + throw new ParseException("Could not read site units row on line number " + CsvReader.Row); var values = GetNextCsvRowValues(); if (values == null) - throw new ParseException("Could not read site value row on line number " + _csv.Row); + throw new ParseException("Could not read site value row on line number " + CsvReader.Row); var items = new List>(); for (var i = 0; i < titles.Length && i < values.Length; i++) @@ -101,13 +72,13 @@ namespace LeafWeb.Core.Services { var titles = GetNextCsvRowValues(); if (titles == null) - throw new ParseException("Could not read photosynthetic header row on line number " + _csv.Row); + throw new ParseException("Could not read photosynthetic header row on line number " + CsvReader.Row); var units = GetNextCsvRowValues(); if (units == null) - throw new ParseException("Could not read photosynthetic units row on line number " + _csv.Row); + throw new ParseException("Could not read photosynthetic units row on line number " + CsvReader.Row); var values = GetNextCsvRowValues(); if (values == null) - throw new ParseException("Could not read photosynthetic value row on line number " + _csv.Row); + throw new ParseException("Could not read photosynthetic value row on line number " + CsvReader.Row); var items = new List>(); for (var i = 0; i < titles.Length && i < values.Length; i++) @@ -122,10 +93,10 @@ namespace LeafWeb.Core.Services { var titles = GetNextCsvRowValues(); if (titles == null) - throw new ParseException("Could not read data header row on line number " + _csv.Row); + throw new ParseException("Could not read data header row on line number " + CsvReader.Row); var units = GetNextCsvRowValues(); if (units == null) - throw new ParseException("Could not read data units row on line number " + _csv.Row); + throw new ParseException("Could not read data units row on line number " + CsvReader.Row); var valueArrays = new List(); while (true) @@ -138,11 +109,6 @@ namespace LeafWeb.Core.Services return ParsedObjectFactory.Create(titles, valueArrays.ToArray()); } - public void Dispose() - { - _reader.Dispose(); - } - public static void ExportCsv(string filename, IEnumerable leafInputs) { using (var textWriter = new StreamWriter(filename)) diff --git a/Core/Utility/BoolTypeConverter.cs b/Core/Utility/BoolTypeConverter.cs new file mode 100644 index 0000000..eccfe67 --- /dev/null +++ b/Core/Utility/BoolTypeConverter.cs @@ -0,0 +1,47 @@ +using System; +using System.ComponentModel; +using System.Globalization; + +namespace LeafWeb.Core.Utility +{ + // http://stackoverflow.com/a/19022931/99492 + public class BoolTypeConverter : TypeConverter + { + public static void Register() + { + TypeDescriptor.AddAttributes(typeof(Boolean), + new TypeConverterAttribute(typeof(BoolTypeConverter))); + } + + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + if (destinationType == typeof(bool)) + { + return true; + } + return base.CanConvertTo(context, destinationType); + } + + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + if (value is string) + { + var s = value as string; + if (string.IsNullOrEmpty(s)) + return false; + switch (s.Trim().ToUpper()) + { + case "TRUE": + case "YES": + case "1": + case "-1": + return true; + + default: + return false; + } + } + return base.ConvertTo(context, culture, value, destinationType); + } + } +} \ No newline at end of file diff --git a/Core/Services/ParseException.cs b/Core/Utility/ParseException.cs similarity index 80% rename from Core/Services/ParseException.cs rename to Core/Utility/ParseException.cs index 13ba277..5cda437 100644 --- a/Core/Services/ParseException.cs +++ b/Core/Utility/ParseException.cs @@ -1,6 +1,6 @@ using System; -namespace LeafWeb.Core.Services +namespace LeafWeb.Core.Utility { public class ParseException : Exception { diff --git a/Core/Services/ParseInfoAttribute.cs b/Core/Utility/ParseInfoAttribute.cs similarity index 95% rename from Core/Services/ParseInfoAttribute.cs rename to Core/Utility/ParseInfoAttribute.cs index ef39b68..a313a6b 100644 --- a/Core/Services/ParseInfoAttribute.cs +++ b/Core/Utility/ParseInfoAttribute.cs @@ -1,9 +1,8 @@ using System; using System.Linq; using System.Runtime.CompilerServices; -using LeafWeb.Core.Utility; -namespace LeafWeb.Core.Services +namespace LeafWeb.Core.Utility { [AttributeUsage(AttributeTargets.Property)] public class ParseInfoAttribute : Attribute diff --git a/Core/Services/ParsedObjectFactory.cs b/Core/Utility/ParsedObjectFactory.cs similarity index 67% rename from Core/Services/ParsedObjectFactory.cs rename to Core/Utility/ParsedObjectFactory.cs index 2ea33e0..a08e11e 100644 --- a/Core/Services/ParsedObjectFactory.cs +++ b/Core/Utility/ParsedObjectFactory.cs @@ -1,12 +1,19 @@ using System; +using System.ComponentModel; using System.Linq; using System.Reflection; using Fasterflect; -namespace LeafWeb.Core.Services +namespace LeafWeb.Core.Utility { public static class ParsedObjectFactory where T : new() { + static ParsedObjectFactory() + { + // register this for usage by TypeDescriptor.GetConverter + BoolTypeConverter.Register(); + } + private static PropertyInfo[] GetProperties() { var propertyInfos = typeof(T).Properties(); @@ -52,7 +59,7 @@ namespace LeafWeb.Core.Services { 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)); + throw new ParseException($"Cannot convert value '{value}' for {property.Name} at line number {lineNumber}"); property.Set(obj, convertedVal); } } @@ -83,7 +90,7 @@ namespace LeafWeb.Core.Services { 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)); + throw new ParseException($"Cannot convert value '{value}' for {property.Name} in position {position}"); property.Set(obj, convertedVal); } } @@ -113,7 +120,7 @@ namespace LeafWeb.Core.Services { 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)); + throw new ParseException($"Cannot convert value '{value}' for {property.Name} in position {position}"); property.Set(obj, convertedVal); } } @@ -127,26 +134,55 @@ namespace LeafWeb.Core.Services private static PropertyInfo MatchProperty(PropertyInfo[] properties, string title, int position) { - var property = - properties - .FirstOrDefault(p => p.Attribute().IsTitleMatch(title)); + return + properties.FirstOrDefault(p => p.Attribute().IsTitleMatch(title)) ?? + properties.FirstOrDefault(p => p.Attribute().IsPositionMatch(position)); + } - if (property == null) + private static PropertyInfo MatchPropertyExact(PropertyInfo[] properties, string title, int position) + { + return + properties + .FirstOrDefault(p => + { + var attribute = p.Attribute(); + return attribute.IsTitleMatch(title) && attribute.IsPositionMatch(position); + }); + } + + public static bool IsPropertiesTitlesMatch(string[] titles) + { + var properties = GetProperties(); + var propertyMatch = 0; + var propertyNoMatch = 0; + + for (var i = 0; i < titles.Length; i++) { - property = - properties - .FirstOrDefault(p => p.Attribute().IsPositionMatch(position)); + var title = titles[i]; + var position = i + 1; + if (string.IsNullOrEmpty(title)) + continue; + + var property = MatchPropertyExact(properties, title, position); + + if (property != null) + propertyMatch++; + else + propertyNoMatch++; } - return property; + + return propertyMatch / (double) propertyNoMatch > .9; } private static bool TryConvertValue(PropertyInfo property, object value, out object convertedValue) { - try + 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); + // convertedValue = Convert.ChangeType(value, t); + var converter = TypeDescriptor.GetConverter(t); + convertedValue = converter.ConvertTo(value, t); } catch (Exception) { diff --git a/Core/Utility/StringExtensions.cs b/Core/Utility/StringExtensions.cs index cffcb11..2a8b942 100644 --- a/Core/Utility/StringExtensions.cs +++ b/Core/Utility/StringExtensions.cs @@ -1,5 +1,4 @@ -using System; -using System.Globalization; +using System.Globalization; using System.Linq; namespace LeafWeb.Core.Utility @@ -9,9 +8,9 @@ namespace LeafWeb.Core.Utility public static string SplitCamelCase(this string str) { return str.Aggregate( - String.Empty, + string.Empty, (current, c) => - current + (Char.IsUpper(c) && current.Length > 0 ? " " + c : c.ToString(CultureInfo.InvariantCulture))); + current + (char.IsUpper(c) && current.Length > 0 ? " " + c : c.ToString(CultureInfo.InvariantCulture))); } } } diff --git a/Web/Charter/LeafWebCharter.ascx.cs b/Web/Charter/LeafWebCharter.ascx.cs index 8f9e9c3..1d9b842 100644 --- a/Web/Charter/LeafWebCharter.ascx.cs +++ b/Web/Charter/LeafWebCharter.ascx.cs @@ -6,23 +6,24 @@ using System.IO; using System.Web.UI; using System.Web.UI.DataVisualization.Charting; using System.Web.UI.WebControls; +using LeafWeb.Core.Charter; using LeafWeb.Core.Models; namespace LeafWeb.Web.Charter { public partial class LeafWebCharter : UserControl { - protected void ReadFile(StreamReader sr, ref String errMsg) + protected void ReadFile(StreamReader sr, ref string errMsg) { - var pisOut = new CntrlComparison(); - if (!pisOut.ReadFromStream(sr, ref errMsg)) + var pisOut = new CurveDataList(); + if (!pisOut.ReadFromStream(sr)) { ErrorLBL.Text = errMsg; ErrorLBL.Visible = true; return; } Session["LeafChartData"] = pisOut; - var aCopy = (CntrlComparison) Session["LeafChartData"]; + var aCopy = (CurveDataList) Session["LeafChartData"]; // The data was successfully read from the file. We must now // display the curveIDs from the file and prompt the user to pick @@ -31,12 +32,12 @@ namespace LeafWeb.Web.Charter var curveDT = new DataTable(); curveDT.Columns.Add(new DataColumn("curveID")); - var curveData = pisOut.GetCurveData(); + var curveData = pisOut.CurveData; for (var i = 0; i < curveData.Count; i++) { var aCurve = curveData[i]; var dr = curveDT.NewRow(); - dr["curveID"] = aCurve.GetCurveId(); + dr["curveID"] = aCurve.CurveId; curveDT.Rows.Add(dr); } @@ -48,7 +49,7 @@ namespace LeafWeb.Web.Charter // cntrlcomparison - public void ProduceCharts(CntrlComparison pisOut) + public void ProduceCharts(CurveDataList pisOut) { // If the session has timed out, use the selected index from the GridView // to determine which job to chart. @@ -62,14 +63,14 @@ namespace LeafWeb.Web.Charter // break; // } - var curveData = pisOut.GetCurveData(); + var curveData = pisOut.CurveData; var curve = curveData[1]; - var curveId = curve.GetCurveId(); - CurveSeries(curveId, curve.CndctFixedCmpPntFixedParams(), ChartChloro1, ChartInter1, "Internal conductance fixed, compensation point and M-M constants fixed"); - CurveSeries(curveId, curve.CndctFixedCmpPntEstimatedParams(), ChartChloro2, ChartInter2, "Internal conductance fixed, compensation point and M-M constants estimated"); - CurveSeries(curveId, curve.CndctEstimatedCmpPntFixedParams(), ChartChloro3, ChartInter3, "Internal conductance estimated, compensation point and M-M constants fixed"); - CurveSeries(curveId, curve.CndctEstimatedCmpPntEstimatedParams(), ChartChloro4, ChartInter4, "Internal conductance estimated, compensation point and M-M constants estimated"); + var curveId = curve.CurveId; + CurveSeries(curveId, curve.FixedCndFixedCmp, ChartChloro1, ChartInter1, "Internal conductance fixed, compensation point and M-M constants fixed"); + CurveSeries(curveId, curve.FixedCndEstimatedCmp, ChartChloro2, ChartInter2, "Internal conductance fixed, compensation point and M-M constants estimated"); + CurveSeries(curveId, curve.EstimatedCndFixedCmp, ChartChloro3, ChartInter3, "Internal conductance estimated, compensation point and M-M constants fixed"); + CurveSeries(curveId, curve.EstimatedCndEstimatedCmp, ChartChloro4, ChartInter4, "Internal conductance estimated, compensation point and M-M constants estimated"); } private static Chart GetChart() @@ -203,11 +204,9 @@ namespace LeafWeb.Web.Charter private static void CurveSeries(string curveId, CurveParamSet paramSet, Chart chloroChart, Chart interChart, string chartTitle) { - var paramSetAnet = paramSet.GetAnetMeasData(); - - var anetMeasChloro1 = paramSetAnet[0]; - var anetMeasChloro2 = paramSetAnet[1]; - var anetMeasChloro3 = paramSetAnet[2]; + var anetMeasChloro1 = paramSet.AnetMeasChloro1Data; + var anetMeasChloro2 = paramSet.AnetMeasChloro2Data; + var anetMeasChloro3 = paramSet.AnetMeasChloro3Data; // Set the points for the symbol series for paramater set 1, chloroplastic setAnetMeasPoints(anetMeasChloro1, chloroChart.Series["Rubisco-limited"]); @@ -217,9 +216,9 @@ namespace LeafWeb.Web.Charter setAnetMeasPoints(anetMeasChloro3, tpuSeries); chloroChart.Series.Add(tpuSeries); - var anetMeasInter1 = paramSetAnet[3]; - var anetMeasInter2 = paramSetAnet[4]; - var anetMeasInter3 = paramSetAnet[5]; + var anetMeasInter1 = paramSet.AnetMeasInter1Data; + var anetMeasInter2 = paramSet.AnetMeasInter2Data; + var anetMeasInter3 = paramSet.AnetMeasInter3Data; // Set the points for the symbol series for paramater set 1, intercellular setAnetMeasPoints(anetMeasInter1, interChart.Series["Rubisco-limited"]); @@ -229,18 +228,18 @@ namespace LeafWeb.Web.Charter setAnetMeasPoints(anetMeasInter3, tpuSeries); interChart.Series.Add(tpuSeries); - var acChloroList = paramSet.GetAcChloroData(); - var ajChloroList = paramSet.GetAjChloroData(); - var atChloroList = paramSet.GetAtChloroData(); + var acChloroList = paramSet.AcChloroData; + var ajChloroList = paramSet.AjChloroData; + var atChloroList = paramSet.AtChloroData; // Set the points on the asymptote curve for parameter set 1, chloroplast setAsymptotePoints(acChloroList, chloroChart.Series["acCurve"]); setAsymptotePoints(ajChloroList, chloroChart.Series["ajCurve"]); setAsymptotePoints(atChloroList, chloroChart.Series["atCurve"]); - var acInterList = paramSet.GetAcInterData(); - var ajInterList = paramSet.GetAjInterData(); - var atInterList = paramSet.GetAtInterData(); + var acInterList = paramSet.AcInterData; + var ajInterList = paramSet.AjInterData; + var atInterList = paramSet.AtInterData; // Set the points on the asymptote curve for parameter set 1, intercellular setAsymptotePoints(acInterList, interChart.Series["acCurve"]); @@ -251,8 +250,7 @@ namespace LeafWeb.Web.Charter var titleFont = new Font("Times New Roman", 12, FontStyle.Bold); var title = new Title("LeafWeb curveID = " + curveId + - "\n" + chartTitle); - title.Font = titleFont; + "\n" + chartTitle) {Font = titleFont}; chloroChart.Titles.Add(title); interChart.Titles.Add(title); diff --git a/Web/Content/Documents/aci_pce_final.pdf b/Web/Content/Documents/aci_pce_final.pdf index 353a8c7..1817602 100644 Binary files a/Web/Content/Documents/aci_pce_final.pdf and b/Web/Content/Documents/aci_pce_final.pdf differ diff --git a/Web/Web.csproj b/Web/Web.csproj index 529fb3d..fa798cb 100644 --- a/Web/Web.csproj +++ b/Web/Web.csproj @@ -143,8 +143,6 @@ - - LeafWebCharter.ascx ASPXCodeBehind @@ -152,8 +150,6 @@ LeafWebCharter.ascx - - @@ -173,7 +169,7 @@ - + Web.config @@ -185,6 +181,12 @@ + + + {25baed75-7e75-4d11-90d9-358472054df6} + Core + + 10.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)