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.
Related
I have to send a variables Json to Mailgun but it only accepts the curly braces format when using multi level json files. So,
How can I pass from this:
{ "vehicle.type": "car"}
To this, with C#
{"vehicle": {"type": "car"}}
Having into consideration that sometimes it could be up to 3 nested elements. Like element1.element2.element3: value
Here is what I recommend.
note: I am using the Newtonsoft.Json library available via Nuget, if you are using .NET Core, you can use the built in System.Text.Json library.
Because we have multiple properties in the object with flattened property keys, qualified with .s and we need to convert these properties into a hierarchical, nested JSON structure, merging siblings appropriately at each level, a simple string replacement is neither safe nor effective.
Therefore, the approach here will be to parse the flattened property keys, such as "hospital.hospitalExtraData1.Street" recursively inferring and creating a hierarchy of nested objects.
Let's begin
var originalJson = #"{
""hospital.Name"": ""BestOneEver"",
""hospital.Estatus"": ""Active"",
""hospital.hospitalExtraData1.Street"": ""43"",
""hospital.hospitalExtraData1.Color"": ""Blue"",
""hospital.hospitalExtraData1.hospitalExtraData2.IsExpensive"": ""No"",
""hospital.hospitalExtraData1.hospitalExtraData2.Works24Hrs"": ""Yes"",
""patient.Name"": ""Leonel Messi"",
""patient.Age"": ""23""
}";
var original = JsonConvert.DeserializeObject<IDictionary<string, object>>(originalJson);
Now we have an object model we can work with and restructure.
We will do this using recursion
var original = JsonConvert.DeserializeObject<IDictionary<string, object>>(originalJson);
IDictionary<string, object> Expand(IDictionary<string, object> input)
{
var result = new Dictionary<string, object>();
foreach (var property in input)
{
var (key, remainder) = ParseKey(property.Key);
if (!result.ContainsKey(key))
{
result[key] = remainder != null
? Expand(new Dictionary<string, object>
{
[remainder] = property.Value
})
: property.Value;
}
else if (result[key] is IDictionary<string, object> inner)
{
inner[remainder] = property.Value;
result[key] = Expand(inner);
}
else
{
result[key] = property.Value;
}
}
return result;
}
(string key, string remainder) ParseKey(string key)
{
var dotIndex = key.IndexOf('.');
if (dotIndex != -1)
{
return (key.Substring(0, dotIndex), key.Substring(dotIndex + 1));
}
return (key, null);
}
var expanded = Expand(original);
var expandedJson = JsonConvert.SerializeObject(expanded, Newtonsoft.Json.Formatting.Indented);
Result:
{
"hospital": {
"Name": "BestOneEver",
"Estatus": "Active",
"hospitalExtraData1": {
"Street": "43",
"Color": "Blue",
"hospitalExtraData2": {
"IsExpensive": "No",
"Works24Hrs": "Yes"
}
}
},
"patient": {
"Name": "Leonel Messi",
"Age": "23"
}
}
Here is tricky way using string replacement.
replace for any dot(.) with this (": {") and add close tag (}) at the end for every dot(.) count. Good luck!
Try This:
IDictionary<string, object> Expand(IDictionary<string, object> d)
{
var result = new Dictionary<string, object>();
foreach (KeyValuePair<string, object> item in d)
{
var segments = item.Key.Split('.');
if (segments.Length > 1)
{
if (result.ContainsKey(segments[0]))
{
dynamic obj = new ExpandoObject();
obj = result[segments[0]];
IDictionary<string, object> myObj = obj;
myObj.Add(string.Join(".", segments.Skip(1)), item.Value);
result[segments[0]] = Expand(myObj);
}
else
{
result[segments[0]] = Expand(new Dictionary<string, object>
{
[string.Join(".", segments.Skip(1))] = item.Value
});
}
}
else
{
result[segments[0]] = item.Value;
}
}
return result;
}
I am trying to generate hreflang tags like so:
<link hreflang="en-DE" rel="alternate" href="/en-DE" />
I currently am putting together my dictionary as below, and I thought I could use a 2nd foreach loop to generate the tags with the key value pairs:
public static IHtmlString HrefLangLinks(this PageData currentPage)
{
var hrefLangTags = string.Empty;
var availablePageLanguages = currentPage.ExistingLanguages.Select(culture => culture.Name).ToArray();
Dictionary<string, string> langs = new Dictionary<string, string>();
var contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();
var urlResolver = ServiceLocator.Current.GetInstance<UrlResolver>();
foreach (string cultureName in availablePageLanguages)
{
var culturePage = contentLoader.Get<PageData>(currentPage.ContentGuid, new LanguageSelector(cultureName));
var culturePath = urlResolver.GetVirtualPath(culturePage.ContentLink, culturePage.Language.Name);
langs.Add(cultureName, culturePath.GetUrl());
foreach (KeyValuePair<string, string> entry in langs)
{
hrefLangTags += ("<link hreflang=\"{0}\" rel=\"alternate\" href=\"{1}\" >", langs.Keys, langs.Values);
}
}
return new HtmlString(hrefLangTags);
}
Is there a simple and elegant way to iterate through the dictionary and create my tags?
Why are you even using a dictionay? You could just iterate through "availablePageLanguages" and append the needed data to your "hrefLangTags".
So like this:
public static IHtmlString HrefLangLinks(this PageData currentPage)
{
var hrefLangTags = string.Empty;
var availablePageLanguages = currentPage.ExistingLanguages.Select(culture => culture.Name).ToArray();
var contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();
var urlResolver = ServiceLocator.Current.GetInstance<UrlResolver>();
foreach (string cultureName in availablePageLanguages)
{
var culturePage = contentLoader.Get<PageData>(currentPage.ContentGuid, new LanguageSelector(cultureName));
var culturePath = urlResolver.GetVirtualPath(culturePage.ContentLink, culturePage.Language.Name);
hrefLangTags += String.Format("<link hreflang=\"{0}\" rel=\"alternate\" href=\"{1}\" >", culturName, culturePath.GetUrl());
}
return new HtmlString(hrefLangTags);
}
Your method does not make use of the first principle of SOLID (Single responsibility). You want to return links based on data input. But you are generating the needed data in the same method that is responsible for converting it to a list of IHtmlStrings.
The code beneath is may not be compact but it's a bit easier to read because the nested foreach loop is removed. You could call the method below by chaining them like: CreateLanguageLinksList(CreateWorkingLanguageDictionary(currentPage)) or do it like so
var languageDictionary = CreateWorkingLanguageDictionary(currentPage);
var listOfIHtmlStrings = CreateLanguageLinksList(languageDictionary);
The code above is easier to read, so the cognetive load is less straining on the mind.
public Dictionary<string, string> CreateWorkingLanguageDictionary(this PageData currentPage)
{
var languageDictionary = new Dictionary<string, string>();
var contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();
var urlResolver = ServiceLocator.Current.GetInstance<UrlResolver>();
foreach (string cultureName in availablePageLanguages)
{
var culturePage = contentLoader.Get<PageData>(currentPage.ContentGuid, new LanguageSelector(cultureName));
var culturePath = urlResolver.GetVirtualPath(culturePage.ContentLink, culturePage.Language.Name);
languageDictionary.Add(cultureName, culturePath.GetUrl());
}
return languageDictionary;
}
public IList<IHtmlString> CreateLanguageLinksList(Dictionary<string, string> langs)
{
var htmlStringList = new List<IHtmlString>();
foreach (KeyValuePair<string, string> entry in langs)
{
htmlStringList.add(new HtmlString("<link hreflang=\"{0}\" rel=\"alternate\" href=\"{1}\" >", langs.Keys, langs.Values));
}
return htmlStringList;
}
Just for fun, you could do it in linq, it's a little more succinct:
public static IHtmlString HrefLangLinks(this PageData currentPage)
{
var contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();
var urlResolver = ServiceLocator.Current.GetInstance<UrlResolver>();
var links = currentPage.ExistingLanguages.Select(culture =>
{
var culturePage = contentLoader.Get<PageData>(currentPage.ContentGuid, new LanguageSelector(culture.Name));
var culturePath = urlResolver.GetVirtualPath(culturePage.ContentLink, culturePage.Language.Name);
return $"<link hreflang=\"{culture.Name}\" rel=\"alternate\" href=\"{culturePath.GetUrl()}\" >";
});
return new HtmlString(string.Join("",links));
}
Disclaimer: This is totally untested or verified.
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 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.
I've got a strange problem using the Castle NVelocity...
If the template string ends with a $ character, it throws the following and exception:
Lexical error: NVelocity.Runtime.Parser.TokenMgrError: Lexical error
at line 1, column 94. Encountered: after : ""
If i add a space, or any other character, to the end of the string it works as expected.
Does anybody have a workaround for this?
Here's some sample code that reproduces the problem:
class Program
{
static void Main(string[] args)
{
var result = Merge("The quick brown ${Animal} jumps over the lazy dog$", new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("Animal", "fox") });
Console.WriteLine("Success: {0}", result.Key);
Console.WriteLine("Message: {0}", result.Value);
Console.Read();
}
public static KeyValuePair<bool, string> Merge(string template, IList<KeyValuePair<string, string>> data)
{
var ret = new KeyValuePair<bool, string>(false, null);
try
{
if (data != null)
{
var engine = new VelocityEngine();
engine.Init();
var context = new VelocityContext();
foreach (var tokenData in data)
{
context.Put(tokenData.Key, tokenData.Value);
}
var templateContent = template;
var sb = new StringBuilder();
var textWriter = new StringWriter(sb);
engine.Evaluate(context, textWriter, String.Empty, templateContent);
ret = new KeyValuePair<bool, string>(true, sb.ToString());
}
}
catch (Exception ex)
{
ret = new KeyValuePair<bool, string>(false, ex.Message);
}
return ret;
}
}
You have a couple of options:
If you have no influence over the input string to be merged, make sure that they don't have a trailing dollar character, at least not for the merge process:
Example:
bool dollarAtEnd = false;
if (input.EndsWith('$'))
{
input += " ";
dollarAtEnd = true;
}
var result = Merge(input, ...);
if (dollarAtEnd)
{
result = result.Substring(1, result.Length - 1);
}
If you can control the input string, but only have the requirement that some of them should end with a dollar character, you can do as follows:
Example:
"#set($dollar='$')The quick brown ${Animal} jumps over the lazy dog$dollar"
Or pass "dollar" as variable to the VelocityContext, rather than specifying it inline.
$ denotes the start of a variable name, if you want to use a $ literal, you need to escape it to \$.
http://velocity.apache.org/engine/devel/user-guide.html#Getting_literal