I have 3 functions where the only difference in is the values I point out with comment
//-- point of difference
The majority of the function is the same across all three. The "DRY" factor is haunting my sleep :). I was wondering; can these could be merged easily and readably?
I have had situations like this before and I am hoping to learn something here.
private string RenderRequestType(string render, NameValueCollection nvp, string prefix, string regexWild, string suffix)
{
string regex = prefix + regexWild + suffix;
MatchCollection matches = Regex.Matches(render, regex);
foreach (Match match in matches)
{
foreach (Capture capture in match.Captures)
{
string name = capture.Value.Replace(prefix, "", StringComparison.CurrentCultureIgnoreCase).Replace(suffix, "", StringComparison.CurrentCultureIgnoreCase);
//-- point of difference
string value = nvp[name];
render = render.Replace(capture.Value, value);
}
}
return render;
}
private string RenderSessionType(string render, HttpContext httpContext, string prefix, string regexWild, string suffix)
{
string regex = prefix + regexWild + suffix;
MatchCollection matches = Regex.Matches(render, regex);
foreach (Match match in matches)
{
foreach (Capture capture in match.Captures)
{
string name = capture.Value.Replace(prefix, "", StringComparison.CurrentCultureIgnoreCase).Replace(suffix, "", StringComparison.CurrentCultureIgnoreCase);
//-- point of difference
object session = httpContext.Session[name];
string value = (session != null ? session.ToString() : "");
render = render.Replace(capture.Value, value);
}
}
return render;
}
private string RenderCookieType(string render, HttpContext httpContext, string prefix, string regexWild, string suffix)
{
string regex = prefix + regexWild + suffix;
MatchCollection matches = Regex.Matches(render, regex);
foreach (Match match in matches)
{
foreach (Capture capture in match.Captures)
{
string name = capture.Value.Replace(prefix, "", StringComparison.CurrentCultureIgnoreCase).Replace(suffix, "", StringComparison.CurrentCultureIgnoreCase);
//-- point of difference
HttpCookie cookie = httpContext.Request.Cookies[name];
string value = (cookie != null ? cookie.Value : "");
render = render.Replace(capture.Value, value);
}
}
return render;
}
You could modify the function to take a Func<string, string> to do the lookup:
private string RenderType(string render, Func<string, string> lookupFunc, string prefix, string regexWild, string suffix)
{
string regex = prefix + regexWild + suffix;
MatchCollection matches = Regex.Matches(render, regex);
foreach (Match match in matches)
{
foreach (Capture capture in match.Captures)
{
string name = capture.Value.Replace(prefix, "", StringComparison.CurrentCultureIgnoreCase).Replace(suffix, "", StringComparison.CurrentCultureIgnoreCase);
//-- point of difference
string value = lookupFunc(name);
render = render.Replace(capture.Value, value);
}
}
return render;
}
Then write your functions in terms of this one, e.g.:
private string RenderRequestType(string render, NameValueCollection nvp, string prefix, string regexWild, string suffix)
{
return RenderType(render, name => nvp[name], prefix, regexWild, suffix);
}
Pass in a Func<string, string> to get the value associated with a given name. In the first case that would just use nvp's indexer; in the second it would use the session. You could either use separate methods to create the delegates, or lambda expressions. (I'd definitely use a lambda expression for the first one; I might use a separate method for the second.)
In my opinion the best solution is to use lambda expressions.
Instead of second argument to your functions, put there lambda which will transform string name to string value.
Related
I have the following sensitive data:
"Password":"123","RootPassword":"123qwe","PassPhrase":"phrase"
I would like to get the following safe data:
"Password":"***","RootPassword":"***","PassPhrase":"***"
It's my code:
internal class Program
{
private static void Main(string[] args)
{
var data = "\"Password\":\"123\",\"RootPassword\":\"123qwe\",\"PassPhrase\":\"phrase\"";
var safe1 = PasswordReplacer.Replace1(data);
var safe2 = PasswordReplacer.Replace2(data);
}
}
public static class PasswordReplacer
{
private const string RegExpReplacement = "$1***$2";
private const string Template = "(\"{0}\":\").*?(\")";
private static readonly string[] PasswordLiterals =
{
"password",
"RootPassword",
"PassPhrase"
};
public static string Replace1(string sensitiveInfo)
{
foreach (var literal in PasswordLiterals)
{
var pattern = string.Format(Template, literal);
var regex = new Regex(pattern, RegexOptions.IgnoreCase);
sensitiveInfo = regex.Replace(sensitiveInfo, RegExpReplacement);
}
return sensitiveInfo;
}
public static string Replace2(string sensitiveInfo)
{
var multiplePattern = "(\"password\":\")|(\"RootPassword\":\")|(\"PassPhrase\":\").*?(\")"; //?
var regex = new Regex(string.Format(Template, multiplePattern), RegexOptions.IgnoreCase);
return regex.Replace(sensitiveInfo, RegExpReplacement);
}
}
Replace1 method works as expected. But it does it one by one. My question is is it possble to do the same but using single regex match ? If so I need help with Replace2.
The Replace2 can look like
public static string Replace2(string sensitiveInfo)
{
var multiplePattern = $"(\"(?:{string.Join("|", PasswordLiterals)})\":\")[^\"]*(\")";
return Regex.Replace(sensitiveInfo, multiplePattern, RegExpReplacement, RegexOptions.IgnoreCase);
}
See the C# demo.
The multiplePattern will hold a pattern like ("(?:password|RootPassword|PassPhrase)":")[^"]*("), see the regex demo. Quick details:
("(?:password|RootPassword|PassPhrase)":") - Group 1 ($1): a " char followed with either password, RootPassword or PassPhrase and then a ":" substring
[^"]* - any zero or more chars other than " as many as possible
(") - Group 2 ($2): a " char.
New to C#, and having trouble finding ways to compare data so far collected from conf file, and outputting it to either text or CSV.
I so far have the skeleton of data extraction code from said conf file, however as I'm new to C# and coding overall, I'm having trouble understanding how to reference that data or compare it.
So far have tried File.WriteAllLiness and defining a variable, but not sure which element to parse, or at which point in the code I should introduce it.
Nothing to hide really, so here's the full output so far:
namespace CompareVal
{
class Program
{
static void Main(string[] args)
{
var lines = File.ReadAllLines(#"D:\*\*\Cleanup\Script Project\Test-Raw-Conf.txt");
var ipAddresses = GetIPAddresses(lines);
var routes = GetRoutes(lines);
var ipRules = GetIPRules(lines);
Console.WriteLine ();
}
static Dictionary<string, string[]> GetIPAddresses(string[] lines)
{
var result = new Dictionary<string, string[]>();
foreach (var line in lines)
{
if (!line.StartsWith("add IPAddress"))
{
continue;
}
Match match;
if (line.Contains("Address=\""))
{
match = Regex.Match(line, "add IPAddress (.*?) Address=\"(.*?)\"");
}
else
{
match = Regex.Match(line, "add IPAddress (.*?) Address=(.*?)$");
}
var name = match.Groups[1].Value;
var value = match.Groups[2].Value;
var items = value.Replace(" ", "").Split(',');
result.Add(name, items);
}
return result;
}
static List<Route> GetRoutes(string[] lines)
{
var result = new List<Route>();
string currentRoutingTable = null;
foreach (var line in lines)
{
if (line.StartsWith("cc RoutingTable"))
{
currentRoutingTable = line.Split(' ')[2].Trim();
}
if (line == "cc .." && currentRoutingTable != null)
{
currentRoutingTable = null;
}
if (line.StartsWith(" add Route"))
{
var #interface = Regex.Match(line, "Interface=(.*?) ").Groups[1].Value;
var gateway = Regex.Match(line, "Gateway=(.*?) ").Groups[1].Value;
var network = Regex.Match(line, "Network=(.*?) ").Groups[1].Value;
result.Add(new Route
{
RoutingTable = currentRoutingTable,
Interface = #interface,
Gateway = gateway,
Network = network
});
}
}
return result;
}
static List<IPRule> GetIPRules(string[] lines)
{
var result = new List<IPRule>();
string currentIPRuleSet = null;
foreach (var line in lines)
{
if (line.StartsWith("cc IPRuleSet"))
{
currentIPRuleSet = line.Split(' ')[2].Trim();
}
if (line == "cc .." && currentIPRuleSet != null)
{
currentIPRuleSet = null;
}
if (line.StartsWith(" add IPRule"))
{
var rule = new IPRule
{
IPRuleSet = currentIPRuleSet,
SourceInterface = GetProperty(line, "SourceInterface"),
DestinationInterface = GetProperty(line, "DestinationInterface"),
};
if (line.Contains("SourceNetwork=\""))
{
rule.SourceNetwork = GetQuotedProperty(line, "SourceNetwork").Replace(" ", "").Split(',');
}
else
{
rule.SourceNetwork = GetProperty(line, "SourceNetwork").Replace(" ", "").Split(',');
}
if (line.Contains("DestinationNetwork=\""))
{
rule.DestinationNetwork = GetQuotedProperty(line, "DestinationNetwork").Replace(" ", "").Split(',');
}
else
{
rule.DestinationNetwork = GetProperty(line, "DestinationNetwork").Replace(" ", "").Split(',');
}
result.Add(rule);
}
}
return result;
}
static string GetProperty(string input, string propertyName)
{
return Regex.Match(input, string.Format("{0}=(.*?) ", propertyName)).Groups[1].Value;
}
static string GetQuotedProperty(string input, string propertyName)
{
return Regex.Match(input, string.Format("{0}=\"(.*?)\" ", propertyName)).Groups[1].Value;
}
class Route
{
public string RoutingTable;
public string Interface;
public string Gateway;
public string Network;
}
class IPRule
{
public string IPRuleSet;
public string SourceInterface;
public string DestinationInterface;
public string[] SourceNetwork;
public string[] DestinationNetwork;
}
}
}
I'm hoping to compare values gathered by IPRule, Route and IPAddress classes, and have a method of outputting each associated value in a list. Each IPAddress is contains a unique string name, but can use any numerical IP address. The idea is to determine when the same IP has been used multiple times, regardless of IPAddress string name, and then compare this to routes, and flag when they are used in IPRules.
For reference, here are some samples of source data:
For IPAddresses, they can be formed in 1 of 2 ways - as a direct IP definition, or as a reference to another IPAddress object (or multi-reference):
add IPAddress Test Address=192.168.1.0/24
IPAddress referencing multiple other IPAddresses:
add IPAddress TestGroup Address="Test1, Test2, Test3"
For routes:
add Route Interface=if5 Gateway=if5_gw Network=Test ProxyARPInterfaces=""
And for IPRules:
add IPRule SourceInterface=if5 DestinationInterface=if3 SourceNetwork=Test1 DestinationNetwork=Test2 Service=dns-all Action=Allow
The above definitions will always follow the same pattern, so the data extraction code has been constructed to expect prefixes to each element, and sort them into their own dictionary or list.
I am trying to remove empty url type parameters from a string using C#. My code sample is here.
public static string test ()
{
string parameters = "one=aa&two=&three=aaa&four=";
string pattern = "&[a-zA-Z][a-zA-Z]*=&";
string replacement = "";
Regex rgx = new Regex(pattern);
string result = rgx.Replace(parameters, replacement);
return parameters;
}
public static void Main(string[] args)
{
Console.WriteLine(test());
}
I tried the code in rextester
output: one=aa&two=&three=aaa&four=
expected output: one=aa&three=aaa
You absolutely do not need to roll your own Regex for this, try using HttpUtility.ParseQueryString():
public static string RemoveEmptyUrlParameters(string input)
{
var results = HttpUtility.ParseQueryString(input);
Dictionary<string, string> nonEmpty = new Dictionary<string, string>();
foreach(var k in results.AllKeys)
{
if(!string.IsNullOrWhiteSpace(results[k]))
{
nonEmpty.Add(k, results[k]);
}
}
return string.Join("&", nonEmpty.Select(kvp => $"{kvp.Key}={kvp.Value}"));
}
Fiddle here
Regex:
(?:^|&)[a-zA-Z]+=(?=&|$)
This matches start of string or an ampersand ((?:^|&)) followed by at least one (english) letter ([a-zA-Z]+), an equal sign (=) and then nothing, made sure by the positive look-ahead ((?=&|$)) which matches end of string or a new parameter (started by &).
Code:
public static string test ()
{
string parameters = "one=aa&two=&three=aaa&four=";
string pattern = "(?:^|&)[a-zA-Z]+=(?=&|$)";
string replacement = "";
Regex rgx = new Regex(pattern);
string result = rgx.Replace(parameters, replacement);
return result;
}
public static void Main(string[] args)
{
Console.WriteLine(test());
}
Note that this also returns the correct variable (as pointed out by Joel Anderson)
See it live here at ideone.
The results of the Regex replace is not returned by the function. The function returns the variable "parameters", which is never updated or changed.
string parameters = "one=aa&two=&three=aaa&four=";
...
string result = rgx.Replace(parameters, replacement);
return parameters;
....
Perhaps you meant
return results;
I'm trying to parse messages transmited over TCP for my own network protocol using regex without success.
My commands start with ! followed by COMMAND_NAME and a list of arguments in the format or ARGUMENT_NAME=ARGUMENT_VALUE enclosed in <>
for example:
!LOGIN?<USERNAME='user'><PASSWORD='password'>;
my code :
public class CommandParser
{
private Dictionary<string, string> arguments = new Dictionary<string, string>();
public CommandParser(string input)
{
Match commandMatch = Regex.Match(input, #"\!([^)]*)\&");
if (commandMatch.Success)
{
CommandName = commandMatch.Groups[1].Value;
}
// Here we call Regex.Match.
MatchCollection matches = Regex.Matches(input,"(?<!\\S)<([a-z0-9]+)=(\'[a-z0-9]+\')>(?!\\S)",
RegexOptions.IgnoreCase);
//
foreach (Match argumentMatch in matches)
{
arguments.Add(
argumentMatch.Groups[1].Value,
argumentMatch.Groups[2].Value);
}
}
public string CommandName { get; set; }
public Dictionary<string, string> Arguments
{
get { return arguments; }
}
/// <summary>
///
/// </summary>
public int ArgumentCount
{
get { return arguments.Count; }
}
}
To find the command name, finding the first word after the "!" should be enough:
/\!\w*/g
To match the key/value pairs in groups, you could try something like:
(\w+)='([a-zA-Z_]*)'
An example of the above regex can be found here.
You do not need regex here and avoid them unless that's a last option left. You could do this with simple C# logic.
string input = "!LOGIN?<USERNAME='user'><PASSWORD='password'>";
string command = input.Substring(1, input.IndexOf('?') - 1);
Console.WriteLine($"command: {command}");
var parameters = input
.Replace($"!{command}?", string.Empty)
.Replace("<", "")
.Split(">".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
string[] kvpair;
foreach(var kv in parameters) {
kvpair = kv.Split('=');
Console.WriteLine($"pname: {kvpair[0]}, pvalue: {kvpair[1]}");
}
Output:
command: LOGIN
pname: USERNAME, pvalue: 'user'
pname: PASSWORD, pvalue: 'password'
I'm trying to replace text. I'm using a dictionary for the task.
public static string cleanString(this String str) {
Dictionary<string, string> dict = new Dictionary<string,string>();
dict.Add("JR", "Junior");
dict.Add("SR", "Senior");
foreach (KeyValuePair<string,string> d in dict) {
if (str.BlindContains(p.Key)) {
str = str.BlindReplace(str, p.Value);
}
}
return str;
}
BlindContains and BlindReplace just ignore the case of the replacement (and BC ensures the string is not part of another word):
public static bool BlindContains(this String str, string toCheck)
{
if (Regex.IsMatch(str, #"\b" + toCheck + #"\b", RegexOptions.IgnoreCase))
return str.IndexOf(toCheck, StringComparison.OrdinalIgnoreCase) >= 0;
return false;
}
public static string BlindReplace(this String str, string oldStr, string newStr)
{
return Regex.Replace(str, oldStr, newStr, RegexOptions.IgnoreCase);
}
The problem
If I call my method on a a string, the following occurs:
string mystring = "The jr. is less than the sr."
mystring.cleanString()
returns "Junior"
However, when I print
Console.WriteLine(Regex.Replace(mystring, "jr.", "junior", Regex.IgnoreCase));
I get the output: "The junior is less than the sr."
Why does the loop compromise the task?
You should be passing the key in your dictionary (Which contains the text to search for), rather than the actual string you are searching in.
It should be:
str = str.BlindReplace(p.Key, p.Value);
As opposed to:
str = str.BlindReplace(str, p.Value);
You are currently replacing your string with the value "Junior" because you specified your string as the text to search for. (Which will make it replace the entire string instead of just the keyword)
In cleanString implementation, I think you made an error in your call to BlindReplace. Instead of:
str = str.BlindReplace(str, p.Value);
I believe you should have called:
str = str.BlindReplace(d.Key, d.Value);