Read delimited file and transform to a positional layout - c#

I have a .txt file with this example, with a lot of lines:
20200610;My name ;00000001;Name of my company for example; this is the end;01;
I need to transform this layout to a positional one. the first column with 10 chars, the second one with 30, etc. after this, generate a new file with this new layout.
20200610 My name 00000001(etc)
How can I do this transformation in C#? the file is already read.

Assuming a very simple (no quoted fields) input, use String.Split then use String.Format with the alignment component, interpolating in the field lengths.
For example:
var src = "20200610;My name ;00000001;Name of my company for example; this is the end;01;";
var fieldLens = new[] { 10, 30, 8, 30, 30, 2, 1 };
var res = src.Split(';').Select((s, n) => String.Format($"{{0,{-fieldLens[n]}}}", s)).Join();

A possible solution by creating a string extension method
// StringExtension.cs
public static class StringExtension
{
public static String ToCustomLength(this String value, int cnt)
{
if (value.Length == cnt)
return value;
else if (value.Length < cnt)
value = value.PadRight(cnt, ' ');
else
value = value.Substring(0, cnt);
return value;
}
}
// Program.cs
class cData
{
public string SomeDate { get; set; }
public string SomeName { get; set; }
public string SomeID { get; set; }
public string SomeCompany { get; set; }
public string SomeCompany2 { get; set; }
public string SomeID2 { get; set; }
public cData(string t)
{
// Enforce the rules here
string[] t2 = t.Split(';');
if (t2.Count() != 7)
throw new Exception("Invalid String passed");
SomeDate = t2[0].ToCustomLength(10);
SomeName = t2[1].ToCustomLength(30);
SomeID = t2[2].ToCustomLength(20);
SomeCompany = t2[3].ToCustomLength(30);
SomeCompany2 = t2[4].ToCustomLength(30);
SomeID2 = t2[5].ToCustomLength(5);
}
}
class Program
{
static void Main(string[] args)
{
cData data = new cData("20200610; My name; 00000001; Name of my company for example; this is the end; 01;");
}
}

Related

Is there a way to serialize / deserialize an object to a string in C#?

Is there any kind of text serializer in C#, which is able to serialize / deserialize this shortened example...
public class Record
{
// Letters 1-4
public string Identifier { get; set; }
// Letters 5-12
public string ProcessAbbreviation { get; set; }
// Letters 13-16
public string Name { get; set; }
}
... into this string?
AAAABBBB CCCC
Note, that the string must contain whitespaces if the property hasn't the desired length.
Although it must be possible to serialize / deserialize into the other direction, e.g. string into object.
I've already tried to find something, which suits my requirement, but I couldn't find anything.
I highly appreciate any kind of help, cheers! :)
There's not going to be an existing library to do this, but it's very easy to write your own serialisation and deserialisation:
public class Record
{
// Letters 1-4
public string Identifier { get; set; }
// Letters 5-12
public string ProcessAbbreviation { get; set; }
// Letters 13-16
public string Name { get; set; }
public string Serialize()
{
return $"{Identifier, -4}{ProcessAbbreviation, -8}{Name, -4}";
}
public static Record Deserialize(string input)
{
if (input is not { Length: 16 })
throw new ArgumentException("input must be 16 characters long");
return new Record
{
Identifier = input.Substring( 0, 4).Trim(),
ProcessAbbreviation = input.Substring( 4, 8).Trim(),
Name = input.Substring(12, 4).Trim()
};
}
}
Test code:
public static void Main()
{
var rec = new Record { Identifier = "AAAA", ProcessAbbreviation = "BBBB", Name = "CCCC" };
var serialised = rec.Serialize();
Console.WriteLine("|" + serialised + "|");
var r = Record.Deserialize(serialised);
Console.WriteLine($"|{r.Identifier}|{r.ProcessAbbreviation}|{r.Name}|");
}
Try it on DotNetFiddle

Extracting a key-value pair from inside a string

I have the following string:
string myString = "{'gridObject':'[1,2,3,4],[5,6,7,8]'}";
How do I process this into an object so that I can do this:
charts[0] //=> [1,2,3,4]
charts[0][1] //=> 2
If I can convert it to this object, even better:
public class gridObject {
public int datarow {get; set;}
public int datacol {get; set;}
public int datasizex {get; set;}
public int datasizey {get; set;}
}
This is what I would do.
Create your classes first,
public class GridObject
{
public int datarow { get; set; }
public int datacol { get; set; }
public int datasizex { get; set; }
public int datasizey { get; set; }
}
public class GridObjectCollection
{
public GridObject[] GridObjects { get; set; }
}
Then, to see what JSON you need, serialize it once: (JsonConvert is part of Json.NET, you can get it with NuGet)
GridObjectCollection gridObjects = new GridObjectCollection();
gridObjects.GridObjects = new GridObject[]
{
new GridObject() { datacol = 1, datarow = 2, datasizex = 3, datasizey = 4 },
new GridObject() { datacol = 5, datarow = 6, datasizex = 7, datasizey = 8 }
};
Console.WriteLine
(
JsonConvert.SerializeObject
(
gridObjects,
new JsonSerializerSettings() { Formatting = Formatting.Indented }
)
);
Here you can see that the valid JSON content which will produce these classes when deserialized is like:
{
"GridObjects": [
{
"datarow": 2,
"datacol": 1,
"datasizex": 3,
"datasizey": 4
},
{
"datarow": 6,
"datacol": 5,
"datasizex": 7,
"datasizey": 8
}
]
}
Then, just try a deserialization just to make sure:
var f = JsonConvert.DeserializeObject<GridObjectCollection>
(
"{'GridObjects':[{'datarow':2,'datacol':1,'datasizex':3,'datasizey':4},{'datarow':6,'datacol':5,'datasizex':7,'datasizey':8}]}"
);
Here is one way to do it:
public static gridObject[] Parse(string str)
{
int first = str.IndexOf("[");
int last = str.LastIndexOf("]");
str = str.Substring(first, last - first + 1);
string[] big_parts = str.Split(new string[] {"[", "],[", "]"} , StringSplitOptions.RemoveEmptyEntries);
return big_parts.Select(x =>
{
string[] small_parts = x.Split(',');
return new gridObject()
{
datarow = Convert.ToInt32(small_parts[0]),
datacol = Convert.ToInt32(small_parts[1]),
datasizex = Convert.ToInt32(small_parts[2]),
datasizey = Convert.ToInt32(small_parts[3]),
};
}).ToArray();
}
It first searches for the the first [ and the last ] and trims anything before [ and after ].
Then, it splits the string based on [, ],[, ].
This will give us 1,2,3,4 and 5,6,7,8 for your example.
Then for each one of them, we split based on , and convert the results into a gridObject object.
Done with custom parsing:
public class GridObject
{
public int datarow { get; set; }
public int datacol { get; set; }
public int datasizex { get; set; }
public int datasizey { get; set; }
}
/// <summary>
/// MySuperObject class which holds a reference to inner array of integers
/// </summary>
public class MySuperObject
{
public List<int> Items { get; set; } // Inner array of list of integers
public MySuperObject()
{
Items = new List<int>();
}
public override string ToString()
{
// Need to override ToString to return something like "[1,2,3,4]"
var result = "";
foreach (var item in Items)
{
if (result.Length > 0)
result += ",";
result += item.ToString();
}
return string.Format("[{0}]", result);
}
/// <summary>
/// Function to generate GridObject from existing set of integers
/// </summary>
public GridObject GetGridObject()
{
var result = new GridObject();
if (Items.Count >= 1) result.datarow = Items[0];
if (Items.Count >= 2) result.datacol = Items[1];
if (Items.Count >= 3) result.datasizex = Items[2];
if (Items.Count >= 4) result.datasizey = Items[3];
return result;
}
}
// Parse functions
public List<MySuperObject> Parse(string value)
{
if (string.IsNullOrEmpty(value))
throw new ArgumentException("value cannot be null or empty!", "value");
var result = new List<MySuperObject>();
// First get the indexes of first [ and last ]
var idxStart = value.IndexOf("[");
var idxEnd = value.LastIndexOf("]");
// Check validity
if (idxStart < 0 || idxEnd < 0 || idxEnd <= idxStart)
return result; // Return empty list
value = value.Substring(idxStart, idxEnd - idxStart + 1).Trim();
// Split by [] after replacing spaces with empty strings (and removing first and last [ and ])
var arr = value.Replace(" ", "").Trim('[',']').Split(new[] { "],[" }, StringSplitOptions.RemoveEmptyEntries);
foreach (var str in arr)
{
// Construct list of integers with a help of LINQ
var nums = str.Split(',').Select(t => Convert.ToInt32(t)).ToList();
// Create and add MySuperObject to existing list which will be returned
result.Add(new MySuperObject
{
Items = new List<int>(nums),
});
}
return result;
}
And here is the usage of such parsing:
var myString = "{'gridObject':'[1,2,3,4],[5,6,7,8]'}";
var value = Parse(myString);
// Get all grid objects
var allGridObjects = value.Select(t => t.GetGridObject()).ToList();
Of course, this could need a little bit more of error checking but basically, this MySuperObject is used to use any number of integers you desire, while providing you with a helper method of "GetGridObject" to fill you grid object with appropriate numbers from array of numbers.

Get property names as strings from a input string

I am currently creating a module which will create merge fields in word using Gembox.Document from C#. First of all, I just want to say that this is a task that I have been given, so wether this is a bad way to do it or not, this is the way they want it.
I have a Windows forms application where there is a textbox and a button. In the textbox, they want the possibility to paste a dto/model, for example:
"public class Example
{
public string Name { get; set; }
public string Surname { get; set; }
public string Cellphone { get; set; }
public string Address { get; set; }
public string CompanyName { get; set; }
public DateTime CurrentDate { get; set; }
}"
I already have the logic to add mergefields to word with a method where I pass in a string[] which contains all the merge field names.
PROBLEM IS: I need to be able to somehow substring this big string above which contains a dto/model written as a string in the textbox, to get the property names and add them to the string[], since they will be the merge field names.
I hope I could explain myself well enough. This is my first question here and I am not used to explain my problems in English.
EDIT:
To specify the problem: I need to get the property names out of this string and put them into an string[]:
string s = #"public string Name { get; set; }
public string Surname { get; set; }
public string Cellphone { get; set; }
public string Address { get; set; }
public string CompanyName { get; set; }
public DateTime CurrentDate { get; set; }"
I think that maybe you should parse this text (using parser not own solution) and than search syntax tree to find properties names. I think about something similar to this:
Using NRefactory for analyzing C# code
This code returns complete tree or error (I use NRefactory but you can use Roslyn):
var parser = new CSharpParser();
var syntaxTree = parser.Parse(programCode);
than search syntaxTree field for properties.
Example code:
const string code = #"public class Example {
public string Name { get; set; }
public string Surname { get; set; }
public string Cellphone { get; set; }
public string Address { get; set; }
public string CompanyName { get; set; }
public DateTime CurrentDate { get; set; }
}";
var syntaxTree = new CSharpParser().Parse(code, "program.cs");
var listOfPropertiesNames = syntaxTree.Children
.SelectMany(astParentNode => astParentNode.Children)
.OfType<PropertyDeclaration>()
.Select(astPropertyNode => astPropertyNode.Name)
.ToList();
This snippet extract properties names.
You can use the CSharpCodeProvider class to compile your code to an assembly, and then use reflection to find the types in the compiled assembly.
var sourcePart = #"public class Example
{
public string Name { get; set; }
public string Surname { get; set; }
public string Cellphone { get; set; }
public string Address { get; set; }
public string CompanyName { get; set; }
public DateTime CurrentDate { get; set; }
}";
var sourceTemplate = #"using System;
#code
";
var code = sourceTemplate.Replace("#code", sourcePart);
CSharpCodeProvider c = new CSharpCodeProvider();
CompilerParameters cp = new CompilerParameters();
CompilerResults cr = c.CompileAssemblyFromSource(cp, code);
if (cr.Errors.Count > 0)
{
MessageBox.Show("ERROR: " + cr.Errors[0].ErrorText,
"Error evaluating cs code", MessageBoxButtons.OK,
MessageBoxIcon.Error);
return;
}
var a = cr.CompiledAssembly;
var type = a.GetTypes().Single();
string[] propertyNames = type.GetProperties().Select(p => p.Name).ToArray();
UPDATE:
Remeber however, that a type loaded in an app domain can't be unloaded, and will keep consuming memory until the application exit.
So if the user works with this function a lot, memory would be consumed incrementally.
If this becomes a problem, you can workaround this by creating a separate app domain or spawn another process to serve this function, but it's another question.
You can create custom static method to parse your text. What it does it jumps across string from one index of '{' to next index goes backward and checks if there is '(' or ')' char (which indicates that it is a method and not a property and it should skip it) and goes backwards to find the beginning of the property. After that it extracts value, then jumps to next index of '{' char and so on :
static string[] GetProperties(string dirty)
{
List<string> properties = new List<string>();
int i = dirty.IndexOf("{ ");
StringBuilder sb = new StringBuilder();
int propEndIndex = -1; int i2 = -1;
for (; i != -1; i = dirty.IndexOf("{ ", i + 1))
{
i2 = i - 1;
for (; dirty[i2] == ' '; i2--) { }
if (dirty[i2] == '(' || dirty[i2] == ')') continue;
propEndIndex = i2 + 1;
for (; dirty[i2] != ' '; i2--) { }
for (i2++; i2 < propEndIndex; i2++)
sb.Append(dirty[i2]);
properties.Add(sb.ToString());
sb.Clear();
}
return properties.ToArray();
}
Example of usage :
Stopwatch sw = new Stopwatch();
var s = #"public class Example
{
public string Name { get; set; }
public string Surname { get; set; }
public string Cellphone { get; set; }
public string Address { get; set; }
public string CompanyName { get; set; }
public DateTime CurrentDate { get; set; }
public void MyMethod() { }
}";
sw.Start();
string[] props = GetProperties(s);
sw.Stop();
foreach (var item in props)
Console.WriteLine(item);
Console.WriteLine("\n\nMethod is executed in " + sw.ElapsedMilliseconds + " ms");
Console.ReadKey();
Output:
Name
Surname
CellPhone
Address
CompanyName
CurrentDate
Method is executed in 1 ms

How to re-factor a spec flow data table and a Test Context properties

Field Length position Value
TRANS LENGTH 4 5 1234
TRANS DEST 7 9 DEV
TRANS ORGN 7 16 PROD
TRANS DATE 6 23 2014-03-30
TRANS ID 4 35 44
read table
Here I am using the field as a Key and {length, position, and value} as a value
public class SpecFlowData
{
public string Field { get; set; }
public string Value { get; set; }
public string Position { get; set; }
public string Length { get; set; }
public Dictionary<string, SpecFlowData> GetData(Table table)
{
var data = table.CreateSet<SpecFlowData>().ToList();
var result = data
.ToDictionary(x => x.Field, x => new SpecFlowData
{
Value = x.Value,
Position = x.Position,
Length = x.Length
});
return result;
}
}
public class TestContext
{
public SpecFlowData TRANSLENGETH{ get; set; }
public SpecFlowData TRANSDEST{ get; set; }
public SpecFlowData TRANSORGN { get; set; }
public SpecFlowData TRANSDATE { get; set; }
public SpecFlowData TRANSID { get; set; }
}
here I am trying to populate the values so that I can do my assertion
[Given(#"Request is sent with the following data:")]
public void GivenRequestIsSentWithTheFollowingData(Table table)
{
var data = SpecFlowData.GetRequestData(table);
TestContext.TRANSLENGETH= data["TRANSLENGETH"];
TestContext.TRANSDEST= data["TRANSDEST"];
TestContext.TRANSORGN = data["TRANSORGN "];
}
[Then(#"Response will contain the expected data:")]
public void ThenResponseWillContainTheExpectedData()
{
var Response = "HERE I HAVE the string response message that I am asserting ";
Assert.AreEqual(Response.Substring(TestContext.TransLength.Position, TestContext.TransLength.Length), TestContext.TransLength);
Assert.AreEqual(Response.Substring(TestContext.TransDest.Position, TestContext.TransDest.Length), TestContext.TransDest);
...... more TestContext properties to be asserted.
}
But this seams like a redundant code. How can I re-factor it?
You can simply use the indexer to get your values, since you have a Dictionary, you don't need to use FirstOrDefault method, just specify the key and then the index of the value:
string value = data["TRANS ID"][0];
string position = data["TRANS ID"][1];
string length = data["TRANS ID"][2];
But, I would use a Dictionary<string, SpecFlowData> instead of Dictionary<string, IList<string>> then change the method like this:
public Dictionary<string, SpecFlowData> GetData(Table table)
{
var data = table.CreateSet<SpecFlowData>().ToList();
var result = data
.ToDictionary(x => x.Field, x => new SpecFlowData
{
Value = x.Value,
Position = x.Position,
Length = x.Length
});
return result;
}
Then get the values like this:
string value = data["TRANS ID"].Value;
string position = data["TRANS ID"].Position;
string length = data["TRANS ID"].Length;

Parsing text file using C#

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#

Categories