I have been trying to create a simple csv file using the csv helper. However, the result, I am getting is not what I expected.
For some reason which I cannot find it, the first value is shifting towards the right and appear as the header.
Could someone point me what I am doing wrong here?
public class Record
{
public string Vrm { get; set; }
public string Version { get; set; }
public DateTime Started { get; set; }
public DateTime? Completed { get; set; }
public string Status { get; set; }
public string Comments { get; set; }
}
static void Main(string[] args)
{
var source = new List<Record> {
new Record {
Status = "Success",
Version = "enhance",
Started = DateTime.Parse("2017-11-15 13:27:56.9933333"),
Completed = DateTime.Parse("2017-11-15 13:27:57.7300000"),
Vrm = "16aux",
Comments = "Completed Successfully"
}
};
var month = DateTime.UtcNow.Month;
var year = DateTime.UtcNow.Year;
var fileName = $"TestFile_{month}{year}.csv";
using (var sw = new StreamWriter(fileName))
{
var writer = new CsvWriter(sw);
try
{
writer.WriteHeader<Record>();
foreach (var record in source)
{
writer.WriteField(record.Vrm);
writer.WriteField(record.Version);
writer.WriteField(record.Started);
writer.WriteField(record.Completed);
writer.WriteField(record.Status);
writer.WriteField(record.Comments);
writer.NextRecord();
}
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
var i = sw;
}
}
The result is something like this:
Read up on http://joshclose.github.io/CsvHelper/writing#writing-all-records
You need to advance the writer one line by calling writer.NextRecord(); after writer.WriteHeader<Record>();.
You could also simply write all data at once, using csv.WriteRecords( records ); instead of foreaching over them
Related
Essentially I have to read and update the CSVfile (only one column) with the current date after the test finishes executing(ie,there are some values written in at the start of the test execution and then I need to update the same file to input another value). I also have a DateTime error which isn't getting resolved no matter what I try.
Sample of CSV start of test
RunId ProductArea Product Component PageObject Control TimeTakenByLocatorJson
Run_645987 R201 BN2018.5 N778 BC1 C143
CSV one column Needs to get updated after test
( TimeTakenByLocatorJson)
RunId ProductArea Product Component PageObject Control TimeTakenByLocatorJson
Run_645987 R201 BN2018.5 N778 BC1 C143 2021-07-19
I've been trying to update a CSV file using CSVhelper. The code I have is in Java and when I tried translating the same code in C# it doesn't work.
This is the code in Java
public synchronized void writeEndCSV(String runId)
{
CSVWriter csvWriter = null;
try
{
String setupCSVLocation = Reporting.getSetupCSVLocation();
CSVReader csvReader = new CSVReader(new FileReader(setupCSVLocation));
List<String[]> records = csvReader.readAll();
for(int i=0;i<records.size();i++)
{
if(records.get(i)[SETUP_RUNID].equalsIgnoreCase(runId));
{
records.get(i)[SETUP_TimeTakenByLocatorJSON] = Reporting.getExecutionEndDate();
}
}
csvReader.close();
csvWriter = new CSVWriter(new FileWriter(setupCSVLocation));
csvWriter.writeAll(records);
csvWriter.flush();
csvWriter.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
This is my code in C# (I'm new to .Net so I'm not sure about many parts)
public void writeEnd(string runId)
{
var records = Enumerable.Empty<LocatorTime>();
try
{
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
// Don't write the header again.
HasHeaderRecord = false,
};
using (var reader = new StreamReader(#"D:\Reports\" + runId + #"\LocatorTime.csv"))
using (var csv = new CsvReader(reader, config))
{
//csv.Context.RegisterClassMap<LocatorTime>();
records = csv.GetRecords<LocatorTime>().ToList();
foreach (var record in records)
{
if (record.RunID == runId)
{
record.TimeTakenByLocatorJSON = DateTime.Now;
}
// Console.WriteLine("inside loop");
}
}//Endof Stream Reader
using (var stream = File.Open(#"D:\Reports\" + runId + #"\LocatorTime.csv", FileMode.Append)) //not sure what the file mode should be
using (var writer = new StreamWriter(stream))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
csv.WriteRecords(records);
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}//end func writeEnd
This is the class used for the csv file & are also the column names in the csvfile
public class LocatorTime
{
public string RunID { get; set; }
public string ProductArea { get; set; }
public string Product { get; set; }
public string Component { get; set; }
public string PageObject { get; set; }
public string Control { get; set; }
public DateTime TimeTakenByLocatorJSON //only this value needs to be written for update at end of exec
{
get;
set;
}/*error because of DateTime datatype how to resolve?*/ }//LocatorTimeClass
/*public void SetExeDate() //tried this for removing DateTime error, didn't work
{
DateTime today = DateTime.Today; // As DateTime
string s_today = today.ToString("yyyy-MM-dd"); // As String
//TimeTakenByLocatorJSON = s_today.Trim();
TimeTakenByLocatorJSON = Convert.ToDateTime(s_today);}
*/
public sealed class LocatorTimeMap : ClassMap<LocatorTime> //is mapping helpful for updating? currently commented out
{
public LocatorTimeMap()
{
Map(m => m.RunID).Index(0);
Map(m => m.ProductArea).Index(1);
Map(m => m.Product).Index(2);
Map(m => m.Component).Index(3);
Map(m => m.PageObject).Index(4);
Map(m => m.Control).Index(5);
Map(m => m.TimeTakenByLocatorJSON).Index(6); //error
}
}
I had used the below link as reference for trying to update the CSV file hence the use of "HasHeaderRecord = false"
https://joshclose.github.io/CsvHelper/examples/writing/appending-to-an-existing-file/
I noticed 3 things. 1st - In your sample data, you have 7 column headers, but only 6 columns of data. 2nd - Your class names for "RunId" and "TimeTakenByLocatorJson" don't exactly match the columns in your sample data. 3rd - Your config says "Don't write the header again.", but you are using it for reading.
For the 1st issue, I'm going to assume this was a misprint and I'll add another column of data.
For the 2nd issue, there are at least 3 ways to handle it. You already handled it one way by mapping to indexes in LocatorTimeMap. I'll give you a 2nd way by casting the header to lower case. A 3rd way is to use the name attribute
For the 3rd issue, the header is there for reading and I assume you want the header when you write it, so you can leave HasHeaderRecord = false out.
void Main()
{
writeEnd("Run_645987");
}
// You can define other methods, fields, classes and namespaces here
public void writeEnd(string runId)
{
var records = Enumerable.Empty<LocatorTime>();
try
{
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
// This will convert both the header coming in and your class property to lower case.
PrepareHeaderForMatch = args => args.Header.ToLower()
};
var input = new StringBuilder();
input.AppendLine("RunId,ProductArea,Product,Component,PageObject,Control,TimeTakenByLocatorJson");
input.AppendLine("Run_645987,R201,BN2018.5,N778,BC1,control1,2021-07-19");
using (var reader = new StringReader(input.ToString()))
//using (var reader = new StreamReader(#"D:\Reports\" + runId + #"\LocatorTime.csv"))
using (var csv = new CsvReader(reader, config))
{
records = csv.GetRecords<LocatorTime>().ToList();
foreach (var record in records)
{
if (record.RunID == runId)
{
record.TimeTakenByLocatorJSON = DateTime.Now;
}
}
}//Endof Stream Reader
//using (var stream = File.Open(#"D:\Reports\" + runId + #"\LocatorTime.csv", FileMode.Append)) //not sure what the file mode should be
//using (var writer = new StreamWriter(stream))
using (var csv = new CsvWriter(Console.Out, CultureInfo.InvariantCulture))
{
csv.WriteRecords(records);
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}//end func writeEnd
public class LocatorTime
{
public string RunID { get; set; }
public string ProductArea { get; set; }
public string Product { get; set; }
public string Component { get; set; }
public string PageObject { get; set; }
public string Control { get; set; }
public DateTime TimeTakenByLocatorJSON //only this value needs to be written for update at end of exec
{
get;
set;
}
}//LocatorTimeClass
This error is showing up in my code, there is a second one that is as follows:
XmlException: The existing data at the root level is invalid. Line 1, position 1
I checked this second one saying there is a error with the file when there isn't any since I have 5 files inside my XMLFiles directory.
public static void Main()
{
XmlSerializer serializer = new XmlSerializer(typeof(ImportSession));
MemoryStream stream = new MemoryStream();
using (StreamWriter sw = new StreamWriter(stream))
{
sw.Write(stream);
sw.Flush();
stream.Position = 0;
}
Console.ReadKey();
foreach (string filename in Directory.EnumerateFiles(#"C:\XMLFiles", "*.xml"))
{
ProcessFile(filename, stream, serializer);
}
void ProcessFile(string Filename, MemoryStream stream, XmlSerializer serializer)
{
bool temErro = false;
Console.WriteLine("A processar xml: " + Filename);
XmlDocument xml = new XmlDocument();
xml.Load(Filename);
ImportSession session = (ImportSession)serializer.Deserialize(stream);
foreach (Batch batch in session.Batches)
{
foreach (Document doc in batch.Documents)
{
foreach (Page page in doc.Pages)
{
if (!string.IsNullOrEmpty(batch.Processed.ToString()))
{
if (!string.IsNullOrEmpty(page.HasError.ToString()))
{
string Import = page.ImportFileName;
Console.WriteLine("Página com erro:" + Import);
temErro = true;
}
}
}
}
}
if (temErro)
Console.WriteLine("Ficheiro com erro: " + Filename);
else
Console.WriteLine("Ficheiro processado: " + Filename);
Console.WriteLine(Filename);
}
}
public class ImportSession
{
public Batch[] Batches { get; set; }
}
public class Batch
{
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public string Description { get; set; }
[XmlAttribute]
public string BatchClassName { get; set; }
[XmlAttribute]
public bool Processed { get; set; }
public Document[] Documents { get; set; }
}
public class Document
{
[XmlAttribute]
public string FormTypeName { get; set; }
public IndexField[] IndexFields { get; set; }
public Page[] Pages { get; set; }
}
public class IndexField
{
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public string Value { get; set; }
}
public class Page
{
[XmlAttribute]
public string ImportFileName { get; set; }
[XmlAttribute]
public string ErrorCode { get; set; }
[XmlAttribute]
public string ErrorMessage { get; set; }
[XmlIgnore]
public bool HasError => !string.IsNullOrWhiteSpace(ErrorMessage);
}
This app right now is only trying to read all the files and point out some parts that need to show up in the console and it was doing it but I was adviced on here to change into this object oriented and memory stream.
This:
MemoryStream stream = new MemoryStream();
using (StreamWriter sw = new StreamWriter(stream))
{
sw.Write(stream);
sw.Flush();
stream.Position = 0;
is basically meaningless. Whatever the contents of stream are meant to be: it isn't this. Ask yourself:
What is stream meant to contain?
At the moment it contains... itself, sort of, but not really?
If you intend the stream to be the file contents: just use File.OpenRead
I think this is based on a misunderstanding from answers to previous questions on the topic.
This should make it work. BUT keep in mind, that it is in no way production-ready.
public static void Main()
{
XmlSerializer serializer = new XmlSerializer(typeof(ImportSession));
foreach (string filename in Directory.EnumerateFiles(#"C:\XMLFiles", "*.xml"))
{
ProcessFile(filename, serializer);
}
Console.ReadKey();
}
private static void ProcessFile(string Filename, XmlSerializer serializer)
{
bool temErro = false;
Console.WriteLine("A processar xml: " + Filename);
using (var file = File.OpenRead(Filename)) {
var session = (ImportSession)serializer.Deserialize(file);
// from here on the rest of your code ...
To minimize the code that keeps the file opened:
ImportSession session;
using (var file = File.OpenRead(Filename))
{
session = (ImportSession)serializer.Deserialize(file);
}
// file will be closed by disposal of FileStream using this notation
// rest of code
Addendum
if (!string.IsNullOrEmpty(batch.Processed.ToString()))
{ // Will ALWAYS be entered!
if (!string.IsNullOrEmpty(page.HasError.ToString()))
{ // Will ALWAYS be entered!
string Import = page.ImportFileName;
Console.WriteLine("Página com erro:" + Import);
temErro = true;
}
}
Let's look at it:
!string.IsNullOrEmpty(page.HasError.ToString()) is always true. Why?
page.HasError is of type bool. So, page.HasError.ToString() "Converts the value of this instance to its equivalent string representation (either "True" or "False")."
So, it will never be null or empty. So, string.IsNullOrEmpty will always be false, and !string.IsNullOrEmpty therefore always be true.
If you want to check the boolean value, you simply do if( page.HasError ) => "Page has an error"
Hello everyone recently i have been making a c# app to read a xml file and it worked but now i was tasked with making it do that and read certain nodes from multiple xml files inside a certain folder and now i doesen´t work and shows that error in the title.
using System;
using System.Xml;
using System.IO;
namespace XmlReaderConsoleAPP
{
class Program
{
static void Main()
{
ProcessFile(#"C:\XMLFiles\SaintGobain_Pam_20210118.xml");
try
{
var path = #"C:\XMLFiles\SaintGobain_Pam_20210118.xml";
DirectoryInfo di = new DirectoryInfo(path);
foreach (var file in Directory.GetFiles(path))
{
}
Console.ReadKey();
}
catch(Exception ex)
{
Console.WriteLine("Erro: {0}", ex.Message);
return;
}
static void ProcessFile(string Filename)
{
XmlDocument xml = new XmlDocument();
xml.LoadXml(Filename);
XmlNodeList xnLista = xml.SelectNodes(#"//ImportSession/Batches/batch/Documents/Document/Pages/Page");
Console.WriteLine($"Selected {xnLista.Count} nodes");
int i = 0;
foreach (XmlNode xn in xnLista)
{
Console.WriteLine($"{++i} {xn.Name}: {xn.Attributes["ImportFileName"].Value}");
}
XmlNodeList xnLista2 = xml.SelectNodes(#"//IndexFields/IndexField");
Console.WriteLine($"Selected {xnLista2.Count} nodes");
int j = 0;
foreach (XmlNode xn in xnLista2)
{
Console.WriteLine($"{++j} {xn.Name}: {xn.Attributes["Value"].Value}");
//string error = xn.Attributes["ErrorMessage"]?.Value;
//if (string.IsNullOrEmpty(error))
//{
//}
//elsex
//{
//}
}
Console.WriteLine(Filename);
}
}
}
}
If you guys find the error help me out cause i need it.
Ps: the xml files follow the same roots ImportSession/Batches/Batch/Documents/Document/Pages/Page
Let's first clean up a little:
namespace XmlReaderConsoleAPP
{
class Program
{
static void Main()
{
var path = #"C:\XMLFiles";
var filter = #"*.xml"; // Add filter to only enumerate .xml files
foreach (var file in Directory.EnumerateFiles(path, filter))
{
// Move try/catch inside the loop to skip errornous files.
try
{
ProcessFile( file );
}
catch(Exception ex) // Usually, try to catch the specific as possible
{
Console.WriteLine("Error: {0} {1}in File {2}",
ex.Message, Environment.NewLine, file);
}
}
Console.ReadKey();
}
} // End of Main
static void ProcessFile(string Filename)
{
XmlDocument xml = new XmlDocument();
xml.LoadXml(Filename);
XmlNodeList xnLista = xml.SelectNodes(#"//ImportSession/Batches/batch/Documents/Document/Pages/Page");
Console.WriteLine($"Selected {xnLista.Count} nodes");
int i = 0;
foreach (XmlNode xn in xnLista)
{
Console.WriteLine($"{++i} {xn.Name}: {xn.Attributes["ImportFileName"].Value}");
}
XmlNodeList xnLista2 = xml.SelectNodes(#"//IndexFields/IndexField");
Console.WriteLine($"Selected {xnLista2.Count} nodes");
int j = 0;
foreach (XmlNode xn in xnLista2)
{
Console.WriteLine($"{++j} {xn.Name}: {xn.Attributes["Value"].Value}");
}
Console.WriteLine(Filename);
}
} // class
} // namespace
Next: System.Xml.XmlException means there is something wrong with the xml input file.
What can you do about that?
Skip it, so the others may still be processed. Log the error, so you can later deep dive into the file, or request it be fixed and sent again.
If you have an XML Schema, you can validate the xml and easier find what exactly is wrong. See XML schema (XSD) validation with XmlSchemaSet and/or XmlDocument.Validate Method.
You can examine the input file manually and find the error. Mind that the message may sometimes be deceiving. I'd start with checking if it is well-formed and valid xml (there are tools for that).
I was able to resolve the problem with objects and their serialization.
You can find a way to do it with these two posts:
Objects:https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/objects
Objects serialization:https://learn.microsoft.com/en-us/dotnet/standard/serialization/how-to-serialize-an-object
Giving some more context.
Start by making empty strings at the start of the method you're doing, it being a foreach loop or a if statement
class Program
{
static DataAccess da = new DataAccess();
public static void Main()
{
XmlSerializer serializer = new XmlSerializer(typeof(ImportSession));
foreach (string filename in Directory.EnumerateFiles(Properties.Settings.Default.PastaXML, "*.xml"))
{
string fName = "";
string BatchCName = "";
string batchName = "";
string description = "";
ProcessFile(filename, serializer, fName, BatchCName, batchName, description );
}
Console.ReadKey();
}
}
After that start in my case my processfile function
private static void ProcessFile(string filename, XmlSerializer serializer, string fName, string batchName, string description, string BatchCName)
{
Console.WriteLine("A processar xml: " + filename);
bool temErro = false;
using (FileStream file = File.OpenRead(filename))
{
var session = (ImportSession)serializer.Deserialize(file);
foreach (Batch batch in session.Batches)
{
fName = Path.GetFileName(filename);
BatchCName = batch.BatchClassName;
batchName = batch.Name;
description = batch.Description;
foreach (Document doc in batch.Documents)
{
foreach (Page page in doc.Pages)
{
# if (!string.IsNullOrEmpty(batch.Processed.ToString()))
{
if (page.HasError)
{
string Import = page.ImportFileName;
Console.WriteLine("Página com erro:" + Import + fName);
temErro = true;
da.SP_Insert(filename,fName,BatchCName,batchName,description,1,Import,0, "");
There are two if statements inside this processfile that run checks on if there is a error inside the file and if the file as already been processed.
I also added Insert into a stored procedure to make sure everything is complete in this edit I'm making.
The objects I used are as follows
public class ImportSession
{
public Batch[] Batches { get; set; }
}
public class Batch
{
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public string Description { get; set; }
[XmlAttribute]
public string BatchClassName { get; set; }
[XmlAttribute]
public bool Processed { get; set; }
public Document[] Documents { get; set; }
}
public class Document
{
[XmlAttribute]
public string FormTypeName { get; set; }
public IndexField[] IndexFields { get; set; }
public Page[] Pages { get; set; }
}
public class IndexField
{
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public string Value { get; set; }
}
public class Page
{
[XmlAttribute]
public string ImportFileName { get; set; }
[XmlAttribute]
public string ErrorCode { get; set; }
[XmlAttribute]
public string ErrorMessage { get; set; }
[XmlIgnore]
public bool HasError => !string.IsNullOrWhiteSpace(ErrorMessage);
}
And my stored procedure insert in case any of you are wondering how to do it
public void SP_Insert(string XMLPath, string XMLName, string BatchClassName, string BatchName,
string BatchDescription, int Error, string ErrorImagePath, int Done, string DonePath)
{
try
{
ManageConnectionState();
SqlCommand command = new SqlCommand("Sp_Insert", connection);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("#XMLPath",SqlDbType.NText).Value = XMLPath;
command.Parameters.Add("#XMLName",SqlDbType.NText).Value = XMLName;
command.Parameters.Add("#BatchClassName",SqlDbType.NText).Value= BatchClassName;
command.Parameters.Add("#BatchName",SqlDbType.NText ).Value = BatchName;
command.Parameters.Add("#BatchDescription", SqlDbType.NText ).Value = BatchDescription;
command.Parameters.Add("#Error",SqlDbType.Bit).Value = Error;
command.Parameters.Add("#ErrorImagePath", SqlDbType.NText).Value = ErrorImagePath;
command.Parameters.Add("#Done", SqlDbType.Bit ).Value=Done;
command.Parameters.Add("#DonePath", SqlDbType.NText).Value = DonePath;
command.ExecuteScalar();
}
catch (Exception ex)
{
Console.WriteLine("Erro: " + ex.Message);
}
finally
{
ManageConnectionState();
connection.Close();
}
}
Please comment if you don´t understand something ill help out.
I'm using LINQtoCSV within a program that allows the user to import an order from a CSV file. I have all the code working however, if the CSV file doesn't have the exact column headers then it doesn't work.
Below is my class that LINQtoCSV reads into -
public class orderProduct
{
public orderProduct() { }
public string product { get; set; }
public string price { get; set; }
public string orderQty { get; set; }
public string value { get; set; }
public string calculateValue()
{
return (Convert.ToDouble(price) * Convert.ToDouble(orderQty)).ToString();
}
}
If the CSV file doesn't have the exact headers it won't work. The data I actually only need is the first 4 strings.
Below is my function that actually reads in the data.
private void csvParse()
{
// order.Clear();
string fileName = txt_filePath.Text.ToString().Trim();
try
{
CsvContext cc = new CsvContext();
CsvFileDescription inputFileDescription = new CsvFileDescription
{
SeparatorChar = ',',
FirstLineHasColumnNames = true
};
IEnumerable<orderProduct> fromCSV = cc.Read<orderProduct>(fileName, inputFileDescription);
foreach (var d in fromCSV)
{
MessageBox.Show($#"Product:{d.product},Quantity:""{d.orderQty}"",Price:""{d.price}""");
orderReturn.Add(d);
}
this.DialogResult = DialogResult.Yes;
this.Close();
}
catch (Exception ex)
{
if (ex.ToString().Contains("being used by another process"))
{
MessageBox.Show("Error: Please close the file in Excel and try again");
}
else
{
MessageBox.Show(ex.ToString());
}
}
}
I want the user to be able to just pass in a file and then select the relevant columns which relate to the corresponding values and then read in the data ignoring any columns that haven't been selected.
Hope this all makes sense, is something like this possible within LINQtoCSV
You have to add IgnoreUnknownColumns = true to your CsvFileDescription
CSV:
product,price,someColumn,orderQty,value,otherColumn
my product,$123,xx,2,$246,aa
my other product,$10,yy,3,$30,bb
Working code (I modified your code a little bit, to run it in a console)
using System;
using System.Collections.Generic;
using LINQtoCSV;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
csvParse();
Console.ReadLine();
}
private static void csvParse()
{
string fileName = "../../../test.csv"; // provide a valid path to the file
CsvContext cc = new CsvContext();
CsvFileDescription inputFileDescription = new CsvFileDescription
{
SeparatorChar = ',',
FirstLineHasColumnNames = true,
IgnoreUnknownColumns = true // add this line
};
IEnumerable<orderProduct> fromCSV = cc.Read<orderProduct>(fileName, inputFileDescription);
foreach (var d in fromCSV)
{
Console.WriteLine($#"Product:{d.product},Quantity:""{d.orderQty}"",Price:""{d.price}""");
}
}
}
public class orderProduct
{
public orderProduct() { }
public string product { get; set; }
public string price { get; set; }
public string orderQty { get; set; }
public string value { get; set; }
public string calculateValue()
{
return (Convert.ToDouble(price) * Convert.ToDouble(orderQty)).ToString();
}
}
}
Output:
Product:my product,Quantity:"2",Price:"$123"
Product:my other product,Quantity:"3",Price:"$10"
If your properties have different names than CSV columns, you should use CsvColumn attribute:
public class OrderProduct
{
[CsvColumn(Name = "product")]
public string Product { get; set; }
[CsvColumn(Name = "price")]
public string Price { get; set; }
[CsvColumn(Name = "orderQty")]
public string OrderQuantity { get; set; }
public string Value { get; set; }
public string calculateValue()
{
return (Convert.ToDouble(Price) * Convert.ToDouble(OrderQuantity)).ToString();
}
}
Or if you prefer mapping columns by their indices:
public class OrderProduct
{
[CsvColumn(FieldIndex = 0)]
public string Product { get; set; }
[CsvColumn(FieldIndex = 1)]
public string Price { get; set; }
[CsvColumn(FieldIndex = 2)]
public string OrderQuantity { get; set; }
public string Value { get; set; }
public string calculateValue()
{
return (Convert.ToDouble(Price) * Convert.ToDouble(OrderQuantity)).ToString();
}
}
If you have to specify the columns on the fly, the only way seems to be to read raw data and process it yourself (the solution is based on this article):
internal class DataRow : List<DataRowItem>, IDataRow
{
}
...
int productColumnIndex = 0; // your users will provide it
var fromCSV = cc.Read<DataRow>(fileName);
foreach (var row in fromCSV)
{
var orderProduct = new OrderProduct
{
Product = row[productColumnIndex].Value,
};
Console.WriteLine(orderProduct.Product);
}
Looking for a good way to parse out of this text file, the values highlighted with the yellow boxes using C#. Each section is delineated by a TERM # which I forgot to highlight. Tried this:
string fileName = "ATMTerminalTotals.txt";
StreamReader sr = new StreamReader(fileName);
string[] delimiter = new string[] { " " };
while (!sr.EndOfStream)
{
string[] lines = sr.ReadLine().Split(delimiter, StringSplitOptions.RemoveEmptyEntries);
foreach (string line in lines)
{
Console.WriteLine(line);
}
}
Console.ReadLine();
Safe to say I am reading lines correctly and removing "white spaces." Although, as an amateur to programming, not sure of a valid way to accurately "know" that I am getting the values from this report that I need. Any advice?
i've tested this with a very simple program to parse the given file,
basically i've created two basic classes, a page class holding a collection of terminal report class (the tran type rows)
these rows maybe even can be represented as transaction and a billing class too
first parsed the data, setting the parameters needed and lastly just accessing the properties
just rushed it to be as simple as possible, no error handling etc... its just to give you a sense of how id start solving these kind of tasks, hope it helps
Adam
namespace TerminalTest
{
class Program
{
public class TerminalReport
{
public string Word { get; set; }
public int Denials { get; set; }
public int Approvals { get; set; }
public int Reversals { get; set; }
public double Amount { get; set; }
public int ON_US { get; set; }
public int Alphalink { get; set; }
public int Interchange { get; set; }
public int Surcharged { get; set; }
public static TerminalReport FromLine(string line)
{
TerminalReport report = new TerminalReport();
report.Word = line.Substring(0, 11);
line = line.Replace(report.Word, string.Empty).Trim();
string[] split = line.Split(' ');
int i = 0;
// transaction summary
report.Denials = int.Parse(split[i++]);
report.Approvals = int.Parse(split[i++]);
report.Reversals = int.Parse(split[i++]);
report.Amount = double.Parse(split[i++]);
// billing counts
report.ON_US = int.Parse(split[i++]);
report.Alphalink = int.Parse(split[i++]);
report.Interchange = int.Parse(split[i++]);
report.Surcharged = int.Parse(split[i++]);
return report;
}
}
public class TerminalPage
{
public int PageNumber { get; set; }
public double TotalSurcharges { get; set; }
public List<TerminalReport> Rows { get; set; }
public TerminalPage(int num)
{
PageNumber = num;
Rows = new List<TerminalReport>();
}
public int TotalDenials
{
get
{
return rows.Sum(r => r.Denials);
}
}
public int TotalApprovals
{
get
{
return Rows.Sum(r => r.Approvals;
}
}
public int TotalReversals
{
get
{
return Rows.Sum(r => r.Reversals;
}
}
public double TotalAmount
{
get
{
return Rows.Sum(r => r.Amount);
}
}
public int TotalON_US
{
get
{
return Rows.Sum(r => r.ON_US);
}
}
public int TotalAlphalink
{
get
{
return Rows.Sum(r => r.Alphalink);
}
}
public int TotalInterchange
{
get
{
return Rows.Sum(r => r.Interchange);
}
}
public int TotalSurcharged
{
get
{
return Rows.Sum(r => r.Surcharged);
}
}
}
private static string CleanString(string text)
{
return Regex.Replace(text, #"\s+", " ").Replace(",", string.Empty).Trim();
}
private static List<TerminalPage> ParseData(string filename)
{
using (StreamReader sr = new StreamReader(File.OpenRead(filename)))
{
List<TerminalPage> pages = new List<TerminalPage>();
int pageNumber = 1;
TerminalPage page = null;
bool parse = false;
while (!sr.EndOfStream)
{
string line = sr.ReadLine();
line = CleanString(line);
if (line.StartsWith("TRAN TYPE"))
{
// get rid of the ----- line
sr.ReadLine();
parse = true;
if (page != null)
{
pages.Add(page);
}
page = new TerminalPage(pageNumber++);
}
else if (line.StartsWith("="))
{
parse = false;
}
else if (line.StartsWith("TOTAL SURCHARGES:"))
{
line = line.Replace("TOTAL SURCHARGES:", string.Empty).Trim();
page.TotalSurcharges = double.Parse(line);
}
else if (parse)
{
TerminalReport r = TerminalReport.FromLine(line);
page.Rows.Add(r);
}
}
if (page != null)
{
pages.Add(page);
}
return pages;
}
}
static void Main(string[] args)
{
string filename = #"C:\bftransactionsp.txt";
List<TerminalPage> pages = ParseData(filename);
foreach (TerminalPage page in pages)
{
Console.WriteLine("TotalSurcharges: {0}", page.TotalSurcharges);
foreach (TerminalReport r in page.Rows)
Console.WriteLine(r.Approvals);
}
}
}
}
I'm not sure I'd split it by spaces actually.. the textfile looks like its split into columns. You might want to read like 10 chars (or whatever the width of the column is) at a time... and I'd parse the whole file into a dictionary so you get entries like
dict["WDL FRM CHK"]["# DENIALS"] = 236
then you can easily retrieve the values you want from there, and if you ever need more values in the future, you've got them.
Alternatively, you can use regexs. You can grab the first value with a regex like
^WDL FRM CHK\s+(?<denials>[0-9,]+)\s+(?<approvals>[0-9,]+)$
using
m.Groups["approvals"]
anyway I recommend you to wrap your StreamReader with using block:
using (StreamReader sr = new StreamReader(fileName))
{
// do stuff
}
Read more on MSDN
Given that it seems to have a standard, regular format, I would use regular expressions. You can check the starting code to figure out what row you're on, then an expression that will parse out the numbers and ignore whitespace will, very likely, be easier than handling it manually.
using System;
using System.Text.RegularExpressions;
namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
Regex exp = new Regex(#"WDL FRM CHK(\s)+[1-9,]+(\s)+(?<approvals>[1-9,]+)(\s)+");
string str = "WDL FRM CHK 236 1,854 45,465 123 3";
Match match = exp.Match(str);
if (match.Success)
{
Console.WriteLine("Approvals: " + match.Groups["approvals"].Value);
}
Console.ReadLine();
}
}
}
Apdated from the following article to parse one of your numbers:
How to match a pattern by using regular expressions and Visual C#