How can I read a text file and loop though repeating sections? - c#

I have an ANSI 835 (text) file. For simplicity's sake it looks like this:
ISA*00
GS*Foo*12345
ST*835*000001
LX*1
CLP*123456
NM1*Lastname
REF*010101
DTM*20120512
SVC*393939
LQ*19
LX*2
CLP*23456
NM1*Smith
REF*58774
DTM*20120601
SVC*985146
LX*3
CLP*34567
NM1*Doe
REF*985432
DTM*20121102
SVC*864253
LQ*19
LQ*84
The records are broken up into LX segments. Everything after LX*1 is one record, everything after LX*2 is another record, and so on. I need to get certain items from each line, assign them to variables, and eventually add them as a row to a datagridview. Again for simplicity's sake, I have the following variables and here's what should go in each:
string ItemNumber should be the group of characters after the * in the CLP line
string LastName should be the group of characters after the * in the NM1 line
string Date should be the group of characters after the * in the REF line
string Error should be the group of characters after the * in the LQ line
The biggest problem I'm facing is that there may be more than one LQ line in each LX segment. In that case, the 2nd error can just be added to the end of the first error, separated by a comma.
I tried loading the file into a string array and going line by line, but I'm not sure how say "start at LX*1 and do stuff until you hit LX*2".
string[] lines = File.ReadAllLines(MyFile);
foreach (string line in lines)
{
string[] splitline = line.Split('*');
if (splitline[0] = "LX")
{
//this is where i need to loop through the next lines
//until i hit the next line starting with LX.
}
}
Any ideas? As always, thank you for your time!

Start with a simple data model:
public class LXRecord
{
public string ItemNumber { get; set; }
public string LastName { get; set; }
public string Date { get; set; }
public List<string> Errors { get; set; }
public LXRecord()
{
Errors = new List<String>();
}
}
Define your significant tokens:
public static class Tokens
{
public const string TOKEN_SPLITTER = "*";
public const string NEW_RECORD = "LX";
public const string ITEM_NUMBER = "CLP";
public const string LAST_NAME = "NM1";
public const string DATE = "REF";
public const string ERROR = "LQ";
}
Loop through the lines, do a switch/case on the tokens, and just start a new LXRecord when you see the "LX" flag:
List<LXRecord> records = new List<LXRecord>();
LXRecord currentRecord = null;
foreach(string line in lines)
{
int tokenIndex = line.IndexOf(Tokens.TOKEN_SPLITTER);
if (tokenIndex < 1 || tokenIndex == line.Length - 1) //no token or no value?
continue;
string token = line.Substring(0, tokenIndex);
string value = line.Substring(tokenIndex + 1);
switch(token)
{
case(Tokens.NEW_RECORD) :
currentRecord = new LXRecord();
records.Add(currentRecord);
break;
case(Tokens.ITEM_NUMBER) :
currentRecord.ItemNumber = value;
break;
case(Tokens.LAST_NAME) :
currentRecord.LastName = value;
break;
case(Tokens.DATE) :
currentRecord.Date = value;
break;
case(Tokens.ERROR) :
currentRecord.Errors.Add(value);
break;
}
}
Notice this way you can relatively easily ignore non-supported flags, add new flags, or add parsing (for example, ItemNumber could use Int32.Parse and store it as an integer, or "Date" could store a DateTime) In this case, I chose to store the errors as a List<String>, but you could comma delimit it instead if you wish. I also avoided splitting on the * character in case the content contained a second asterisk as well.
EDIT: From your comment, you can have some more complicated/specialized parsing in the case or moved into another method. Instead of the case I have above for "LAST_NAME", you could have:
case(Tokens.LAST_NAME) :
ParseName(currentRecord, value);
break;
Where ParseName is:
public static void ParseName(LXRecord record, string value)
{
int tokenIndex = value.IndexOf(Tokens.TOKEN_SPLITTER);
if (tokenIndex < 1 || tokenIndex == value.Length - 1) //no last name and first name?
{
record.LastName = value;
}
else
{
record.LastName = value.Substring(0, tokenIndex);
record.FirstName = value.Substring(tokenIndex + 1);
}
}
The token check might be tweaked there, but it should give you a good idea.

Related

How can i read the 3rd last element from a list with c#?

I am trying to write a parser that reads the third to last element from a list. This is a number or nothing. This is my code:
public int parseLastId(String filepath)
{
List<string> lines = new List<string>();
String text = File.ReadAllLines(filepath).ToString();
lines = text.Split(',').ToList();
int id = Convert.ToInt32(lines[lines.Count - 3]);
return id;
}
the text in the file is like this:
1,Joe,2,Jeff,
File.ReadAllLines will read every line from a text file.
In your text file, you only have 1 line.
So you can change
String text = File.ReadAllLines(filepath).ToString();
to
// Get the first line from the text file.
String text = File.ReadAllLines(filepath)[0];
The rest of your program is fine then.
Idiomatic C#
Just to note, that your code isn't written in standard C# style.
In particular:
string should be used instead of String.
Method names should start with a capital letter
The call to .ToList() on text.Split(',') is unnecessary.
Here is an edited version of your code that's more in line with typical C# standards - hope that helps :)
public int ParseLastId(string filepath)
{
var text = File.ReadAllLines(filepath)[0];
var lines = text.Split(',');
var id = Convert.ToInt32(lines[lines.Length - 3]);
return id;
}
Error handling
You state that you want to return nothing if the program cannot parse the entry. To do that, you will want to return a "nullable int" (int?) from your method, and use the int.TryParse method.
For example:
public int? ParseLastId(string filepath)
{
var text = File.ReadAllLines(filepath)[0];
var lines = text.Split(',');
if (int.TryParse(lines[lines.Length - 3], out var id)
{
return id;
}
else
{
return null;
}
}
I assume by "return nothing" you mean 0
public int parseLastId(string filepath)
{
string text = File.ReadAllText(filepath);
string[] lines = text.Split(',');
return lines.Length >= 3 && int.TryParse(lines[lines.Length - 3], out int id) ? id : 0;
}

How to change an unknown number in a text file for C#?

I'm trying to create a "warning system" for my Discord Server's bot where when using the command, it'll pull information from a text file and then update it. I can somewhat envision what I want to happen step by step, but I'm confused at how to pull what would be reading from a file as an example:
bob#5368 3
mark#8459 6
cindy#1254 2
I only want to change the number behind the name, which I'm not quite sure how to basically find the user I'm looking for, and then pull the number and set that to 'totalWarn', add it up (totalWarn =+ warnNum), and then update that user with the new number.
Note: 'warningFile' is the file path that's already defined outside of this block of code.
public async Task warn(IGuildUser user, int warnNum, [Remainder] string reason)
{
int totalWarn = 0;
if (user == null)
await ReplyAsync("Please include a name");
if (warnNum <= 0)
await ReplyAsync("The warning level must increase by 1 or more.");
if (reason == null)
await ReplyAsync("Please include a reason.");
string nickName = user.Username;
//Check for whether text file exists or not.
if (!File.Exists(warningFile))
{
//Create the File
FileStream fs = File.Create(warningFile);
}
//Reads all the text in the file and puts it into an array.
string[] arr = File.ReadAllLines(warningFile);
for (int i = 0; i < arr.Length; i++)
{
string line = arr[i];
}
totalWarn = +warnNum;
await Context.Channel.SendMessageAsync($"{user.Username}'s warning level has increased by {warnNum} for {reason}.\n"
+ $"Your warning level is now {totalWarn}. Please take this time to review the server rules.");
}
I'm not looking for somebody to complete it for me, just some help to push myself in the right direction as I'm completely lost, and the information regarding changing text isn't quite helpful.
Here is how to parse a line from the file using regex:
void Main()
{
string line = "bob#5368 3";
GuildUser user = new GuildUser( line );
user.Warnings++;
user.ToString().Dump();
}
public class GuildUser
{
public string Name { get; set;}
public int Id { get; set;}
public int Warnings { get; set;}
public GuildUser( string line )
{
Match match = Regex.Match( line, #"(.+)?#(\d+) (\d+)" );
if ( !match.Success ) throw new Exception( "Couldn't parse line: " + line );
Name = match.Groups[1].Value;
Id = int.Parse( match.Groups[2].Value );
Warnings = int.Parse( match.Groups[3].Value );
}
public override string ToString()
{
return $"{Name}#{Id} {Warnings}";
}
}
You could use File.ReadAllLines, File.WriteAllLines.
I'd probably use some linq as well.
I updated to add a "ToString" method to rewrite the line.
Then you can just add to the warnings and get the new string calling ToString().
Another way:
public static void stuff()
{
string name = "bob";
string firstNumber = "";
string secondNumber = "";
int lineIndex = 0;
List<string> lines = new List<string>(File.ReadAllLines(#"C:\Text.txt"));
foreach(string line in lines)
{
if(line.Contains(name))
{
lineIndex = lines.IndexOf(line);
string[] parts = line.Split('#');
name = parts[0];
firstNumber = parts[1].Split(' ')[0];
secondNumber = parts[1].Split(' ')[1];
firstNumber = (Convert.ToInt32(firstNumber) + 1).ToString();
/*
* instert code here for changing the number you want to change
*/
lines[lineIndex] = name + "#" + firstNumber + " " + secondNumber;
File.WriteAllLines(#"C:\Text.txt",lines.ToArray());
break;
}
}
}

C#_How to insert a string into each columns table SQL

How to insert a string into each columns table SQL
Example, I have a string :
/Hanoi/a2.3b6.7c8.4/Tphcm/n7.2a5.2
I tried many ways, however I got this result:
I don't know anyway to insert it into a table, I want they look like:
Can you suggest any ideal for me, please?
This is my way how to do if i need to develop it.
class Program
{
static void Main(string[] args)
{
List<Location> locations = new List<Location>();
string foo = "/Hanoi/a2.3b6.7c8.4/Tphcm/n7.2a5.2";
var bo = foo.Remove(0, 1).Split('/'); // split data by '/'
// even bo elenment is address and odd element has specific data like number and letter
for (int i = 0; i < bo.Length; i = i + 2)
{
var str = bo[i]; // Address
var str1 = bo[i + 1]; // Letter and Number
var arrLetters = str1.Where(c => char.IsLetter(c)).ToArray(); // Get Letters
for (int j = 0; j < arrLetters.Length; j++)
{
string splittedLetter = string.Empty;
string number = string.Empty;
if (j+1 != arrLetters.Length)
{
splittedLetter = str1.Split(arrLetters[j + 1])[0];
number = Regex.Replace(splittedLetter, "[A-Za-z ]", "");
str1 = str1.Replace(splittedLetter, string.Empty);
}
else
{
number = Regex.Replace(str1, "[A-Za-z ]", "");
}
// add to list, db or where you want :)
locations.Add(new Location
{
Address = str,
Letter = arrLetters[j].ToString(),
Number = number
});
}
}
Console.ReadKey();
}
}
class Location
{
public string Address { get; set; }
public string Letter { get; set; }
public string Number { get; set; }
}
List members data like your db result.
I hope this solution help to you
Extract the required information from the input string mentioned /Hanoi/a2.3b6.7c8.4/Tphcm/n7.2a5.2 by the parameter "/"
a. split complete string in to an array
string str = "/Hanoi/a2.3b6.7c8.4/Tphcm/n7.2a5.2";
string[] words = str.Split('/');
b. Extract required data from the words array
Write a sql query/LINQ to insert each string/numbers in to the related columns
Let us know if you see any issues in implementing this.

How to get first or first two digits from "12345-678" using a substring C# Console

I am creating a C# Console app which is Replacing a Hyphen for Zeros to complete the maximum length of my String from an identification card "123456-72"and I am facing a hard time when have to sort in my array.
I would like sort the first digit or character from this "123456-72" as well in some cases I need to sort from the first two digit "391234-56".
This example is working fine, but only for first character. I need to
Example:
class IdentificationNumber {
private string IDNumber;
private int Customertype;
private string check;
private string pad;
private string longFormat;
SortedList GroupID = new SortedList();
public IdentificationNumber(string IDNumber) {
this.IDNumber= IDNumber;
}
public string getLongFormat() {
var ReplaceHyp = IDNumber.Replace("-", "");
int Customertype= Int32.Parse(IDNumber.Substring(0,2));
//Array
//GroupID .Add(1,"Blue");
//GroupID .Add(2,"Blue");
GroupID .Add(38,"White");
GroupID .Add(39,"Blue");
pad="";
check = GroupID.GetByIndex(GroupID.IndexOfKey(Customertype)).ToString();
Console.WriteLine(Customertype);
Console.WriteLine(check);
switch (check) {
case("White"):
longFormat= ReplaceHyp.Substring(0,6)+pad.PadLeft((14 -ReplaceHyp.Length),'0')+ReplaceHyp.Substring(6,(ReplaceHyp.Length-6));
break;
case("Blue"):
longFormat= ReplaceHyp.Substring(0,7)+pad.PadLeft((14 -ReplaceHyp.Length),'0')+ReplaceHyp.Substring(7,(ReplaceHyp.Length-7));
break;
}
return longFormat;
}
}
Any solution or suggestion?
Here is a skeleton of the comparator method you might need:
public static int CompareStrings(string s1, string s2)
{
int Customertype1 = Int32.Parse(s1.Substring(0,2));
int Customertype2 = Int32.Parse(s2.Substring(0,2));
string check1 = GroupID.GetByIndex(GroupID.IndexOfKey(Customertype1)).ToString();
string check2 = GroupID.GetByIndex(GroupID.IndexOfKey(Customertype2)).ToString();
if (Customertype1 > Customertype2)
return 1;
if (Customertype1 < Customertype2)
return -1;
else
{
var ReplaceHyp1 = s1.Replace("-", "");
switch (check1) {
case("White"):
longFormat1 = ReplaceHyp1.Substring(0,6)+pad.PadLeft((14 -ReplaceHyp1.Length),'0')+ReplaceHyp1.Substring(6,(ReplaceHyp1.Length-6));
break;
case("Blue"):
longFormat1 = ReplaceHyp1.Substring(0,7)+pad.PadLeft((14 -ReplaceHyp1.Length),'0')+ReplaceHyp1.Substring(7,(ReplaceHyp1.Length-7));
break;
}
var ReplaceHyp2 = s2.Replace("-", "");
switch (check2) {
case("White"):
longFormat2 = ReplaceHyp2.Substring(0,6)+pad.PadLeft((14 -ReplaceHyp2.Length),'0')+ReplaceHyp2.Substring(6,(ReplaceHyp2.Length-6));
break;
case("Blue"):
longFormat2 = ReplaceHyp2.Substring(0,7)+pad.PadLeft((14 -ReplaceHyp2.Length),'0')+ReplaceHyp2.Substring(7,(ReplaceHyp2.Length-7));
break;
}
return stringCompare(longFormat1, longFormat2);
}
}
This code badly needs to be refactored! Depending on your exact needs, I think that the checks of Customertype1/2 can be removed.

Find and Replace RegEx with wildcard search and addition of value

The below code is from my other questions that I have asked here on SO. Everyone has been so helpful and I almost have a grasp with regards to RegEx but I ran into another hurdle.
This is what I basically need to do in a nutshell. I need to take this line that is in a text file that I load into my content variable:
X17.8Y-1.Z0.1G0H1E1
I need to do a wildcard search for the X value, Y value, Z value, and H value. When I am done, I need this written back to my text file (I know how to create the text file so that is not the problem).
X17.8Y-1.G54G0T2
G43Z0.1H1M08
I have code that the kind users here have given me, except I need to create the T value at the end of the first line, and use the value from the H and increment it by 1 for the T value. For example:
X17.8Y-1.Z0.1G0H5E1
would translate as:
X17.8Y-1.G54G0T6
G43Z0.1H5M08
The T value is 6 because the H value is 5.
I have code that does everything (does two RegEx functions and separates the line of code into two new lines and adds some new G values). But I don't know how to add the T value back into the first line and increment it by 1 of the H value. Here is my code:
StreamReader reader = new StreamReader(fDialog.FileName.ToString());
string content = reader.ReadToEnd();
reader.Close();
content = Regex.Replace(content, #"X[-\d.]+Y[-\d.]+", "$0G54G0");
content = Regex.Replace(content, #"(Z(?:\d*\.)?\d+)[^H]*G0(H(?:\d*\.)?\d+)\w*", "\nG43$1$2M08"); //This must be created on a new line
This code works great at taking:
X17.8Y-1.Z0.1G0H5E1
and turning it into:
X17.8Y-1.G54G0
G43Z0.1H5M08
but I need it turned into this:
X17.8Y-1.G54G0T6
G43Z0.1H5M08
(notice the T value is added to the first line, which is the H value +1 (T = H + 1).
Can someone please modify my RegEx statement so I can do this automatically? I tried to combine my two RegEx statements into one line but I failed miserably.
Update1: Stephen in the comments below suggests, "there's no arithmetic operators in regex, you'll need to use a group to pull out the H value, turn it into an int, add one and build a new string.". But I have no idea on how to do this in C# code.
The easiest way to do this is with a simple program that uses a few Regex patterns that capture (named) groups, I had a little spare time so here you go:
Program.cs
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
const string InputFileName = #"input.txt";
const string OutputFileName = #"output.txt";
List<Line> parsedLineList = new List<Line>();
using (StreamReader sr = new StreamReader(InputFileName))
{
string inputLine;
int lineNum = 0;
while ((inputLine = sr.ReadLine()) != null)
{
lineNum++;
Line parsedLine = new Line(inputLine);
if (parsedLine.IsMatch)
{
parsedLineList.Add(parsedLine);
}
else
{
Debug.WriteLine("Line {0} did not match pattern {1}", lineNum, inputLine);
}
}
}
using (StreamWriter sw = new StreamWriter(OutputFileName))
{
foreach (Line line in parsedLineList)
{
sw.WriteLine(line.ToString());
}
}
}
}
}
With input.txt containing:
X17.8Y-1.Z0.1G0H1E1
this program creates output.txt containing:
X17.8Y-1.G54G0T2G43Z0.1H1M08
The above code in Program.cs requires the following simple Line and Fragment class definitions:
Line.cs
namespace Fragments
{
class Line
{
private readonly static Regex Pattern =
new Regex(#"^(?<X>X[^Y]+?)(?<Y>Y[^Z]+?)(?<Z>Z[^G]+?)(?<G>G[^H]+?)(?<H>H[^E]+?)(?<E>E[^$])$");
public readonly string OriginalText;
public string Text
{
get
{
return this.X.ToString() + this.Y.ToString() + this.G54.ToString() + this.G.ToString() + this.T.ToString() + Environment.NewLine +
this.G43.ToString() + this.Z.ToString() + this.H.ToString() + this.M08.ToString();
}
}
public readonly bool IsMatch;
public Fragment X { get; set; }
public Fragment Y { get; set; }
public readonly Fragment G54 = new Fragment("G54");
public Fragment G { get; set; }
public Fragment T { get; set; }
public readonly Fragment G43 = new Fragment("G43");
public Fragment Z { get; set; }
public Fragment H { get; set; }
public readonly Fragment M08 = new Fragment("M08");
public Fragment E { get; set; }
public Line(string text)
{
this.OriginalText = text;
Match match = Line.Pattern.Match(text);
this.IsMatch = match.Success;
if (match.Success)
{
this.X = new Fragment(match.Groups["X"].Value);
this.Y = new Fragment(match.Groups["Y"].Value);
this.G = new Fragment(match.Groups["G"].Value);
this.Z = new Fragment(match.Groups["Z"].Value);
this.H = new Fragment(match.Groups["H"].Value);
this.E = new Fragment(match.Groups["E"].Value);
this.T = new Fragment('T', this.H.Number + 1.0);
}
}
public override string ToString()
{
return this.Text;
}
}
}
Fragment.cs
namespace Fragments
{
class Fragment
{
private readonly static Regex Pattern =
new Regex(#"^(?<Letter>[A-Z]{1})(?<Number>.+)$");
public readonly string Text;
public readonly bool IsMatch;
public readonly char Letter;
public readonly double Number;
public Fragment(string text)
{
this.Text = text;
Match match = Fragment.Pattern.Match(text);
this.IsMatch = match.Success;
if (match.Success)
{
this.Letter = match.Groups["Letter"].Value[0];
string possibleNumber = match.Groups["Number"].Value;
double parsedNumber;
if (double.TryParse(possibleNumber, out parsedNumber))
{
this.Number = parsedNumber;
}
else
{
Debug.WriteLine("Couldn't parse double from input {0}", possibleNumber);
}
}
else
{
Debug.WriteLine("Fragment {0} did not match fragment pattern", text);
}
}
public Fragment(char letter, double number)
{
this.Letter = letter;
this.Number = number;
this.Text = letter + number.ToString();
this.IsMatch = true;
}
public override string ToString()
{
return this.Text;
}
}
}
Create a new C# Console Application project, add these three files, update your using statements and you're ready to go. You can very easily alter the code in Program.cs to read the input and output filenames from Main's command line arguments to make the program reusable.
I'm not sure you can do this just with Regular Expressions, and even in case you can, thinking on maintainability of the code, I wouldn't implement it that way. What you can easily do with RegEx, is to capture the pieces you need into groups, and from those create the output expression.
Here's the code for that:
System.Text.StringBuilder content = new System.Text.StringBuilder();
using (var reader = new StreamReader(fDialog.FileName.ToString()))
{
string line = reader.ReadLine();
while (line != null)
{
var matchingExpression = Regex.Match(line, #"(X[-\d.]+)(Y[-\d.]+)(Z(?:\d*\.)?\d+)[^H]*G0H((?:\d*\.)?\d+)\w*");
content.AppendFormat(
System.Globalization.CultureInfo.InvariantCulture,
"{0}{1}G54G0T{2}\n",
matchingExpression.Groups[0].Value,
matchingExpression.Groups[1].Value,
Int32.Parse(matchingExpression.Groups[3].Value) + 1);
content.AppendFormat(
System.Globalization.CultureInfo.InvariantCulture,
"G43{0}H{1}M08\n",
matchingExpression.Groups[2].Value,
matchingExpression.Groups[3].Value);
line = reader.ReadLine();
}
}
And to get the output string you should do:
content.ToString();

Categories

Resources