Parsing multi-line string in C# - c#

I have a string that looks like this:
TYPE Email Forwarding
SIGNATURE mysig.html
COMPANY Smith Incorp
CLIENT NAME James Henries
... heaps of others ....
I need to get the values of Type, Signature, Company and Client Name. There are others but once I can find a soution on how to do these, I can do the rest. I have tried to split and trim the string but then it splits fields like CLIENT NAME or on values like Email Forwarding.

I would put all of the "key" values into a collection, and then parse the string into another collection and then compare the values of the collections.
Here is a rough outline of how you could get the values:
static void Main(string[] args)
{
//Assuming that you know all of the keys before hand
List<string> keys = new List<string>() { "TYPE", "SIGNATURE", "COMPANY", "CLIENT NAME" };
//Not sure of the origin of your string to parse. You would have to change
//this to read a file or query the DB or whatever
string multilineString =
#"TYPE Email Forwarding
SIGNATURE mysig.html
COMPANY Smith Incorp
CLIENT NAME James Henries";
//Split the string by newlines.
var lines = multilineString.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
//Iterate over keys because you probably have less keys than data in the event of duplicates
foreach (var key in keys)
{
//Reduce list of lines to check based on ones that start with a given key
var filteredLines = lines.Where(l => l.Trim().StartsWith(key)).ToList();
foreach (var line in filteredLines)
{
Console.WriteLine(line.Trim().Remove(0, key.Length + 1));
}
}
Console.ReadLine();
}

That will do your job.
If it is multiple lines then you can loop through each line and call KeyValue extension method as given below:
public static class Program
{
public static void Main()
{
var value = "TYPE Email Forwarding".KeyValue();
var value1 = "CLIENT NAME James Henries".KeyValue();
}
public static KeyValuePair<string, string> KeyValue(this string rawData)
{
var splitValue = rawData.Split(new[] { ' ' }, System.StringSplitOptions.RemoveEmptyEntries);
KeyValuePair<string, string> returnValue;
var key = string.Empty;
var value = string.Empty;
foreach (var item in splitValue)
{
if (item.ToUpper() == item)
{
if (string.IsNullOrWhiteSpace(key))
{
key += item;
}
else
{
key += " " + item;
}
}
else
{
if (string.IsNullOrWhiteSpace(value))
{
value += item;
}
else
{
value += " " + item;
}
}
}
returnValue = new KeyValuePair<string, string>(key, value);
return returnValue;
}
}
Please note that this logic will work only when keys are all upper and the values are not all upper case. Otherwise, there is no way to identify which one is key (without having a manual track on keys) and which one is not.

Related

String interpolation: How do I make this function work with any type

This is a function to work with lists in string interpolation. It takes a List and an inner Func, and it appends the string result of the inner Func called for each member of the list, with a separator.
So the following builds a valid start of an Insert statement...
static void Main(string[] args)
{
var tableName = "customers";
var cols = new List<dynamic>
{
new { Name = "surname"},
new { Name = "firstname"},
new { Name = "dateOfBirth"}
};
Func<List<dynamic>, Func<dynamic, string>, string, string> ForEach = (list, func, separator) =>
{
var bldr = new StringBuilder();
var first = true;
foreach (var obj in list)
{
if (!first)
bldr.Append(separator);
first = false;
bldr.Append(func(obj));
}
return bldr.ToString();
};
var InsertStatement = $"Insert into { tableName } ( {ForEach(cols, col => col.Name, ", ")} )";
Console.WriteLine(InsertStatement);
Console.ReadLine();
}
Outputs...
Insert into customers ( surname, firstname, dateOfBirth )
It works for dynamic. How do I make it work for any type? The outer Func shouldn't care about the Type in the list, it just passes it through to the inner Func.
The .NET framework already gives you a generic function to achieve what you are trying to do String.Join and you can combine it with a LINQ Select statement, which will allow you to use a lambda on a generic type to select the property that you want to print. You can view the source code of these methods if you are interested as they are open source.
using System;
using System.Collections.Generic;
using System.Linq;
public class MyType
{
public string Name { get; set; }
}
public class Program
{
public static void Main()
{
var tableName = "customers";
var cols = new List<MyType>
{
new MyType { Name = "surname"},
new MyType { Name = "firstname"},
new MyType { Name = "dateOfBirth"}
};
var InsertStatement = $"Insert into { tableName } ( {String.Join(", ", cols.Select(col => col.Name))} )";
Console.WriteLine(InsertStatement);
}
}
Replace dynamic with object, or TValue with a type constraint stipulating it must be a class (where TValue : class), and call obj.ToString() instead of just obj
However, this doesn't guarantee it would "work with any type" - for that you need to know that those types all follow a contract to output the desired column name as their string representation. To get more specificity, require that your accepted types must implement some interface eg IColumnName and put that interface into the type constraint instead
You can create the text easily like this:
var query = $"INSERT INTO {tableName}({string.Join(",", cols.Select(x=>x.Name))})";
However, if for learning purpose you are going to handle the case using a generic method, you can create a generic function like the following and then easily use a for loop and strip additional separator using TrimEnd, or as a better option, like String.Join implementation of .NET Framework get enumerator like this:
string Join<TItem>(
IEnumerable<TItem> items, Func<TItem, string> itemTextSelecor, string separator)
{
var en = items.GetEnumerator();
if (!en.MoveNext())
return String.Empty;
var builder = new StringBuilder();
if (en.Current != null)
builder.Append(itemTextSelecor(en.Current));
while (en.MoveNext())
{
builder.Append(separator);
if (en.Current != null)
builder.Append(itemTextSelecor(en.Current));
}
return builder.ToString();
}
And use it this way:
var tableName = "customers";
var cols = new[]
{
new { Name = "surname"},
new { Name = "firstname"},
new { Name = "dateOfBirth"}
};
var InsertStatement = $"INSERT INTO {tableName} ({Join(cols, col => col.Name, ", ")})"
+ $"VALUES({Join(cols, col => $"#{col.Name}", ", ")})";

Need help comparing extracted data and outputting to file

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.

String replace with recursive dictionary values

I have config for all the root api urls in app.config file that is loaded into dictionary of key and value pair.
Dictionary<string, string> variables = new Dictionary<string, string>()
{
{ "clientRefUrl", "[clientBaseUrl]/RealtimeReferenceData"},
{ "clientRealtimeUrl", "[clientBaseUrl]/RealtimeClinicalData"},
{ "localApiUrl", "LocalApi/Generic"},
{ "integrationRootFolder", "C:\\LocalServer\\Integration"},
{ "clientBaseUrl", "https://company.com/api"},
{ "clientAuthBaseUrl", "https://auth.company.com/api"}
};
I have an api url that comes from the config file like <endpoint name="saveuser" address="[clientRefUrl]/SaveUser" />.
I want to build that url in c# code as https://company.com/api/RealtimeReferenceData/SaveUser.
I am able to do this using the following method but the problem is that client has to make sure they don't move the clientBaseUrl to the top of the list. Or, any dependent key to the top of the list.
public static string EvaluateStringWithVariables(string strExpr)
{
Dictionary<string, string> variables = new Dictionary<string, string>()
{
{ "clientRefUrl", "[clientBaseUrl]/RealtimeReferenceData"},
{ "clientRealtimeUrl", "[clientBaseUrl]/RealtimeClinicalData"},
{ "localApiUrl", "LocalApi/Generic"},
{ "integrationRootFolder", "C:\\LocalServer\\Integration"},
{ "clientBaseUrl", "https://company.com/api"},
{ "clientAuthBaseUrl", "https://auth.company.com/api"}
};
foreach (string variable in variables.Keys)
{
var pattern = #"\[" + variable + #"\]";
strExpr = Regex.Replace(strExpr, pattern, variables[variable]);
}
return strExpr;
}
Is there a better way to do the same without any restriction. I tried another solution that uses regex and recursion:
public static string EvaluateStringWithVariables(string strExpr)
{
Dictionary<string, string> variables = new Dictionary<string, string>()
{
{ "clientRefUrl", "[clientBaseUrl]/RealtimeReferenceData"},
{ "clientRealtimeUrl", "[clientBaseUrl]/RealtimeClinicalData"},
{ "localApiUrl", "LocalApi/Generic"},
{ "integrationRootFolder", "C:\\LocalServer\\Integration"},
{ "clientBaseUrl", "https://company.com/api"},
{ "clientAuthBaseUrl", "https://auth.company.com/api"}
};
Regex regEx = new Regex(#"\[(\w+)\]", RegexOptions.Compiled);
strExpr = regEx.Replace(strExpr, match =>
{
string val = String.Empty;
if (variables.TryGetValue(match.Groups[1].Value, out val))
{
return val;
}
return match.Value;
});
Match rmatch = regEx.Match(strExpr);
if (rmatch.Success)
{
return EvaluateStringWithVariables(strExpr);
}
return strExpr;
}
But, recursion didn't go well when I had to evaluate a string like:
strExpr = "[integrationRootFolder]\\myfolder\\[msg.ClientId]\\In\\[personid]_[tabid]_[documentname]_[msg.ClientId].pdf"; which keep on trying to evaluate other variables that is not part of dictionary.
I think your regex solution was quite close, you just need to check to see if you found any values in the dictionary when you did the replace and if you did then call it recursively.
public static string EvaluateStringWithVariables(string strExpr)
{
Dictionary<string, string> variables = new Dictionary<string, string>()
{
{ "clientRefUrl", "[clientBaseUrl]/RealtimeReferenceData"},
{ "clientRealtimeUrl", "[clientBaseUrl]/RealtimeClinicalData"},
{ "localApiUrl", "LocalApi/Generic"},
{ "integrationRootFolder", "C:\\LocalServer\\Integration"},
{ "clientBaseUrl", "https://company.com/api"},
{ "clientAuthBaseUrl", "https://auth.company.com/api"}
};
Regex regEx = new Regex(#"\[(\w+)\]", RegexOptions.Compiled);
bool foundMatch = false;
strExpr = regEx.Replace(strExpr, match =>
{
string val = String.Empty;
if (variables.TryGetValue(match.Groups[1].Value, out val))
{
foundMatch = true;
return val;
}
return match.Value;
});
Match rmatch = regEx.Match(strExpr);
if (rmatch.Success && foundMatch )
{
return EvaluateStringWithVariables(strExpr);
}
return strExpr;
}
There's a quick but a suboptimal solution/fix for your first approach.
string prev;
do {
prev = strExpr;
foreach (string variable in variables.Keys)
{
var pattern = #"\[" + variable + #"\]";
strExpr = Regex.Replace(strExpr, pattern, variables[variable]);
}
}
until (prev == strExpr);
It will repeat substitutions until there's no replacements in the string.
You should care about possible loops in your substitution definitions. If there's any, do-loop will never ends. Add necessary checks or limit repetition count.

How to get ienumerated element from sister collection

I often use two related collections and want to access the corresponding elements in a foreach loop.
But, there is no ".Index" property, is there a direct way of doing this, WITHOUT incrementing a counter?
public void PrepareData()
{
var lines = ReadAllLines(#"\\tsclient\T\Bbtra\wapData.txt");
var headers = lines[0].Split(',');
var values = lines.Last().Split(',');
foreach(var value in values.Skip(1))
{
string message = "Data: "+headers[value.Index]+' '+value
}
}
You could use Enumerable.Zip and an anonymous type:
string[] names={"Rod", "Jane", "Freddy"}
int[] ages={28,32,26;};
var pairs=names.Zip(ages, (name,age) => new{Name=name, Age=age});
foreach(var pair in pairs)
{
string name=pair.Name;
string age=pair.Age;
}
Yet another variant, use Select overloading with index like
public void PrepareData()
{
var lines = ReadAllLines(#"\\tsclient\T\Bbtra\wapData.txt");
var headers = lines[0].Split(',');
var values = lines.Last().Split(',').Select((el,index)=>new {value=el, index=index});
foreach(var value in values.Skip(1))
{
string message = "Data: "+headers[value.index]+' '+value.value
}
}
depends on data in headers and values, better variant can be
public void PrepareData()
{
var lines = ReadAllLines(#"\\tsclient\T\Bbtra\wapData.txt");
var headers = lines[0].Split(',');
var values = lines.Last().Split(',');
foreach(var item in values.Skip(1).Select((el,index)=>new {value=el, index=index}))
{
string message = "Data: "+headers[item.index]+' '+item.value
}
}
I'm thinking you want a .Zip.
var pairs = headers.Zip(values,
(header, value) => new Tuple<string, string>(header, value));
Do it with IndexOf
var lines = ReadAllLines(#"\\tsclient\T\Bbtra\wapData.txt");
var headers = lines[0].Split(',');
var values = lines.Last().Split(',');
foreach(var value in values.Skip(1))
{
string message = "Data: " + headers[Array.IndexOf(values, value)] + ' ' + value;
}
or maybe with iterators :
var lines = System.IO.File.ReadAllLines(#"\\tsclient\T\Bbtra\wapData.txt");
var headers = lines[0].Split(',');
var values = lines.Last().Split(',');
var e1 = headers.GetEnumerator();
var e2 = values.GetEnumerator();
while(e1.MoveNext() && e2.MoveNext())
{
string message = "Data: " + e1.Current.ToString() + ' ' + e2.Current.ToString();
}

How to convert List to a string and back

I retrieved a list of users from database, something like
List<User> users = <..list of users from db...>
Name, LastName, DateOfBirth //multidimensional array??
Now I want to store this list as a string and I want be able to reuse it i.e.
string strUsers = users.ToArray().ToString();
How to recreate a list of users from strUsers?
Is it possible?
Use the string.Join method, e.g.
var joined = string.Join(",", users.Select(u => u.Name));
This would give you a single string of user's names separated by ','.
Or for multiple columns:
var joined = string.Join(",",
users.Select(u => u.FirstName + " " + u.LastName ));
You can reverse the process using string.Split, e.g.
var split = joined.Split( new [] {','} );
If you have a lot of users and a lot of columns, it would be better to write your own custom converter class.
public static class UsersConverter
{
// Separates user properties.
private const char UserDataSeparator = ',';
// Separates users in the list.
private const char UsersSeparator = ';';
public static string ConvertListToString(IEnumerable<User> usersList)
{
var stringBuilder = new StringBuilder();
// Build the users string.
foreach (User user in usersList)
{
stringBuilder.Append(user.Name);
stringBuilder.Append(UserDataSeparator);
stringBuilder.Append(user.Age);
stringBuilder.Append(UsersSeparator);
}
// Remove trailing separator.
stringBuilder.Remove(stringBuilder.Length - 1, 1);
return stringBuilder.ToString();
}
public static List<User> ParseStringToList(string usersString)
{
// Check that passed argument is not null.
if (usersString == null) throw new ArgumentNullException("usersString");
var result = new List<User>();
string[] userDatas = usersString.Split(UsersSeparator);
foreach (string[] userData in userDatas.Select(x => x.Split(UserDataSeparator)))
{
// Check that user data contains enough arguments.
if (userData.Length < 2) throw new ArgumentException("Users string contains invalid data.");
string name = userData[0];
int age;
// Try parsing age.
if (!int.TryParse(userData[1], out age))
{
throw new ArgumentException("Users string contains invalid data.");
}
// Add to result list.
result.Add(new User { Name = name, Age = age });
}
return result;
}
}
You will win performance wise using the StringBuilder to build up your users string. You could also easily expand the converter to take account different separators/additional logic etc.
If you need a more generic solution (to be able to use for any class), you could create a converter which uses reflection to iterate over all the public fields, get/set properties to see what can be extracted as string and later reverse the process to convert your string back to the list.
I think what you're looking for is something that lets you dump all users to a string and get the users back from the string, correct?
I suggest something like this:
Add a method that returns an XElement to the Users type:
public XElement GetXElement()
{
return new XElement("User", new XElement("Name", this.FirstName)) //and so on...
}
and then one that decodes the string into a user:
static User GetUserFromXElement(string xml)
{
XElement temp = XElement.Parse(xml);
User temp = new User();
foreach (XElement inner in temp.Elements())
{
switch inner.Name
{
case "Name":
temp.Name = inner.Value
break;
//whatever
}
}
}
And then do this:
public string UsersToElements (List<Users> toWrite)
{
Stringbuilder sb = new StringBuilder();
StringWriter sw = new StringWriter(sb);
XElement root = new XElement("root");
XDocument temp = new XDocument(root);
foreach (User user in toWrite)
{
root.Append(user.GetXElement());
}
temp.Save(sw);
return sw.ToString();
}
and this:
public List<Users> ElementsToUsers (string xml)
{
List<Users> usrsList = new List<Users>();
XDocument temp = XDocument.Load(xml);
foreach (XElement e in XDocument.Root.Elements())
{
usrsList.Append(Users.GetUserFromXElement(e));
}
return usrsList;
}
JSON solution (using JSON.NET)
public JObject GetJObject()
{
return new JObject("user", new JProperty("name", this.FirstName)); //so on
}
static User GetUserFromJObject(string json)
{
JObject obj = JObject.Parse(json);
return new User() { FirstName = (string)obj["user"]["name"] }; //so on
}
public string UsersToElements (List<Users> users)
{
JObject root = new JObject(from usr in users select new JAttribute("user", usr.GetJObject());
return root.ToString();
}
public List<users> ElementsToUsers(string json)
{
List<Users> users = new List<Users>();
JObject temp = JObject.Parse(json);
foreach (JObject o in (JEnumerable<JObject>)temp.Children())
{
users.Add(Users.GetUserFromJObject(o.ToString());
}
return users;
}
I have no idea if ths works :/ (well the XML I know it does, not so sure about the JSON)
Use this code
string combindedString = string.Join( ",", myList );
var Array = combindedString.Split( new [] {','} );

Categories