C# implement function about way synchronization - c#

I did this:
businessCRM.Description =
string.Format("{0}\n" +
"Name: {1}\n" +
"Street: {2}\n " +
"Number: {3}\n" +
"floor: {4}\n" +
business.Name,
business.Address.Street,
business.Address.Number,
business.Address.floor,
);
And I would like to do the same but inversely in other method something like this to do way synchronization (round trip):
business.Address.Street = businessCRM.Description;
business.Address.Number = businessCRM.Description;
business.Address.floor = businessCRM.Description;
But I don't know if this is completely correct, any idea?

The real solution would be to put an additional Address member in your businessCRM class, so you can store the data and the description and don't need to extract the data from the description later. Parsing it will only lead to problems.

You can pull it apart into a dictionary using something like the below; note I had to special-case the first line (has no prefix), and it won't work well if your data includes newlines internally in the values:
var parts = new Dictionary<string, string>(
StringComparer.InvariantCultureIgnoreCase);
string firstLine;
using(var reader = new StringReader(description))
{
string line;
firstLine = reader.ReadLine();
var splitBy = new[] { ':' };
while ((line = reader.ReadLine()) != null)
{
var pair = line.Split(splitBy, 2, StringSplitOptions.None);
if (pair.Length == 2) parts[pair[0].Trim()] = pair[1].Trim();
}
}
string tmp;
string name, street, number, floor; // in your case, you could assign to
// the properties directly
name = parts.TryGetValue("Name", out tmp) ? tmp : "";
street = parts.TryGetValue("Street", out tmp) ? tmp : "";
number = parts.TryGetValue("Number", out tmp) ? tmp : "";
floor = parts.TryGetValue("floor", out tmp) ? tmp : "";

First of all see #Marc Gravell comment under question - it would be much easer to handle "deserialization".
If not, here is solution.
my code may be slow - you need to cache reflection info (like property info collection)
there is no error handling and checking (ex. for nullity - do it manually ;))
Code assumes that input is always proper input (f.e. new line char inside value), format etc.
Code:
var addressType = business.Address.GetType();
foreach (var line in businessCRM.Description
.Split(new[] { "\n", Environment.NewLine },
StringSplitOptions.RemoveEmptyEntries))
{
var propSelectorIndex = line.IndexOf(":");
if (propSelectorIndex == -1) continue;
var propName = line.Subtring(0, propSelectorIndex);
var propInfo = addressType.GetProperties(BindigsFlag.Public
| BindigsFlag.Instance)
.FirstOrDefault(prop => prop.Name == propName);
if (propInfo == null) throw new InvalidOperationException();
var newPropValue = line.Substring(propSelectorIndex + 2);
// + 2 to omit : char and additional space
propInfo.SetValue(business.Address, newPropValue, null);
}

Related

Can't properly rebuild a string with Replacement values from Dictionary

I am trying to build a file using a template. I am processing the file in a while loop line by line. The first section of the file, first 35 lines are header information. The infromation is surrounded by # signs. Take this string for example:
Field InspectionStationID 3 {"PVA TePla #WSM#", "sw#data.tool_context.TOOL_SOFTWARE_VERSION#", "#data.context.TOOL_ENTITY#"}
The expected output should be:
Field InspectionStationID 3 {"PVA TePla", "sw0.2.002", "WSM102"}
This header section uses a different mapping than the rest of the file so I wanted to parse the file line by line from top to bottom and use a different logic for each section so that I don't waste time parsing the entire file at once multiple times for different sections.
The logic uses two dictionaries populated from an xml file. Because the file has mutliple tables, I combined them in the two dictionaries like so:
var headerCdataIndexKeyVals = Dictionary<string, int>(){
{"data.tool_context.TOOL_SOFTWARE_VERSION", 1},
{"data.context.TOOL_ENTITY",0}
};
var headerCdataArrayKeyVals = new Dictionary<string, List<string>>();
var tool_contextCdataList = new list <string>{"HM654", "sw0.2.002"};
var contextCdataList = new List<string>{"WSM102"}
headerCdataArrayKeyVals.add("tool_context", tool_contextCdataList);
headerCdataArrayKeyVals.add("context", contextCdataList);
To help me map the values to their respective positions in the string in one go and without having to loop through multiple dictionaries.
I am using the following logic:
public static string FindSubsInDelimetersAndReturn(string str, char openDelimiter, char closeDelimiter, HeaderMapperData mapperData )
{
string newString = string.Empty;
// Stores the indices of
Stack <int> dels = new Stack <int>();
for (int i = 0; i < str.Length; i++)
{
var let = str[i];
// If opening delimeter
// is encountered
if (str[i] == openDelimiter && dels.Count == 0)
{
dels.Push(i);
}
// If closing delimeter
// is encountered
else if (str[i] == closeDelimiter && dels.Count > 0)
{
// Extract the position
// of opening delimeter
int pos = dels.Peek();
dels.Pop();
// Length of substring
int len = i - 1 - pos;
// Extract the substring
string headerSubstring = str.Substring(pos + 1, len);
bool hasKey = mapperData.HeaderCdataIndexKeyVals.TryGetValue(headerSubstring.ToUpper(), out int headerCdataIndex);
string[] headerSubstringSplit = headerSubstring.Split('.');
string headerCDataVal = string.Empty;
if (hasKey)
{
if (headerSubstring.Contains("CONTAINER.CONTEXT", StringComparison.OrdinalIgnoreCase))
{
headerCDataVal = mapperData.HeaderCdataArrayKeyVals[headerSubstringSplit[1].ToUpper() + '.' + headerSubstringSplit[2].ToUpper()][headerCdataIndex];
//mapperData.HeaderCdataArrayKeyVals[]
}
else
{
headerCDataVal = mapperData.HeaderCdataArrayKeyVals[headerSubstringSplit[1].ToUpper()][headerCdataIndex];
}
string strToReplace = openDelimiter + headerSubstring + closeDelimiter;
string sub = str.Remove(i + 1);
sub = sub.Replace(strToReplace, headerCDataVal);
newString += sub;
}
else if (headerSubstring == "WSM" && closeDelimiter == '#')
{
string sub = str.Remove(len + 1);
newString += sub.Replace(openDelimiter + headerSubstring + closeDelimiter, "");
}
else
{
newString += let;
}
}
}
return newString;
}
}
But my output turns out to be:
"\tFie\tField InspectionStationID 3 {\"PVA TePla#WSM#\", \"sw0.2.002\tField InspectionStationID 3 {\"PVA TePla#WSM#\", \"sw#data.tool_context.TOOL_SOFTWARE_VERSION#\", \"WSM102"
Can someone help understand why this is happening and how I can go about correcting it so I get the output:
Field InspectionStationID 3 {"PVA TePla", "sw0.2.002", "WSM102"}
Am i even trying to solve this the right way or is there a better cleaner way to do it? Btw if the key is not in the dictionary I replace it with empty string

Dynamic library (System.Linq.Dynamic) SQL LIKE Operator

Someone has posted a similar question here How Dynamic library (System.Linq.Dynamic) support LIKE Operator?
But it's not exactly what I want. The Contains operator mentioned in that post only do this in SQL "%SOMETHING%". But the LIKE operator in SQL can do this "SOME%THING". Is there a similar operator for Dynamic LINQ? If not, is there a solution for this? Maybe with Regex? Also is there a single character wildcard? E.g. "SOM$THING" returns "SOMETHING" or "SOM3THING"
Edit: I am developing a WPF application which should read log files in XML format. Each element contains 34 fields. So instead of writing a very long WHERE, I have used System.Reflection.PropertyInfo to iterate each field to write the query and then use System.Linq.Dynamic to execute it.
Edit2: I have edited the code so it's more readable for the viewers.
Here is some code:
Example 1:
prop.Name = "FirstName", paramterString = "Ke%in", returns "Kevin",
"Kelvin"...
Example 2:
prop.Name = "FirstName", paramterString = "Ke$in", returns "Kevin",
"Kelin"...
var query = "";
StringBuilder sb = new StringBuilder();
foreach (var prop in stringProps)
{
sb.Append($"({prop.Name} != null And {prop.Name}.Contains({parameterString})");
}
query = sb.ToString().Substring(0, sb.Length - 4);
filteredData = filteredData.Where(query);
One of the requirement is to implement a SQL LIKE operator, so the users can use something like this to get the result they want: FirstName LIKE 'SOME%THING'
Since there is no LIKE operator in Dynamic Linq library, I have created it my own version. The code is not pretty, but it does work. Hopefully someone can optimize it or suggest a better way to do this.
public string ParseWildcardInParameterString(string parameter, string propertyName)
{
string stringWithWildcard = parameter;
if (parameter.Contains("%") || parameter.Contains("$"))
{
stringWithWildcard = parameter;
string[] substrings = parameter.Split(new Char[] { '%', '$' }, StringSplitOptions.RemoveEmptyEntries);
string[] wildcards = ParseWildcards(parameter);
if (substrings.Any())
{
StringBuilder sb = new StringBuilder();
int substringsCount = substrings.Length;
for (int i = 0; i < substringsCount; i++)
{
if (!substrings[i].EndsWith("\\"))
{
int index = parameter.IndexOf(substrings[i]);
if (i < substringsCount - 1)
{
index = parameter.IndexOf(substrings[i + 1], index + 1);
if (index > -1)
{
string secondPart = wildcards[i].Equals("%") ?
$"{propertyName}.IndexOf(\"{substrings[i + 1]}\", {propertyName}.IndexOf(\"{substrings[i]}\") + \"{substrings[i]}\".Length) > -1" :
$"{propertyName}.IndexOf(\"{substrings[i + 1]}\", {propertyName}.IndexOf(\"{substrings[i]}\") + \"{substrings[i]}\".Length + 1) == {propertyName}.IndexOf(\"{substrings[i]}\") + \"{substrings[i]}\".Length + 1";
sb.Append($"({propertyName}.IndexOf(\"{substrings[i]}\") > -1 And {secondPart}) And ");
}
}
}
}
stringWithWildcard = sb.Remove(sb.Length - 5, 5).Append(") Or ").ToString();
}
}
return stringWithWildcard;
}
private string[] ParseWildcards(string parameter)
{
IList<string> wildcards = new List<string>();
foreach (var chararcter in parameter.ToCharArray())
{
if (chararcter.Equals('%') || chararcter.Equals('$'))
{
wildcards.Add(chararcter.ToString());
}
}
return wildcards.ToArray();
}
Can you try if the Like functionality in System.Linq.Dynamic.Core does work for you?
Code example would be:
var dynamicFunctionsLike1 = context.Cars.Where(config, "DynamicFunctions.Like(Brand, \"%a%\")");
For full example, see ConsoleAppEF2.1.1/Program.cs

Differentiating between different values with a Line.Contains()

Something I was curious about, I'm coding a utility for a old game that I play and this allows for custom NPC's. Long story short, I'm coding a reader for these custom NPC files. I've gotten most of the reading down with a line.contains() method (all code will be shown later) but there's a problem. The file can contain either just "height" or "gfxheight" which both do different things. Using line.contains("width") will make it output both width and gfxwidth twice. I don't really know any good way to explain it so here's the file:
width=32
height=32
gfxwidth=64
gfxheight=32
nofireball=1
noiceball=1
noyoshi=1
grabside=0
The Console output when I read it in and do what I need to split the lines and such:
And here's the code I use for height and gfxheight (of course there are others but these are the only problems I have when reading):
if (line.Contains("height"))
{
var split = line.Split(new char[] { '=' }, 2);
decimal dc;
//var val = int.Parse(split.ToString());
Console.WriteLine(split[0].ToString() + " is equal to " + split[1].ToString());
npcHeight.Value = Decimal.Parse(split[1].ToString());
npcHeight.Enabled = true;
npcHCb.Checked = true;
}
if (line.Contains("gfxheight"))
{
var split = line.Split(new char[] { '=' }, 2);
//var val = int.Parse(split.ToString());
Console.WriteLine(split[0].ToString() + " is equal to " + split[1].ToString());
}
Of course there's also the code for width and gfxwidth and the other various codes but I'm not going to bother posting those because I can apply what I get for the height to those.
So what would I have to do to differentiate between them? Suggestions?
Thanks in advanced,
Mike
Read the file into a string array, then parse it into a dictionary.
var file = File.ReadAllLines(yourFile);
var config = (from line in file
let s = line.Split('=')
select new { Key = s[0], Value = s[1] })
.ToDictionary(x => x.Key, x => x.Value);
Now you can access anything you want by referencing the key:
var gfxHeight = config["gfxheight"]; // gfxHeight is a string containing "32"
If you know the value after the = is always a number, you could parse it:
var config = (from line in file
let s = line.Split('=')
select new { Key = s[0], Value = int.Parse(s[1]) })
.ToDictionary(x => x.Key, x => x.Value);
var gfxHeight = config["gfxheight"]; // gfxHeight is an int containing 32
Instead of trying to figure out what each line is before splitting it, try splitting it first. This parsing approach leverages the format of the file and has a much-reduced dependency on its data:
foreach (var line in lines) {
var data = line.Split('=', 2);
if (data.Length != 2) {
continue;
}
var attrib = data[0];
var value = data[1];
Console.WriteLine(attrib + " is equal to " + value);
switch (attrib) {
case "height":
// ...
break;
case "gfxheight":
// ...
break;
}
}
I figured out a solution actually! Be warned: I haven't refreshed the page yet to see any proposed answers.
if (line.Contains("width"))
{
if (line.Contains("gfx"))
{
var split = line.Split(new char[] { '=' }, 2);
//var val = int.Parse(split.ToString());
Console.WriteLine(split[0].ToString() + " is equal to " + split[1].ToString());
npcWidth.Value = Decimal.Parse(split[1].ToString());
npcWidth.Enabled = true;
npcWCb.Checked = true;
}
else
{
var split = line.Split(new char[] { '=' }, 2);
Console.WriteLine(split[0].ToString() + " is equal to " + split[1].ToString());
pNpcWidth.Value = Decimal.Parse(split[1].ToString());
pNpcWidth.Enabled = true;
pNpcWidthCb.Checked = true;
}
}
Basically that ^
What it does is checks if the line is width with that line.Contains method. And if it does, it then checks to see if it contains gfx in it (as in gfxheight, gfxwidth, etc) and if it does, then that's the gfxheight or gfxwidth value. If not, it's the regular height/width.

Fast multi-string replacement

I need to perform multi string replacement. I have a string where several parts need to be changed according to substitution map.
All replacement must be done in one action - it means if "a" should be replaced with "b" and also "b" must be replaced with "c" and input string is "abc", the result will be "bcc"
I have a sollution based on building regex and then replacing all matches. I wrote it some time ago, now I'm refactoring the code and not so satisfied with it. Is there better (faster, simplier) sollution?
This is what I have:
public static string Replace(string s, Dictionary<string, string> substitutions)
{
string pattern = "";
int i = 0;
foreach (string ch in substitutions.Keys)
{
if (i == 0)
pattern += "(" + Regex.Escape(ch) + ")";
else
pattern += "|(" + Regex.Escape(ch) + ")";
i++;
}
var r = new Regex(pattern);
var parts = r.Split(s);
string ret = "";
foreach (string part in parts)
{
if (part.Length == 1 && substitutions.ContainsKey(part[0].ToString()))
{
ret += substitutions[part[0].ToString()];
}
else
{
ret += part;
}
}
return ret;
}
And test case:
var test = "test aabbcc";
var output = Replace(test, new Dictionary<string, string>{{"a","b"},{"b","y"}});
Assert.That(output=="test bbyycc");
You can replace all this with
var r = new Regex(string.Join("|", substitutions.Keys.Select(k => "(" + k + ")")));
return r.Replace(s, m => substitutions[m.Value]);
The key things are making use of the string.Join method rather than implementing it yourself, and making use of this Regex.Replace overload and delegates to do the replacement.

mask query dynamic query string value

I am building a wrapper to process payments. We want to log requests, but don't want to store sensitive credit card data. A query string will be passed similar to what is below
amount=100.00&expMonth=01&expYear=14&cardnumber=4111111111111111
I want to mask the first 12 digits of the credit card number with X values. However, the cardnumber key will not always be in the same spot.
My first leaning is to create a NameValueCollection and check for the key and do a string.format("XXXX-XXXX-XXXX-{0}", substring of the value
var qs = HttpUtility.ParseQueryString(request);
foreach (string key in qs)
{
if (key == "creditcard")
{
}
}
Can someone point me in the right direction?
I need to save the string in the same format with just the credit card number masked.
Seems like a sensible approach, but maybe using Get method on the NameValueCollection would be easier. Like so:
String maskedCardNumber = null;
var qs = HttpUtility.ParseQueryString(request);
var cardNumber = qs.Get("cardnumber");
if (cardNumber != null)
{
var substring = cardNumber.Substring(cardNumber.Length - Math.Min(4, cardNumber.Length));
maskedCardNumber = String.Format("XXXX-XXXX-XXXX-{0}", substring);
}
This works great, there may be a more elegant solution though.
var maskedRequest = "";
var qs = HttpUtility.ParseQueryString(request);
foreach (string item in qs.AllKeys)
{
if (item != "cardnumber")
{
maskedRequest = maskedRequest + item + "=" + qs.Get(item) + "&";
}
else
{
maskedRequest = maskedRequest + item + "=" + string.Format("XXXX-XXXX-XXXX-{0}", qs.Get(item).Substring(12, 4)) + "&";
}
}
maskedRequest = maskedRequest.Remove(maskedRequest.Length - 1)

Categories