I have a database column that is a text field, and this text field contains values that look like
I=5212;A=97920;D=20181121|I=5176;A=77360;D=20181117|I=5087;A=43975;D=20181109
and can vary sometimes to look like:
I=29;A=20009.34;D=20190712;F=300|I=29;A=2259.34;D=20190714;F=300
Where 'I' represents the invoice Id, 'A' the invoice amount, 'D' the date in YYYYMMDD format and 'F' the original foreign currency value if the invoice was from a foreign supplier.
I am fetching that column and binding it to a datagrid which has a button labelled "Show Amount". On button click, it fetches the selected row and splits the string to extract "A"
I need to fetch all the sections with A= within the column result... i.e
A=97920
A=77360
A=43975
Then sum them all together and display the result on a label.
I have tried splitting using '|' first, extracting the substring 'A=' then splitting it using ';' to get the amount after "=".
string cAlloc;
string[] amount;
string InvoiceTotal;
string SupplierAmount;
string BalanceUnpaid;
DataRowView dv = invoicesDataGrid.SelectedItem as DataRowView;
if (dv != null)
{
cAlloc = dv.Row.ItemArray[7].ToString();
InvoiceTotal = dv.Row.ItemArray[6].ToString();
if (invoicesDataGrid.Columns[3].ToString() == "0")
{
lblAmount.Foreground = Brushes.Red;
lblAmount.Content = "No Amount Has Been Paid Out to the Supplier";
}
else
{
amount = cAlloc.Split('|');
foreach (string i in amount)
{
string toBeSearched = "A=";
string code = i.Substring(i.IndexOf(toBeSearched) + toBeSearched.Length);
string[] res = code.Split(';');
SupplierAmount = res[0];
float InvTotIncl = float.Parse(InvoiceTotal, CultureInfo.InvariantCulture.NumberFormat);
float AmountPaid = float.Parse(SupplierAmount, CultureInfo.InvariantCulture.NumberFormat);
float BalUnpaid = InvTotIncl - AmountPaid;
BalanceUnpaid = Convert.ToString(BalUnpaid);
if (BalUnpaid == 0)
{
lblAmount.Content = "Amount Paid = " + SupplierAmount + " No Balance Remaining, Supplier Invoice Paid in Full";
}
else if (BalUnpaid < 0)
{
lblAmount.Content = "Amount Paid = " + SupplierAmount + " Supplier Paid an Excess of " + BalanceUnpaid;
}
else
{
lblAmount.Content = "Amount Paid = " + SupplierAmount + " You Still Owe the Supplier a Total of " + BalanceUnpaid; ;
}
}
}
But I am only able to extract A=43975, the very last "A=". Instead of all three, plus I have not figured out how to sum the strings. Somebody help... please.
Regex is prefered solution. Alternatively split, split and split.
var cAlloc = "I=29;A=20009.34;D=20190712;F=300|I=29;A=2259.34;D=20190714;F=300";
var amount = cAlloc.Split('|');
decimal sum = 0;
foreach (string i in amount)
{
foreach (var t in i.Split(';'))
{
var p = t.Split('=');
if (p[0] == "A")
{
var s = decimal.Parse(p[1], CultureInfo.InvariantCulture);
sum += s;
break;
}
}
}
var in1 = "I=5212;A=97920;D=20181121|I=5176;A=77360;D=20181117|I=5087;A=43975;D=20181109";
var in2 = "I=29;A=20009.34;D=20190712;F=300|I=29;A=2259.34;D=20190714;F=300";
var reg = #"A=(\d+(\.\d+)?)";
Regex.Matches(in1, reg).OfType<Match>().Sum(m => double.Parse(m.Groups[1].Value));
Regex.Matches(in2, reg).OfType<Match>().Sum(m => double.Parse(m.Groups[1].Value));
You're doing too much work for something like this. Here's a simpler solution using Regex.
If the invoice amount is always located as a second value in the set you can access it directly by index after split:
var str = "I=5212;A=97920;D=20181121|I=5176;A=77360;D=20181117|I=5087;A=43975;D=20181109";
var invoices = str.Trim().Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
var totalSum = 0M;
foreach (var invoice in invoices)
{
var invoiceParts = invoice.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
var invoiceAmount = decimal.Parse(invoiceParts[1].Trim().Substring(2));
totalSum += invoiceAmount;
}
Otherwise, you can use a little more "flexible" solution like this:
var str = "I=5212;A=97920;D=20181121|I=5176;A=77360;D=20181117|I=5087;A=43975;D=20181109";
var invoices = str.Trim().Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
var totalSum = 0M;
foreach (var invoice in invoices)
{
var invoiceParts = invoice.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
var invoiceAmount = decimal.Parse(invoiceParts.First(ip => ip.Trim().ToLower().StartsWith("a=")).Substring(2));
totalSum += invoiceAmount;
}
Import the input: "Deserialisation"
With the following given input, we have a list of object with property name I,A, and D.
var input = "I=5212;A=97920;D=20181121|I=5176;A=77360;D=20181117|I=5087;A=43975;D=20181109";
Give this simple class:
public class inputClass
{
public decimal I { get; set; }
public decimal A { get; set; }
public decimal D { get; set; }
}
Parsing it will look like:
var inputItems =
input.Split('|')
.Select(
x =>
x.Split(';')
.ToDictionary(
y => y.Split('=')[0],
y => y.Split('=')[1]
)
)
.Select(
x => //Manual parsing from dictionary to inputClass.
//If dictionary Key match an object property we could use something more generik.
new inputClass
{
I = decimal.Parse(x["I"], CultureInfo.InvariantCulture.NumberFormat),
A = decimal.Parse(x["A"], CultureInfo.InvariantCulture.NumberFormat),
D = decimal.Parse(x["D"], CultureInfo.InvariantCulture.NumberFormat),
}
)
.ToList();
It look complexe? lets give the inputClass the responsability to initialise it self based on string
PropertyName=Value[; PropertyName=Value] :
public inputClass(string input, NumberFormatInfo numberFormat)
{
var dict = input
.Split(';')
.ToDictionary(
y => y.Split('=')[0],
y => y.Split('=')[1]
);
I = decimal.Parse(dict["I"], numberFormat);
A = decimal.Parse(dict["A"], numberFormat);
D = decimal.Parse(dict["D"], numberFormat);
}
Then the parsing is simple:
var inputItems = input.Split('|').Select(x => new inputClass(x, CultureInfo.InvariantCulture.NumberFormat));
Once we have a more useable Structure a List of object We can easly compute Sum, Avg, Max, Min:
var sumA = inputItems.Sum(x => x.A);
Producing the output: "Serialisation"
In order to process the input we will define an object like similar to the Input
public class outputClass
{
public decimal I { get; set; }
public decimal A { get; set; }
public decimal D { get; set; }
public decimal F { get; set; }
The Class should be able to produce the String PropertyName=Value[; PropertyName=Value], :
public override string ToString()
{
return $"I={I};A={A};D={D};F={F}";
}
Then producing and string "serialisation" after computing the ListOutput based on the List input:
//process The input into the output.
var outputItems = new List<outputClass>();
foreach (var item in inputItems)
{
// compute things to be able to create the nex output item
item.A++;
outputItems.Add(
new outputClass { A = item.A, D = item.D, I = item.I, F = 42 }
);
}
// "Serialisation"
var outputString = String.Join("|", outputItems);
Online Demo. https://dotnetfiddle.net/VcEQmf
Long story short:
Define a class with the property you will use/display.
Add a constructor that take a string like "I=5212;A=97920;D=20181121"
nb: the String may contain property that will not be map to the object
Override the ToString(), so It can easly produce it's serialisation.
nb: Property and value that are not stored in the object will not be in the serialisation result.
Now You simply have to split on your line/object separator "|" and you are ready to go using real object, not having to care about that weird string anymore.
PS:
There was a little missunderstand about your 2 type of inputs, I mentally saw them as input, output. Dont mind those name. It can be the same class. It doens't change anything in this answer.
Related
I have a CSV file, and I want to get all the data from index position 1 (The Company Name in the sample data) and compare them too each other.
I am currently using this line of code to read in the CSV file line by line,
string[] csvData = System.IO.File.ReadAllLines(#"C:\Path");
Then I would split them by rows and try to run a code to grab the wanted data like this
var comNames = new List<string>();
for (int i = 0; i < csvData.Length; i++){
string[] rows = csvData[i].Split(',');
comNames.Add(rows[1]);
}
But as you all know that won't work for lines 4 and 5 even though it is still the same column. Is there a way for me to delete the CRLF's that are causing this issue so I can make this code work or is there another workaround?
Sample data
Serial Number,Company Name,Employee Markme,Description,Leave
9788189999599,TALES OF SHIVA,Mark,mark,0
9780099578079,1Q84
THE
COMPLETE
TRILOGY,HARUKI MURAKAMI,Mark,0
9780198082897,MY KUMAN,Mark,Mark,0
The code below will work if the following assumptions hold true:
There is always a serial #
There is always a company name
There is always a comma before and after the company name
The serial # is always exactly 13 digits
#1-3 are required for this solution. You can tweak the RegEx pattern to deal with #4.
public List<string> GetListOfCompanies() {
string data = File.ReadAllText(#"C:\Users\adam\Documents\test.csv");
var companies = new List<string>();
var pattern = #"\d{13}";
//replace the line ending with something unique
data = data.Replace(System.Environment.NewLine, "#thisisreallyunique#");
//find each serial number, and grab the item after it
foreach (Match match in Regex.Matches(data, pattern)) {
var temp = data.Substring(match.Index); //cut off everything before this match
var temp2 = temp.Substring(temp.IndexOf(",") + 1); //cut off the serial # and the comma following it
//at this point we have the company name, plus everything after it
var company = temp2.Substring(0, temp2.IndexOf(",")); //cut off everything after it
//oh, and put the spaces back into the company
company = company.Replace("#thisisreallyunique#", " ");
companies.Add(company);
}
return companies;
}
Here a solution using Superpower parser you could customize it more for your use case. It's a nice library that is really expressive but there are faster solutions. You could write it by hand using the Span to avoid unnecessary memory allocations.
using Superpower;
using Superpower.Parsers;
using Superpower.Tokenizers;
var csv = #"Serial Number,Company Name,Employee Markme,Description,Leave
9788189999599,TALES OF SHIVA,Mark,mark,0
9780099578079,1Q84
THE
COMPLETE
TRILOGY,HARUKI MURAKAMI,Mark,0
9780198082897,MY KUMAN,Mark,Mark,0";
var tokenizer = new TokenizerBuilder<CsvToken>()
.Match(Character.EqualTo(','), CsvToken.Comma)
.Match(Span.EqualTo("\n\r"), CsvToken.NewLine)
.Match(Character.EqualTo('\n'), CsvToken.NewLine)
.Match(Character.EqualTo('\r'), CsvToken.NewLine)
.Match(Numerics.Integer, CsvToken.Numeric)
.Match(Span.WithoutAny((char c) => c == '\n' || c == '\r' || c == ',' || (c >= '0' && c <= '9')), CsvToken.String)
.Build();
//Parses the header will fail if not correct
var Header = from columnName1 in Token.EqualToValue(CsvToken.String, "Serial Number").Then(s => Token.EqualTo(CsvToken.Comma).Value(s))
from columnName2 in Token.EqualToValue(CsvToken.String, "Company Name").Then(s => Token.EqualTo(CsvToken.Comma).Value(s))
from columnName3 in Token.EqualToValue(CsvToken.String, "Employee Markme").Then(s => Token.EqualTo(CsvToken.Comma).Value(s))
from columnName4 in Token.EqualToValue(CsvToken.String, "Description").Then(s => Token.EqualTo(CsvToken.Comma).Value(s))
from columnName5 in Token.EqualToValue(CsvToken.String, "Leave")
select new[]
{
columnName1.ToStringValue(),
columnName2.ToStringValue(),
columnName3.ToStringValue(),
columnName4.ToStringValue(),
columnName5.ToStringValue()
};
//A serial number an integer of 13 digets
var SerialNumber = from number in Token.EqualTo(CsvToken.Numeric)
where number.Span.Length == 13
select number.ToStringValue();
// An intiger number
var Number = from number in Token.EqualTo(CsvToken.Numeric)
select number.ToStringValue();
//Text that can hold new lines
var RichText = from values in Token.EqualTo(CsvToken.String)
.Or(Token.EqualTo(CsvToken.Numeric))
.Or(Token.EqualTo(CsvToken.NewLine)).Many()
select string.Join("", values.Select(v => v.ToStringValue()));
//Regular text no new lines
var Text = from values in Token.EqualTo(CsvToken.String)
.Or(Token.EqualTo(CsvToken.Numeric)).Many()
select string.Join("", values.Select(v => v.ToStringValue()));
//Parses the row with defined order
var Row = from serialNumber in SerialNumber.Then(s => Token.EqualTo(CsvToken.Comma).Value(s))
from companyName in RichText.Then(s => Token.EqualTo(CsvToken.Comma).Value(s))
from employeeMarkme in Text.Then(s => Token.EqualTo(CsvToken.Comma).Value(s))
from description in Text.Then(s => Token.EqualTo(CsvToken.Comma).Value(s))
from level in Number
select new Info
{
SerialNumber = serialNumber,
CompanyName = companyName,
EmployeeMarkme = employeeMarkme,
Description = description,
Level = level
};
//The actual parser it must have a single row.
var parser = from header in Header.Then(s => Token.EqualTo(CsvToken.NewLine).AtLeastOnce().Value(s))
from rows in Row.ManyDelimitedBy(Token.EqualTo(CsvToken.NewLine).AtLeastOnce())
select rows;
var tokens = tokenizer.Tokenize(csv);
var result = parser.Parse(tokens);
Console.WriteLine(string.Join(Environment.NewLine, result.AsEnumerable()));
Console.ReadLine();
public enum CsvToken
{
Comma,
Numeric,
String,
NewLine
}
public record Info()
{
public string SerialNumber { get; set; }
public string CompanyName { get; set; }
public string EmployeeMarkme { get; set; }
public string Description { get; set; }
public string Level { get; set; }
}
Here's a dotnet fiddle https://dotnetfiddle.net/0GX3KN
I have got a List of strings (read from a file) in this order and format and need to convert into List of class.
1.0.1.0.1, Type: DateTime, Value: 06/03/2013 11:06:10
1.0.1.0.2, Type: DateTime, Value: 06/03/2014 11:06:10
1.0.1.0.3, Type: DateTime, Value: 06/03/2015 11:06:10
1.0.1.0.4, Type: DateTime, Value: 06/03/2016 11:06:10
1.0.1.0.5, Type: DateTime, Value: 06/03/2017 11:06:10
1.0.1.1.1, Type: Integer, Value: 1
1.0.1.1.2, Type: Integer, Value: 2
1.0.1.1.3, Type: Integer, Value: 3
1.0.0.1.4, Type: Integer, Value: 4
1.0.1.1.5, Type: Integer, Value: 5
1.0.1.2.1, Type: String, Value: Hello
1.0.1.2.2, Type: String, Value: Hello1
1.0.1.2.3, Type: String, Value: Hello2
1.0.1.2.4, Type: String, Value: Hello3
1.0.1.2.5, Type: String, Value: Hello4
Here is my class
public class MyData
{
public DateTime DateTime {get;set;}
public int Index {get;set;}
public string Value {get;set;}
}
Now What I wanted is to convert it into a list of C# class
Something like this...
List<MyData> myDataList = new List<MyData>();
MyData data1 = new MyData();
data1.DateTime = "06/03/2013 11:06:10";
data1.Index = 1;
data1.Value = "Hello";
myDataList.Add(data1);
MyData data2 = new MyData();
data2.DateTime = "06/03/2014 11:06:10";
data2.Index = 2;
data2.Value = "Hello1";
myDataList.Add(data2);
and so on..
This is what I have tried so far.
List<List<string>> allLists = lines
.Select(str => new { str, token = str.Split('.') })
.Where(x => x.token.Length >= 4)
.GroupBy(x => string.Concat(x.token.Take(4)))
.Select(g => g.Select(x => x.str).ToList())
.ToList();
Do I really need to iterate or can I modify My LINQ to get me desired output ?
Here is my iteration.
foreach (var list in allLists)
{
MyData data = new MyData();
var splittedstring = list[0].Split(',').ToList();
if (splittedstring.Count == 3)
{
var valueData = splittedstring [2];
var indexof = valueData.IndexOf(':');
var value = valueData.Substring(indexof + 1);
// But Over here, how will get DateTime and Index ?
data.Value = value;
}
}
First, fix your GroupBy: string.Concat(x.token.Take(4)) may create uncertainties when dot-separated numbers are ambiguous. For example, 1.23.4.5 and 12.3.4.5 would both produce "12345" string. Use string.Join with some non-numeric separator instead:
.GroupBy(x => string.Join("|", x.token.Take(4)))
Now for the main part of your question an easy fix would be to add a static method that parses the list of three strings, and use it in your LINQ query:
List<MyData> dataList = lines
.Select(str => new { str, token = str.Split('.') })
.Where(x => x.token.Length >= 4)
.GroupBy(x => string.Concat(x.token.Take(4)))
.Select(g => g.Select(x => x.str).ToList())
.Where(list => list.Count == 3)
.Select(MyDataFromList)
.ToList();
...
private static MyData MyDataFromList(List<string> parts) {
if (parts.Count != 3) {
throw new ArgumentException(nameof(parts));
}
var byType = parts
.Select(ToTypeAndValue)
.ToDictionary(t => t.Item1, t => t.Item2)
return new MyData {
DateTime = DateTime.Parse(byType["DateTime"])
, Index = int.Parse(byType["Integer"])
, Value = byType["String"]
};
}
private static Tuple<string,string> ToTypeAndValue(string s) {
var tokens = s.Split(',');
if (tokens.Length != 3) return null;
var typeParts = tokens[1].Split(':');
if (typeParts.Length != 2 || typeParts[0] != "Type") return null;
var valueParts = tokens[2].Split(':');
if (valueParts.Length != 2 || valueParts[0] != "Value") return null;
return Tuple.Create(typeParts[1].Trim(), typeParts[2].Trim());
}
Note that the above code makes an assumption that the three types are unique (hence the use of Dictionary<string,string>). This is required, because the structure of your data provides no other way to tie the values to fields of MyData.
You can do this using regular expressions. It would look like:
public List<MyData> GetData(string str){
var regexDate = new Regex(#"\d\.\d\.\d\.\d\.(?<id>\d).*DateTime.*Value:\s*(?<val>.*)");
var regexInteger = new Regex(#"\d\.\d\.\d\.\d\.(?<id>\d).*Integer.*Value:\s*(?<val>.*)");
var regexString = new Regex(#"\d\.\d\.\d\.\d\.(?<id>\d).*String.*Value:\s*(?<val>.*)");
var dict = new Dictionary<int, MyData>();
foreach (Match myMatch in regexDate.Matches(str))
{
if (!myMatch.Success) continue;
var index = int.Parse(myMatch.Groups["id"].Value);
dict[index] = new MyData()
{
Index = index,
DateTime = DateTime.ParseExact(myMatch.Groups["val"].Value, "dd/MM/yyyy HH:mm:ss", CultureInfo.InvariantCulture)
};
}
foreach (Match myMatch in regexInteger.Matches(str))
{
if (!myMatch.Success) continue;
var index = int.Parse(myMatch.Groups["id"].Value);
dict[index].Index = Int32.Parse(myMatch.Groups["val"].Value);
}
foreach (Match myMatch in regexString.Matches(str))
{
if (!myMatch.Success) continue;
var index = int.Parse(myMatch.Groups["id"].Value);
dict[index].Value = myMatch.Groups["val"].Value;
}
return dict.Values
}
Here is my solution to your problem. I have already tested it, you can test it to here: Raw To Custom List
string text = rawData;
//Raw Data Is the exact data you read from textfile without modifications.
List<MyData> myDataList = new List<MyData>();
string[] eElco = text.Split( new[] { Environment.NewLine }, StringSplitOptions.None );
var tmem = eElco.Count();
var eachP = tmem / 3;
List<string> unDefVal = new List<string>();
foreach (string rw in eElco)
{
String onlyVal = rw.Split(new[] { "Value: " } , StringSplitOptions.None)[1];
unDefVal.Add(onlyVal);
}
for (int i = 0; i < eachP; i++)
{
int ind = Int32.Parse(unDefVal[i + eachP]);
DateTime oDate = DateTime.ParseExact(unDefVal[i], "dd/MM/yyyy hh:mm:ss",System.Globalization.CultureInfo.InvariantCulture);
MyData data1 = new MyData();
data1.DateTime = oDate;
data1.Index = ind;
data1.Value = unDefVal[i + eachP + eachP];
myDataList.Add(data1);
Console.WriteLine("Val1 = {0}, Val2 = {1}, Val3 = {2}",
myDataList[i].Index,
myDataList[i].DateTime,
myDataList[i].Value);
}
Here is my solution, using Regex. It could be improved by providing a conditional regex match based on the matched type named group(string), but I think the concept is clearer this way, and the regex easier to work with. As it stands, the date format is not validated to be as OP wrote them, they are assumed to be as OP wrote them.
This solution is tolerant to some extra spaces and parameters containing commas, but intolerant to inexact matches, i.e. extra fields added or removed in the rows in the future, etc.
The idea is to first parse the rows to a more "friendly" format, and then group the friendly format by index and return the MyData rows by iterating each group (by index).
Regex r = new Regex(#"^(?<fieldName>(\d\.)+(?<index>\d*)), *Type: *(?<dataType>.*), *Value: (?<dataValue>.*)$");
public class MyData
{
public DateTime DateTime { get; set; }
public int Index { get; set; }
public string Value { get; set; }
}
class LogRow
{
public int Index { get; set; }
public string Type { get; set; }
public string Value { get; set; }
}
//In a parser I would rather not be too defensive, I let exceptions bubble up
IEnumerable<LogRow> ParseRows(IEnumerable<string> lines)
{
foreach (var line in lines)
{
var match = r.Matches(line).AsQueryable().Cast<Match>().Single();
yield return new LogRow()
{
Index = int.Parse(match.Groups["index"].Value),
Type = match.Groups["dataType"].Value,
Value = match.Groups["dataValue"].Value
};
}
}
IEnumerable<MyData> RowsToData(IEnumerable<LogRow> rows)
{
var byIndex = rows.GroupBy(b => b.Index).OrderBy(b=> b.Key);
//assume that rows exist for all MyData fields for a given index
foreach (var group in byIndex)
{
var rawRow = group.ToDictionary(g => g.Type, g => g);
var date = DateTime.ParseExact(rawRow["DateTime"].Value, "dd/MM/yyyy HH:mm:ss", CultureInfo.InvariantCulture);
yield return new MyData() { Index = group.Key, DateTime = date, Value = rawRow["String"].Value };
}
}
Usage:
var myDataList = RowsToData(ParseRows(File.ReadAllLines("input.txt"))).ToList();
I'd just go for the manual approach... and since that list of integers at the start contains indices for the objects and for the properties, it'd only be logical to use these instead of the type strings.
Using a Dictionary, you can use that object-index to make a new object at the moment you find any of its properties, and store it using that index. And whenever you encounter another properties for the same index, you retrieve the object and fill in that property on it.
public static List<MyData> getObj(String[] lines)
{
Dictionary<Int32, MyData> myDataDict = new Dictionary<Int32, MyData>();
const String valueStart = "Value: ";
foreach (String line in lines)
{
String[] split = line.Split(',');
// Too many fail cases; I just ignore any line that stops matching at any point.
if (split.Length < 3)
continue;
String[] numData = split[0].Trim().Split('.');
if (numData.Length < 5)
continue;
// Using the 4th number as property identifier. Could also use the
// type string, but switch/case on a numeric value is more elegant.
Int32 prop;
if (!Int32.TryParse(numData[3], out prop))
continue;
// Object index, used to reference the objects in the Dictionary.
Int32 index;
if (!Int32.TryParse(numData[4], out index))
continue;
String typeDef = split[1].Trim();
String val = split[2].TrimStart();
if (!val.StartsWith(valueStart))
continue;
val = val.Substring(valueStart.Length);
MyData data;
if (myDataDict.ContainsKey(index))
data = myDataDict[index];
else
{
data = new MyData();
myDataDict.Add(index, data);
}
switch (prop)
{
case 0:
if (!"Type: DateTime".Equals(typeDef))
continue;
DateTime dateVal;
// Don't know if this date format is correct; adapt as needed.
if (!DateTime.TryParseExact(val, "dd/MM/yyyy HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out dateVal))
continue;
data.DateTime = dateVal;
break;
case 1:
if (!"Type: Integer".Equals(typeDef))
continue;
Int32 numVal;
if (!Int32.TryParse(val, out numVal))
continue;
data.Index = numVal;
break;
case 2:
if (!"Type: String".Equals(typeDef)) continue;
data.Value = val;
break;
}
}
return new List<MyData>(myDataDict.Values);
}
I want to count all the characters in my list to see if I surpass the maxvalue of MaxJsonLength.
This is my controller where I have a list with products.
public JsonResult GetAllProducts()
{
List<ProductNew> allProducts = new List<ProductNew>();
var shopIdOfTheDay = 2;
allProducts = _db.Products
.Where(p => p.Category.ShopId == shopIdOfTheDay && p.Availability)
.OrderBy(p => p.Description)
.ToList();
//count the characters in the list here
int total = 0;
foreach (var value in allProducts)
{
string s = value.ToString();
int i;
i = s.Length;
total = total += i;
Console.WriteLine(total);
}
return new JsonResult { Data = allProducts, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
but the foreach loop is not working, because every string value is 109 characters long and that aint right.
If I break ath value.ToString is see that the value is +
{System.Data.Entity.DynamicProxies.ProductNew_7F6B12BDD7841029155EF84C6372688579A97D5AA4EA2378712AC64A67B25290} SeashellBrawlCorvee.Models.ProductNew {System.Data.Entity.DynamicProxies.ProductNew_7F6B12BDD7841029155EF84C6372688579A97D5AA4EA2378712AC64A67B25290}
So that aint right. There are multiple values in value, so that proberly why I cant cast it to String?
I tried doing an extra foreach. Like:
foreach (var value2 in value) {
string s1 = value2.ToString();
int i1;
i1 = s2.Length;
total = total += i1;
Console.WriteLine(total);
}
Something like this, but thats not working to because I get an error: foreach statement cannot operate on vairables of type 'PRoductNew'because 'ProductNew' does not contain a public definition for 'GetEnumerator'
So what to do?
Check string s = value.ToString();, the content of s may not be the result you want.
Because value is a class which is from ProductNew class, you can't get content through .ToString() method. If you want to count all characters, you should count every content of property in class, like this:
foreach (var value in allProducts)
{
string ID = value.ID.ToString();
string Category = value.Category.ToString();
string Description = value.Description.ToString();
total += (ID.Length + Category.Length + Description.Length);
Console.WriteLine(total);
}
I get the datas from an textfile. The File itself is already inserted by ReadAllLines and converted into a string - this works fine for me and I checked the content with a MessageBox.
The Textfile looks like this (This is just 1 line from about thousand):
3016XY1234567891111111ABCDEFGHIJKabcdef+0000001029916XY1111111123456789ABCDEFGHIJKabcdef+00000003801
Now these are 2 records and I need 2 datas from every record.
The "XY Number" - these are the first 16 digits AFTER "16XY" (16XY is always the same value)
Value from the example: XY1234567891111111
The "Price" - that is the 11 digits value after the plus. The last 2 digits specify the amount of Cent.
Value from the example: 102,99$
I Need both of this datas to be in the same row in my Datagrid View and also for all other Datas in this textfile.
All I can imagine is to write a code, which searchs the string after "16XY" and counts the next 16 digits - the same with the Price which searchs for a "plus" and counts the next 11 digits. Just in this case I would need to ignore the first line of the file because there are about 10x"+".
I tried several possibilities to search and count for that values but without any success right now. Im also not sure how to get the datas into the specific Datagrid View.
This is all I have to show at the moment:
List<List<string>> groups = new List<List<string>>();
List<string> current = null;
foreach (var line in File.ReadAllLines(path))
{
if (line.Contains("") && current == null)
current = new List<string>();
else if (line.Contains("") && current != null)
{
groups.Add(current);
current = null;
}
if (current != null)
current.Add(line);
}
//array
string output = string.Join(Environment.NewLine, current.ToArray());
//string
string final = string.Join("", output.ToCharArray());
MessageBox.Show(output);
Thanks in advance!
Create a class or struct to hold data
public class Data
{
String XYValue { set; get; }
Decimal Price { set; get; }
}
Then the reading logic (You might need to add some more checks):
string decimalSeperator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
List<Data> results = new List<Data>();
foreach(string line in File.ReadAllLines(path).Skip(1))
{
if (line == null)
continue;
int indexOfNextXY = 0;
while (true)
{
int indexOfXY = line.IndexOf("16XY", indexOfNextXY) + "16XY".Length;
int indexOfPlus = line.IndexOf("+", indexOfXY + 16) + "+".Length;
indexOfNextXY = line.IndexOf("16XY", indexOfPlus);
string xyValue = line.Substring(indexOfXY - 2, 18); // -2 to get the XY part
string price = indexOfNextXY < 0 ? line.Substring(indexOfPlus) : line.Substring(indexOfPlus, indexOfNextXY - indexOfPlus);
string intPart = price.Substring(0, price.Length - 2);
string decimalPart = price.Substring(price.Length - 2);
price = intPart + decimalSeperator + decimalPart;
results.Add(new Data (){ XYValue = xyValue, Price = Convert.ToDecimal(price) });
if (indexOfNextXY < 0)
break;
}
}
var regex = new Regex(#"\+(\d+)(\d{2})16(XY\d{16})");
var q =
from e in File.ReadLines("123.txt")
let find = regex.Match(e)
where find.Success
select new
{
price = double.Parse(find.Groups[1].Value) + (double.Parse(find.Groups[2].Value) / 100),
value = find.Groups[3]
};
dataGridView1.DataSource = q.ToList();
If you need the whole text file as string, you can manipulate it with .Split method.
The action will look something like this:
var values = final.Split(new string[] { "16XY" }, StringSplitOptions.RemoveEmptyEntries).ToList();
List <YourModel> models = new List<YourModel>();
foreach (var item in values)
{
if (item.IndexOf('+') > 0)
{
var itemSplit = item.Split('+');
if (itemSplit[0].Length > 15 &&
itemSplit[1].Length > 10)
{
models.Add(new YourModel(itemSplit[0].Substring(0, 16), itemSplit[1].Substring(0, 11)));
}
}
}
And you will need some model
public class YourModel
{
public YourModel(string xy, string price)
{
float forTest = 0;
XYNUMBER = xy;
string addForParse = string.Format("{0}.{1}", price.Substring(0, price.Length - 2), price.Substring(price.Length - 2, 2));
if (float.TryParse(addForParse, out forTest))
{
Price = forTest;
}
}
public string XYNUMBER { get; set; }
public float Price { get; set; }
}
After that you can bind it to your gridview.
Given that the "data pairs" are variable each line (and can get truncated to the next line), it is best to use File.ReadAllText() instead. This will give you a single string to work on, eliminating the truncation issue.
var data = File.ReadAllText(path);
Define a model to contain your data:
public class Item {
public string XYNumber { get; set; }
public double Price { get; set; }
}
You can then use regular expressions to find matches and store them in a list:
var list = List<Item>();
var regex = new Regex(#"(XY\d{16})\w+\+(\d{11})");
var match = regex.Match(data);
while (match.Success) {
var ps = match.Group[1].Captures[0].Value.Insert(9, ".");
list.Add(new Item {
XYNumber = match.Group[0].Captures[0].Value,
Price = Convert.ToDouble(ps)
});
match = match.NextMatch();
}
The list can also be used as a data source to a grid view:
gridView.DataSource = list;
Consider employing the Split method. From the example data, I notice there is "16XY" between each value. So something like this:
var data = "3016XY1234567891111111ABCDEFGHIJKabcdef+0000001029916XY1111111123456789ABCDEFGHIJKabcdef+00000003801";
var records = data.Split(new string[] { "16XY" }, StringSplitOptions.RemoveEmptyEntries);
Given the example data this will return the following array:
[0]: "30"
[1]: "1234567891111111ABCDEFGHIJKabcdef+00000010299"
[2]: "1111111123456789ABCDEFGHIJKabcdef+00000003801"
Now it will be easier to count characters in each string and give them meaning in your code.
So we know valuable data is separated by +. Lets split it further and fill a Dictionary<string, double>.
var parsed = new Dictionary<string, double>(records.Length - 1);
foreach (var pairX in records.Skip(1))
{
var fields = pairX.Split('+');
var cents = double.Parse(fields[1]);
parsed.Add(fields[0], cents / 100);
}
// Now you bind to the GridView
gv.DataSource = parsed;
And your 'GridView` declaration should look like this:
<asp:GridView ID="gv" runat="server" AutoGenerateColumns="false">
<Columns>
<asp:BoundField DataField="Key" HeaderText="ID" />
<asp:BoundField DataField="Value" HeaderText="Price" />
</Columns>
</asp:GridView>
in c# I get the following string:
[{"place_id":"92911594","licence":"Data \u00a9 OpenStreetMap contributors, ODbL
1.0. http:\/\/www.openstreetmap.org\/copyright","osm_type":"way","osm_id":"12174
8552","boundingbox":["-33.4480414","-33.4439319","-70.6448054","-70.6266944"],"l
at":"-33.4460658","lon":"-70.6359402","display_name":"Mar\u00edn, Santiago, Prov
incia de Santiago, XIII Regi\u00f3n Metropolitana de Santiago, 8331059, Chile","
class":"highway","type":"tertiary","importance":0.2,"address":{"road":"Mar\u00ed
n","city":"Santiago","county":"Provincia de Santiago","state":"XIII Regi\u00f3n
Metropolitana de Santiago","postcode":"8331059","country":"Chile","country_code"
:"cl"},
"svg":"M -70.626694400000005 33.444016900000001 L
-70.626853100000005 33.443931900000003
-70.628024600000003 33.444266200000001
-70.629083399999999 33.444542800000001
-70.629957200000007 33.444764300000003
-70.630791099999996 33.444983999999998
-70.630836099999996 33.4449915
-70.631027500000002 33.445031299999997
-70.631102900000002 33.445044099999997
-70.631955300000001 33.445186900000003
-70.632777300000001 33.445343999999999
-70.632846900000004 33.445356799999999
-70.6329025 33.445371799999997
-70.633783199999996 33.445576199999998
-70.63402120 0000007 33.445624299999999
-70.635066499999994 33.4458354
-70.635940199999993 33.4460658
-70.637316900000002 33.446388900000002
-70.638039300000003 33.446610700000001
-70.638551500000005 33.446730799999997
-70.639778699999994 33.447019400000002
-70.640489099999996 33.4471962
-70.641005800000002 33.4473074
-70.642197100000004 33.447521299999998
-70.642251900000005 33.447535500000001
-70.643692900000005 33.4478121
-70.644805399999996 33.448041400000001
"}]
i need to get and save in some variable each lat-long pair from svg. For example:
pair1 = "-70.626694400000005 33.444016900000001"
pair2 = "-70.626853100000005 33.443931900000003"
pairx = ""
I'm tryng with split, but i cant get the pairs using regular expression.
Your string is a json string. So, you could create a class representing this json data
public class SomeClass{
pubclis string place_id {get; set;}
public string license
// other members
}
Then, you can use a JavascriptSerializer instance to convert the json data to an instance of SomeClass. After that, you can easily retrieve each instance properties.
See: http://msdn.microsoft.com/en-US/en-en/library/system.web.script.serialization.javascriptserializer(v=vs.110).aspx
There may be better ways, but:
public class Coord
{
public double latitude;
public double longitude;
}
var svg = "M -70.626694400000005 33.444016900000001 L -70.626853100000005 33.443931900000003 -70.628024600000003 33.444266200000001 -70.629083399999999 33.444542800000001 -70.629957200000007 33.444764300000003 -70.630791099999996 33.444983999999998 -70.630836099999996 33.4449915 -70.631027500000002 33.445031299999997 -70.631102900000002 33.445044099999997 -70.631955300000001 33.445186900000003 -70.632777300000001 33.445343999999999";
var formattedSvg = svg.Replace("M ", "").Replace("L ", "");
var values = formattedSvg.Split(' ').ToList().Select(v => Double.Parse(v)).ToArray();
var coords = new List<Coord>();
for (var index = 0; index < values.Length; index += 2)
{
coords.Add(new Coord
{
latitude = values[index],
longitude = values[index + 1]
});
}
Since this is JSON, the first thing you need to do is download JSON.Net. Next, Create a class like this:
public class MyClass
{
[JsonProperty(PropertyName = "svg")]
public string Svg { get; set;}
}
Next, do this:
string svg = (JsonConvert.DeserializeObject<MyClass>(myjsondata)).Svg;
This will give you the Svg string you need. Next you can do something like this:
var pairs = new List<string>();
string[] strings = svg.Split(' ');
int ndx = 0;
while (ndx < strings.Length - 1)
{
if (strings[ndx].StartsWith("M") || strings[ndx].StartsWith("L"))
{
ndx++;
continue;
}
pairs.Add(strings[ndx] + " " + strings[ndx + 1]);
ndx += 2;
}
foreach (string s in pairs)
{
Console.WriteLine(s);
}