From 1cf75e777ba1ff9fb0ba9a13828e68a8d35f7f32 Mon Sep 17 00:00:00 2001 From: Michael Krisper <michael.krisper@tugraz.at> Date: Wed, 8 Apr 2015 12:17:58 +0200 Subject: [PATCH] started with classes for reading job file and writing sum file --- .../Models/Simulation/Impl/JobContainer.cs | 38 +- .../Simulation/Impl/SimulatorFactory.cs | 60 +-- .../Models/Simulation/Impl/VectoSimulator.cs | 393 +++++++++++++++--- VectoCore/Utils/VectoCSVFile.cs | 248 +++++------ 4 files changed, 520 insertions(+), 219 deletions(-) diff --git a/VectoCore/Models/Simulation/Impl/JobContainer.cs b/VectoCore/Models/Simulation/Impl/JobContainer.cs index 74d9191336..fe54618460 100644 --- a/VectoCore/Models/Simulation/Impl/JobContainer.cs +++ b/VectoCore/Models/Simulation/Impl/JobContainer.cs @@ -5,21 +5,29 @@ using Common.Logging; namespace TUGraz.VectoCore.Models.Simulation.Impl { - //todo: add job tracking (state of jobs, iteration, ...) - //todo: add job control (pause, stop) - public class JobContainer - { - private readonly List<IVectoSimulator> _simulators = new List<IVectoSimulator>(); + //todo: add job tracking (state of jobs, iteration, ...) + //todo: add job control (pause, stop) - public void AddJob(IVectoSimulator sim) - { - _simulators.Add(sim); - } - public void RunSimulation() - { - LogManager.GetLogger(GetType()).Info("VectoSimulator started running. Starting Jobs."); - Task.WaitAll(_simulators.Select(job => Task.Factory.StartNew(job.Run)).ToArray()); - } - } + /// <summary> + /// Container for simulation jobs. + /// </summary> + public class JobContainer + { + private readonly List<IVectoSimulator> _simulators = new List<IVectoSimulator>(); + + public void AddJob(IVectoSimulator sim) + { + _simulators.Add(sim); + } + + /// <summary> + /// Runs all jobs, waits until finished. + /// </summary> + public void RunSimulation() + { + LogManager.GetLogger(GetType()).Info("VectoSimulator started running. Starting Jobs."); + Task.WaitAll(_simulators.Select(job => Task.Factory.StartNew(job.Run)).ToArray()); + } + } } \ No newline at end of file diff --git a/VectoCore/Models/Simulation/Impl/SimulatorFactory.cs b/VectoCore/Models/Simulation/Impl/SimulatorFactory.cs index 17e5fff093..196366ee96 100644 --- a/VectoCore/Models/Simulation/Impl/SimulatorFactory.cs +++ b/VectoCore/Models/Simulation/Impl/SimulatorFactory.cs @@ -6,44 +6,46 @@ using TUGraz.VectoCore.Models.SimulationComponent.Impl; namespace TUGraz.VectoCore.Models.Simulation.Impl { - public class SimulatorFactory - { - public static IVectoSimulator CreateTimeBasedEngineOnlyJob(string engineFile, string cycleFile, string resultFile) - { - Action<string> debug = LogManager.GetLogger<SimulatorFactory>().Debug; + public class SimulatorFactory + { + public static IVectoSimulator CreateTimeBasedEngineOnlyJob(string engineFile, string cycleFile, + string resultFile) + { + Action<string> debug = LogManager.GetLogger<SimulatorFactory>().Debug; - debug("Creating VehicleContainer."); - var container = new VehicleContainer(); + debug("Creating VehicleContainer."); + var container = new VehicleContainer(); - debug("SimulationFactory creating cycle."); - var cycleData = DrivingCycleData.ReadFromFileEngineOnly(cycleFile); - var cycle = new EngineOnlyDrivingCycle(container, cycleData); + debug("Creating cycle."); + var cycleData = DrivingCycleData.ReadFromFileEngineOnly(cycleFile); + var cycle = new EngineOnlyDrivingCycle(container, cycleData); - debug("SimulationFactory creating engine."); - var engineData = CombustionEngineData.ReadFromFile(engineFile); - var engine = new CombustionEngine(container, engineData); + debug("Creating engine."); + var engineData = CombustionEngineData.ReadFromFile(engineFile); + var engine = new CombustionEngine(container, engineData); - debug("Creating gearbox."); - var gearBox = new EngineOnlyGearbox(container); + debug("Creating gearbox."); + var gearBox = new EngineOnlyGearbox(container); - debug("SimulationFactory creating auxiliary"); - var aux = new EngineOnlyAuxiliary(container, new AuxiliariesDemandAdapter(cycleData)); + debug("Creating auxiliary"); + var aux = new EngineOnlyAuxiliary(container, new AuxiliariesDemandAdapter(cycleData)); - debug("SimulationFactory connecting auxiliary with engine."); - aux.Connect(engine.OutShaft()); + debug("Connecting auxiliary with engine."); + aux.Connect(engine.OutShaft()); - debug("SimulationFactory connecting gearbox with auxiliary."); - gearBox.InShaft().Connect(aux.OutShaft()); + debug("Connecting gearbox with auxiliary."); + gearBox.InShaft().Connect(aux.OutShaft()); - debug("SimulationFactory connecting cycle with gearbox."); - cycle.InShaft().Connect(gearBox.OutShaft()); + debug("Connecting cycle with gearbox."); + cycle.InShaft().Connect(gearBox.OutShaft()); - var dataWriter = new ModalDataWriter(resultFile); + var dataWriter = new ModalDataWriter(resultFile); - debug("Creating Simulator."); - var simulator = new VectoSimulator(container, cycle, dataWriter); + debug("Creating Simulator."); + //todo: load job file? + var simulator = new VectoSimulator(container, cycle, dataWriter); - return simulator; - } - } + return simulator; + } + } } \ No newline at end of file diff --git a/VectoCore/Models/Simulation/Impl/VectoSimulator.cs b/VectoCore/Models/Simulation/Impl/VectoSimulator.cs index 3c551a88ae..2eb099ffe1 100644 --- a/VectoCore/Models/Simulation/Impl/VectoSimulator.cs +++ b/VectoCore/Models/Simulation/Impl/VectoSimulator.cs @@ -1,64 +1,347 @@ -using System; +using System; +using System.Collections.Generic; +using System.Data; using Common.Logging; +using Newtonsoft.Json; using TUGraz.VectoCore.Models.Connector.Ports; using TUGraz.VectoCore.Models.Connector.Ports.Impl; using TUGraz.VectoCore.Models.Simulation.Data; using TUGraz.VectoCore.Models.SimulationComponent; +using TUGraz.VectoCore.Utils; namespace TUGraz.VectoCore.Models.Simulation.Impl { - public class VectoSimulator : IVectoSimulator - { - private TimeSpan _absTime = new TimeSpan(seconds: 0, minutes: 0, hours: 0); - private TimeSpan _dt = new TimeSpan(seconds: 1, minutes: 0, hours: 0); - - public VectoSimulator(IVehicleContainer container, IDrivingCycle cycle, IModalDataWriter dataWriter) - { - Container = container; - Cycle = cycle; - DataWriter = dataWriter; - } - - protected IDrivingCycle Cycle { get; set; } - protected IModalDataWriter DataWriter { get; set; } - protected IVehicleContainer Container { get; set; } - - public IVehicleContainer GetContainer() - { - return Container; - } - - public void Run() - { - LogManager.GetLogger(GetType()).Info("VectoJob started running."); - IResponse response; - do { - response = Cycle.Request(_absTime, _dt); - while (response is ResponseFailTimeInterval) { - _dt = (response as ResponseFailTimeInterval).DeltaT; - response = Cycle.Request(_absTime, _dt); - } - - if (response is ResponseCycleFinished) { - break; - } - - - DataWriter[ModalResultField.time] = (_absTime + TimeSpan.FromTicks(_dt.Ticks / 2)).TotalSeconds; - DataWriter[ModalResultField.simulationInterval] = _dt.TotalSeconds; - - Container.CommitSimulationStep(DataWriter); - - // set _dt to difference to next full second. - _absTime += _dt; - _dt = TimeSpan.FromSeconds(1) - TimeSpan.FromMilliseconds(_dt.Milliseconds); - } while (response is ResponseSuccess); - - Container.FinishSimulation(DataWriter); - - //todo: write vsum file - - LogManager.GetLogger(GetType()).Info("VectoJob finished."); - } - } + public class VectoJob + { + /// <summary> + /// A class which represents the json data format for serializing and deserializing the VectoJob files. + /// </summary> + /// <remarks> + ///{ + /// "Header": { + /// "CreatedBy": " ()", + /// "Date": "3/4/2015 2:09:13 PM", + /// "AppVersion": "2.0.4-beta3", + /// "FileVersion": 2 + /// }, + /// "Body": { + /// "SavedInDeclMode": false, + /// "VehicleFile": "24t Coach.vveh", + /// "EngineFile": "24t Coach.veng", + /// "GearboxFile": "24t Coach.vgbx", + /// "Cycles": [ + /// "W:\\VECTO\\CITnet\\VECTO\\bin\\Debug\\Declaration\\MissionCycles\\LOT2_rural Engine Only.vdri" + /// ], + /// "Aux": [ + /// { + /// "ID": "ALT1", + /// "Type": "Alternator", + /// "Path": "24t_Coach_ALT.vaux", + /// "Technology": "" + /// }, + /// { + /// "ID": "ALT2", + /// "Type": "Alternator", + /// "Path": "24t_Coach_ALT.vaux", + /// "Technology": "" + /// }, + /// { + /// "ID": "ALT3", + /// "Type": "Alternator", + /// "Path": "24t_Coach_ALT.vaux", + /// "Technology": "" + /// } + /// ], + /// "VACC": "Coach.vacc", + /// "EngineOnlyMode": true, + /// "StartStop": { + /// "Enabled": false, + /// "MaxSpeed": 5.0, + /// "MinTime": 0.0, + /// "Delay": 0 + /// }, + /// "LAC": { + /// "Enabled": true, + /// "Dec": -0.5, + /// "MinSpeed": 50.0 + /// }, + /// "OverSpeedEcoRoll": { + /// "Mode": "OverSpeed", + /// "MinSpeed": 70.0, + /// "OverSpeed": 5.0, + /// "UnderSpeed": 5.0 + /// } + /// } + /// } + /// </remarks> + public class Data + { + [JsonProperty(Required = Required.Always)] public DataHeader Header; + [JsonProperty(Required = Required.Always)] public DataBody Body; + + public class DataHeader + { + [JsonProperty(Required = Required.Always)] public string CreatedBy; + [JsonProperty(Required = Required.Always)] public DateTime Date; + [JsonProperty(Required = Required.Always)] public string AppVersion; + [JsonProperty(Required = Required.Always)] public double FileVersion; + } + + public class DataBody + { + [JsonProperty("SavedInDeclMode")] public bool SavedInDeclMode; + + [JsonProperty(Required = Required.Always)] public string VehicleFile; + [JsonProperty(Required = Required.Always)] public string EngineFile; + [JsonProperty(Required = Required.Always)] public string GearboxFile; + [JsonProperty(Required = Required.Always)] public IList<string> Cycles; + [JsonProperty(Required = Required.Always)] public IList<AuxData> Aux; + [JsonProperty(Required = Required.Always)] public string VACC; + [JsonProperty(Required = Required.Always)] public bool EngineOnlyMode; + [JsonProperty(Required = Required.Always)] public StartStopData StartStop; + [JsonProperty(Required = Required.Always)] public LACData LAC; + [JsonProperty(Required = Required.Always)] public OverSpeedEcoRollData OverSpeedEcoRoll; + + public class AuxData + { + [JsonProperty(Required = Required.Always)] public string ID; + [JsonProperty(Required = Required.Always)] public string Type; + [JsonProperty(Required = Required.Always)] public string Path; + [JsonProperty(Required = Required.Always)] public string Technology; + } + + public class StartStopData + { + [JsonProperty(Required = Required.Always)] public bool Enabled; + [JsonProperty(Required = Required.Always)] public double MaxSpeed; + [JsonProperty(Required = Required.Always)] public double MinTime; + [JsonProperty(Required = Required.Always)] public double Delay; + } + + public class LACData + { + [JsonProperty(Required = Required.Always)] public bool Enabled; + [JsonProperty(Required = Required.Always)] public double Dec; + [JsonProperty(Required = Required.Always)] public double MinSpeed; + } + + public class OverSpeedEcoRollData + { + [JsonProperty(Required = Required.Always)] public string Mode; + [JsonProperty(Required = Required.Always)] public double MinSpeed; + [JsonProperty(Required = Required.Always)] public double OverSpeed; + [JsonProperty(Required = Required.Always)] public double UnderSpeed; + } + } + } + } + + + /// <summary> + /// Simulator for one vecto simulation job. + /// </summary> + public class VectoSimulator : IVectoSimulator + { + private TimeSpan _absTime = new TimeSpan(seconds: 0, minutes: 0, hours: 0); + private TimeSpan _dt = new TimeSpan(seconds: 1, minutes: 0, hours: 0); + + public VectoSimulator(string name, string fileName, IVehicleContainer container, IDrivingCycle cycle, + IModalDataWriter dataWriter) + { + Name = name; + FileName = fileName; + Container = container; + Cycle = cycle; + DataWriter = dataWriter; + } + + public string FileName { get; set; } + + protected string Name { get; set; } + + protected IDrivingCycle Cycle { get; set; } + protected IModalDataWriter DataWriter { get; set; } + protected IVehicleContainer Container { get; set; } + + public IVehicleContainer GetContainer() + { + return Container; + } + + public void Run() + { + LogManager.GetLogger(GetType()).Info("VectoJob started running."); + IResponse response; + do { + response = Cycle.Request(_absTime, _dt); + while (response is ResponseFailTimeInterval) { + _dt = (response as ResponseFailTimeInterval).DeltaT; + response = Cycle.Request(_absTime, _dt); + } + + if (response is ResponseCycleFinished) { + break; + } + + DataWriter[ModalResultField.time] = (_absTime + TimeSpan.FromTicks(_dt.Ticks / 2)).TotalSeconds; + DataWriter[ModalResultField.simulationInterval] = _dt.TotalSeconds; + + Container.CommitSimulationStep(DataWriter); + + // set _dt to difference to next full second. + _absTime += _dt; + _dt = TimeSpan.FromSeconds(1) - TimeSpan.FromMilliseconds(_dt.Milliseconds); + } while (response is ResponseSuccess); + + Container.FinishSimulation(DataWriter); + + + WriteSummary(); + + LogManager.GetLogger(GetType()).Info("VectoJob finished."); + } + + + /* + Name Unit Description + Job [-] Job number. Format is "x-y" with x = file number and y = cycle number + Input File [-] Name of the input file + Cycle [-] Name of the cycle file + time [s] Total simulation time + distance [km] Total travelled distance + speed [km/h] Average vehicle speed + ∆altitude [m] Altitude difference between start and end of cycle + Ppos [kW] Average positive engine power + Pneg [kW] Average negative engine power + FC [g/km] Average fuel consumption + FC-AUXc [g/km] Fuel consumption after Auxiliary-Start/Stop Correction. (Based on FC.) + FC-WHTCc [g/km] Fuel consumption after WHTC Correction. (Based on FC-AUXc.) + Pbrake [kW] Average brake power (not including engine drag) + EposICE [kWh] Total positive engine work + EnegICE [kWh] Total negative engine work (engine brake) + Eair [kWh] Total work of air resistance + Eroll [kWh] Total work of rolling resistance + Egrad [kWh] Total work of gradient resistance + Eacc [kWh] Total work from accelerations (<0) / decelerations (>0) + Eaux [kWh] Total energy demand of auxiliaries + Eaux_xxx [kWh] Energy demand of auxiliary with ID xxx. See also Aux Dialog and Driving Cycle. + Ebrake [kWh] Total work dissipated in mechanical braking (sum of service brakes, retader and additional engine exhaust brakes) + Etransm [kWh] Total work of transmission losses + Eretarder [kWh] Total retarder losses + Mass [kg] Vehicle mass (equals Curb Weight Vehicle plus Curb Weight Extra Trailer/Body, see Vehicle Editor) + Loading [kg] Vehicle loading (see Vehicle Editor) + a [m/s2] Average acceleration + a_pos [m/s2] Average acceleration in acceleration phases* + a_neg [m/s2] Average deceleration in deceleration phases* + Acc.Noise [m/s2] Acceleration noise + pAcc [%] Time share of acceleration phases* + pDec [%] Time share of deceleration phases* + pCruise [%] Time share of cruise phases* + pStop [%] Time share of stop phases* + */ + + private static class SummaryFile + { + private static DataTable table; + + static SummaryFile() + { + table = new DataTable(); + table.Columns.Add("Job [-]", typeof(string)); + table.Columns.Add("Input File [-]", typeof(string)); + table.Columns.Add("Cycle [-]", typeof(string)); + table.Columns.Add("Time [s]", typeof(double)); + table.Columns.Add("distance [km]", typeof(double)); + table.Columns.Add("speed [km/h]", typeof(double)); + table.Columns.Add("∆altitude [m]", typeof(double)); + table.Columns.Add("Ppos [kw]", typeof(double)); + table.Columns.Add("Pneg [kw]", typeof(double)); + table.Columns.Add("FC [g/km]", typeof(double)); + table.Columns.Add("FC-AUXc [g/km]", typeof(double)); + table.Columns.Add("FC-WHTCc [g/km]", typeof(double)); + table.Columns.Add("Pbrake [kw]", typeof(double)); + table.Columns.Add("EposICE [kwh]", typeof(double)); + table.Columns.Add("EnegICE [kwh]", typeof(double)); + table.Columns.Add("Eair [kwh]", typeof(double)); + table.Columns.Add("Eroll [kwh]", typeof(double)); + table.Columns.Add("Egrad [kwh]", typeof(double)); + table.Columns.Add("Eacc [kwh]", typeof(double)); + table.Columns.Add("Eaux [kwh]", typeof(double)); + table.Columns.Add("Eaux_xxx [kwh]", typeof(double)); + table.Columns.Add("Ebrake [kwh]", typeof(double)); + table.Columns.Add("Etransm [kwh]", typeof(double)); + table.Columns.Add("Eretarder [kwh]", typeof(double)); + table.Columns.Add("Mass [kg]", typeof(double)); + table.Columns.Add("Loading [kg]", typeof(double)); + table.Columns.Add("a [m/s2]", typeof(double)); + table.Columns.Add("a_pos [m/s2]", typeof(double)); + table.Columns.Add("a_neg [m/s2]", typeof(double)); + table.Columns.Add("Acc.Noise [m/s2]", typeof(double)); + table.Columns.Add("pAcc [%]", typeof(double)); + table.Columns.Add("pDec [%]", typeof(double)); + table.Columns.Add("pCruise [%]", typeof(double)); + table.Columns.Add("pStop [%]", typeof(double)); + } + } + + + private void WriteSummary(ModalResults modData) + { + //var data = new ModalResults(); + + var row = table.NewRow(); + //row["Job [-]"] = Name; + //row["Input File [-]"] = FileName; + //row["Cycle [-]"] = Container.CycleFileName(); + //row["time [s]"] = data.Compute("Max(time)", ""); + //row["distance [km]"] = data.Compute("Max(distance)", ""); + //row["speed [km/h]"] = data.Compute("Avg(speed)", ""); + //row["∆altitude [m]"] = data.Rows[data.Rows.Count-1].Field<double>("altitude") - data.Rows[0].Field<double>("altitude"); + //row["Ppos [kw]"] = data.Compute("Avg(Peng)", "Peng > 0"); + //row["Pneg [kw]"] = data.Compute("Avg(Peng)", "Peng < 0"); + //row["FC [g/km]"] = data.Compute("Avg(FC)", ""); + //row["FC-AUXc [g/km]"] = data.Compute("Avg(FC-AUXc)", ""); + //row["FC-WHTCc [g/km]"] = data.Compute("Avg(FC-WHTCc)", ""); + //row["Pbrake [kw]"] = data.Compute("Avg(Pbrake)", ""); + //row["EposICE [kwh]"] = data.Compute("Avg(pos)", "pos > 0"); + //row["EnegICE [kwh]"] = data.Compute("Avg(pos)", "pos < 0"); + //row["Eair [kwh]"] = data.Compute("Sum(air)", ""); + //row["Eroll [kwh]"] = data.Compute("Sum(roll)", ""); + //row["Egrad [kwh]"] = data.Compute("Sum(grad)", ""); + //row["Eacc [kwh]"] = data.Compute("Sum(acc)", ""); + //row["Eaux [kwh]"] = data.Compute("Sum(aux)", ""); + + //todo auxiliaries + //foreach (var auxCol in data.Auxiliaries) { + // row["Eaux_" + auxCol.Name + " [kwh]"] = data.Compute("Sum(aux_" + auxCol.Name + ")", ""); + //} + + + //row["Ebrake [kwh]"] = data.Compute("Sum(brake)", ""); + //row["Etransm [kwh]"] = data.Compute("Sum(transm)", ""); + //row["Eretarder [kwh]"] = data.Compute("Sum(retarder)", ""); + //row["Mass [kg]"] = Container.VehicleMass(); + //row["Loading [kg]"] = Container.LoadingMass(); + //row["a [m/s2]"] = data.Compute("Avg(a)", ""); + + ////todo: a3s = average over 3 seconds!!! + //row["a_pos [m/s2]"] = data.Compute("Avg(a)", "a > 0.125"); + //row["a_neg [m/s2]"] = data.Compute("Avg(a)", "a < -0.125"); + + //// todo: is this really acc.noise? + //row["Acc.Noise [m/s2]"] = data.Compute("Sum(a)", "a < 0.125 and a > -0.125"); + + //row["pAcc [%]"] = (double) data.Compute("Sum(time_interval)", "a > 0.125") / + // (double) data.Compute("Sum(time_interval)", ""); + //row["pDec [%]"] = (double) data.Compute("Sum(time_interval)", "a > 0.125") / + // (double) data.Compute("Sum(time_interval)", ""); + //row["pCruise [%]"] = (double) data.Compute("Sum(time_interval)", "a > 0.125") / + // (double) data.Compute("Sum(time_interval)", ""); + //row["pStop [%]"] = (double) data.Compute("Sum(time_interval)", "a < 0.125 and a > -0.125") / + // (double) data.Compute("Sum(time_interval)", ""); + + table.ImportRow(row); + VectoCSVFile.Write(FileName, table); + } + } } \ No newline at end of file diff --git a/VectoCore/Utils/VectoCSVFile.cs b/VectoCore/Utils/VectoCSVFile.cs index b17f6b5b9a..44fe91be41 100644 --- a/VectoCore/Utils/VectoCSVFile.cs +++ b/VectoCore/Utils/VectoCSVFile.cs @@ -12,124 +12,132 @@ using TUGraz.VectoCore.Exceptions; namespace TUGraz.VectoCore.Utils { - /// <summary> - /// Class for Reading and Writing VECTO CSV Files. - /// </summary> - /// <remarks> - /// The following format applies to all CSV (Comma-separated values) Input Files used in VECTO: - /// List Separator: Comma "," - /// Decimal-Mark: Dot "." - /// Comments: "#" at the beginning of the comment line. Number and position of comment lines is not limited. - /// Header: One header line (not a comment line) at the beginning of the file. - /// All Combinations between max-format and min-format possible. Only "id"-field is used. - /// max: id (name) [unit], id (name) [unit], ... - /// min: id,id,... - /// </remarks> - public static class VectoCSVFile - { - private const char Separator = ','; - private const char Comment = '#'; - - /// <summary> - /// Reads a CSV file which is stored in Vecto-CSV-Format. - /// </summary> - /// <param name="fileName"></param> - /// <exception cref="FileIOException"></exception> - /// <returns>A DataTable which represents the CSV File.</returns> - public static DataTable Read(string fileName) - { - try { - var lines = File.ReadAllLines(fileName); - lines = RemoveComments(lines); - - var validColumns = GetValidHeaderColumns(lines.First()); - - if (validColumns.Length > 0) { - // Valid Columns found => header was valid => skip header line - lines = lines.Skip(1).ToArray(); - } else { - var log = LogManager.GetLogger(typeof (VectoCSVFile)); - log.Warn("No valid Data Header found. Interpreting the first line as data line."); - // set the validColumns to: {"0", "1", "2", "3", ...} for all columns in first line. - validColumns = GetColumns(lines.First()).Select((_, index) => index.ToString()).ToArray(); - } - - var table = new DataTable(); - foreach (var col in validColumns) { - table.Columns.Add(col); - } - - for (var i = 0; i < lines.Length; i++) { - var line = lines[i]; - - var cells = line.Split(Separator); - if (cells.Length != table.Columns.Count) { - throw new CSVReadException(string.Format("Line {0}: The number of values is not correct.", i)); - } - - try { - table.Rows.Add(line.Split(Separator)); - } catch (InvalidCastException e) { - throw new CSVReadException( - string.Format("Line {0}: The data format of a value is not correct. {1}", i, e.Message), e); - } - } - - return table; - } catch (Exception e) { - throw new VectoException(string.Format("File {0}: {1}", fileName, e.Message)); - } - } - - private static string[] GetValidHeaderColumns(string line) - { - Contract.Requires(line != null); - double test; - var validColumns = GetColumns(line). - Where(col => !double.TryParse(col, NumberStyles.Any, CultureInfo.InvariantCulture, out test)); - return validColumns.ToArray(); - } - - private static IEnumerable<string> GetColumns(string line) - { - Contract.Requires(line != null); - - line = Regex.Replace(line, @"\[.*?\]", ""); - line = Regex.Replace(line, @"\(.*?\)", ""); - line = line.Replace("<", ""); - line = line.Replace(">", ""); - return line.Split(Separator).Select(col => col.Trim()); - } - - private static string[] RemoveComments(string[] lines) - { - Contract.Requires(lines != null); - - lines = lines. - Select(line => line.Contains('#') ? line.Substring(0, line.IndexOf(Comment)) : line). - Where(line => !string.IsNullOrEmpty(line)). - ToArray(); - return lines; - } - - public static void Write(string fileName, DataTable table) - { - var sb = new StringBuilder(); - - var header = table.Columns.Cast<DataColumn>().Select(col => col.Caption ?? col.ColumnName); - sb.AppendLine(string.Join(", ", header)); - - foreach (DataRow row in table.Rows) { - var formattedList = new List<string>(); - foreach (var item in row.ItemArray) { - var formattable = item as IFormattable; - var formattedValue = formattable != null ? formattable.ToString("", CultureInfo.InvariantCulture) : item.ToString(); - formattedList.Add(formattedValue); - } - sb.AppendLine(string.Join(Separator.ToString(), formattedList)); - } - - File.WriteAllText(fileName, sb.ToString()); - } - } + /// <summary> + /// Class for Reading and Writing VECTO CSV Files. + /// </summary> + /// <remarks> + /// The following format applies to all CSV (Comma-separated values) Input Files used in VECTO: + /// List Separator: Comma "," + /// Decimal-Mark: Dot "." + /// Comments: "#" at the beginning of the comment line. Number and position of comment lines is not limited. + /// Header: One header line (not a comment line) at the beginning of the file. + /// All Combinations between max-format and min-format possible. Only "id"-field is used. + /// max: id (name) [unit], id (name) [unit], ... + /// min: id,id,... + /// </remarks> + public static class VectoCSVFile + { + private const char Separator = ','; + private const char Comment = '#'; + + /// <summary> + /// Reads a CSV file which is stored in Vecto-CSV-Format. + /// </summary> + /// <param name="fileName"></param> + /// <exception cref="FileIOException"></exception> + /// <returns>A DataTable which represents the CSV File.</returns> + public static DataTable Read(string fileName) + { + try { + var lines = File.ReadAllLines(fileName); + lines = RemoveComments(lines); + + var validColumns = GetValidHeaderColumns(lines.First()); + + if (validColumns.Length > 0) { + // Valid Columns found => header was valid => skip header line + lines = lines.Skip(1).ToArray(); + } else { + var log = LogManager.GetLogger(typeof(VectoCSVFile)); + log.Warn("No valid Data Header found. Interpreting the first line as data line."); + // set the validColumns to: {"0", "1", "2", "3", ...} for all columns in first line. + validColumns = GetColumns(lines.First()).Select((_, index) => index.ToString()).ToArray(); + } + + var table = new DataTable(); + foreach (var col in validColumns) { + table.Columns.Add(col); + } + + for (var i = 0; i < lines.Length; i++) { + var line = lines[i]; + + var cells = line.Split(Separator); + if (cells.Length != table.Columns.Count) { + throw new CSVReadException(string.Format("Line {0}: The number of values is not correct.", i)); + } + + try { + table.Rows.Add(line.Split(Separator)); + } catch (InvalidCastException e) { + throw new CSVReadException( + string.Format("Line {0}: The data format of a value is not correct. {1}", i, e.Message), e); + } + } + + return table; + } catch (Exception e) { + throw new VectoException(string.Format("File {0}: {1}", fileName, e.Message)); + } + } + + private static string[] GetValidHeaderColumns(string line) + { + Contract.Requires(line != null); + double test; + var validColumns = GetColumns(line). + Where(col => !double.TryParse(col, NumberStyles.Any, CultureInfo.InvariantCulture, out test)); + return validColumns.ToArray(); + } + + private static IEnumerable<string> GetColumns(string line) + { + Contract.Requires(line != null); + + line = Regex.Replace(line, @"\[.*?\]", ""); + line = Regex.Replace(line, @"\(.*?\)", ""); + line = line.Replace("<", ""); + line = line.Replace(">", ""); + return line.Split(Separator).Select(col => col.Trim()); + } + + private static string[] RemoveComments(string[] lines) + { + Contract.Requires(lines != null); + + lines = lines. + Select(line => line.Contains('#') ? line.Substring(0, line.IndexOf(Comment)) : line). + Where(line => !string.IsNullOrEmpty(line)). + ToArray(); + return lines; + } + + /// <summary> + /// Writes the datatable to the csv file. + /// Uses the column caption as header (with fallback to column name) for the csv header. + /// </summary> + /// <param name="fileName">Path to the file.</param> + /// <param name="table">The Datatable.</param> + public static void Write(string fileName, DataTable table) + { + var sb = new StringBuilder(); + + var header = table.Columns.Cast<DataColumn>().Select(col => col.Caption ?? col.ColumnName); + sb.AppendLine(string.Join(", ", header)); + + foreach (DataRow row in table.Rows) { + var formattedList = new List<string>(); + foreach (var item in row.ItemArray) { + var formattable = item as IFormattable; + var formattedValue = formattable != null + ? formattable.ToString("", CultureInfo.InvariantCulture) + : item.ToString(); + formattedList.Add(formattedValue); + } + sb.AppendLine(string.Join(Separator.ToString(), formattedList)); + } + + File.WriteAllText(fileName, sb.ToString()); + } + } } \ No newline at end of file -- GitLab