i Need to parse a proprietary string from a tcp Server.
the string i get is the following:
!re.tag=3=.id=*1=name=1 Hour=owner=admin=name-for-users==validity=3h=starts-at=logon=price=0=override-shared-users=off~!re.tag=3=.id=*2=name=3 Hour=owner=admin=name-for-users==validity=3h=starts-at=logon=price=0=override-shared-users=off~!done.tag=3~
So when striping off the !done.tag.... and Splitting the string at the ~ I can break down the (in this case) two objects to
!re.tag=3=.id=*1=name=1 Hour=owner=admin=name-for-users==validity=3h=starts-at=logon=price=0=override-shared-users=off~
!re.tag=3=.id=*2=name=3 Hour=owner=admin=name-for-users==validity=3h=starts-at=logon=price=0=override-shared-users=off~
then im facing the Problem, how to split the properties and their values.
!re.tag=3
=.id=*2
=name=3 Hour
=owner=admin
=name-for-users=
=validity=3h
=starts-at=logon
=price=0
=override-shared-users=off
Normally i'll do a split on the equals sign, like this:
List<string> arProfiles = profilString.Split('=').ToList();
and then i can guess(!) that the value of the "name" property is at Position 5.
Is there a more proper way to parse these kind of strings (these while i'll get the same kind of strings from different functions)
Paul
//so. we've got the response here
var response = "!re.tag=3=.id=*1=name=1 Hour=owner=admin=name-for-users==validity=3h=starts-at=logon=price=0=override-shared-users=off~!re.tag=3=.id=*2=name=3 Hour=owner=admin=name-for-users==validity=3h=starts-at=logon=price=0=override-shared-users=off~!done.tag=3~";
// first we split the line into sections
var sections = Regex.Matches(response, #"!(?<set>.*?)~").Cast<Match>().Select(s=>s.Groups["set"].Value).ToArray();
// next we can parse any section into key/value pairs
var parsed = Regex.Matches(sections[0], #"(?<key>.*?)=(?<value>[^=]*)=?").Cast<Match>()
.Select(pair => new
{
key = pair.Groups["key"].Value,
value = pair.Groups["value"].Value,
}).ToArray();
Don't forget
using System.Text.RegularExpressions;
Seems like each of the values (not the parameter names) are surrounded by a pair of "=".
This should give you what you want, more or less:
var input = "!re.tag=3=.id=*1=name=1 Hour=(...etc...)";
Dictionary<string, string> values = new Dictionary<string, string>();
while(input.Count() > 0){
var keyChars = input.TakeWhile(x=> x != '=');
var currTag = new string(keyChars.ToArray());
var valueChars = input.Skip(currTag.Count() + 1).TakeWhile(x=> x != '=');
var value = new string(valueChars.ToArray());
values.Add(currTag, value);
input = new string(input.Skip(currTag.Length + value.Lenght + 2)
.ToArray());
}
This results in the following keys and values:
!re.tag | 3
.id | *1
name | 1 Hour
owner | admin
name-for-users |
validity | 3h
starts-at | logon
price | 0
override-shared-users | off~
Each parameter name is starting and ending with '=' symbol. That means you need to process string looking for a first value between two '='. That ever will come after that and before next '=' symbol or end of the string is the value of that property. Property may have an empty value, so it must be handled as well.
The first part of the string is different:
!re.tag=3
You'll have to remove or process it individually.
Way to parse it would be:
var inString = #"=.id=*1=name=1 Hour=owner=admin=name-for-users==validity=3h=starts-at=logon=price=0=override-shared-users=off~";
int startOfParameterName = 0;
int endOfParameterName = 0;
int startOfParameterValue = 0;
bool paramerNameEndFound = false;
bool paramerNameStartFound = false;
var arProfiles = new Dictionary<string, string>();
for(int index = 0; index < inString.Length; index++)
{
if (inString[index] == '=' || index == inString.Length - 1)
{
if (paramerNameEndFound || index == inString.Length - 1)
{
var parameterName = inString.Substring(startOfParameterName, endOfParameterName - startOfParameterName);
var parameterValue = startOfParameterValue == index ? string.Empty : inString.Substring(startOfParameterValue, index - startOfParameterValue);
arProfiles.Add(parameterName, parameterValue);
startOfParameterName = index + 1;
paramerNameEndFound = false;
paramerNameStartFound = true;
}
else
{
if (paramerNameStartFound == false)
{
paramerNameStartFound = true;
startOfParameterName = index + 1;
}
else
{
paramerNameEndFound = true;
endOfParameterName = index;
startOfParameterValue = index + 1;
}
}
}
}
Where is a room to perfection, but it works!
Related
i dont know how to fix this runtime error:
http://postimg.org/image/hh9vl7hi9/
The value of roomsInfo is : "#114|mag|nir|1||dan|nir|1||\0"
and it crash when the value is : "dan|nir|1||\0"
(in the second time of the while) when i try to do this line:
roomsInfo = roomsInfo.Substring(roomsInfo.IndexOf('|') + 1, roomsInfo.IndexOf('\0'));
this is the full code:
String roomsInfo = Program.sendToServ("#10||");
String[] room_name = new String[100];
String[] admin_name = new String[100];
String[] number_of_people = new String[100];
int check = 0, count = 0;
if(roomsInfo.IndexOf('\0') > 5)
{
roomsInfo = roomsInfo.Substring(roomsInfo.IndexOf('|')+1, roomsInfo.IndexOf('\0'));
while (roomsInfo[roomsInfo.IndexOf('|') + 2] != '\0' && roomsInfo[roomsInfo.IndexOf('|') + 1] != '\0') // #114|roomName1|RoomAdmin1|count1||roomName2|RoomAdmin2|count2||
{
if (check == 0)
{
room_name[count] = roomsInfo.Substring(0, roomsInfo.IndexOf('|'));
check = 1;
roomsInfo = roomsInfo.Substring(roomsInfo.IndexOf('|') + 1, roomsInfo.IndexOf('\0'));
}
if (check == 1)
{
admin_name[count] = roomsInfo.Substring(0, roomsInfo.IndexOf('|'));
check = 2;
roomsInfo = roomsInfo.Substring(roomsInfo.IndexOf('|') + 1, roomsInfo.IndexOf('\0'));
}
if (check == 2)
{
number_of_people[count] = roomsInfo.Substring(0, roomsInfo.IndexOf('|'));
check = 0;
count++;
roomsInfo = roomsInfo.Substring(roomsInfo.IndexOf('|') + 2, roomsInfo.IndexOf('\0'));
}
}
}
Thank you!
Nir
In order to Substring Correctly You must Get the length of substring Just like when you get length of Vector in Math.
for example you have two points in one line. say 5 and 13. To get the length between 5 and 13 you must subtract 5 from 13. so 13 - 5 = 8
int startIndex = roomsInfo.IndexOf('|') + 1;
int endIndex = roomsInfo.IndexOf('\0');
int length = endIndex - startIndex;
roomsInfo = roomsInfo.Substring(startIndex, length); // Will Get nir|1||
If you want to get the Last char too. you must add 1 to length
roomsInfo = roomsInfo.Substring(startIndex, length + 1); // Will Get nir|1||\0
From what i see in your code. your Substrings are All wrong. so you must do this to all of them.
One line solution
roomsInfo = roomsInfo.Substring(roomsInfo.IndexOf('|') + 1, roomsInfo.IndexOf('\0') - roomsInfo.IndexOf('|') - 1);
Again if you want to get last char too you must add 1 to the length.
I'm guessing that it might be something to do with the || between records (is that a record delimiter, I assume?) and your substring indexing.
What Glenn is saying would work to split a record into fields, but in your case all of your records are chained together and (I assume delimited by || ?) then finally terminated by a null zero. Therefore you would first need to split your string into set of records, before splitting the records into fields.
struct RoomInfo
{
public String RoomName;
public String AdminName;
public String WhatIsNir;
public int NumberOfPeople;
}
var roomsInfo = new List<RoomInfo>();
String allData = "#114|mag|nir|1||#115|dan|nir|1||\0".TrimEnd('\0');
String[] delimiters = new string[] { "||" };
String[] records = allData.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
foreach (var record in records)
{
String[] fields = record.Split('|');
roomsInfo.Add(new RoomInfo
{
RoomName = fields[0],
AdminName = fields[1],
WhatIsNir = fields[2],
NumberOfPeople = int.Parse(fields[3])
});
}
Another issue might be that your data and your fields are a mismatch. I.e. where do your three fields repeat in the following string? "#114|mag|nir|1||dan|nir|1||\0". I see four fields in that string, so I added one (room name) in the above example.
Of course, to use this solution is to say that there's a commitment to your data format. If this is something you are able to change (i.e. not determined by a 3rd party), then I would change it to something a little more standard. Even a basic form of CSV would be similar, but might be better.
Your implementation is extremely complex. It can be done with few lines of code, which is easier to maintain:
string roomsInfo = Program.sendToServ("#10||");
var room_name = new List<string>();
var admin_name = new List<string>();
var number_of_people = new List<string>();
if(roomsInfo.IndexOf('\0') > 5)
{
roomsInfo = roomsInfo.Substring(roomsInfo.IndexOf('|')+1, roomsInfo.IndexOf('\0'));
var records = roomsInfo.Split(new[] {"||"}, StringSplitOptions.None);
foreach (var rec in records)
{
var fields = rec.Split(new [] {'|'}, StringSplitOptions.None);
room_name.Add(fields[0]);
admin_name.Add(fields[1]);
number_of_people.Add(fields[2]);
}
}
This can be improved, of course, in order to control exceptions.
I've got binary number and I need to:
1) negate all bytes
2) add 1 to negate number
So, I wrote this:
public string u2_number_plus = "1001";
public string u2_number_minus = "";
public string binToU2()
{
int length = u2_number_plus.Length;
int temp = 1;
//negate all bytes
for (int a = 0; a < length; a++)
{
if (u2_number_plus[a] == '1')
u2_number_minus += '0';
else
u2_number_minus += '1';
}
//add 1 to my new (because negate) number
for (int b = length - 1; b >= 0; b--)
{
if (u2_number_minus[b] == 0 && temp == 1)
{
u2_number_minus = u2_number_minus.Replace(u2_number_minus[b], '1');
temp = 0;
}
else if (u2_number_minus[b] == 1 && temp == 1)
{
u2_number_minus = u2_number_minus.Replace(u2_number_minus[b], '0');
temp = 1;
}
else
break;
}
return u2_number_minus;
}
My function binToU2() returns negate but not increment value.
If input data is 1001 I should get 0111, but function returns just 0110. Where I made a mistake?
When you are doing the checking of u2_number_minus[b] you need to compare it against '0' and '1' not the number 0 and 1.
if (u2_number_minus[b] == '0' && temp == 1)
There is also another error, the use of Replace changes all occurrences of the specified character in the string, but we only want to change the one at the specified position. C# does not have replaceAt, but a helper function can be created to do this. See Replacing a char at a given index in string?. I used Jon Skeet's code here:
public static class ReplaceHelper
{
public static string ReplaceAt(this string input, int index, char newChar)
{
if (input == null)
{
throw new ArgumentNullException("input");
}
char[] chars = input.ToCharArray();
chars[index] = newChar;
return new string(chars);
}
}
and change the Replace lines to use ReplaceAt eg
u2_number_minus = u2_number_minus.ReplaceAt(b, '1');
don't really get what you want to do or where you need this for, but anyways, maybe you want to use a BitArray instead of struggling with string manipulation.
BitArray is actually storing bits and gives you basic functionality to negate the array or use other operations...
Let me give you an example:
// define a bit array with length=4 and false as default value for each bit.
var bits = new BitArray(4, false);
bits.Not(); // negate --> all 4 bits are now true.
// your example:
bits = new BitArray(new bool[] { true, false, false, true });
// to inverst/negate it
bits.Not();
// convert to string:
string bitString = string.Empty;
foreach (var bit in bits)
{
bitString += (bool)bit ? "1" : "0";
}
Console.WriteLine(bitString);
// from string:
string longBitString = "01000101001001010100010010010";
var longIntArray = longBitString.ToCharArray().Select(p => p.Equals('0') ? false : true).ToArray();
var longBitArray = new BitArray(longIntArray);
I have a mouse move coordinate,
For example:
s = string.Format("{0:D4},{1:D4}", nx, ny);
the result s is "0337,0022"
the question is how to show only two digits in front only?
I would like to get:
s is "03,00"
Here is another example:
s = "0471,0306"
I want to be:
s = "04,03"
and when the coordinate is "-"
example
s = "-0471,0306"
I want to be:
s = "-04,03"
s =string.Format("{0},{1}",
string.Format("{0:D4}", nx).Substring(0,2),
string.Format("{0:D4}", ny).Substring(0,2));
Just split the string on the comma and then sub-string the first two characters of each portion, like this:
string result = String.Empty;
string s = String.Format("{0:D4},{1:D4}", nx, ny);
string[] values = s.Split(',');
int counter = 0;
foreach (string val in values)
{
StringBuilder sb = new StringBuilder();
int digitsCount = 0;
// Loop through each character in string and only keep digits or minus sign
foreach (char theChar in val)
{
if (theChar == '-')
{
sb.Append(theChar);
}
if (Char.IsDigit(theChar))
{
sb.Append(theChar);
digitsCount += 1;
}
if (digitsCount == 2)
{
break;
}
}
result += sb.ToString();
if (counter < values.Length - 1)
{
result += ",";
}
counter += 1;
}
Note: This will work for any amount of comma separated values you have in your s string.
Assuming that nx and ny are integers
s = nx.ToString("D4").Substring(0,2) // leftmost digits
+ ny.ToString("D4").Substring(0,2) // leftmost digits
"D4" ensure the size of the string that must be enought for substring boundaries
I'd do it this way:
Func<int, string> f = n => n.ToString("D4").Substring(0, 2);
var s = string.Format("{0},{1}", f(nx), f(ny));
Check the number before you use Substring.
var s1 = nx.ToString();
var s2 = ny.ToString();
// Checks if the number is long enough
string c1 = (s1.Count() > 2) ? s1.Substring(0, 2) : s1;
string c2 = (s2.Count() > 2) ? s2.Substring(0, 2) : s2;
Console.WriteLine("{0},{1}",c1,c2);
I have a file which contains lots of numbers which I want to reduce to construct a new file. First, I extract all the text using File.ReadAllText, then I split and extract numbers from each line that contains a number which are separated by commas or spaces. After scan, I replace all occurrences of each found number with the new reduced number but the problem is that this method is error prone since some numbers get replaced more than once
Here's the code I'm using:
List<float> oPaths = new List<float>();
List<float> nPaths = new List<float>();
var far = File.ReadAllText("paths.js");
foreach(var s in far.Split('\n'))
{
//if it starts with this that means there are some numbers
if (s.StartsWith("\t\tpath:"))
{
var paths = s.Substring(10).Split(new[]{',', ' '});
foreach(var n in paths)
{
float di;
if(float.TryParse(n, out di))
{
if(oPaths.Contains(di)) break;
oPaths.Add(di);
nPaths.Add(di * 3/4);
}
}
}
}
//second iteration to replace old numbers with new ones
var ns = far;
for (int i = 0; i < oPaths.Count; i++)
{
var od = oPaths[i].ToString();
var nd = nPaths[i].ToString();
ns = ns.Replace(od, nd);
}
File.WriteAllText("npaths.js", ns);
As you can see, the above method is redundant as it does not replace the strings on real time. Maybe my head is full, but I'm just lost on how to go about this. Any ideas?
Thanks.
I think a regex can help here
string text = File.ReadAllText(file);
string newtext = Regex.Replace(text, #"\b(([0-9]+)?\.)?[0-9]+\b", m =>
{
float f;
if (float.TryParse(m.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out f)) f *= 3.0f / 4;
return f.ToString();
});
File.WriteAllText(file, newtext);
Just after typing the question I realized the answer was to iterate character by character and replace accordingly. Here's the code I used to get this to work:
string nfar = "";
var far = File.ReadAllText("paths.js");
bool neg = false;
string ccc = "";
for(int i = 0; i < far.Length; i++)
{
char c = far[i];
if (Char.IsDigit(c) || c == '.')
{
ccc += c;
if (far[i + 1] == ' ' || far[i + 1] == ',')
{
ccc = neg ? "-" + ccc : ccc;
float di;
if (float.TryParse(ccc, out di))
{
nfar += (di*0.75f).ToString();
ccc = "";
neg = false;
}
}
}
else if (c == '-')
{
neg = true;
}
else
{
nfar += c;
}
}
File.WriteAllText("nfile.js", nfar);
Comments and/or optimization suggestions are welcome.
My ultimate goal here is to turn the following string into JSON, but I would settle for something that gets me one step closer by combining the fieldname with each of the values.
Sample Data:
Field1:abc;def;Field2:asd;fgh;
Using Regex.Replace(), I need it to at least look like this:
Field1:abc,Field1:def,Field2:asd,Field2:fgh
Ultimately, this result would be awesome if it can be done via Regex in a single call.
{"Field1":"abc","Field2":"asd"},{"Field1":"def","Field2":"fgh"}
I've tried many different variations of this pattern, but can't seem to get it right:
(?:(\w+):)*?(?:([^:;]+);)
Only one other example I could find that is doing something similar, but just enough differences that I can't quite put my finger on it.
Regex to repeat a capture across a CDL?
EDIT:
Here's my solution. I'm not going to post it as a "Solution" because I want to give credit to one that was posted by others. In the end, I took a piece from each of the posted solutions and came up with this one. Thanks to everyone who posted. I gave credit to the solution that compiled, executed fastest and had the most accurate results.
string hbi = "Field1:aaa;bbb;ccc;ddd;Field2:111;222;333;444;";
Regex re = new Regex(#"(\w+):(?:([^:;]+);)+");
MatchCollection matches = re.Matches(hbi);
SortedDictionary<string, string> dict = new SortedDictionary<string, string>();
for (int x = 0; x < matches.Count; x++)
{
Match match = matches[x];
string property = match.Groups[1].Value;
for (int i = 0; i < match.Groups[2].Captures.Count; i++)
{
string key = i.ToString() + x.ToString();
dict.Add(key, string.Format("\"{0}\":\"{1}\"", property, match.Groups[2].Captures[i].Value));
}
}
Console.WriteLine(string.Join(",", dict.Values));
Now you have two problems
I don't think regular expressions will be the best way to handle this. You should probably start by splitting on semicolons, then loop through the results looking for a value that starts with "Field1:" or "Field2:" and collect the results into a Dictionary.
Treat this as pseudo code because I have not compiled or tested it:
string[] data = input.Split(';');
dictionary<string, string> map = new dictionary<string, string>();
string currentKey = null;
foreach (string value in data)
{
// This part should change depending on how the fields are defined.
// If it's a fixed set you could have an array of fields to search,
// or you might need to use a regular expression.
if (value.IndexOf("Field1:") == 0 || value.IndexOf("Field2:"))
{
string currentKey = value.Substring(0, value.IndexOf(":"));
value = value.Substring(currentKey.Length+1);
}
map[currentKey] = value;
}
// convert map to json
I had an idea that it should be possible to do this in a shorter and more clear way. It ended up not being all that much shorter and you can question if it's more clear. At least it's another way to solve the problem.
var str = "Field1:abc;def;Field2:asd;fgh";
var rows = new List<Dictionary<string, string>>();
int index = 0;
string value;
string fieldname = "";
foreach (var s in str.Split(';'))
{
if (s.Contains(":"))
{
index = 0;
var tmp = s.Split(':');
fieldname = tmp[0];
value = tmp[1];
}
else
{
value = s;
index++;
}
if (rows.Count < (index + 1))
rows.Insert(index, new Dictionary<string, string>());
rows[index][fieldname] = value;
}
var arr = rows.Select(dict =>
String.Join("," , dict.Select(kv =>
String.Format("\"{0}\":\"{1}\"", kv.Key, kv.Value))))
.Select(r => "{" + r + "}");
var json = String.Join(",", arr );
Debug.WriteLine(json);
Outputs:
{"Field1":"abc","Field2":"asd"},{"Field1":"def","Field2":"fgh"}
I would go with RegEx as the simplest and most straightforward way to parse the strings, but I'm sorry, pal, I couldn't come up with a clever-enough replacement string to do this in one shot.
I hacked it out for fun through, and the monstrosity below accomplishes what you need, albeit hideously. :-/
Regex r = new Regex(#"(?<FieldName>\w+:)*(?:(?<Value>(?:[^:;]+);)+)");
var matches = r.Matches("Field1:abc;def;Field2:asd;fgh;moo;"); // Modified to test "uneven" data as well.
var tuples = new[] { new { FieldName = "", Value = "", Index = 0 } }.ToList(); tuples.Clear();
foreach (Match match in matches)
{
var matchGroups = match.Groups;
var fieldName = matchGroups[1].Captures[0].Value;
int index = 0;
foreach (Capture cap in matchGroups[2].Captures)
{
var tuple = new { FieldName = fieldName, Value = cap.Value, Index = index };
tuples.Add(tuple);
index++;
}
}
var maxIndex = tuples.Max(tup => tup.Index);
var jsonItemList = new List<string>();
for (int a = 0; a < maxIndex+1; a++)
{
var jsonBuilder = new StringBuilder();
jsonBuilder.Append("{");
foreach (var tuple in tuples.Where(tup => tup.Index == a))
{
jsonBuilder.Append(string.Format("\"{0}\":\"{1}\",", tuple.FieldName, tuple.Value));
}
jsonBuilder.Remove(jsonBuilder.Length - 1, 1); // trim last comma.
jsonBuilder.Append("}");
jsonItemList.Add(jsonBuilder.ToString());
}
foreach (var item in jsonItemList)
{
// Write your items to your document stream.
}