Comma delimited class property assignment problem - c#

I'm loading a comma delimited file into a list, no problems here. Except the last segment is separated by a semicolon of which I need to put into an array that has a property name. In this case Sequence and Rotation. The code below works except giving it a property name.
Any advice would be much appreciated. I've kept the code below to bare-bones to avoid clutter.
Example CSV File: Bar001,P02;90
class PartDetail
{
public string Description
{
get;
set;
}
public string[] BottomEdge
{
get;
set;
}
public class SpecificDetails << < I want to use this class to specify the property name. {
public string Sequence
{
get;
set;
}
public string Rotation
{
get;
set;
}
}
public PartDetail(string line)
{
string[] parts = line.Split(',');
this.Description = parts[0];
this.BottomEdge = parts[1].Split(';'); << It 's here where I am struggling.
}
}

I think this will do the trick
class PartDetail
{
....
//create an instance of your SpecificDetails class
SpecificDetails Details = new SpecificDetails();
...
public PartDetail(string line)
{
string[] parts = line.Split(',');
this.Description = parts[0];
this.BottomEdge = parts[1].Split(';');
//assign the value to the properties of the "Details" instance
this.Details.Sequence = this.BottomEdge[0];
this.Details.Rotation = this.BottomEdge[1];
}
}

Use line.Split(new char[]{ ',', ';'}) and just pick the last index of the returned array to be the closing segment of your logic.

Dot net has a built in CSV parser, so there is no need to roll your own (https://coding.abel.nu/2012/06/built-in-net-csv-parser/)
using (TextFieldParser parser = new TextFieldParser(path))
{
parser.CommentTokens = new string[] { "#" };
parser.SetDelimiters(new string[] { ";" });
parser.HasFieldsEnclosedInQuotes = true;
// Skip over header line.
parser.ReadLine();
while (!parser.EndOfData)
{
string[] fields = parser.ReadFields();
}
}
Once parsed, you are left with the semicolon problem, which can easily be solved with string.Split.

Related

Using string.Split() in AutoMapper while conversion

I have two classes A and B and I am converting class A to B, and there's one property that is a string but has two date times within one string value.
class A
{
public string dates { get; set; }
}
class B: A
{
public B()
{
Map(m=> m.dates)
.TypeConverter<DateTimeConverter>()
.TypeConverterOption.Format("yyyy-MM-ddTHH:mm:ssz");
}
}
The problem is that when I have one value in the dates, it's working perfectly, but when it's multiple values with a new line splitter, it gives the error.
Working Fine on 2023-01-30T01:00:00
expected output: 2023-01-30T01:00:00z (works as expected)
An issue on 2023-01-30T01:00:00\r\n2023-01-30T01:00:00\r\n
Expected output: 2023-01-30T01:00:00z\r\n2023-01-30T01:00:00z\r\n
Any solution?
you can just use linq to split your string to and, out put it in any format that you want.
class A
{
public string dates { get; set; }
}
class B : A
{
public B()
{
Map();
}
private void Map()
{
var ss = "2017-01-11T10:27:00\r\n2011-09-04T12:30:22\r\n2020-07-17T03:51:20";
var n = ss.Length;
var list = new List<string>();
var sp = ss.Split('\n');
sp.ToList().ForEach(x => list.Add(DateTime.Parse(x).ToString("yyyy-MM-ddTHH:mm:ssz")));
dates = string.Join("__", list.ToArray());
}
}
if it is Windows the "\n" it is works as "\r\n" during splitting the string, the split() method takes argument type char, therefore you can not split it using two chars "\r\n" or string.
if it is Mac change the "\n" with "\r".

How to create a Class List with different numbers of inputs in C#

I'm working on my first real c# project and I have faced a problem with my way of creating List based on a Class, which I have no idea how to solve.
I’m trying to write some code, which takes an input file (txt/csv) of multiple constructions with multiple layers, put it into my program, and later write the constructions into a new txt/csv file.
When having the same numbers of layers, it works fine. But when the constructions have different numbers of layers it causes trouble and I get a “System.IndexOutOfRangeException”.
My question is: Can I make the Class which I’m basing my List on, dynamic (I don’t know if it is the technical term), so it work with different numbers of inputs? Both when Adding the construction to the program and when I write it to a new file?
My code is:
class Program
{
static void Main(string[] args)
{
// Filepath for the input and output file
string filePathIn_constructions = #"C:\Library\Constructions.txt";
string filePathOut = #"C:\Library\EPlus_Inputfile.txt";
// Creating a list of constructions based on the class. The list is made from the file "filePathIn_constructions"
List<Construction> allConstructions = new List<Construction>();
List<string> lines_constructions = File.ReadAllLines(filePathIn_constructions).ToList(); // add it to a list
// Adding all the data from the fil to the variable "allConstructions"
foreach (var line in lines_constructions)
{
string[] entries = line.Split(',');
Construction newConstruction = new Construction();
newConstruction.EIndex = entries[0];
newConstruction.Name = entries[1];
newConstruction.Layer1 = entries[2];
newConstruction.Layer2 = entries[3];
newConstruction.Layer3 = entries[4];
newConstruction.Layer4 = entries[5];
newConstruction.Layer5 = entries[6];
allConstructions.Add(newConstruction); // Add it to our list of constructions
}
List<string> output = new List<string>();
foreach (var x in allConstructions) // Printing the new
{
output.Add($"{x.EIndex}, {x.Name}, {x.Layer1}, {x.Layer2}, {x.Layer3}, {x.Layer4}, {x.Layer5}");
}
File.WriteAllLines(txtFilePathOut, output);
}
}
My Class for the Constructions is
public class Construction
{
public string EIndex { get; set; }
public string Name { get; set; }
public string Layer1 { get; set; }
public string Layer2 { get; set; }
public string Layer3 { get; set; }
public string Layer4 { get; set; }
public string Layer5 { get; set; }
}
An example of a input/output file could be
Construction,ConcreteWall,Concrete;
Construction,Brickwall1,Birck,Isulation,Brick;
Construction,Brickwall2,Birck,AirGap,Isulation,Brick;
Construction,Wood/Concrete Wall,Wood,Isulation,Concrete,Gypson;
Construction,Wood Wall,Wood,AirGap,Gypson,Isulaiton,Gypson;
I hope someone can help. Thanks.
Edit: I have to be able to excess the construction Name seperatly, because i'm using it to do some sorting of the.
public class Construction
{
public string EIndex { get; set; }
public string Name { get; set; }
public List<string> Layers { get; set; } = new List<string>();
}
foreach (var line in lines_constructions)
{
string[] entries = line.Split(',');
Construction newConstruction = new Construction();
newConstruction.EIndex = entries[0];
newConstruction.Name = entries[1];
for (int i=2; i < entries.Length; i++) {
newConstruction.Layers.Add(entries[i]);
}
allConstructions.Add(newConstruction);
}
foreach(var x in allConstuctions) {
File.AppendAllText(output, $"{x.EIndex}, {x.Name}, {string.Join(", ", x.Layers)}");
}
It is because you are trying to reach a cell of an array that doesn't exist (documentation)
In your input/output file you have lines that have between 3 and 7 values, and you are building an array entries out of those values. This means that you will have arrays with between 3 and 7 cells
The problem is that right after creating those arrays you try to access on every array the cells 0, 1, 2... up to the 7th, even for arrays that have only 3 cells!
What you could do to fix this in a simple way is to add columns to have the same number of separator on each lines (you defined the separator of your lines as column with line.Split(',')). This way, every arrays that you will create will always have 7 cells, even if the value inside is null

Update column value of CSV in C#

In the coding, I want to replace the column value of CSV. However, it can`t replace the value in CSV.
CSV file:
"Name","Age"
"michael","16"
"miko","15"
"Tom","24"
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
string text = File.ReadAllText(#"C:\test.csv");
TestDataModel users = new TestDataModel();
text = users.Name.Replace("m", "n");
File.WriteAllText(#"C:\test.csv", text);
}
public class TestDataModel
{
public string Name { get; set; }
public int Age { get; set; }
}
}
}
there is a lot of misconception in code you provided, and some of the solutions for your problem might not be begginer friendly.
Especially when they are not 'global' solutions. For your case I tried to explain parts of code in comments
using System.Text.RegularExpressions;
var csvFilePath = #"C:\test.csv";
// Split csv file into lines instead of raw text.
string[] csvText = File.ReadAllLines(csvFilePath);
var models = new List<TestDataModel>();
// Regex that matches your CSV file.
// Explained here: https://regex101.com/r/t589CW/1
var csvRegex = new Regex("\"(.*)\",\"(.*)\"");
for (int i = 0; i < csvText.Length; i++)
{
// Skip headers of file.
// That is: "Name","Age"
if (i == 0)
{
continue;
}
// Check for potential white spaces at the end of the file.
if (string.IsNullOrWhiteSpace(csvText[i]))
{
continue;
}
models.Add(new TestDataModel
{
// Getting a name from regex group match.
Name = csvRegex.Match(csvText[i]).Groups[1].Value,
// Getting an age from regex group and parse it into integer.
Age = int.Parse(csvRegex.Match(csvText[i]).Groups[2].Value),
});
}
// Creating headers for altered CSV.
string alteredCsv = "\"Name\",\"Age\"\n";
// Loop through your models to modify them as you wish and add csv text in correct format.
foreach (var testDataModel in models)
{
testDataModel.Name = testDataModel.Name.Replace('m', 'n');
alteredCsv += $"\"{testDataModel.Name}\",\"{testDataModel.Age}\"\n";
}
var outputFilePath = #"C:\test2.csv";
File.WriteAllText(outputFilePath, alteredCsv);
public class TestDataModel
{
public string Name { get; set; }
public int Age { get; set; }
}
However this answer contains many topics that you might want to get familiar with such as:
Regex/Regex in C#
Data Serialization/Deserialization
Working with Linq
String templates
I/O Operations
Try this
public static void Replace()
{
string text = File.ReadAllText(#"C:\test.csv");
string _text = text.Replace("m", "n");
File.WriteAllText(#"C:\New_test.csv", _text);
}

Reading multiple records kept in a text file

Basically i have the user open a text document that is formatted like this currently.
Burger.jpg,Double Down KFC,Food,30/06/95,This is a burger
it then splits the info into an array then into variables and then into text boxes.
obviously if i wanted multiple records i may have to format it differently, (thats what i need help with)
But if i had it like this what would be the most efficient way of taking these records from the text file and storing them separately so i can flick through them. For example with a combo box on my form. When the record is selected the form populates with that records data.
multiple records:
Burger.jpg,Double Down KFC,Food,30/06/95,This is a burger
Person.jpg,Smile,People,23/06/95,This is a Person
Here is my code currently for this part.
private void LoadFile()
{
StreamReader reader = new StreamReader(fileName);
content = reader.ReadLine();
doc = content.Split(',');
filename = Convert.ToString(doc[0]);
fileNameTextBox.Text = doc[0];
description = doc[1];
descriptionTextBox.Text = doc[1];
category = doc[2];
categoryComboBox.Text = doc[2];
//dateTaken = Convert.ToDouble(doc[3]);
dateTakenTextBox.Text = doc[3];
comments = doc[4];
commentsTextBox.Text = doc[4];
}
This code currently works but only for the first record as it is using one array, and i obviously will need multiple ways of storing the other lines.
I Think the best option if i was going to give it a guess would be to use a List of some sort with a Class that generates Records, but that is where i am stuck and need help.
(usually my questions on here get downvoted as i am not concise enough if that is the case comment and i will try to alter my question.
Thanks everyone.
I would create a class that holds the information of a record
public class ImageInfo
{
public string FileName { get; set; }
public string Description { get; set; }
public string Category { get; set; }
public DateTime Date { get; set; }
public string Comments { get; set; }
public override string ToString()
{
return FileName;
}
}
Now you can write a method that returns the image infos
public List<ImageInfo> ReadImageInfos(string fileName)
{
string[] records = File.ReadAllLines(fileName);
var images = new List<ImageInfo>(records.Length);
foreach (string record in records) {
string[] columns = record.Split(',');
if (columns.Length >= 5) {
var imageInfo = new ImageInfo();
imageInfo.FileName = columns[0];
imageInfo.Description = columns[1];
imageInfo.Category = columns[2];
DateTime d;
if (DateTime.TryParseExact(columns[3], "dd/MM/yy",
CultureInfo.InvariantCulture, DateTimeStyles.None, out d))
{
imageInfo.Date = d;
}
imageInfo.Comments = columns[4];
images.Add(imageInfo);
}
}
return images;
}
Now you can fill the textboxes with one of these records like this
List<ImageInfo> images = ReadImageInfos(fileName);
if (images.Count > 0) {
ImageInfo image = images[0];
fileNameTextBox.Text = image.FileName;
descriptionTextBox.Text = image.Description;
categoryComboBox.Text = image.Category;
dateTakenTextBox.Text = image.Date.ToShortDateString();
commentsTextBox.Text = image.Comments;
}
The advantage of this approach is that the two operations of reading and displaying the records are separate. This makes it easier to understand and modify the code.
You can add ImageInfo objects to a ComboBox or ListBox directly instead of adding file names if you override the ToString method in the ImageInfo class.
public override string ToString()
{
return FileName;
}
Add the items to a combo box like this:
myComboBox.Items.Add(image); // Where image is of type ImageInfo.
You can retrieve the currently selected item with:
ImageInfo image = (ImageInfo)myComboBox.SelectedItem;
Most likely you will be doing this in the SelectedIndexChanged event handler.
void myComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
ImageInfo image = (ImageInfo)myComboBox.SelectedItem;
myTextBox.Text = image.FileName;
}
Create a class that resembles a row of your data, then iterate over the file, making your split and constructing a new class instance with your split data. Store this in a List<> (or some other appropriate structure) ensure you store it such that it can be referenced later. Don't change your UI as you are loading and parsing the file (as Mike has suggested), also as mike suggests you need to read until the EOF is reached (plenty of examples of this on the web) MSDN example.
Also, streamreader implements IDisposable, so you need to dispose of it, or wrap it in a using statement to clean up.
Example class, you could even pass the row in as a constructor argument:
public class LineItem
{
public string FileName { get; set; }
public string Description { get; set; }
public string Category { get; set; }
public DateTime DateTaken { get; set; }
public string Comments { get; set; }
public LineItem(string textRow)
{
if (!string.IsNullOrEmpty(textRow) && textRow.Contains(','))
{
string[] parts = textRow.Split(',');
if (parts.Length == 5)
{
// correct length
FileName = parts[0];
Description = parts[1];
Category = parts[2];
Comments = parts[4];
// this needs some work
DateTime dateTaken = new DateTime();
if (DateTime.TryParse(parts[3], out dateTaken))
{
DateTaken = dateTaken;
}
}
}
}
}
Your code is not iterating through the records within the file.
You want to continue reading until the end of the file.
while (content != eof)
{
// split content
// populate text boxes
}
But this will overwrite your text boxes with each pass of the loop.
Also, you want to separate your code - do not mix I/O process with code that updates the UI.
The name of the method implies you are loading a file, but the method is doing far more than that. I would suggest changing the method to read the file, split each record into a class object which then gets stored into an array - and return that array.
A separate method will take that array and populate your table or grid or whatever is in the UI. Ideally, you have the gridview bind to the array.
If you keep all your entries the same:
name,food,type,blah blah
name,food,type,blah blah
you can add another split into your code:
line = content.Split('\n');
foreach (line in filename)
{
doc = line.Split(',');
//do stuff...
As for the option for string multiple entries, a method I have used is implementing a list of Models:
class ModelName
{
string Name { get; set; }
string foodType { get; set; }
//etc...
public void ModelName()
{
Name = null;
foodType = null;
//etc...
}
}
List<Model> ModelList;
foreach (line in filename)
{
doc = line.Split(',');
Model.Name = doc[1];
//etc...
And have a different list, and a different Model for each type of entry (person or food)

populate IList<string> from long string

I currently have the following code
public IList<string> CensoredWords { get; private set; }
public Censor()
{
this.CensoredWords = new List<string>
{
"word1",
"word2",
"word4"
};
}
my web.config file contains the following
<add key="keywords" value="word1,word2,word3" />
obviously not very good especially if you want to add new words ect so ive modified it a bit to this
public Censor()
{
this.CensoredWords = new List<string> { System.Web.Configuration.WebConfigurationManager.AppSettings["keywords"] };
}
ive also tried
public Censor()
{
string[] keywords = System.Web.Configuration.WebConfigurationManager.AppSettings["keywords"].Split(new char[] { ',' });
foreach(string keyword in keywords)
{
this.CensoredWords.add(keyword);
}
}
but for some reason nothing seems to be working, can anyone tell me why
Actually, looking at it you have the following:
public IList<string> CensoredWords { get; private set; }
Try setting it to not be private.
As an aside this code works:
string keywords = "Value1,Value2,Value3";
List<string> censoredWords = keywords.Split(',').ToList();
Since you are reading the keys from the web.config file (thus presuming you don't want to edit the censored words from the application itself) you don't need the set property.
You could read the values in a static object so they are available from anywhere without re-parsing the list every time:
private static readonly List<string> _censoredWords = System.Configuration.ConfigurationManager.AppSettings["keywords"].Split(',').ToList();
public static IList<string> CensoredWords
{
get
{
return _keywords;
}
}
This should fix:
this.CensoredWords = new List<string>(System.Web.Configuration.WebConfigurationManager.AppSettings["keywords"].Split(','));

Categories